From 62c50dec2446135e5b3edbae06e02837d7a0f5ba Mon Sep 17 00:00:00 2001 From: panteliselef Date: Tue, 19 Aug 2025 18:54:17 +0300 Subject: [PATCH 01/14] feat(vue,astro): Add Billing buttons --- packages/astro/src/astro-components/index.ts | 3 + .../unstyled/CheckoutButton.astro | 76 +++++++++++++++++++ .../unstyled/PlanDetailsButton.astro | 63 +++++++++++++++ .../unstyled/SubscriptionDetailsButton.astro | 72 ++++++++++++++++++ packages/astro/src/react/CheckoutButton.tsx | 53 +++++++++++++ .../astro/src/react/PlanDetailsButton.tsx | 40 ++++++++++ .../src/react/SubscriptionDetailsButton.tsx | 45 +++++++++++ packages/astro/src/react/index.ts | 14 +++- packages/astro/src/react/utils.tsx | 11 ++- .../vue/src/components/CheckoutButton.vue | 53 +++++++++++++ .../vue/src/components/PlanDetailsButton.vue | 40 ++++++++++ .../components/SubscriptionDetailsButton.vue | 50 ++++++++++++ packages/vue/src/components/index.ts | 3 + packages/vue/src/index.ts | 6 ++ packages/vue/src/utils/childrenUtils.ts | 9 ++- 15 files changed, 534 insertions(+), 4 deletions(-) create mode 100644 packages/astro/src/astro-components/unstyled/CheckoutButton.astro create mode 100644 packages/astro/src/astro-components/unstyled/PlanDetailsButton.astro create mode 100644 packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro create mode 100644 packages/astro/src/react/CheckoutButton.tsx create mode 100644 packages/astro/src/react/PlanDetailsButton.tsx create mode 100644 packages/astro/src/react/SubscriptionDetailsButton.tsx create mode 100644 packages/vue/src/components/CheckoutButton.vue create mode 100644 packages/vue/src/components/PlanDetailsButton.vue create mode 100644 packages/vue/src/components/SubscriptionDetailsButton.vue diff --git a/packages/astro/src/astro-components/index.ts b/packages/astro/src/astro-components/index.ts index 180b6cac519..970c1f45cf9 100644 --- a/packages/astro/src/astro-components/index.ts +++ b/packages/astro/src/astro-components/index.ts @@ -12,6 +12,9 @@ export { default as AuthenticateWithRedirectCallback } from './control/Authentic export { default as SignInButton } from './unstyled/SignInButton.astro'; export { default as SignUpButton } from './unstyled/SignUpButton.astro'; export { default as SignOutButton } from './unstyled/SignOutButton.astro'; +export { default as SubscriptionDetailsButton } from './unstyled/SubscriptionDetailsButton.astro'; +export { default as CheckoutButton } from './unstyled/CheckoutButton.astro'; +export { default as PlanDetailsButton } from './unstyled/PlanDetailsButton.astro'; /** * UI Components diff --git a/packages/astro/src/astro-components/unstyled/CheckoutButton.astro b/packages/astro/src/astro-components/unstyled/CheckoutButton.astro new file mode 100644 index 00000000000..ac1eccba362 --- /dev/null +++ b/packages/astro/src/astro-components/unstyled/CheckoutButton.astro @@ -0,0 +1,76 @@ +--- +import type { HTMLTag, Polymorphic } from 'astro/types'; +import type { __experimental_CheckoutButtonProps } from '@clerk/types'; +import type { ButtonProps } from '../../types'; +import { addUnstyledAttributeToFirstTag, logAsPropUsageDeprecation } from './utils'; + +type Props = Polymorphic> & __experimental_CheckoutButtonProps; + +import { generateSafeId } from '@clerk/astro/internal'; + +const safeId = generateSafeId(); + +if ('as' in Astro.props) { + logAsPropUsageDeprecation(); +} + +const { + as: Tag = 'button', + asChild, + planId, + planPeriod, + for: _for, + onSubscriptionComplete, + newSubscriptionRedirectUrl, + checkoutProps, + ...props +} = Astro.props; + +const checkoutOptions = { + planId, + planPeriod, + for: _for, + onSubscriptionComplete, + newSubscriptionRedirectUrl, + ...checkoutProps, +}; + +let htmlElement = ''; + +if (asChild) { + htmlElement = await Astro.slots.render('default'); + htmlElement = addUnstyledAttributeToFirstTag(htmlElement, safeId); +} +--- + +{ + asChild ? ( + + ) : ( + + Checkout + + ) +} + + diff --git a/packages/astro/src/astro-components/unstyled/PlanDetailsButton.astro b/packages/astro/src/astro-components/unstyled/PlanDetailsButton.astro new file mode 100644 index 00000000000..6c102ef73a5 --- /dev/null +++ b/packages/astro/src/astro-components/unstyled/PlanDetailsButton.astro @@ -0,0 +1,63 @@ +--- +import type { HTMLTag, Polymorphic } from 'astro/types'; +import type { __experimental_PlanDetailsButtonProps } from '@clerk/types'; +import type { ButtonProps } from '../../types'; +import { addUnstyledAttributeToFirstTag, logAsPropUsageDeprecation } from './utils'; + +type Props = Polymorphic> & __experimental_PlanDetailsButtonProps; + +import { generateSafeId } from '@clerk/astro/internal'; + +const safeId = generateSafeId(); + +if ('as' in Astro.props) { + logAsPropUsageDeprecation(); +} + +const { + as: Tag = 'button', + asChild, + plan, + planId, + initialPlanPeriod, + planDetailsProps, + ...props +} = Astro.props; + +const planDetailsOptions = { + plan, + planId, + initialPlanPeriod, + ...planDetailsProps, +}; + +let htmlElement = ''; + +if (asChild) { + htmlElement = await Astro.slots.render('default'); + htmlElement = addUnstyledAttributeToFirstTag(htmlElement, safeId); +} +--- + +{ + asChild ? ( + + ) : ( + + Plan details + + ) +} + + diff --git a/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro b/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro new file mode 100644 index 00000000000..90b13578819 --- /dev/null +++ b/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro @@ -0,0 +1,72 @@ +--- +import type { HTMLTag, Polymorphic } from 'astro/types'; +import type { __experimental_SubscriptionDetailsButtonProps } from '@clerk/types'; +import type { ButtonProps } from '../../types'; +import { addUnstyledAttributeToFirstTag, logAsPropUsageDeprecation } from './utils'; + +type Props = Polymorphic> & __experimental_SubscriptionDetailsButtonProps; + +import { generateSafeId } from '@clerk/astro/internal'; + +const safeId = generateSafeId(); + +if ('as' in Astro.props) { + logAsPropUsageDeprecation(); +} + +const { + as: Tag = 'button', + asChild, + for: _for, + subscriptionDetailsProps, + onSubscriptionCancel, + ...props +} = Astro.props; + +const subscriptionDetailsOptions = { + for: _for, + onSubscriptionCancel, + ...subscriptionDetailsProps, +}; + +let htmlElement = ''; + +if (asChild) { + htmlElement = await Astro.slots.render('default'); + htmlElement = addUnstyledAttributeToFirstTag(htmlElement, safeId); +} +--- + +{ + asChild ? ( + + ) : ( + + Subscription details + + ) +} + + diff --git a/packages/astro/src/react/CheckoutButton.tsx b/packages/astro/src/react/CheckoutButton.tsx new file mode 100644 index 00000000000..193e74263b4 --- /dev/null +++ b/packages/astro/src/react/CheckoutButton.tsx @@ -0,0 +1,53 @@ +import type { __experimental_CheckoutButtonProps } from '@clerk/types'; +import React from 'react'; + +import { assertSingleChild, normalizeWithDefaultValue, safeExecute, withClerk } from './utils'; +import type { WithClerkProp } from './utils'; + +export type { __experimental_CheckoutButtonProps as CheckoutButtonProps }; + +export const CheckoutButton = withClerk( + ({ clerk, children, ...props }: WithClerkProp>) => { + const { + planId, + planPeriod, + for: _for, + onSubscriptionComplete, + newSubscriptionRedirectUrl, + checkoutProps, + ...rest + } = props; + + // Note: Auth checks are moved to runtime since Astro React components + // don't have access to auth context at render time like Vue/React apps do + + children = normalizeWithDefaultValue(children, 'Checkout'); + const child = assertSingleChild(children)('CheckoutButton'); + + const clickHandler = () => { + if (!clerk) { + return; + } + + return clerk.__internal_openCheckout({ + planId, + planPeriod, + for: _for, + onSubscriptionComplete, + newSubscriptionRedirectUrl, + ...checkoutProps, + }); + }; + + const wrappedChildClickHandler: React.MouseEventHandler = async e => { + if (child && typeof child === 'object' && 'props' in child) { + await safeExecute(child.props.onClick)(e); + } + return clickHandler(); + }; + + const childProps = { ...rest, onClick: wrappedChildClickHandler }; + return React.cloneElement(child as React.ReactElement, childProps); + }, + 'CheckoutButton', +); diff --git a/packages/astro/src/react/PlanDetailsButton.tsx b/packages/astro/src/react/PlanDetailsButton.tsx new file mode 100644 index 00000000000..d4f1d8e2f72 --- /dev/null +++ b/packages/astro/src/react/PlanDetailsButton.tsx @@ -0,0 +1,40 @@ +import type { __experimental_PlanDetailsButtonProps } from '@clerk/types'; +import React from 'react'; + +import { assertSingleChild, normalizeWithDefaultValue, safeExecute, withClerk } from './utils'; +import type { WithClerkProp } from './utils'; + +export type { __experimental_PlanDetailsButtonProps as PlanDetailsButtonProps }; + +export const PlanDetailsButton = withClerk( + ({ clerk, children, ...props }: WithClerkProp>) => { + const { plan, planId, initialPlanPeriod, planDetailsProps, ...rest } = props; + + children = normalizeWithDefaultValue(children, 'Plan details'); + const child = assertSingleChild(children)('PlanDetailsButton'); + + const clickHandler = () => { + if (!clerk) { + return; + } + + return clerk.__internal_openPlanDetails({ + plan, + planId, + initialPlanPeriod, + ...planDetailsProps, + } as __experimental_PlanDetailsButtonProps); + }; + + const wrappedChildClickHandler: React.MouseEventHandler = async e => { + if (child && typeof child === 'object' && 'props' in child) { + await safeExecute(child.props.onClick)(e); + } + return clickHandler(); + }; + + const childProps = { ...rest, onClick: wrappedChildClickHandler }; + return React.cloneElement(child as React.ReactElement, childProps); + }, + 'PlanDetailsButton', +); diff --git a/packages/astro/src/react/SubscriptionDetailsButton.tsx b/packages/astro/src/react/SubscriptionDetailsButton.tsx new file mode 100644 index 00000000000..2038dc0387c --- /dev/null +++ b/packages/astro/src/react/SubscriptionDetailsButton.tsx @@ -0,0 +1,45 @@ +import type { __experimental_SubscriptionDetailsButtonProps } from '@clerk/types'; +import React from 'react'; + +import { assertSingleChild, normalizeWithDefaultValue, safeExecute, withClerk } from './utils'; +import type { WithClerkProp } from './utils'; + +export type { __experimental_SubscriptionDetailsButtonProps as SubscriptionDetailsButtonProps }; + +export const SubscriptionDetailsButton = withClerk( + ({ + clerk, + children, + ...props + }: WithClerkProp>) => { + const { for: _for, subscriptionDetailsProps, onSubscriptionCancel, ...rest } = props; + children = normalizeWithDefaultValue(children, 'Subscription details'); + const child = assertSingleChild(children)('SubscriptionDetailsButton'); + + // Note: Auth checks are moved to runtime since Astro React components + // don't have access to auth context at render time like Vue/React apps do + + const clickHandler = () => { + if (!clerk) { + return; + } + + return clerk.__internal_openSubscriptionDetails({ + for: _for, + onSubscriptionCancel, + ...subscriptionDetailsProps, + }); + }; + + const wrappedChildClickHandler: React.MouseEventHandler = async e => { + if (child && typeof child === 'object' && 'props' in child) { + await safeExecute(child.props.onClick)(e); + } + return clickHandler(); + }; + + const childProps = { ...rest, onClick: wrappedChildClickHandler }; + return React.cloneElement(child as React.ReactElement, childProps); + }, + 'SubscriptionDetailsButton', +); diff --git a/packages/astro/src/react/index.ts b/packages/astro/src/react/index.ts index 9b8cbf229f4..107e54c2fa5 100644 --- a/packages/astro/src/react/index.ts +++ b/packages/astro/src/react/index.ts @@ -1,10 +1,20 @@ import { SignInButton, type SignInButtonProps } from './SignInButton'; import { SignOutButton, type SignOutButtonProps } from './SignOutButton'; import { SignUpButton, type SignUpButtonProps } from './SignUpButton'; +import { SubscriptionDetailsButton, type SubscriptionDetailsButtonProps } from './SubscriptionDetailsButton'; +import { CheckoutButton, type CheckoutButtonProps } from './CheckoutButton'; +import { PlanDetailsButton, type PlanDetailsButtonProps } from './PlanDetailsButton'; export * from './uiComponents'; export * from './controlComponents'; export * from './hooks'; -export { SignInButton, SignOutButton, SignUpButton }; +export { SignInButton, SignOutButton, SignUpButton, SubscriptionDetailsButton, CheckoutButton, PlanDetailsButton }; -export type { SignInButtonProps, SignOutButtonProps, SignUpButtonProps }; +export type { + SignInButtonProps, + SignOutButtonProps, + SignUpButtonProps, + SubscriptionDetailsButtonProps, + CheckoutButtonProps, + PlanDetailsButtonProps, +}; diff --git a/packages/astro/src/react/utils.tsx b/packages/astro/src/react/utils.tsx index f52fdb769ce..7a0b2f617af 100644 --- a/packages/astro/src/react/utils.tsx +++ b/packages/astro/src/react/utils.tsx @@ -49,7 +49,16 @@ export type WithClerkProp = T & { // TODO-SHARED: Duplicate from @clerk/clerk-react export const assertSingleChild = (children: React.ReactNode) => - (name: 'SignInButton' | 'SignUpButton' | 'SignOutButton' | 'SignInWithMetamaskButton') => { + ( + name: + | 'SignInButton' + | 'SignUpButton' + | 'SignOutButton' + | 'SignInWithMetamaskButton' + | 'SubscriptionDetailsButton' + | 'CheckoutButton' + | 'PlanDetailsButton', + ) => { try { return React.Children.only(children); } catch { diff --git a/packages/vue/src/components/CheckoutButton.vue b/packages/vue/src/components/CheckoutButton.vue new file mode 100644 index 00000000000..c056d943c4e --- /dev/null +++ b/packages/vue/src/components/CheckoutButton.vue @@ -0,0 +1,53 @@ + + + diff --git a/packages/vue/src/components/PlanDetailsButton.vue b/packages/vue/src/components/PlanDetailsButton.vue new file mode 100644 index 00000000000..ba094c41412 --- /dev/null +++ b/packages/vue/src/components/PlanDetailsButton.vue @@ -0,0 +1,40 @@ + + + diff --git a/packages/vue/src/components/SubscriptionDetailsButton.vue b/packages/vue/src/components/SubscriptionDetailsButton.vue new file mode 100644 index 00000000000..5dec21240ac --- /dev/null +++ b/packages/vue/src/components/SubscriptionDetailsButton.vue @@ -0,0 +1,50 @@ + + + diff --git a/packages/vue/src/components/index.ts b/packages/vue/src/components/index.ts index 37827c8ad8a..bc0110098ae 100644 --- a/packages/vue/src/components/index.ts +++ b/packages/vue/src/components/index.ts @@ -28,3 +28,6 @@ export { default as SignInButton } from './SignInButton.vue'; export { default as SignUpButton } from './SignUpButton.vue'; export { default as SignOutButton } from './SignOutButton.vue'; export { default as SignInWithMetamaskButton } from './SignInWithMetamaskButton.vue'; +export { default as SubscriptionDetailsButton } from './SubscriptionDetailsButton.vue'; +export { default as CheckoutButton } from './CheckoutButton.vue'; +export { default as PlanDetailsButton } from './PlanDetailsButton.vue'; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 325f66ea890..5d6c74fb8ca 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -8,5 +8,11 @@ export * from './composables'; export { clerkPlugin, type PluginOptions } from './plugin'; export { updateClerkOptions } from './utils'; +export type { + __experimental_SubscriptionDetailsButtonProps as SubscriptionDetailsButtonProps, + __experimental_CheckoutButtonProps as CheckoutButtonProps, + __experimental_PlanDetailsButtonProps as PlanDetailsButtonProps, +} from '@clerk/types'; + setErrorThrowerOptions({ packageName: PACKAGE_NAME }); setClerkJsLoadingErrorPackageName(PACKAGE_NAME); diff --git a/packages/vue/src/utils/childrenUtils.ts b/packages/vue/src/utils/childrenUtils.ts index 55b76b61c14..9cacf87b0db 100644 --- a/packages/vue/src/utils/childrenUtils.ts +++ b/packages/vue/src/utils/childrenUtils.ts @@ -3,7 +3,14 @@ import { h, Text, type VNode } from 'vue'; import { errorThrower } from '../errors/errorThrower'; import { multipleChildrenInButtonComponent } from '../errors/messages'; -type ButtonName = 'SignInButton' | 'SignUpButton' | 'SignOutButton' | 'SignInWithMetamaskButton'; +type ButtonName = + | 'SignInButton' + | 'SignUpButton' + | 'SignOutButton' + | 'SignInWithMetamaskButton' + | 'SubscriptionDetailsButton' + | 'CheckoutButton' + | 'PlanDetailsButton'; export const normalizeWithDefaultValue = (slotContent: VNode[] | undefined, defaultValue: string) => { // Render a button with the default value if no slot content is provided From 15631976f21085260781b031106b6042548b8ed0 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 20 Aug 2025 16:05:01 +0300 Subject: [PATCH 02/14] add tests --- .../src/pages/billing/checkout-btn.astro | 18 ++++++++++++++++++ .../src/pages/billing/plan-details-btn.astro | 12 ++++++++++++ .../billing/subscription-details-btn.astro | 12 ++++++++++++ integration/templates/vue-vite/src/router.ts | 16 ++++++++++++++++ .../vue-vite/src/views/billing/CheckoutBtn.vue | 16 ++++++++++++++++ .../src/views/billing/PlanDetailsBtn.vue | 9 +++++++++ .../views/billing/SubscriptionDetailsBtn.vue | 9 +++++++++ integration/tests/pricing-table.test.ts | 14 -------------- 8 files changed, 92 insertions(+), 14 deletions(-) create mode 100644 integration/templates/astro-node/src/pages/billing/checkout-btn.astro create mode 100644 integration/templates/astro-node/src/pages/billing/plan-details-btn.astro create mode 100644 integration/templates/astro-node/src/pages/billing/subscription-details-btn.astro create mode 100644 integration/templates/vue-vite/src/views/billing/CheckoutBtn.vue create mode 100644 integration/templates/vue-vite/src/views/billing/PlanDetailsBtn.vue create mode 100644 integration/templates/vue-vite/src/views/billing/SubscriptionDetailsBtn.vue diff --git a/integration/templates/astro-node/src/pages/billing/checkout-btn.astro b/integration/templates/astro-node/src/pages/billing/checkout-btn.astro new file mode 100644 index 00000000000..9f3e10b2a32 --- /dev/null +++ b/integration/templates/astro-node/src/pages/billing/checkout-btn.astro @@ -0,0 +1,18 @@ +--- +import { SignedIn } from '@clerk/astro/components'; +import { CheckoutButton } from '@clerk/astro/components'; +import Layout from '../../layouts/Layout.astro'; +--- + + +
+ + + Checkout Now + + +
+
diff --git a/integration/templates/astro-node/src/pages/billing/plan-details-btn.astro b/integration/templates/astro-node/src/pages/billing/plan-details-btn.astro new file mode 100644 index 00000000000..85dda8f4be5 --- /dev/null +++ b/integration/templates/astro-node/src/pages/billing/plan-details-btn.astro @@ -0,0 +1,12 @@ +--- +import { PlanDetailsButton } from '@clerk/astro/components'; +import Layout from '../../layouts/Layout.astro'; +--- + + +
+ + Plan details + +
+
diff --git a/integration/templates/astro-node/src/pages/billing/subscription-details-btn.astro b/integration/templates/astro-node/src/pages/billing/subscription-details-btn.astro new file mode 100644 index 00000000000..f871fa33a03 --- /dev/null +++ b/integration/templates/astro-node/src/pages/billing/subscription-details-btn.astro @@ -0,0 +1,12 @@ +--- +import { SubscriptionDetailsButton } from '@clerk/astro/components'; +import Layout from '../../layouts/Layout.astro'; +--- + + +
+ + Subscription details + +
+
diff --git a/integration/templates/vue-vite/src/router.ts b/integration/templates/vue-vite/src/router.ts index c598b2b0bff..31fc822e18e 100644 --- a/integration/templates/vue-vite/src/router.ts +++ b/integration/templates/vue-vite/src/router.ts @@ -47,6 +47,22 @@ const routes = [ path: '/user', component: () => import('./views/Profile.vue'), }, + // Billing button routes + { + name: 'CheckoutBtn', + path: '/billing/checkout-btn', + component: () => import('./views/billing/CheckoutBtn.vue'), + }, + { + name: 'PlanDetailsBtn', + path: '/billing/plan-details-btn', + component: () => import('./views/billing/PlanDetailsBtn.vue'), + }, + { + name: 'SubscriptionDetailsBtn', + path: '/billing/subscription-details-btn', + component: () => import('./views/billing/SubscriptionDetailsBtn.vue'), + }, ]; const router = createRouter({ diff --git a/integration/templates/vue-vite/src/views/billing/CheckoutBtn.vue b/integration/templates/vue-vite/src/views/billing/CheckoutBtn.vue new file mode 100644 index 00000000000..c8713f51ad8 --- /dev/null +++ b/integration/templates/vue-vite/src/views/billing/CheckoutBtn.vue @@ -0,0 +1,16 @@ + + + diff --git a/integration/templates/vue-vite/src/views/billing/PlanDetailsBtn.vue b/integration/templates/vue-vite/src/views/billing/PlanDetailsBtn.vue new file mode 100644 index 00000000000..55446772fd3 --- /dev/null +++ b/integration/templates/vue-vite/src/views/billing/PlanDetailsBtn.vue @@ -0,0 +1,9 @@ + + + diff --git a/integration/templates/vue-vite/src/views/billing/SubscriptionDetailsBtn.vue b/integration/templates/vue-vite/src/views/billing/SubscriptionDetailsBtn.vue new file mode 100644 index 00000000000..e6fb55dc6a3 --- /dev/null +++ b/integration/templates/vue-vite/src/views/billing/SubscriptionDetailsBtn.vue @@ -0,0 +1,9 @@ + + + diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index 783ff3e7ada..3a07c1b0b22 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -29,10 +29,6 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('renders pricing details of a specific plan', async ({ page, context }) => { - if (!app.name.includes('next')) { - return; - } - const u = createTestUtils({ app, page, context }); await u.po.page.goToRelative('/billing/plan-details-btn'); @@ -62,10 +58,6 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('when signed in, clicking get started button opens checkout drawer', async ({ page, context }) => { - if (!app.name.includes('next')) { - return; - } - const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); @@ -92,9 +84,6 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('when signed in, clicking checkout button open checkout drawer', async ({ page, context }) => { - if (!app.name.includes('next')) { - return; - } const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); @@ -129,9 +118,6 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('Displays subscription details drawer', async ({ page, context }) => { - if (!app.name.includes('next')) { - return; - } const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); From 86b0d3be16b859bdb7f8799f7f0fc596238400ae Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 20 Aug 2025 16:12:19 +0300 Subject: [PATCH 03/14] mark as experimental --- .../src/pages/billing/checkout-btn.astro | 3 +-- .../src/pages/billing/plan-details-btn.astro | 2 +- .../billing/subscription-details-btn.astro | 2 +- .../vue-vite/src/views/billing/CheckoutBtn.vue | 3 ++- .../src/views/billing/PlanDetailsBtn.vue | 2 +- .../views/billing/SubscriptionDetailsBtn.vue | 2 +- packages/astro/src/astro-components/index.ts | 6 +++--- packages/astro/src/react/index.ts | 17 +++++++++++------ packages/vue/package.json | 4 ++++ packages/vue/src/components/index.ts | 3 --- packages/vue/src/experimental.ts | 9 +++++++++ packages/vue/src/index.ts | 6 ------ packages/vue/tsup.config.ts | 6 +++--- 13 files changed, 37 insertions(+), 28 deletions(-) create mode 100644 packages/vue/src/experimental.ts diff --git a/integration/templates/astro-node/src/pages/billing/checkout-btn.astro b/integration/templates/astro-node/src/pages/billing/checkout-btn.astro index 9f3e10b2a32..736992e6033 100644 --- a/integration/templates/astro-node/src/pages/billing/checkout-btn.astro +++ b/integration/templates/astro-node/src/pages/billing/checkout-btn.astro @@ -1,6 +1,5 @@ --- -import { SignedIn } from '@clerk/astro/components'; -import { CheckoutButton } from '@clerk/astro/components'; +import { SignedIn, __experimental_CheckoutButton as CheckoutButton } from '@clerk/astro/components'; import Layout from '../../layouts/Layout.astro'; --- diff --git a/integration/templates/astro-node/src/pages/billing/plan-details-btn.astro b/integration/templates/astro-node/src/pages/billing/plan-details-btn.astro index 85dda8f4be5..ed35edd3d51 100644 --- a/integration/templates/astro-node/src/pages/billing/plan-details-btn.astro +++ b/integration/templates/astro-node/src/pages/billing/plan-details-btn.astro @@ -1,5 +1,5 @@ --- -import { PlanDetailsButton } from '@clerk/astro/components'; +import { __experimental_PlanDetailsButton as PlanDetailsButton } from '@clerk/astro/components'; import Layout from '../../layouts/Layout.astro'; --- diff --git a/integration/templates/astro-node/src/pages/billing/subscription-details-btn.astro b/integration/templates/astro-node/src/pages/billing/subscription-details-btn.astro index f871fa33a03..b8233ffb34a 100644 --- a/integration/templates/astro-node/src/pages/billing/subscription-details-btn.astro +++ b/integration/templates/astro-node/src/pages/billing/subscription-details-btn.astro @@ -1,5 +1,5 @@ --- -import { SubscriptionDetailsButton } from '@clerk/astro/components'; +import { __experimental_SubscriptionDetailsButton as SubscriptionDetailsButton } from '@clerk/astro/components'; import Layout from '../../layouts/Layout.astro'; --- diff --git a/integration/templates/vue-vite/src/views/billing/CheckoutBtn.vue b/integration/templates/vue-vite/src/views/billing/CheckoutBtn.vue index c8713f51ad8..39c23365733 100644 --- a/integration/templates/vue-vite/src/views/billing/CheckoutBtn.vue +++ b/integration/templates/vue-vite/src/views/billing/CheckoutBtn.vue @@ -12,5 +12,6 @@ diff --git a/integration/templates/vue-vite/src/views/billing/PlanDetailsBtn.vue b/integration/templates/vue-vite/src/views/billing/PlanDetailsBtn.vue index 55446772fd3..cc51a1035bc 100644 --- a/integration/templates/vue-vite/src/views/billing/PlanDetailsBtn.vue +++ b/integration/templates/vue-vite/src/views/billing/PlanDetailsBtn.vue @@ -5,5 +5,5 @@ diff --git a/integration/templates/vue-vite/src/views/billing/SubscriptionDetailsBtn.vue b/integration/templates/vue-vite/src/views/billing/SubscriptionDetailsBtn.vue index e6fb55dc6a3..5ff010ab10c 100644 --- a/integration/templates/vue-vite/src/views/billing/SubscriptionDetailsBtn.vue +++ b/integration/templates/vue-vite/src/views/billing/SubscriptionDetailsBtn.vue @@ -5,5 +5,5 @@ diff --git a/packages/astro/src/astro-components/index.ts b/packages/astro/src/astro-components/index.ts index 970c1f45cf9..1fa8b1f554a 100644 --- a/packages/astro/src/astro-components/index.ts +++ b/packages/astro/src/astro-components/index.ts @@ -12,9 +12,9 @@ export { default as AuthenticateWithRedirectCallback } from './control/Authentic export { default as SignInButton } from './unstyled/SignInButton.astro'; export { default as SignUpButton } from './unstyled/SignUpButton.astro'; export { default as SignOutButton } from './unstyled/SignOutButton.astro'; -export { default as SubscriptionDetailsButton } from './unstyled/SubscriptionDetailsButton.astro'; -export { default as CheckoutButton } from './unstyled/CheckoutButton.astro'; -export { default as PlanDetailsButton } from './unstyled/PlanDetailsButton.astro'; +export { default as __experimental_SubscriptionDetailsButton } from './unstyled/SubscriptionDetailsButton.astro'; +export { default as __experimental_CheckoutButton } from './unstyled/CheckoutButton.astro'; +export { default as __experimental_PlanDetailsButton } from './unstyled/PlanDetailsButton.astro'; /** * UI Components diff --git a/packages/astro/src/react/index.ts b/packages/astro/src/react/index.ts index 107e54c2fa5..c16086cc435 100644 --- a/packages/astro/src/react/index.ts +++ b/packages/astro/src/react/index.ts @@ -1,20 +1,25 @@ +import { CheckoutButton, type CheckoutButtonProps } from './CheckoutButton'; +import { PlanDetailsButton, type PlanDetailsButtonProps } from './PlanDetailsButton'; import { SignInButton, type SignInButtonProps } from './SignInButton'; import { SignOutButton, type SignOutButtonProps } from './SignOutButton'; import { SignUpButton, type SignUpButtonProps } from './SignUpButton'; import { SubscriptionDetailsButton, type SubscriptionDetailsButtonProps } from './SubscriptionDetailsButton'; -import { CheckoutButton, type CheckoutButtonProps } from './CheckoutButton'; -import { PlanDetailsButton, type PlanDetailsButtonProps } from './PlanDetailsButton'; export * from './uiComponents'; export * from './controlComponents'; export * from './hooks'; -export { SignInButton, SignOutButton, SignUpButton, SubscriptionDetailsButton, CheckoutButton, PlanDetailsButton }; +export { SignInButton, SignOutButton, SignUpButton }; +export { + SubscriptionDetailsButton as __experimental_SubscriptionDetailsButton, + CheckoutButton as __experimental_CheckoutButton, + PlanDetailsButton as __experimental_PlanDetailsButton, +}; export type { SignInButtonProps, SignOutButtonProps, SignUpButtonProps, - SubscriptionDetailsButtonProps, - CheckoutButtonProps, - PlanDetailsButtonProps, + SubscriptionDetailsButtonProps as __experimental_SubscriptionDetailsButtonProps, + CheckoutButtonProps as __experimental_CheckoutButtonProps, + PlanDetailsButtonProps as __experimental_PlanDetailsButtonProps, }; diff --git a/packages/vue/package.json b/packages/vue/package.json index e5903dbd883..133db439ba2 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -30,6 +30,10 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" }, + "./experimental": { + "types": "./dist/experimental.d.ts", + "default": "./dist/experimental.js" + }, "./internal": { "types": "./dist/internal.d.ts", "default": "./dist/internal.js" diff --git a/packages/vue/src/components/index.ts b/packages/vue/src/components/index.ts index bc0110098ae..37827c8ad8a 100644 --- a/packages/vue/src/components/index.ts +++ b/packages/vue/src/components/index.ts @@ -28,6 +28,3 @@ export { default as SignInButton } from './SignInButton.vue'; export { default as SignUpButton } from './SignUpButton.vue'; export { default as SignOutButton } from './SignOutButton.vue'; export { default as SignInWithMetamaskButton } from './SignInWithMetamaskButton.vue'; -export { default as SubscriptionDetailsButton } from './SubscriptionDetailsButton.vue'; -export { default as CheckoutButton } from './CheckoutButton.vue'; -export { default as PlanDetailsButton } from './PlanDetailsButton.vue'; diff --git a/packages/vue/src/experimental.ts b/packages/vue/src/experimental.ts new file mode 100644 index 00000000000..e5814f1ec07 --- /dev/null +++ b/packages/vue/src/experimental.ts @@ -0,0 +1,9 @@ +export { default as SubscriptionDetailsButton } from './components/SubscriptionDetailsButton.vue'; +export { default as CheckoutButton } from './components/CheckoutButton.vue'; +export { default as PlanDetailsButton } from './components/PlanDetailsButton.vue'; + +export type { + __experimental_SubscriptionDetailsButtonProps as SubscriptionDetailsButtonProps, + __experimental_CheckoutButtonProps as CheckoutButtonProps, + __experimental_PlanDetailsButtonProps as PlanDetailsButtonProps, +} from '@clerk/types'; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 5d6c74fb8ca..325f66ea890 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -8,11 +8,5 @@ export * from './composables'; export { clerkPlugin, type PluginOptions } from './plugin'; export { updateClerkOptions } from './utils'; -export type { - __experimental_SubscriptionDetailsButtonProps as SubscriptionDetailsButtonProps, - __experimental_CheckoutButtonProps as CheckoutButtonProps, - __experimental_PlanDetailsButtonProps as PlanDetailsButtonProps, -} from '@clerk/types'; - setErrorThrowerOptions({ packageName: PACKAGE_NAME }); setClerkJsLoadingErrorPackageName(PACKAGE_NAME); diff --git a/packages/vue/tsup.config.ts b/packages/vue/tsup.config.ts index c5c1a246a46..fe7ddaac46c 100644 --- a/packages/vue/tsup.config.ts +++ b/packages/vue/tsup.config.ts @@ -9,7 +9,7 @@ type EsbuildPlugin = NonNullable[number]; export default defineConfig(() => { return { clean: true, - entry: ['./src/index.ts', './src/internal.ts', './src/errors.ts'], + entry: ['./src/index.ts', './src/experimental.ts', './src/internal.ts', './src/errors.ts'], format: ['esm'], bundle: true, sourcemap: true, @@ -17,13 +17,13 @@ export default defineConfig(() => { dts: false, esbuildPlugins: [ // Adds .vue files support - vuePlugin() as EsbuildPlugin, + vuePlugin(), // Automatically generates runtime props from TypeScript types/interfaces for all // control and UI components, adding them to Vue components during build via // Object.defineProperty autoPropsPlugin({ include: ['**/*.ts'], - }) as EsbuildPlugin, + }), ], define: { PACKAGE_NAME: `"${name}"`, From faa409ccbd491dfb679f9d4b195f4cc7fa727b46 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 20 Aug 2025 16:35:11 +0300 Subject: [PATCH 04/14] add changeset --- .changeset/cold-parks-push.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/cold-parks-push.md diff --git a/.changeset/cold-parks-push.md b/.changeset/cold-parks-push.md new file mode 100644 index 00000000000..b720789abec --- /dev/null +++ b/.changeset/cold-parks-push.md @@ -0,0 +1,6 @@ +--- +'@clerk/astro': minor +'@clerk/vue': minor +--- + +Expose billing buttons as experimental From 5224ca17df4fc0cf416e07f2eae62ab9ad3bc90c Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 20 Aug 2025 16:46:55 +0300 Subject: [PATCH 05/14] skip astro tests --- integration/tests/pricing-table.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index 3a07c1b0b22..be2ce4d558e 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -29,6 +29,8 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('renders pricing details of a specific plan', async ({ page, context }) => { + test.skip(app.name.includes('astro'), 'Still working on it'); + const u = createTestUtils({ app, page, context }); await u.po.page.goToRelative('/billing/plan-details-btn'); @@ -84,6 +86,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('when signed in, clicking checkout button open checkout drawer', async ({ page, context }) => { + test.skip(app.name.includes('astro'), 'Still working on it'); const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); @@ -118,6 +121,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('Displays subscription details drawer', async ({ page, context }) => { + test.skip(app.name.includes('astro'), 'Still working on it'); const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); From 070e81698a1aa694bd447ea84840a808af5e53e8 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 20 Aug 2025 16:53:42 +0300 Subject: [PATCH 06/14] add experimental tags --- packages/vue/src/experimental.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/vue/src/experimental.ts b/packages/vue/src/experimental.ts index e5814f1ec07..18816459452 100644 --- a/packages/vue/src/experimental.ts +++ b/packages/vue/src/experimental.ts @@ -1,9 +1,41 @@ +/** + * @experimental + * These components and their prop types are unstable and may change in future releases. + * They are part of Clerk's Billing feature which is available under public beta. + */ export { default as SubscriptionDetailsButton } from './components/SubscriptionDetailsButton.vue'; + +/** + * @experimental + * These components and their prop types are unstable and may change in future releases. + * They are part of Clerk's Billing feature which is available under public beta. + */ export { default as CheckoutButton } from './components/CheckoutButton.vue'; + +/** + * @experimental + * These components and their prop types are unstable and may change in future releases. + * They are part of Clerk's Billing feature which is available under public beta. + */ export { default as PlanDetailsButton } from './components/PlanDetailsButton.vue'; export type { + /** + * @experimental + * These components and their prop types are unstable and may change in future releases. + * They are part of Clerk's Billing feature which is available under public beta. + */ __experimental_SubscriptionDetailsButtonProps as SubscriptionDetailsButtonProps, + /** + * @experimental + * These components and their prop types are unstable and may change in future releases. + * They are part of Clerk's Billing feature which is available under public beta. + */ __experimental_CheckoutButtonProps as CheckoutButtonProps, + /** + * @experimental + * These components and their prop types are unstable and may change in future releases. + * They are part of Clerk's Billing feature which is available under public beta. + */ __experimental_PlanDetailsButtonProps as PlanDetailsButtonProps, } from '@clerk/types'; From 183001fe8f25b965fa25400c8f73908537178b22 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 20 Aug 2025 17:11:44 +0300 Subject: [PATCH 07/14] fix lint --- .../astro-components/unstyled/PlanDetailsButton.astro | 10 +--------- .../unstyled/SubscriptionDetailsButton.astro | 6 ++++-- packages/astro/src/react/CheckoutButton.tsx | 6 +++--- packages/astro/src/react/PlanDetailsButton.tsx | 6 +++--- packages/astro/src/react/SubscriptionDetailsButton.tsx | 6 +++--- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/packages/astro/src/astro-components/unstyled/PlanDetailsButton.astro b/packages/astro/src/astro-components/unstyled/PlanDetailsButton.astro index 6c102ef73a5..090c6d6f3c0 100644 --- a/packages/astro/src/astro-components/unstyled/PlanDetailsButton.astro +++ b/packages/astro/src/astro-components/unstyled/PlanDetailsButton.astro @@ -14,15 +14,7 @@ if ('as' in Astro.props) { logAsPropUsageDeprecation(); } -const { - as: Tag = 'button', - asChild, - plan, - planId, - initialPlanPeriod, - planDetailsProps, - ...props -} = Astro.props; +const { as: Tag = 'button', asChild, plan, planId, initialPlanPeriod, planDetailsProps, ...props } = Astro.props; const planDetailsOptions = { plan, diff --git a/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro b/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro index 90b13578819..c81d8ae6c3f 100644 --- a/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro +++ b/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro @@ -1,10 +1,12 @@ --- -import type { HTMLTag, Polymorphic } from 'astro/types'; import type { __experimental_SubscriptionDetailsButtonProps } from '@clerk/types'; + +import type { HTMLTag, Polymorphic } from 'astro/types'; import type { ButtonProps } from '../../types'; import { addUnstyledAttributeToFirstTag, logAsPropUsageDeprecation } from './utils'; -type Props = Polymorphic> & __experimental_SubscriptionDetailsButtonProps; +type Props = Polymorphic> & + __experimental_SubscriptionDetailsButtonProps; import { generateSafeId } from '@clerk/astro/internal'; diff --git a/packages/astro/src/react/CheckoutButton.tsx b/packages/astro/src/react/CheckoutButton.tsx index 193e74263b4..20e8cd7991b 100644 --- a/packages/astro/src/react/CheckoutButton.tsx +++ b/packages/astro/src/react/CheckoutButton.tsx @@ -1,8 +1,8 @@ import type { __experimental_CheckoutButtonProps } from '@clerk/types'; import React from 'react'; -import { assertSingleChild, normalizeWithDefaultValue, safeExecute, withClerk } from './utils'; import type { WithClerkProp } from './utils'; +import { assertSingleChild, normalizeWithDefaultValue, safeExecute, withClerk } from './utils'; export type { __experimental_CheckoutButtonProps as CheckoutButtonProps }; @@ -39,9 +39,9 @@ export const CheckoutButton = withClerk( }); }; - const wrappedChildClickHandler: React.MouseEventHandler = async e => { + const wrappedChildClickHandler: React.MouseEventHandler = e => { if (child && typeof child === 'object' && 'props' in child) { - await safeExecute(child.props.onClick)(e); + void safeExecute(child.props.onClick)(e); } return clickHandler(); }; diff --git a/packages/astro/src/react/PlanDetailsButton.tsx b/packages/astro/src/react/PlanDetailsButton.tsx index d4f1d8e2f72..a6b43316e5e 100644 --- a/packages/astro/src/react/PlanDetailsButton.tsx +++ b/packages/astro/src/react/PlanDetailsButton.tsx @@ -1,8 +1,8 @@ import type { __experimental_PlanDetailsButtonProps } from '@clerk/types'; import React from 'react'; -import { assertSingleChild, normalizeWithDefaultValue, safeExecute, withClerk } from './utils'; import type { WithClerkProp } from './utils'; +import { assertSingleChild, normalizeWithDefaultValue, safeExecute, withClerk } from './utils'; export type { __experimental_PlanDetailsButtonProps as PlanDetailsButtonProps }; @@ -26,9 +26,9 @@ export const PlanDetailsButton = withClerk( } as __experimental_PlanDetailsButtonProps); }; - const wrappedChildClickHandler: React.MouseEventHandler = async e => { + const wrappedChildClickHandler: React.MouseEventHandler = e => { if (child && typeof child === 'object' && 'props' in child) { - await safeExecute(child.props.onClick)(e); + void safeExecute(child.props.onClick)(e); } return clickHandler(); }; diff --git a/packages/astro/src/react/SubscriptionDetailsButton.tsx b/packages/astro/src/react/SubscriptionDetailsButton.tsx index 2038dc0387c..c737a72a909 100644 --- a/packages/astro/src/react/SubscriptionDetailsButton.tsx +++ b/packages/astro/src/react/SubscriptionDetailsButton.tsx @@ -1,8 +1,8 @@ import type { __experimental_SubscriptionDetailsButtonProps } from '@clerk/types'; import React from 'react'; -import { assertSingleChild, normalizeWithDefaultValue, safeExecute, withClerk } from './utils'; import type { WithClerkProp } from './utils'; +import { assertSingleChild, normalizeWithDefaultValue, safeExecute, withClerk } from './utils'; export type { __experimental_SubscriptionDetailsButtonProps as SubscriptionDetailsButtonProps }; @@ -31,9 +31,9 @@ export const SubscriptionDetailsButton = withClerk( }); }; - const wrappedChildClickHandler: React.MouseEventHandler = async e => { + const wrappedChildClickHandler: React.MouseEventHandler = e => { if (child && typeof child === 'object' && 'props' in child) { - await safeExecute(child.props.onClick)(e); + void safeExecute(child.props.onClick)(e); } return clickHandler(); }; From 2fdb49b2f985752ce81c6a66a3678eacb0d53035 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 20 Aug 2025 18:18:53 +0300 Subject: [PATCH 08/14] rename the test --- .../astro-node/src/pages/billing/plan-details-btn.astro | 2 +- integration/tests/pricing-table.test.ts | 2 +- packages/astro/src/astro-components/index.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/integration/templates/astro-node/src/pages/billing/plan-details-btn.astro b/integration/templates/astro-node/src/pages/billing/plan-details-btn.astro index ed35edd3d51..85dda8f4be5 100644 --- a/integration/templates/astro-node/src/pages/billing/plan-details-btn.astro +++ b/integration/templates/astro-node/src/pages/billing/plan-details-btn.astro @@ -1,5 +1,5 @@ --- -import { __experimental_PlanDetailsButton as PlanDetailsButton } from '@clerk/astro/components'; +import { PlanDetailsButton } from '@clerk/astro/components'; import Layout from '../../layouts/Layout.astro'; --- diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index be2ce4d558e..a74cb99c434 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -29,7 +29,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('renders pricing details of a specific plan', async ({ page, context }) => { - test.skip(app.name.includes('astro'), 'Still working on it'); + // test.skip(app.name.includes('astro'), 'Still working on it'); const u = createTestUtils({ app, page, context }); await u.po.page.goToRelative('/billing/plan-details-btn'); diff --git a/packages/astro/src/astro-components/index.ts b/packages/astro/src/astro-components/index.ts index 1fa8b1f554a..7f017e6fc6d 100644 --- a/packages/astro/src/astro-components/index.ts +++ b/packages/astro/src/astro-components/index.ts @@ -14,7 +14,7 @@ export { default as SignUpButton } from './unstyled/SignUpButton.astro'; export { default as SignOutButton } from './unstyled/SignOutButton.astro'; export { default as __experimental_SubscriptionDetailsButton } from './unstyled/SubscriptionDetailsButton.astro'; export { default as __experimental_CheckoutButton } from './unstyled/CheckoutButton.astro'; -export { default as __experimental_PlanDetailsButton } from './unstyled/PlanDetailsButton.astro'; +export { default as PlanDetailsButton } from './unstyled/PlanDetailsButton.astro'; /** * UI Components From c9e754eb369a8d70939752a3d79658ce0e6d9278 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Tue, 9 Sep 2025 17:08:11 +0300 Subject: [PATCH 09/14] remove skipped tests --- integration/tests/pricing-table.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index ed19032e2ba..149bd3eedd2 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -32,7 +32,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('renders pricing details of a specific plan', async ({ page, context }) => { - test.skip(app.name.includes('astro'), 'Still working on it'); + // test.skip(app.name.includes('astro'), 'Still working on it'); const u = createTestUtils({ app, page, context }); await u.po.page.goToRelative('/billing/plan-details-btn'); @@ -82,7 +82,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl page, context, }) => { - test.skip(app.name.includes('astro'), 'Still working on it'); + // test.skip(app.name.includes('astro'), 'Still working on it'); const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); @@ -97,7 +97,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('when signed in, clicking checkout button open checkout drawer', async ({ page, context }) => { - test.skip(app.name.includes('astro'), 'Still working on it'); + // test.skip(app.name.includes('astro'), 'Still working on it'); const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); @@ -132,7 +132,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('opens subscription details drawer', async ({ page, context }) => { - test.skip(app.name.includes('astro'), 'Still working on it'); + // test.skip(app.name.includes('astro'), 'Still working on it'); const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); From 46a3560431beba0b7530e1e3905f6e3c9411ff11 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Tue, 9 Sep 2025 17:27:07 +0300 Subject: [PATCH 10/14] Revert "remove skipped tests" This reverts commit c9e754eb369a8d70939752a3d79658ce0e6d9278. --- integration/tests/pricing-table.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index 149bd3eedd2..ed19032e2ba 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -32,7 +32,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('renders pricing details of a specific plan', async ({ page, context }) => { - // test.skip(app.name.includes('astro'), 'Still working on it'); + test.skip(app.name.includes('astro'), 'Still working on it'); const u = createTestUtils({ app, page, context }); await u.po.page.goToRelative('/billing/plan-details-btn'); @@ -82,7 +82,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl page, context, }) => { - // test.skip(app.name.includes('astro'), 'Still working on it'); + test.skip(app.name.includes('astro'), 'Still working on it'); const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); @@ -97,7 +97,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('when signed in, clicking checkout button open checkout drawer', async ({ page, context }) => { - // test.skip(app.name.includes('astro'), 'Still working on it'); + test.skip(app.name.includes('astro'), 'Still working on it'); const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); @@ -132,7 +132,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl }); test('opens subscription details drawer', async ({ page, context }) => { - // test.skip(app.name.includes('astro'), 'Still working on it'); + test.skip(app.name.includes('astro'), 'Still working on it'); const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); From 7b27c8a787afa3f1a0f03b8074593164cf7b3eb6 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Tue, 9 Sep 2025 17:41:09 +0300 Subject: [PATCH 11/14] Apply suggestions from code review --- .changeset/cold-parks-push.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/cold-parks-push.md b/.changeset/cold-parks-push.md index b720789abec..0bd5237b97c 100644 --- a/.changeset/cold-parks-push.md +++ b/.changeset/cold-parks-push.md @@ -1,6 +1,5 @@ --- '@clerk/astro': minor -'@clerk/vue': minor --- Expose billing buttons as experimental From dfc13a358cf715c81d64f91a15259ce6f8e13f22 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 11 Sep 2025 16:25:11 +0300 Subject: [PATCH 12/14] fix callback calling --- .../unstyled/CheckoutButton.astro | 12 +++++++++--- .../unstyled/SubscriptionDetailsButton.astro | 18 ++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/astro/src/astro-components/unstyled/CheckoutButton.astro b/packages/astro/src/astro-components/unstyled/CheckoutButton.astro index ac1eccba362..ab1eaccfcec 100644 --- a/packages/astro/src/astro-components/unstyled/CheckoutButton.astro +++ b/packages/astro/src/astro-components/unstyled/CheckoutButton.astro @@ -4,7 +4,8 @@ import type { __experimental_CheckoutButtonProps } from '@clerk/types'; import type { ButtonProps } from '../../types'; import { addUnstyledAttributeToFirstTag, logAsPropUsageDeprecation } from './utils'; -type Props = Polymorphic> & __experimental_CheckoutButtonProps; +type Props = Polymorphic> & + Omit<__experimental_CheckoutButtonProps, 'onSubscriptionComplete'> & { clickIdentifier?: string }; import { generateSafeId } from '@clerk/astro/internal'; @@ -20,7 +21,6 @@ const { planId, planPeriod, for: _for, - onSubscriptionComplete, newSubscriptionRedirectUrl, checkoutProps, ...props @@ -30,7 +30,6 @@ const checkoutOptions = { planId, planPeriod, for: _for, - onSubscriptionComplete, newSubscriptionRedirectUrl, ...checkoutProps, }; @@ -71,6 +70,13 @@ if (asChild) { throw new Error('Wrap `` with a check for an active organization.'); } + if (checkoutOptions.clickIdentifier) { + const clickEvent = new CustomEvent('clerk:subscription-complete', { detail: checkoutOptions.clickIdentifier }); + checkoutOptions.onSubscriptionComplete = () => { + document.dispatchEvent(clickEvent); + }; + } + return clerk.__internal_openCheckout(checkoutOptions); }); diff --git a/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro b/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro index c81d8ae6c3f..7c4ea001458 100644 --- a/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro +++ b/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro @@ -6,7 +6,7 @@ import type { ButtonProps } from '../../types'; import { addUnstyledAttributeToFirstTag, logAsPropUsageDeprecation } from './utils'; type Props = Polymorphic> & - __experimental_SubscriptionDetailsButtonProps; + Omit<__experimental_SubscriptionDetailsButtonProps, 'onSubscriptionCancel'> & { clickIdentifier?: string }; import { generateSafeId } from '@clerk/astro/internal'; @@ -16,18 +16,10 @@ if ('as' in Astro.props) { logAsPropUsageDeprecation(); } -const { - as: Tag = 'button', - asChild, - for: _for, - subscriptionDetailsProps, - onSubscriptionCancel, - ...props -} = Astro.props; +const { as: Tag = 'button', asChild, for: _for, subscriptionDetailsProps, ...props } = Astro.props; const subscriptionDetailsOptions = { for: _for, - onSubscriptionCancel, ...subscriptionDetailsProps, }; @@ -68,6 +60,12 @@ if (asChild) { 'Wrap `` with a check for an active organization.', ); } + if (subscriptionDetailsOptions.clickIdentifier) { + const clickEvent = new CustomEvent('clerk:subscription-cancel', { detail: subscriptionDetailsOptions.clickIdentifier }); + subscriptionDetailsOptions.onSubscriptionCancel = () => { + document.dispatchEvent(clickEvent); + }; + } return clerk.__internal_openSubscriptionDetails(subscriptionDetailsOptions); }); From 62dae1f1a0b97079e6338da591f5b0fcd8a3ecc6 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 11 Sep 2025 16:28:30 +0300 Subject: [PATCH 13/14] fix format --- .../astro-components/unstyled/SubscriptionDetailsButton.astro | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro b/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro index 7c4ea001458..952cb86f820 100644 --- a/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro +++ b/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro @@ -61,7 +61,9 @@ if (asChild) { ); } if (subscriptionDetailsOptions.clickIdentifier) { - const clickEvent = new CustomEvent('clerk:subscription-cancel', { detail: subscriptionDetailsOptions.clickIdentifier }); + const clickEvent = new CustomEvent('clerk:subscription-cancel', { + detail: subscriptionDetailsOptions.clickIdentifier, + }); subscriptionDetailsOptions.onSubscriptionCancel = () => { document.dispatchEvent(clickEvent); }; From ff60376e26419aeef17912f7de0b6d1c6527e259 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 11 Sep 2025 16:48:12 +0300 Subject: [PATCH 14/14] fix --- .../astro/src/astro-components/unstyled/CheckoutButton.astro | 4 ++++ .../astro-components/unstyled/SubscriptionDetailsButton.astro | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/astro/src/astro-components/unstyled/CheckoutButton.astro b/packages/astro/src/astro-components/unstyled/CheckoutButton.astro index ab1eaccfcec..e232b4a9b9a 100644 --- a/packages/astro/src/astro-components/unstyled/CheckoutButton.astro +++ b/packages/astro/src/astro-components/unstyled/CheckoutButton.astro @@ -23,6 +23,7 @@ const { for: _for, newSubscriptionRedirectUrl, checkoutProps, + clickIdentifier, ...props } = Astro.props; @@ -31,6 +32,7 @@ const checkoutOptions = { planPeriod, for: _for, newSubscriptionRedirectUrl, + clickIdentifier, ...checkoutProps, }; @@ -70,6 +72,8 @@ if (asChild) { throw new Error('Wrap `` with a check for an active organization.'); } + console.log('checkoutOptions', checkoutOptions); + if (checkoutOptions.clickIdentifier) { const clickEvent = new CustomEvent('clerk:subscription-complete', { detail: checkoutOptions.clickIdentifier }); checkoutOptions.onSubscriptionComplete = () => { diff --git a/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro b/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro index 952cb86f820..387cf21e7b2 100644 --- a/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro +++ b/packages/astro/src/astro-components/unstyled/SubscriptionDetailsButton.astro @@ -16,10 +16,11 @@ if ('as' in Astro.props) { logAsPropUsageDeprecation(); } -const { as: Tag = 'button', asChild, for: _for, subscriptionDetailsProps, ...props } = Astro.props; +const { as: Tag = 'button', asChild, for: _for, clickIdentifier, subscriptionDetailsProps, ...props } = Astro.props; const subscriptionDetailsOptions = { for: _for, + clickIdentifier, ...subscriptionDetailsProps, };