diff --git a/.changeset/add-fallback-to-authenticated-content.md b/.changeset/add-fallback-to-authenticated-content.md new file mode 100644 index 00000000..0e63e5b6 --- /dev/null +++ b/.changeset/add-fallback-to-authenticated-content.md @@ -0,0 +1,6 @@ +--- +"@aws-blocks/auth-common": patch +"@aws-blocks/blocks": patch +--- + +Add optional `fallback` parameter to `AuthenticatedContent` for rendering alternative content when the user is not authenticated. diff --git a/packages/auth-common/README.md b/packages/auth-common/README.md index 23f73710..a7e78d9a 100644 --- a/packages/auth-common/README.md +++ b/packages/auth-common/README.md @@ -115,6 +115,25 @@ document.body.appendChild( ); ``` +### Fallback for unauthenticated users + +Pass an optional third argument to display alternative content when the user is NOT signed in: + +```typescript +const loginPrompt = document.createElement('p'); +loginPrompt.textContent = 'Please sign in to continue.'; + +document.body.appendChild( + AuthenticatedContent(authApi, (user) => { + const el = document.createElement('div'); + el.textContent = `Welcome, ${user.username}`; + return el; + }, loginPrompt) +); +``` + +When no fallback is provided the container renders nothing while signed out (backward-compatible). + ## Auth State Change Subscription Subscribe to auth state changes from any source (same window + other tabs): diff --git a/packages/auth-common/src/ui.test.ts b/packages/auth-common/src/ui.test.ts index dd5f1bd4..cfe9c519 100644 --- a/packages/auth-common/src/ui.test.ts +++ b/packages/auth-common/src/ui.test.ts @@ -567,6 +567,48 @@ describe('AuthenticatedContent', () => { assert.strictEqual(el.children.length, 0); }); + + test('renders fallback when signed out and fallback is provided', async () => { + const api = mockApi(signedOutState()); + const fallback = document.createElement('div'); + fallback.textContent = 'Please sign in'; + const el = AuthenticatedContent(api, (user) => { + const span = document.createElement('span'); + span.textContent = `Hello ${user.username}`; + return span; + }, fallback); + await flush(); + + assert.ok(el.textContent?.includes('Please sign in')); + }); + + test('renders content (not fallback) when signed in and fallback is provided', async () => { + const api = mockApi(signedInState()); + const fallback = document.createElement('div'); + fallback.textContent = 'Please sign in'; + const el = AuthenticatedContent(api, (user) => { + const span = document.createElement('span'); + span.textContent = `Hello ${user.username}`; + return span; + }, fallback); + await flush(); + + assert.ok(el.textContent?.includes('Hello alice')); + assert.ok(!el.textContent?.includes('Please sign in')); + }); + + test('renders nothing when signed out and no fallback is provided (backward compat)', async () => { + const api = mockApi(signedOutState()); + const el = AuthenticatedContent(api, (user) => { + const span = document.createElement('span'); + span.textContent = `Hello ${user.username}`; + return span; + }); + await flush(); + + assert.strictEqual(el.children.length, 0); + assert.strictEqual(el.textContent, ''); + }); }); describe('onAuthChange', () => { diff --git a/packages/auth-common/src/ui.ts b/packages/auth-common/src/ui.ts index 2f9b453f..5bb70dd6 100644 --- a/packages/auth-common/src/ui.ts +++ b/packages/auth-common/src/ui.ts @@ -280,11 +280,12 @@ export function onAuthChange( /** * Container that renders content only when the user is signed in. * Automatically re-renders when auth state changes (same window + cross-tab). - * Shows nothing when signed out. + * Shows the optional `fallback` when signed out, or nothing if no fallback is provided. * * @param api - The state machine API from `auth.createApi()` * @param render - Called with the authenticated user. Return the content to display. - * @returns An HTMLElement that shows content when signed in, empty when signed out. + * @param fallback - Optional. A DOM node to display when the user is NOT signed in. + * @returns An HTMLElement that shows content when signed in, the fallback (or nothing) when signed out. * * @example * ```typescript @@ -296,17 +297,33 @@ export function onAuthChange( * }) * ); * ``` + * + * @example + * ```typescript + * // With a fallback for unauthenticated users + * const loginPrompt = document.createElement('p'); + * loginPrompt.textContent = 'Please sign in to continue.'; + * + * document.body.appendChild( + * AuthenticatedContent(authApi, (user) => { + * const el = document.createElement('div'); + * el.textContent = `Welcome, ${user.username}`; + * return el; + * }, loginPrompt) + * ); + * ``` */ export function AuthenticatedContent( api: AuthStateApi, render: (user: AuthUser) => Node, + fallback?: Node, ): HTMLElement { const container = document.createElement('div'); onAuthChange(api, (user) => { if (user) { container.replaceChildren(render(user)); } else { - container.replaceChildren(); + container.replaceChildren(...(fallback ? [fallback] : [])); } }); return container;