Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions .changeset/twelve-ideas-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/nuxt': minor
'@clerk/vue': minor
---

Introducing `<experimental_PricingTable/>`
8 changes: 6 additions & 2 deletions integration/presets/longRunningApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ export const createLongRunningApps = () => {
config: next.appRouter,
env: envs.withSessionTasks,
},
{ id: 'next.appRouter.withBillingStaging', config: next.appRouter, env: envs.withBillingStaging },
{ id: 'next.appRouter.withBilling', config: next.appRouter, env: envs.withBilling },
{ id: 'withBillingStaging.next.appRouter', config: next.appRouter, env: envs.withBillingStaging },
{ id: 'withBilling.next.appRouter', config: next.appRouter, env: envs.withBilling },
{ id: 'withBillingStaging.vue.vite', config: vue.vite, env: envs.withBillingStaging },
{ id: 'withBilling.vue.vite', config: vue.vite, env: envs.withBilling },
// { id: 'withBillingStaging.nuxt.node', config: nuxt.node, env: envs.withBillingStaging },
// { id: 'withBilling.nuxt.node', config: nuxt.node, env: envs.withBilling },
{
id: 'next.appRouter.withLegalConsent',
config: next.appRouter,
Expand Down
3 changes: 3 additions & 0 deletions integration/templates/nuxt-node/pages/pricing-table.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<experimental_PricingTable />
</template>
11 changes: 11 additions & 0 deletions integration/templates/vue-vite/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ const routes = [
path: '/custom-pages/organization-profile',
component: () => import('./views/custom-pages/OrganizationProfile.vue'),
},
{
name: 'PricingTable',
path: '/pricing-table',
component: () => import('./views/PricingTable.vue'),
},
// This was added for billing tests
{
name: 'User',
path: '/user',
component: () => import('./views/Profile.vue'),
},
];

const router = createRouter({
Expand Down
7 changes: 7 additions & 0 deletions integration/templates/vue-vite/src/views/PricingTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script setup lang="ts">
import { experimental_PricingTable } from '@clerk/vue';
</script>

<template>
<experimental_PricingTable />
</template>
3 changes: 1 addition & 2 deletions integration/testUtils/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { createClerkClient as backendCreateClerkClient } from '@clerk/backend';
import { createPageObjects, createAppPageObject, type EnhancedPage } from '@clerk/testing/playwright/unstable';
import { createAppPageObject, createPageObjects, type EnhancedPage } from '@clerk/testing/playwright/unstable';
import type { Browser, BrowserContext, Page } from '@playwright/test';

import type { Application } from '../models/application';
import { createEmailService } from './emailService';
import { createInvitationService } from './invitationsService';
import { createOrganizationsService } from './organizationsService';

import type { FakeOrganization, FakeUser } from './usersService';
import { createUserService } from './usersService';

Expand Down
15 changes: 14 additions & 1 deletion integration/tests/pricing-table.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl
test('renders pricing table with plans', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.po.page.goToRelative('/pricing-table');

await u.po.page.locator('.cl-pricingTable-root').waitFor({ state: 'attached' });

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 }) => {
const u = createTestUtils({ app, page, context });
await u.po.page.goToRelative('/pricing-table');
await u.po.page.locator('.cl-pricingTable-root').waitFor({ state: 'attached' });

await u.po.page.getByText('Get started').first().click();
await u.po.signIn.waitForMounted();
await expect(u.po.page.getByText('Checkout')).toBeHidden();
Expand All @@ -39,18 +44,23 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl
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.page.locator('.cl-pricingTable-root').waitFor({ state: 'attached' });
await u.po.page.getByText('Get started').first().click();
await expect(u.po.page.getByText('Checkout')).toBeVisible();
await u.po.page.locator('.cl-checkout-root').waitFor({ state: 'attached' });
await expect(u.po.page.getByText(/Checkout/i)).toBeVisible();
});

test('can subscribe to a plan', 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.page.locator('.cl-pricingTable-root').waitFor({ state: 'attached' });
// We have two plans, so subscribe to the first one
await u.po.page.getByText('Get started').first().click();

await u.po.page.locator('.cl-checkout-root').waitFor({ state: 'attached' });

// Stripe uses multiple iframes, so we need to find the correct one
const frame = u.po.page.frameLocator('iframe[src*="elements-inner-payment"]');
await frame.getByLabel('Card number').fill('4242424242424242');
Expand All @@ -67,7 +77,10 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl
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.page.locator('.cl-pricingTable-root').waitFor({ state: 'attached' });

await u.po.page.getByText('Manage subscription').click();

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();
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"test:integration:ap-flows": "pnpm test:integration:base --grep @ap-flows",
"test:integration:astro": "E2E_APP_ID=astro.* pnpm test:integration:base --grep @astro",
"test:integration:base": "pnpm playwright test --config integration/playwright.config.ts",
"test:integration:billing": "E2E_APP_ID=next.appRouter.withBilling pnpm test:integration:base --grep @billing",
"test:integration:billing": "E2E_APP_ID=withBilling.* pnpm test:integration:base --grep @billing",
"test:integration:cleanup": "pnpm playwright test --config integration/playwright.cleanup.config.ts",
"test:integration:deployment:nextjs": "pnpm playwright test --config integration/playwright.deployments.config.ts",
"test:integration:elements": "E2E_APP_ID=elements.* pnpm test:integration:base --grep @elements",
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export default defineNuxtModule<ModuleOptions>({
'SignedIn',
'SignedOut',
'Waitlist',
'experimental_PricingTable',
];
components.forEach(component => {
void addComponent({
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt/src/runtime/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ export {
SignUpButton,
SignOutButton,
SignInWithMetamaskButton,
experimental_PricingTable,
} from '@clerk/vue';
1 change: 1 addition & 0 deletions packages/vue/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { default as GoogleOneTap } from './ui-components/GoogleOneTap.vue';
export { default as Waitlist } from './ui-components/Waitlist.vue';
export { default as CreateOrganization } from './ui-components/CreateOrganization.vue';
export { default as OrganizationList } from './ui-components/OrganizationList.vue';
export { default as experimental_PricingTable } from './ui-components/PricingTable.vue';
export { UserProfile } from './ui-components/UserProfile';
export { OrganizationProfile } from './ui-components/OrganizationProfile';
export { OrganizationSwitcher } from './ui-components/OrganizationSwitcher';
Expand Down
17 changes: 17 additions & 0 deletions packages/vue/src/components/ui-components/PricingTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
import { ClerkHostRenderer } from '../ClerkHostRenderer';
import type { __experimental_PricingTableProps } from '@clerk/types';
import { useClerk } from '../../composables';

const clerk = useClerk();
const props = defineProps<__experimental_PricingTableProps>();
</script>

<template>
<ClerkHostRenderer
:mount="clerk?.__experimental_mountPricingTable"
:unmount="clerk?.__experimental_unmountPricingTable"
:update-props="(clerk as any)?.__unstable__updateProps"
:props="props"
/>
</template>