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

+
+``` 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..9cda7984925 --- /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..cce99fb9b20 100644 --- a/integration/tests/astro/components.test.ts +++ b/integration/tests/astro/components.test.ts @@ -481,4 +481,30 @@ 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 }); + + await u.page.goToRelative('/server-islands'); + // 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'); + 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 + await u.page.goToRelative('/server-islands'); + await expect(u.page.getByText('Loading')).toBeHidden(); + await expect(u.page.getByText("I'm an admin")).toBeVisible(); + }); }); diff --git a/packages/astro/src/astro-components/control/Protect.astro b/packages/astro/src/astro-components/control/Protect.astro index e1ccd138c31..1868132bd34 100644 --- a/packages/astro/src/astro-components/control/Protect.astro +++ b/packages/astro/src/astro-components/control/Protect.astro @@ -27,9 +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 4f0d58a98ea..4a1199d207a 100644 --- a/packages/astro/src/astro-components/control/ProtectSSR.astro +++ b/packages/astro/src/astro-components/control/ProtectSSR.astro @@ -9,6 +9,18 @@ const isUnauthorized = (typeof Astro.props.condition === "function" && !Astro.props.condition(has)) || ((Astro.props.role || Astro.props.permission) && !has(Astro.props)); + +const hasProtectFallback = Astro.slots.has('protect-fallback'); --- -{isUnauthorized ? : } +{ + isUnauthorized ? ( + hasProtectFallback ? ( + + ) : ( + + ) + ) : ( + + ) +}