From 66e2be7052f7891ef565822462619791c277bc0b Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 18 Feb 2025 14:28:00 -0800 Subject: [PATCH 1/9] fix(astro): Avoid naming conflict with Protect component and server islands fallback --- .../astro/src/astro-components/control/Protect.astro | 1 + .../astro/src/astro-components/control/ProtectSSR.astro | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/astro/src/astro-components/control/Protect.astro b/packages/astro/src/astro-components/control/Protect.astro index e1ccd138c31..33e87bb2bd3 100644 --- a/packages/astro/src/astro-components/control/Protect.astro +++ b/packages/astro/src/astro-components/control/Protect.astro @@ -31,5 +31,6 @@ const ProtectComponent = isStaticOutput(isStatic) ? ProtectCSR : ProtectSSR; + diff --git a/packages/astro/src/astro-components/control/ProtectSSR.astro b/packages/astro/src/astro-components/control/ProtectSSR.astro index 4f0d58a98ea..236e0b9f025 100644 --- a/packages/astro/src/astro-components/control/ProtectSSR.astro +++ b/packages/astro/src/astro-components/control/ProtectSSR.astro @@ -9,6 +9,13 @@ const isUnauthorized = (typeof Astro.props.condition === "function" && !Astro.props.condition(has)) || ((Astro.props.role || Astro.props.permission) && !has(Astro.props)); + +// Note: Astro server islands also use a "fallback" slot for loading states +// See: https://docs.astro.build/en/guides/server-islands/#server-island-fallback-content +// We use "protect-fallback" as our preferred slot name to avoid conflicts +const fallbackSlot = Astro.slots.has('protect-fallback') + ? 'protect-fallback' + : 'fallback'; --- -{isUnauthorized ? : } +{isUnauthorized ? : } From 829e90054c6cf3e4c1c573ea90e9b824af133c1e Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 18 Feb 2025 14:51:42 -0800 Subject: [PATCH 2/9] test(astro): Test new slot --- .../astro-node/src/pages/server-islands.astro | 20 ++++++++++++ integration/tests/astro/components.test.ts | 31 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 integration/templates/astro-node/src/pages/server-islands.astro diff --git a/integration/templates/astro-node/src/pages/server-islands.astro b/integration/templates/astro-node/src/pages/server-islands.astro new file mode 100644 index 00000000000..43fd450440e --- /dev/null +++ b/integration/templates/astro-node/src/pages/server-islands.astro @@ -0,0 +1,20 @@ +--- +import { Protect } from "@clerk/astro/components"; +import Layout from "../layouts/Layout.astro"; +--- + + +
+ +

Loading

+ +

Not an admin

+ Go to Members Page +
+

I'm an admin

+
+
+
diff --git a/integration/tests/astro/components.test.ts b/integration/tests/astro/components.test.ts index ed88f2a29e4..e162713278e 100644 --- a/integration/tests/astro/components.test.ts +++ b/integration/tests/astro/components.test.ts @@ -481,4 +481,35 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f // Components should be rendered on hard reload await u.po.userButton.waitForMounted(); }); + + test('server islands protect component shows correct states', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + // Initial visit - should show loading state + await u.page.goToRelative('/server-islands'); + await expect(u.page.getByText('Loading')).toBeVisible(); + + // Wait for loading to disappear before checking next state + await u.page.getByText('Loading').waitFor({ state: 'hidden' }); + await expect(u.page.getByText('Not an admin')).toBeVisible(); + + // Sign in as admin user + await u.page.goToRelative('/sign-in'); + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ + email: fakeAdmin2.email, + password: fakeAdmin2.password, + }); + await u.po.expect.toBeSignedIn(); + await u.po.organizationSwitcher.waitForMounted(); + await u.po.organizationSwitcher.waitForAnOrganizationToSelected(); + + // Visit page again - loading state should still show first + await u.page.goToRelative('/server-islands'); + await expect(u.page.getByText('Loading')).toBeVisible(); + + // Wait for loading to disappear before checking authorized state + await u.page.getByText('Loading').waitFor({ state: 'hidden' }); + await expect(u.page.getByText("I'm an admin")).toBeVisible(); + }); }); From 40584fb5fefddfcad9ffbd0e1471f81dde74af8f Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 18 Feb 2025 15:07:22 -0800 Subject: [PATCH 3/9] chore(astro): Fix incorrect directive --- integration/templates/astro-node/src/pages/server-islands.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/templates/astro-node/src/pages/server-islands.astro b/integration/templates/astro-node/src/pages/server-islands.astro index 43fd450440e..9cda7984925 100644 --- a/integration/templates/astro-node/src/pages/server-islands.astro +++ b/integration/templates/astro-node/src/pages/server-islands.astro @@ -5,7 +5,7 @@ import Layout from "../layouts/Layout.astro";
- +

Loading

Not an admin

From 4c6b1fe26503c6a997b37033d0504bbc838dea6e Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Wed, 19 Feb 2025 17:05:03 -0800 Subject: [PATCH 4/9] chore(astro): Make sure only one fallback slot renders --- .../astro-components/control/Protect.astro | 12 +++++++++-- .../astro-components/control/ProtectSSR.astro | 21 ++++++++++++------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/astro/src/astro-components/control/Protect.astro b/packages/astro/src/astro-components/control/Protect.astro index 33e87bb2bd3..1868132bd34 100644 --- a/packages/astro/src/astro-components/control/Protect.astro +++ b/packages/astro/src/astro-components/control/Protect.astro @@ -27,10 +27,18 @@ type Props = ProtectProps & { const { isStatic, ...props } = Astro.props; const ProtectComponent = isStaticOutput(isStatic) ? ProtectCSR : ProtectSSR; + +// Note: Astro server islands also use a "fallback" slot for loading states +// See: https://docs.astro.build/en/guides/server-islands/#server-island-fallback-content +// We use "protect-fallback" as our preferred slot name to avoid conflicts +const hasProtectFallback = Astro.slots.has('protect-fallback'); --- - - + {hasProtectFallback ? ( + + ) : ( + + )} diff --git a/packages/astro/src/astro-components/control/ProtectSSR.astro b/packages/astro/src/astro-components/control/ProtectSSR.astro index 236e0b9f025..4a1199d207a 100644 --- a/packages/astro/src/astro-components/control/ProtectSSR.astro +++ b/packages/astro/src/astro-components/control/ProtectSSR.astro @@ -9,13 +9,18 @@ const isUnauthorized = (typeof Astro.props.condition === "function" && !Astro.props.condition(has)) || ((Astro.props.role || Astro.props.permission) && !has(Astro.props)); - -// Note: Astro server islands also use a "fallback" slot for loading states -// See: https://docs.astro.build/en/guides/server-islands/#server-island-fallback-content -// We use "protect-fallback" as our preferred slot name to avoid conflicts -const fallbackSlot = Astro.slots.has('protect-fallback') - ? 'protect-fallback' - : 'fallback'; + +const hasProtectFallback = Astro.slots.has('protect-fallback'); --- -{isUnauthorized ? : } +{ + isUnauthorized ? ( + hasProtectFallback ? ( + + ) : ( + + ) + ) : ( + + ) +} From 6cb4695e4ab7d2324c7afb4c18fdb7c6e7a8e74a Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Wed, 19 Feb 2025 17:14:19 -0800 Subject: [PATCH 5/9] test update --- integration/tests/astro/components.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration/tests/astro/components.test.ts b/integration/tests/astro/components.test.ts index e162713278e..b584a0bee72 100644 --- a/integration/tests/astro/components.test.ts +++ b/integration/tests/astro/components.test.ts @@ -487,6 +487,8 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f // Initial visit - should show loading state await u.page.goToRelative('/server-islands'); + await u.page.waitForClerkJsLoaded(); + await expect(u.page.getByText('Loading')).toBeVisible(); // Wait for loading to disappear before checking next state From 6fd99f7d1df1bd0cbbba7270d98a23e316048a0f Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 20 Feb 2025 11:47:17 -0800 Subject: [PATCH 6/9] test update --- integration/tests/astro/components.test.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/integration/tests/astro/components.test.ts b/integration/tests/astro/components.test.ts index b584a0bee72..1bdbe2787b8 100644 --- a/integration/tests/astro/components.test.ts +++ b/integration/tests/astro/components.test.ts @@ -487,13 +487,13 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f // Initial visit - should show loading state await u.page.goToRelative('/server-islands'); - await u.page.waitForClerkJsLoaded(); - - await expect(u.page.getByText('Loading')).toBeVisible(); - // Wait for loading to disappear before checking next state + // Initial visit - wait for loading state to appear and then disappear + await u.page.getByText('Loading').waitFor({ state: 'visible' }); await u.page.getByText('Loading').waitFor({ state: 'hidden' }); - await expect(u.page.getByText('Not an admin')).toBeVisible(); + + // Wait for next state to be ready + await u.page.getByText('Not an admin').waitFor({ state: 'visible' }); // Sign in as admin user await u.page.goToRelative('/sign-in'); @@ -508,10 +508,12 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f // Visit page again - loading state should still show first await u.page.goToRelative('/server-islands'); - await expect(u.page.getByText('Loading')).toBeVisible(); - // Wait for loading to disappear before checking authorized state + // Initial visit - wait for loading state to appear and then disappear + await u.page.getByText('Loading').waitFor({ state: 'visible' }); await u.page.getByText('Loading').waitFor({ state: 'hidden' }); - await expect(u.page.getByText("I'm an admin")).toBeVisible(); + + // Wait for loading to disappear before checking authorized state + await u.page.getByText("I'm an admin").waitFor({ state: 'visible' }); }); }); From d9659db96a03bc81a457f2d6229097051e84da05 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 20 Feb 2025 12:35:46 -0800 Subject: [PATCH 7/9] fix tests --- integration/presets/astro.ts | 2 +- .../templates/astro-hybrid/package.json | 1 + integration/templates/astro-node/package.json | 1 + integration/tests/astro/components.test.ts | 23 ++++++------------- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/integration/presets/astro.ts b/integration/presets/astro.ts index 7995725b97b..2b5a6ddb38f 100644 --- a/integration/presets/astro.ts +++ b/integration/presets/astro.ts @@ -10,7 +10,7 @@ const astroNode = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm preview') - .addDependency('@clerk/astro', linkPackage('astro')) + // .addDependency('@clerk/astro', linkPackage('astro')) .addDependency('@clerk/types', linkPackage('types')) .addDependency('@clerk/localizations', linkPackage('localizations')); diff --git a/integration/templates/astro-hybrid/package.json b/integration/templates/astro-hybrid/package.json index ea0b0ee4ce8..2fb6e4aa4e4 100644 --- a/integration/templates/astro-hybrid/package.json +++ b/integration/templates/astro-hybrid/package.json @@ -13,6 +13,7 @@ "@astrojs/check": "^0.9.4", "@astrojs/node": "^8.3.4", "@astrojs/react": "^3.6.2", + "@clerk/astro": "2.3.0-snapshot.v20250220200839", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "astro": "^4.15.11", diff --git a/integration/templates/astro-node/package.json b/integration/templates/astro-node/package.json index 075c63a704f..fcb99a0e91a 100644 --- a/integration/templates/astro-node/package.json +++ b/integration/templates/astro-node/package.json @@ -14,6 +14,7 @@ "@astrojs/node": "^9.0.0", "@astrojs/react": "^4.0.0", "@astrojs/tailwind": "^5.1.3", + "@clerk/astro": "2.3.0-snapshot.v20250220200839", "@types/react": "18.3.7", "@types/react-dom": "18.3.0", "astro": "^5.0.3", diff --git a/integration/tests/astro/components.test.ts b/integration/tests/astro/components.test.ts index 1bdbe2787b8..cce99fb9b20 100644 --- a/integration/tests/astro/components.test.ts +++ b/integration/tests/astro/components.test.ts @@ -485,15 +485,11 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f test('server islands protect component shows correct states', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); - // Initial visit - should show loading state await u.page.goToRelative('/server-islands'); - - // Initial visit - wait for loading state to appear and then disappear - await u.page.getByText('Loading').waitFor({ state: 'visible' }); - await u.page.getByText('Loading').waitFor({ state: 'hidden' }); - - // Wait for next state to be ready - await u.page.getByText('Not an admin').waitFor({ state: 'visible' }); + // The loading slot for server islands will appear very quickly. + // Wait for next state (default slot) to be ready + await expect(u.page.getByText('Loading')).toBeHidden(); + await expect(u.page.getByText('Not an admin')).toBeVisible(); // Sign in as admin user await u.page.goToRelative('/sign-in'); @@ -506,14 +502,9 @@ testAgainstRunningApps({ withPattern: ['astro.node.withCustomRoles'] })('basic f await u.po.organizationSwitcher.waitForMounted(); await u.po.organizationSwitcher.waitForAnOrganizationToSelected(); - // Visit page again - loading state should still show first + // Visit page again await u.page.goToRelative('/server-islands'); - - // Initial visit - wait for loading state to appear and then disappear - await u.page.getByText('Loading').waitFor({ state: 'visible' }); - await u.page.getByText('Loading').waitFor({ state: 'hidden' }); - - // Wait for loading to disappear before checking authorized state - await u.page.getByText("I'm an admin").waitFor({ state: 'visible' }); + await expect(u.page.getByText('Loading')).toBeHidden(); + await expect(u.page.getByText("I'm an admin")).toBeVisible(); }); }); From efd2a418230283e6636a43c09c51c872e32505ba Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 20 Feb 2025 12:36:15 -0800 Subject: [PATCH 8/9] remove temporary astro versions --- integration/presets/astro.ts | 2 +- integration/templates/astro-hybrid/package.json | 1 - integration/templates/astro-node/package.json | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/integration/presets/astro.ts b/integration/presets/astro.ts index 2b5a6ddb38f..7995725b97b 100644 --- a/integration/presets/astro.ts +++ b/integration/presets/astro.ts @@ -10,7 +10,7 @@ const astroNode = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm preview') - // .addDependency('@clerk/astro', linkPackage('astro')) + .addDependency('@clerk/astro', linkPackage('astro')) .addDependency('@clerk/types', linkPackage('types')) .addDependency('@clerk/localizations', linkPackage('localizations')); diff --git a/integration/templates/astro-hybrid/package.json b/integration/templates/astro-hybrid/package.json index 2fb6e4aa4e4..ea0b0ee4ce8 100644 --- a/integration/templates/astro-hybrid/package.json +++ b/integration/templates/astro-hybrid/package.json @@ -13,7 +13,6 @@ "@astrojs/check": "^0.9.4", "@astrojs/node": "^8.3.4", "@astrojs/react": "^3.6.2", - "@clerk/astro": "2.3.0-snapshot.v20250220200839", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "astro": "^4.15.11", diff --git a/integration/templates/astro-node/package.json b/integration/templates/astro-node/package.json index fcb99a0e91a..075c63a704f 100644 --- a/integration/templates/astro-node/package.json +++ b/integration/templates/astro-node/package.json @@ -14,7 +14,6 @@ "@astrojs/node": "^9.0.0", "@astrojs/react": "^4.0.0", "@astrojs/tailwind": "^5.1.3", - "@clerk/astro": "2.3.0-snapshot.v20250220200839", "@types/react": "18.3.7", "@types/react-dom": "18.3.0", "astro": "^5.0.3", From afd5b7975d512ddde6d9094812dd5791a2daea9e Mon Sep 17 00:00:00 2001 From: Robert Soriano Date: Thu, 20 Feb 2025 12:44:15 -0800 Subject: [PATCH 9/9] chore: add changeset --- .changeset/fair-insects-sort.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .changeset/fair-insects-sort.md diff --git a/.changeset/fair-insects-sort.md b/.changeset/fair-insects-sort.md new file mode 100644 index 00000000000..0375364208d --- /dev/null +++ b/.changeset/fair-insects-sort.md @@ -0,0 +1,28 @@ +--- +"@clerk/astro": minor +--- + +Introduce `protect-fallback` slot to avoid naming conflicts with Astro's server islands [`fallback` slot](https://docs.astro.build/en/guides/server-islands/#server-island-fallback-content). + +When using Clerk's `` component with `server:defer`, you can now use both slots: +- `fallback`: Default loading content +- `protect-fallback`: Shows when a user doesn't have the `role` or `permission` to access the protected content + +Regular usage without server islands: + +```astro + +

Not an admin

+

You're an admin

+
+``` + +Example with server islands: + +```astro + +

Loading...

+

Not an admin

+

You're an admin

+
+```