diff --git a/.changeset/big-trains-beg.md b/.changeset/big-trains-beg.md new file mode 100644 index 00000000000..2b690092cd5 --- /dev/null +++ b/.changeset/big-trains-beg.md @@ -0,0 +1,5 @@ +--- +"@clerk/astro": minor +--- + +Add support for custom pages and links in the `` Astro component. diff --git a/integration/templates/astro-node/src/pages/custom-pages.astro b/integration/templates/astro-node/src/pages/custom-pages.astro new file mode 100644 index 00000000000..21d598adc7b --- /dev/null +++ b/integration/templates/astro-node/src/pages/custom-pages.astro @@ -0,0 +1,22 @@ +--- +import { UserProfile } from "@clerk/astro/components"; +import Layout from "../layouts/Layout.astro"; +--- + + + + + + Icon + + Custom Terms Page + This is the custom terms page + + + + Icon + + + + + diff --git a/integration/tests/astro/components.test.ts b/integration/tests/astro/components.test.ts index 7ddba1bdc3c..62a697e71d9 100644 --- a/integration/tests/astro/components.test.ts +++ b/integration/tests/astro/components.test.ts @@ -157,6 +157,33 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f await expect(u.page.getByText(`"firstName":"${fakeAdmin.firstName}"`)).toBeVisible(); }); + test('render user profile with custom pages and links', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.page.goToRelative('/sign-in'); + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeAdmin.email, password: fakeAdmin.password }); + await u.po.expect.toBeSignedIn(); + + await u.page.goToRelative('/custom-pages'); + await u.po.userProfile.waitForMounted(); + + // Check if custom pages and links are visible + await expect(u.page.getByRole('button', { name: /Terms/i })).toBeVisible(); + await expect(u.page.getByRole('button', { name: /Homepage/i })).toBeVisible(); + + // Navigate to custom page + await u.page.getByRole('button', { name: /Terms/i }).click(); + await expect(u.page.getByRole('heading', { name: 'Custom Terms Page' })).toBeVisible(); + + // Check reordered default label. Security tab is now the last item. + await u.page.locator('.cl-navbarButton').nth(3).click(); + await expect(u.page.getByRole('heading', { name: 'Security' })).toBeVisible(); + + // Click custom link and check navigation + await u.page.getByRole('button', { name: /Homepage/i }).click(); + await u.page.waitForAppUrl('/'); + }); + test('redirects to sign-in when unauthenticated', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); await u.page.goToRelative('/user'); diff --git a/packages/astro/.eslintrc.cjs b/packages/astro/.eslintrc.cjs index 7288f46d3b7..39dd0364e5a 100644 --- a/packages/astro/.eslintrc.cjs +++ b/packages/astro/.eslintrc.cjs @@ -4,7 +4,11 @@ module.exports = { rules: { 'import/no-unresolved': ['error', { ignore: ['^#'] }], }, - ignorePatterns: ['src/astro-components/index.ts', 'src/astro-components/interactive/UserButton/index.ts'], + ignorePatterns: [ + 'src/astro-components/index.ts', + 'src/astro-components/interactive/UserButton/index.ts', + 'src/astro-components/interactive/UserProfile/index.ts', + ], overrides: [ { files: ['./env.d.ts'], diff --git a/packages/astro/src/astro-components/index.ts b/packages/astro/src/astro-components/index.ts index e78da856ec3..dd6725c1642 100644 --- a/packages/astro/src/astro-components/index.ts +++ b/packages/astro/src/astro-components/index.ts @@ -19,7 +19,7 @@ export { default as SignOutButton } from './unstyled/SignOutButton.astro'; export { default as SignIn } from './interactive/SignIn.astro'; export { default as SignUp } from './interactive/SignUp.astro'; export { UserButton } from './interactive/UserButton'; -export { default as UserProfile } from './interactive/UserProfile.astro'; +export { UserProfile } from './interactive/UserProfile'; export { default as OrganizationProfile } from './interactive/OrganizationProfile.astro'; export { default as OrganizationSwitcher } from './interactive/OrganizationSwitcher.astro'; export { default as OrganizationList } from './interactive/OrganizationList.astro'; diff --git a/packages/astro/src/astro-components/interactive/UserProfile.astro b/packages/astro/src/astro-components/interactive/UserProfile.astro deleted file mode 100644 index 1cf962e5d2a..00000000000 --- a/packages/astro/src/astro-components/interactive/UserProfile.astro +++ /dev/null @@ -1,8 +0,0 @@ ---- -import type { UserProfileProps } from "@clerk/types"; -type Props = UserProfileProps - -import InternalUIComponentRenderer from './InternalUIComponentRenderer.astro' ---- - - diff --git a/packages/astro/src/astro-components/interactive/UserProfile/ProfileRenderer.astro b/packages/astro/src/astro-components/interactive/UserProfile/ProfileRenderer.astro new file mode 100644 index 00000000000..b099591d884 --- /dev/null +++ b/packages/astro/src/astro-components/interactive/UserProfile/ProfileRenderer.astro @@ -0,0 +1,60 @@ +--- +interface Props { + url: string + label: string + type: 'page' | 'link' +} + +const { url, label, type } = Astro.props + +let labelIcon = ''; +let content = '' + +if (Astro.slots.has('label-icon')) { + labelIcon = await Astro.slots.render('label-icon'); +} + +if (Astro.slots.has('default') && type === 'page') { + content = await Astro.slots.render('default'); +} +--- + + diff --git a/packages/astro/src/astro-components/interactive/UserProfile/UserProfile.astro b/packages/astro/src/astro-components/interactive/UserProfile/UserProfile.astro new file mode 100644 index 00000000000..e9e3cfef6cb --- /dev/null +++ b/packages/astro/src/astro-components/interactive/UserProfile/UserProfile.astro @@ -0,0 +1,10 @@ +--- +import type { UserProfileProps, Without } from '@clerk/types' + +type Props = Without + +import InternalUIComponentRenderer from '../InternalUIComponentRenderer.astro' +--- + + + diff --git a/packages/astro/src/astro-components/interactive/UserProfile/UserProfileLink.astro b/packages/astro/src/astro-components/interactive/UserProfile/UserProfileLink.astro new file mode 100644 index 00000000000..211dfa40026 --- /dev/null +++ b/packages/astro/src/astro-components/interactive/UserProfile/UserProfileLink.astro @@ -0,0 +1,14 @@ +--- +import ProfileRenderer from './ProfileRenderer.astro' + +interface Props { + url: string + label: string +} + +const { url, label } = Astro.props +--- + + + + diff --git a/packages/astro/src/astro-components/interactive/UserProfile/UserProfilePage.astro b/packages/astro/src/astro-components/interactive/UserProfile/UserProfilePage.astro new file mode 100644 index 00000000000..01c933bde70 --- /dev/null +++ b/packages/astro/src/astro-components/interactive/UserProfile/UserProfilePage.astro @@ -0,0 +1,23 @@ +--- +import ProfileRenderer from './ProfileRenderer.astro' + +type ReorderItemsLabels = 'account' | 'security' + +type Props = { + label: Label +} & (Label extends ReorderItemsLabels + ? { + url?: string + } + : { + url: string + } +) + +const { url, label } = Astro.props +--- + + + + + diff --git a/packages/astro/src/astro-components/interactive/UserProfile/index.ts b/packages/astro/src/astro-components/interactive/UserProfile/index.ts new file mode 100644 index 00000000000..91db3a87a26 --- /dev/null +++ b/packages/astro/src/astro-components/interactive/UserProfile/index.ts @@ -0,0 +1,8 @@ +import _UserProfile from './UserProfile.astro'; +import UserProfileLink from './UserProfileLink.astro'; +import UserProfilePage from './UserProfilePage.astro'; + +export const UserProfile = Object.assign(_UserProfile, { + Page: UserProfilePage, + Link: UserProfileLink, +});
This is the custom terms page