Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eighty-breads-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Display alert on plan details error
77 changes: 66 additions & 11 deletions packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
import { useClerk } from '@clerk/shared/react';
import type { __internal_PlanDetailsProps, CommercePlanResource, CommerceSubscriptionPlanPeriod } from '@clerk/types';
import type {
__internal_PlanDetailsProps,
ClerkAPIResponseError,
CommercePlanResource,
CommerceSubscriptionPlanPeriod,
} from '@clerk/types';
import * as React from 'react';
import { useMemo, useState } from 'react';
import useSWR from 'swr';

import { Alert } from '@/ui/elements/Alert';
import { Avatar } from '@/ui/elements/Avatar';
import { Drawer } from '@/ui/elements/Drawer';
import { Switch } from '@/ui/elements/Switch';

import { SubscriberTypeContext } from '../../contexts';
import { Box, Col, descriptors, Flex, Heading, localizationKeys, Span, Spinner, Text } from '../../customizables';
import {
Box,
Col,
descriptors,
Flex,
Heading,
localizationKeys,
Span,
Spinner,
Text,
useLocalizations,
} from '../../customizables';

export const PlanDetails = (props: __internal_PlanDetailsProps) => {
return (
Expand All @@ -19,6 +36,39 @@ export const PlanDetails = (props: __internal_PlanDetailsProps) => {
);
};

const BodyFiller = ({ children }: { children: React.ReactNode }) => {
return (
<Drawer.Body
sx={t => ({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
overflowY: 'auto',
Comment on lines +43 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

body already handles a majority of these.

display: 'flex',
flexDirection: 'column',
flex: 1,
overflowY: 'auto',
overflowX: 'hidden',

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are not inherited tho. If i removed the duplicate ones then this is the UI I end up with.
image

padding: t.space.$4,
gap: t.space.$4,
})}
>
{children}
</Drawer.Body>
);
};

const PlanDetailsError = ({ error }: { error: ClerkAPIResponseError }) => {
const { translateError } = useLocalizations();
return (
<BodyFiller>
<Alert
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be full width?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to force it ? If the error message is large enough it will occupy the necessary space.

Copy link
Member

@alexcarpenter alexcarpenter Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, just kind of feels weird at the size imo. Not a blocker though.

Here is a another error rendering full width already.

Screenshot 2025-07-24 at 3 29 39 PM

variant='danger'
colorScheme='danger'
>
{translateError(error.errors[0])}
</Alert>
</BodyFiller>
);
};

const PlanDetailsInternal = ({
planId,
plan: initialPlan,
Expand All @@ -27,29 +77,34 @@ const PlanDetailsInternal = ({
const clerk = useClerk();
const [planPeriod, setPlanPeriod] = useState<CommerceSubscriptionPlanPeriod>(initialPlanPeriod);

const { data: plan, isLoading } = useSWR(
const {
data: plan,
isLoading,
error,
} = useSWR<CommercePlanResource, ClerkAPIResponseError>(
planId || initialPlan ? { type: 'plan', id: planId || initialPlan?.id } : null,
// @ts-expect-error we are handling it above
() => clerk.billing.getPlan({ id: planId || initialPlan?.id }),
{
fallbackData: initialPlan,
revalidateOnFocus: false,
shouldRetryOnError: false,
keepPreviousData: true,
},
);

if (isLoading && !initialPlan) {
return (
<Flex
justify='center'
align='center'
sx={{
height: '100%',
}}
>
<BodyFiller>
<Spinner />
</Flex>
</BodyFiller>
);
}

if (!plan && error) {
return <PlanDetailsError error={error} />;
}

if (!plan) {
return null;
}
Expand Down
Loading