diff --git a/.changeset/tangy-facts-poke.md b/.changeset/tangy-facts-poke.md new file mode 100644 index 00000000000..e748424aa4f --- /dev/null +++ b/.changeset/tangy-facts-poke.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Fixes `newSubscriptionRedirectUrl` usage on `PricingTable`. diff --git a/integration/templates/astro-node/src/pages/pricing-table.astro b/integration/templates/astro-node/src/pages/pricing-table.astro index 8fac1771897..f756e6c8dde 100644 --- a/integration/templates/astro-node/src/pages/pricing-table.astro +++ b/integration/templates/astro-node/src/pages/pricing-table.astro @@ -1,10 +1,12 @@ --- -import { PricingTable } from "@clerk/astro/components"; -import Layout from "../layouts/Layout.astro"; +import { PricingTable } from '@clerk/astro/components'; +import Layout from '../layouts/Layout.astro'; + +const newSubscriptionRedirectUrl = Astro.url.searchParams.get('newSubscriptionRedirectUrl'); --- - -
- + +
+
diff --git a/integration/templates/next-app-router/src/app/pricing-table/page.tsx b/integration/templates/next-app-router/src/app/pricing-table/page.tsx index e3804e9c533..72703279461 100644 --- a/integration/templates/next-app-router/src/app/pricing-table/page.tsx +++ b/integration/templates/next-app-router/src/app/pricing-table/page.tsx @@ -1,5 +1,10 @@ import { PricingTable } from '@clerk/nextjs'; -export default function PricingTablePage() { - return ; +export default async function PricingTablePage({ + searchParams, +}: { + searchParams: Promise<{ newSubscriptionRedirectUrl: string }>; +}) { + const newSubscriptionRedirectUrl = (await searchParams).newSubscriptionRedirectUrl; + return ; } diff --git a/integration/templates/nuxt-node/pages/pricing-table.vue b/integration/templates/nuxt-node/pages/pricing-table.vue index b6201341466..033edc3e544 100644 --- a/integration/templates/nuxt-node/pages/pricing-table.vue +++ b/integration/templates/nuxt-node/pages/pricing-table.vue @@ -1,3 +1,10 @@ + + diff --git a/integration/templates/vue-vite/src/views/PricingTable.vue b/integration/templates/vue-vite/src/views/PricingTable.vue index 195989ad633..fdd5226a13e 100644 --- a/integration/templates/vue-vite/src/views/PricingTable.vue +++ b/integration/templates/vue-vite/src/views/PricingTable.vue @@ -1,7 +1,11 @@ diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index 5ace8461029..9b49fbbc199 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -104,6 +104,42 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl // await expect(u.po.page.getByRole('button', { name: /resubscribe|re-subscribe/i }).first()).toBeVisible(); // }); + test.describe('redirects', () => { + test('default navigates to afterSignInUrl', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.po.signIn.goTo(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + await u.po.page.goToRelative('/pricing-table'); + await u.po.pricingTable.waitForMounted(); + await u.po.pricingTable.startCheckout({ planSlug: 'pro' }); + await u.po.checkout.waitForMounted(); + await u.po.checkout.clickPayOrSubscribe(); + await expect(u.po.page.getByText('Success!')).toBeVisible(); + await page + .locator('.cl-checkout-root') + .getByRole('button', { name: /^continue$/i }) + .click(); + await u.page.waitForAppUrl('/'); + }); + + test('navigates to supplied newSubscriptionRedirectUrl', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.po.signIn.goTo(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + await u.po.page.goToRelative('/pricing-table?newSubscriptionRedirectUrl=/success'); + await u.po.pricingTable.waitForMounted(); + await u.po.pricingTable.startCheckout({ planSlug: 'plus' }); + await u.po.checkout.waitForMounted(); + await u.po.checkout.clickPayOrSubscribe(); + await expect(u.po.page.getByText('Success!')).toBeVisible(); + await page + .locator('.cl-checkout-root') + .getByRole('button', { name: /^continue$/i }) + .click(); + await u.page.waitForAppUrl('/success'); + }); + }); + test.describe('in UserProfile', () => { test('renders pricing table with plans', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 2a8eebcb09d..49a0edb0e76 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -1,6 +1,6 @@ { "files": [ - { "path": "./dist/clerk.js", "maxSize": "594.1kB" }, + { "path": "./dist/clerk.js", "maxSize": "594.2kB" }, { "path": "./dist/clerk.browser.js", "maxSize": "68.3KB" }, { "path": "./dist/clerk.legacy.browser.js", "maxSize": "110KB" }, { "path": "./dist/clerk.headless*.js", "maxSize": "52KB" }, diff --git a/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx b/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx index 8fef670d632..d77dc97d662 100644 --- a/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx +++ b/packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx @@ -56,6 +56,7 @@ const PricingTableRoot = (props: PricingTableProps) => { planPeriod, event, appearance: props.checkoutProps?.appearance, + newSubscriptionRedirectUrl: props.newSubscriptionRedirectUrl, }); return; }; diff --git a/packages/clerk-js/src/ui/contexts/components/Plans.tsx b/packages/clerk-js/src/ui/contexts/components/Plans.tsx index b9a3b8cea2c..9675e9e51c0 100644 --- a/packages/clerk-js/src/ui/contexts/components/Plans.tsx +++ b/packages/clerk-js/src/ui/contexts/components/Plans.tsx @@ -137,6 +137,7 @@ type HandleSelectPlanProps = { mode?: 'modal' | 'mounted'; event?: React.MouseEvent; appearance?: Appearance; + newSubscriptionRedirectUrl?: string; }; export const usePlansContext = () => { @@ -318,7 +319,15 @@ export const usePlansContext = () => { // handle the selection of a plan, either by opening the subscription details or checkout const handleSelectPlan = useCallback( - ({ plan, planPeriod, onSubscriptionChange, mode = 'mounted', event, appearance }: HandleSelectPlanProps) => { + ({ + plan, + planPeriod, + onSubscriptionChange, + mode = 'mounted', + event, + appearance, + newSubscriptionRedirectUrl, + }: HandleSelectPlanProps) => { const subscription = activeOrUpcomingSubscription(plan); const portalRoot = getClosestProfileScrollBox(mode, event); @@ -351,6 +360,7 @@ export const usePlansContext = () => { }, appearance, portalRoot, + newSubscriptionRedirectUrl, }); } }, diff --git a/packages/clerk-js/src/ui/lazyModules/MountedCheckoutDrawer.tsx b/packages/clerk-js/src/ui/lazyModules/MountedCheckoutDrawer.tsx index b7df263f199..3ed00fc63f3 100644 --- a/packages/clerk-js/src/ui/lazyModules/MountedCheckoutDrawer.tsx +++ b/packages/clerk-js/src/ui/lazyModules/MountedCheckoutDrawer.tsx @@ -44,6 +44,7 @@ export function MountedCheckoutDrawer({ onSubscriptionComplete={checkoutDrawer.props.onSubscriptionComplete} portalRoot={checkoutDrawer.props.portalRoot} appearance={checkoutDrawer.props.appearance} + newSubscriptionRedirectUrl={checkoutDrawer.props.newSubscriptionRedirectUrl} /> )}