diff --git a/.changeset/old-cobras-change.md b/.changeset/old-cobras-change.md new file mode 100644 index 00000000000..5c5bfd49239 --- /dev/null +++ b/.changeset/old-cobras-change.md @@ -0,0 +1,7 @@ +--- +'@clerk/localizations': patch +'@clerk/clerk-js': patch +'@clerk/types': patch +--- + +Adjusts the layout of the `PricingTable` plan cards diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index 84e9be11fa8..365a0d44611 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -28,7 +28,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl await expect(u.po.page.getByRole('heading', { name: 'Pro' })).toBeVisible(); }); - test('when signed out, clicking get started button navigates to sign in page', async ({ page, context }) => { + test('when signed out, clicking subscribe button navigates to sign in page', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); await u.po.page.goToRelative('/pricing-table'); @@ -101,7 +101,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl await u.po.pricingTable.clickManageSubscription(); await u.po.page.getByRole('button', { name: 'Cancel subscription' }).click(); await u.po.page.getByRole('alertdialog').getByRole('button', { name: 'Cancel subscription' }).click(); - await expect(u.po.page.getByRole('button', { name: 'Re-subscribe' }).first()).toBeVisible(); + await expect(u.po.page.getByRole('button', { name: /resubscribe|re-subscribe/i }).first()).toBeVisible(); }); test.describe('in UserProfile', () => { diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 09f6fd140cf..0c6154cc81d 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -4,7 +4,7 @@ { "path": "./dist/clerk.browser.js", "maxSize": "68KB" }, { "path": "./dist/clerk.legacy.browser.js", "maxSize": "110KB" }, { "path": "./dist/clerk.headless*.js", "maxSize": "52KB" }, - { "path": "./dist/ui-common*.js", "maxSize": "102.5KB" }, + { "path": "./dist/ui-common*.js", "maxSize": "104KB" }, { "path": "./dist/vendors*.js", "maxSize": "39KB" }, { "path": "./dist/coinbase*.js", "maxSize": "38KB" }, { "path": "./dist/createorganization*.js", "maxSize": "5KB" }, diff --git a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx index 7cf0dec27b5..99140af518f 100644 --- a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx +++ b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx @@ -1,4 +1,4 @@ -import { useClerk } from '@clerk/shared/react'; +import { useClerk, useUser } from '@clerk/shared/react'; import type { __experimental_CommercePlanResource, __experimental_CommerceSubscriptionPlanPeriod, @@ -8,7 +8,6 @@ import * as React from 'react'; import { usePlansContext, usePricingTableContext, useSubscriberTypeContext } from '../../contexts'; import { - Badge, Box, Button, Col, @@ -20,12 +19,9 @@ import { SimpleButton, Span, Text, - useAppearance, } from '../../customizables'; -import { ReversibleContainer, SegmentedControl } from '../../elements'; -import { usePrefersReducedMotion } from '../../hooks'; -import { Check, InformationCircle, Plus } from '../../icons'; -import type { ThemableCssProp } from '../../styledSystem'; +import { Switch } from '../../elements'; +import { Check, Plus } from '../../icons'; import { common, InternalThemeProvider } from '../../styledSystem'; import { colors, getClosestProfileScrollBox } from '../../utils'; interface PricingTableDefaultProps { @@ -63,6 +59,7 @@ export function PricingTableDefault({ '--column-width': 'max(var(--max-column-width), min(var(--grid-min-size, 10rem), 100%))', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(var(--column-width), 1fr))', + gridTemplateRows: 'auto 1fr', gap: `var(--grid-gap-y, var(--grid-gap, ${t.space.$4})) var(--grid-gap, ${t.space.$4})`, alignItems: 'stretch', width: '100%', @@ -102,16 +99,21 @@ interface CardProps { function Card(props: CardProps) { const { plan, planPeriod, setPlanPeriod, onSelect, props: pricingTableProps, isCompact = false } = props; const clerk = useClerk(); - const { mode = 'mounted' } = usePricingTableContext(); + const { isSignedIn } = useUser(); + const { mode = 'mounted', ctaPosition: ctxCtaPosition } = usePricingTableContext(); const subscriberType = useSubscriberTypeContext(); - const ctaPosition = pricingTableProps.ctaPosition || 'bottom'; + const ctaPosition = pricingTableProps.ctaPosition || ctxCtaPosition || 'bottom'; const collapseFeatures = pricingTableProps.collapseFeatures || false; - const { id, slug, features } = plan; - const totalFeatures = features.length; - const hasFeatures = totalFeatures > 0; + const { id, slug } = plan; - const { buttonPropsForPlan, isDefaultPlanImplicitlyActiveOrUpcoming } = usePlansContext(); + const { buttonPropsForPlan, isDefaultPlanImplicitlyActiveOrUpcoming, activeOrUpcomingSubscription, subscriptions } = + usePlansContext(); + + const subscription = activeOrUpcomingSubscription(plan); + const isImplicitlyActiveOrUpcoming = isDefaultPlanImplicitlyActiveOrUpcoming && plan.isDefault; + + const showStatusRow = !!subscription || (isImplicitlyActiveOrUpcoming && isSignedIn); const showPlanDetails = (event?: React.MouseEvent) => { const portalRoot = getClosestProfileScrollBox(mode, event); @@ -130,8 +132,10 @@ function Card(props: CardProps) { elementDescriptor={descriptors.pricingTableCard} elementId={descriptors.pricingTableCard.setId(slug)} sx={t => ({ - display: 'flex', - flexDirection: 'column', + display: 'grid', + gap: 0, + gridTemplateRows: 'subgrid', + gridRow: 'span 4', background: common.mergedColorsBackground( colors.setAlpha(t.colors.$colorBackground, 1), t.colors.$neutralAlpha50, @@ -152,7 +156,14 @@ function Card(props: CardProps) { planPeriod={planPeriod} setPlanPeriod={setPlanPeriod} /> - + {!collapseFeatures ? ( ) : null} + + {showStatusRow && ( + ({ + padding: t.space.$1, + borderTopWidth: t.borderWidths.$normal, + borderTopStyle: t.borderStyles.$solid, + borderTopColor: t.colors.$neutralAlpha100, + })} + > + {subscription?.status === 'active' || (isImplicitlyActiveOrUpcoming && subscriptions.length === 0) ? ( + + ) : ( + + )} + + )} + {(!plan.isDefault || !isDefaultPlanImplicitlyActiveOrUpcoming) && ( ({ marginTop: 'auto', padding: isCompact ? t.space.$3 : t.space.$4, - borderTopWidth: hasFeatures ? t.borderWidths.$normal : 0, + borderTopWidth: t.borderWidths.$normal, borderTopStyle: t.borderStyles.$solid, borderTopColor: t.colors.$neutralAlpha100, background: undefined, + order: ctaPosition === 'top' ? -1 : undefined, })} >