{
cursor: pointer;
`}
>
- {success ? 'Claim completed' : claimed ? 'Missing environment keys' : 'Clerk is in keyless mode'}
+ {success
+ ? 'Your app is ready'
+ : claimed
+ ? 'Missing environment keys'
+ : isSignedIn
+ ? "You've created your first user"
+ : 'Configure your application'}
@@ -360,7 +371,7 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => {
>
{appName}
{' '}
- has been claimed. Configure settings from the{' '}
+ has been configured. You may now customize your settings in the{' '}
{
},
})}
>
- Clerk Dashboard
+ Clerk dashboard
+ .
) : claimed ? (
{
Dashboard.
) : isSignedIn ? (
-
-
- You've created your first user! Link this application to your Clerk account to explore the
- Dashboard.
-
-
+ <>
+
+ Head to the dashboard to customize authentication settings, view user info, and explore more
+ features.
+
+
+
Add SSO connections (eg. GitHub)
+
Set up B2B authentication
+
Enable MFA
+
+ >
) : (
<>
{
>
Temporary API keys are enabled so you can get started immediately.
+
+
Add SSO connections (eg. GitHub)
+
Set up B2B authentication
+
Enable MFA
+
{
text-wrap: pretty;
`}
>
- Claim this application to access the Clerk Dashboard where you can manage auth settings and explore
- more Clerk features.
+ Access the dashboard to customize auth settings and explore Clerk features.
>
)}
@@ -501,7 +544,7 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => {
}
`}
>
- {claimed ? 'Get API keys' : 'Claim application'}
+ {claimed ? 'Get API keys' : 'Configure your application'}
))}
From 94aace73405d510253494bc85fcafa3e2769c7e9 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Fri, 6 Feb 2026 09:52:02 -0500
Subject: [PATCH 2/7] add changeset
---
.changeset/puny-onions-hide.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/puny-onions-hide.md
diff --git a/.changeset/puny-onions-hide.md b/.changeset/puny-onions-hide.md
new file mode 100644
index 00000000000..d43b5d1d811
--- /dev/null
+++ b/.changeset/puny-onions-hide.md
@@ -0,0 +1,5 @@
+---
+'@clerk/clerk-js': patch
+---
+
+Updates keyless prompt content
From 856ab817012eec4d1d8776e9a2bd9f41befd92ea Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Fri, 6 Feb 2026 15:13:55 -0500
Subject: [PATCH 3/7] update tests
---
.../tests/next-quickstart-keyless.test.ts | 154 ++++++++++++++++--
.../unstable/page-objects/keylessPopover.ts | 35 +++-
2 files changed, 173 insertions(+), 16 deletions(-)
diff --git a/integration/tests/next-quickstart-keyless.test.ts b/integration/tests/next-quickstart-keyless.test.ts
index d143b60385e..bfd30a4713c 100644
--- a/integration/tests/next-quickstart-keyless.test.ts
+++ b/integration/tests/next-quickstart-keyless.test.ts
@@ -48,10 +48,22 @@ test.describe('Keyless mode @quickstart', () => {
await app.teardown();
});
- test('Navigates to non-existent page (/_not-found) without a infinite redirect loop.', async ({ page, context }) => {
+ test.skip('Navigates to non-existent page (/_not-found) without a infinite redirect loop.', async ({
+ page,
+ context,
+ }) => {
+ test.setTimeout(60000); // Increase timeout for this test
const u = createTestUtils({ app, page, context });
await u.page.goToAppHome();
- await u.page.waitForClerkJsLoaded();
+
+ // Wait for Clerk.js to load - use a longer timeout for keyless mode
+ await page
+ .waitForFunction(() => window.Clerk?.loaded, { timeout: 30000 })
+ .catch(() => {
+ // If Clerk.js doesn't load after 30s, it might be a redirect loop issue
+ // Continue to check for redirect loops anyway
+ });
+
await u.po.expect.toBeSignedOut();
await u.po.keylessPopover.waitForMounted();
@@ -78,32 +90,66 @@ test.describe('Keyless mode @quickstart', () => {
await u.po.keylessPopover.waitForMounted();
+ // Popover now starts expanded by default
+ expect(await u.po.keylessPopover.isExpanded()).toBe(true);
+
+ // Verify new content appears when expanded
+ const notSignedInContent = u.po.keylessPopover.getNotSignedInContent();
+ await expect(notSignedInContent.temporaryKeysText).toBeVisible();
+ await expect(notSignedInContent.dashboardText).toBeVisible();
+ await expect(notSignedInContent.bulletList).toBeVisible();
+
+ // Test collapsing and expanding
+ await u.po.keylessPopover.toggle();
expect(await u.po.keylessPopover.isExpanded()).toBe(false);
await u.po.keylessPopover.toggle();
expect(await u.po.keylessPopover.isExpanded()).toBe(true);
const claim = await u.po.keylessPopover.promptsToClaim();
+ // Verify the link href contains the expected claim URL pattern before clicking
+ const href = await claim.getAttribute('href');
+ expect(href).toContain('apps/claim');
+ expect(href).toContain('token=');
+
const [newPage] = await Promise.all([context.waitForEvent('page'), claim.click()]);
await newPage.waitForLoadState();
- await newPage.waitForURL(url => {
- const urlToReturnTo = `${dashboardUrl}apps/claim?token=`;
+ // Wait for navigation to either Clerk dashboard or Vercel SSO (which redirects to Clerk)
+ // The claim URL may redirect through Vercel SSO first
+ const urlToReturnTo = `${dashboardUrl}apps/claim?token=`;
- const signUpForceRedirectUrl = url.searchParams.get('sign_up_force_redirect_url');
+ await newPage.waitForURL(
+ url => {
+ // Check if we're on the Clerk dashboard claim sign-in page
+ if (url.hostname.includes('dashboard.clerk') && url.pathname === '/apps/claim/sign-in') {
+ const signUpForceRedirectUrl = url.searchParams.get('sign_up_force_redirect_url');
+ const signInForceRedirectUrl = url.searchParams.get('sign_in_force_redirect_url');
- const signUpForceRedirectUrlCheck =
- signUpForceRedirectUrl?.startsWith(urlToReturnTo) ||
- (signUpForceRedirectUrl?.startsWith(`${dashboardUrl}prepare-account`) &&
- signUpForceRedirectUrl?.includes(encodeURIComponent('apps/claim?token=')));
+ const signUpForceRedirectUrlCheck =
+ signUpForceRedirectUrl?.startsWith(urlToReturnTo) ||
+ (signUpForceRedirectUrl?.startsWith(`${dashboardUrl}prepare-account`) &&
+ signUpForceRedirectUrl?.includes(encodeURIComponent('apps/claim?token=')));
- return (
- url.pathname === '/apps/claim/sign-in' &&
- url.searchParams.get('sign_in_force_redirect_url')?.startsWith(urlToReturnTo) &&
- signUpForceRedirectUrlCheck
- );
- });
+ return signInForceRedirectUrl?.startsWith(urlToReturnTo) && signUpForceRedirectUrlCheck;
+ }
+
+ // Check if we're on Vercel SSO (which will redirect to the claim URL)
+ // This is acceptable as it's part of the authentication flow
+ if (url.hostname.includes('vercel.com') && url.pathname === '/login') {
+ const nextParam = url.searchParams.get('next');
+ return (
+ nextParam?.includes(encodeURIComponent('dashboard.clerk')) &&
+ (nextParam?.includes(encodeURIComponent('apps/claim')) ||
+ nextParam?.includes(encodeURIComponent('sso-api')))
+ );
+ }
+
+ return false;
+ },
+ { timeout: 30000 },
+ );
});
test('Lands on claimed application with missing explicit keys, expanded by default, click to get keys from dashboard.', async ({
@@ -117,6 +163,12 @@ test.describe('Keyless mode @quickstart', () => {
await u.po.keylessPopover.waitForMounted();
expect(await u.po.keylessPopover.isExpanded()).toBe(true);
+
+ // Verify claimed state content
+ const claimedContent = u.po.keylessPopover.getClaimedContent();
+ await expect(claimedContent.title).toBeVisible();
+ await expect(claimedContent.description).toBeVisible();
+
await expect(u.po.keylessPopover.promptToUseClaimedKeys()).toBeVisible();
const [newPage] = await Promise.all([
@@ -130,6 +182,71 @@ test.describe('Keyless mode @quickstart', () => {
});
});
+ test('Signed-in user sees updated prompt content.', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+
+ // Create and sign in a user
+ const fakeUser = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ withPassword: true,
+ });
+ await u.services.users.createBapiUser(fakeUser);
+
+ await u.page.goToAppHome();
+ await u.page.waitForClerkJsLoaded();
+
+ // Sign in
+ await u.po.signIn.goTo();
+ await u.po.signIn.signInWithEmailAndInstantPassword({
+ email: fakeUser.email,
+ password: fakeUser.password,
+ });
+ await u.po.expect.toBeSignedIn();
+
+ // Navigate back to home to see the keyless prompt
+ await u.page.goToAppHome();
+ await u.po.keylessPopover.waitForMounted();
+
+ // Verify prompt is expanded by default when signed in
+ expect(await u.po.keylessPopover.isExpanded()).toBe(true);
+
+ // Verify signed-in content
+ const signedInContent = u.po.keylessPopover.getSignedInContent();
+ await expect(signedInContent.title).toBeVisible();
+ await expect(signedInContent.description).toBeVisible();
+ await expect(signedInContent.bulletList).toBeVisible();
+
+ // Verify bullet items are present
+ await expect(signedInContent.bulletItems.first()).toBeVisible();
+
+ // Verify "Configure your application" button is visible
+ await expect(u.po.keylessPopover.promptsToClaim()).toBeVisible();
+
+ await fakeUser.deleteIfExists();
+ });
+
+ test('Not signed-in user sees updated prompt content.', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.page.goToAppHome();
+ await u.page.waitForClerkJsLoaded();
+ await u.po.expect.toBeSignedOut();
+
+ await u.po.keylessPopover.waitForMounted();
+
+ // Popover starts expanded by default
+ expect(await u.po.keylessPopover.isExpanded()).toBe(true);
+
+ // Verify not signed-in content
+ const notSignedInContent = u.po.keylessPopover.getNotSignedInContent();
+ await expect(notSignedInContent.title).toBeVisible();
+ await expect(notSignedInContent.temporaryKeysText).toBeVisible();
+ await expect(notSignedInContent.bulletList).toBeVisible();
+ await expect(notSignedInContent.dashboardText).toBeVisible();
+
+ // Verify bullet items are present
+ await expect(notSignedInContent.bulletItems.first()).toBeVisible();
+ });
+
test('Claimed application with keys inside .env, on dismiss, keyless prompt is removed.', async ({
page,
context,
@@ -152,6 +269,13 @@ test.describe('Keyless mode @quickstart', () => {
await page.reload();
await u.po.keylessPopover.waitForMounted();
+
+ // Verify success state content
+ const successContent = u.po.keylessPopover.getSuccessContent();
+ await expect(successContent.title).toBeVisible();
+ await expect(successContent.configuredText).toBeVisible();
+ await expect(successContent.dashboardLink).toBeVisible();
+
await u.po.keylessPopover.promptToDismiss().click();
await u.po.keylessPopover.waitForUnmounted();
diff --git a/packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts b/packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts
index 69b5bbd4728..e699f0b8699 100644
--- a/packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts
+++ b/packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts
@@ -4,6 +4,7 @@ export const createKeylessPopoverPageObject = (testArgs: { page: EnhancedPage })
const { page } = testArgs;
// TODO: Is this the ID we really want ?
const elementId = '#--clerk-keyless-prompt-button';
+ const contentId = '#--clerk-keyless-prompt-content';
const self = {
waitForMounted: () => page.waitForSelector(elementId, { state: 'attached' }),
waitForUnmounted: () => page.waitForSelector(elementId, { state: 'detached' }),
@@ -15,7 +16,7 @@ export const createKeylessPopoverPageObject = (testArgs: { page: EnhancedPage })
toggle: () => page.locator(elementId).click(),
promptsToClaim: () => {
- return page.getByRole('link', { name: /^claim application$/i });
+ return page.getByRole('link', { name: /^configure your application$/i });
},
promptToUseClaimedKeys: () => {
return page.getByRole('link', { name: /^get api keys$/i });
@@ -23,6 +24,38 @@ export const createKeylessPopoverPageObject = (testArgs: { page: EnhancedPage })
promptToDismiss: () => {
return page.getByRole('button', { name: /^dismiss$/i });
},
+
+ // Helper methods to check content text for different states
+ getSignedInContent: () => {
+ return {
+ title: page.getByText("You've created your first user", { exact: false }),
+ description: page.getByText(/Head to the dashboard to customize authentication settings/i),
+ bulletList: page.locator(contentId).locator('ul'),
+ bulletItems: page.locator(contentId).getByText(/Add SSO connections|Set up B2B authentication|Enable MFA/i),
+ };
+ },
+ getNotSignedInContent: () => {
+ return {
+ title: page.locator(elementId).getByText('Configure your application', { exact: false }),
+ temporaryKeysText: page.getByText(/Temporary API keys are enabled/i),
+ bulletList: page.locator(contentId).locator('ul'),
+ bulletItems: page.locator(contentId).getByText(/Add SSO connections|Set up B2B authentication|Enable MFA/i),
+ dashboardText: page.getByText(/Access the dashboard to customize auth settings/i),
+ };
+ },
+ getSuccessContent: () => {
+ return {
+ title: page.getByText('Your app is ready', { exact: false }),
+ configuredText: page.getByText(/has been configured/i),
+ dashboardLink: page.getByRole('link', { name: /Clerk dashboard/i }),
+ };
+ },
+ getClaimedContent: () => {
+ return {
+ title: page.getByText('Missing environment keys', { exact: false }),
+ description: page.getByText(/You claimed this application but haven't set keys/i),
+ };
+ },
};
return self;
};
From 77afb233739e5729f5e9bfcac55f20264f932ba5 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Fri, 6 Feb 2026 15:19:37 -0500
Subject: [PATCH 4/7] Update next-quickstart-keyless.test.ts
---
.../tests/next-quickstart-keyless.test.ts | 71 +++++--------------
1 file changed, 18 insertions(+), 53 deletions(-)
diff --git a/integration/tests/next-quickstart-keyless.test.ts b/integration/tests/next-quickstart-keyless.test.ts
index bfd30a4713c..77b984e5e24 100644
--- a/integration/tests/next-quickstart-keyless.test.ts
+++ b/integration/tests/next-quickstart-keyless.test.ts
@@ -48,22 +48,10 @@ test.describe('Keyless mode @quickstart', () => {
await app.teardown();
});
- test.skip('Navigates to non-existent page (/_not-found) without a infinite redirect loop.', async ({
- page,
- context,
- }) => {
- test.setTimeout(60000); // Increase timeout for this test
+ test('Navigates to non-existent page (/_not-found) without a infinite redirect loop.', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.page.goToAppHome();
-
- // Wait for Clerk.js to load - use a longer timeout for keyless mode
- await page
- .waitForFunction(() => window.Clerk?.loaded, { timeout: 30000 })
- .catch(() => {
- // If Clerk.js doesn't load after 30s, it might be a redirect loop issue
- // Continue to check for redirect loops anyway
- });
-
+ await u.page.waitForClerkJsLoaded();
await u.po.expect.toBeSignedOut();
await u.po.keylessPopover.waitForMounted();
@@ -107,49 +95,26 @@ test.describe('Keyless mode @quickstart', () => {
const claim = await u.po.keylessPopover.promptsToClaim();
- // Verify the link href contains the expected claim URL pattern before clicking
- const href = await claim.getAttribute('href');
- expect(href).toContain('apps/claim');
- expect(href).toContain('token=');
-
const [newPage] = await Promise.all([context.waitForEvent('page'), claim.click()]);
await newPage.waitForLoadState();
- // Wait for navigation to either Clerk dashboard or Vercel SSO (which redirects to Clerk)
- // The claim URL may redirect through Vercel SSO first
- const urlToReturnTo = `${dashboardUrl}apps/claim?token=`;
-
- await newPage.waitForURL(
- url => {
- // Check if we're on the Clerk dashboard claim sign-in page
- if (url.hostname.includes('dashboard.clerk') && url.pathname === '/apps/claim/sign-in') {
- const signUpForceRedirectUrl = url.searchParams.get('sign_up_force_redirect_url');
- const signInForceRedirectUrl = url.searchParams.get('sign_in_force_redirect_url');
-
- const signUpForceRedirectUrlCheck =
- signUpForceRedirectUrl?.startsWith(urlToReturnTo) ||
- (signUpForceRedirectUrl?.startsWith(`${dashboardUrl}prepare-account`) &&
- signUpForceRedirectUrl?.includes(encodeURIComponent('apps/claim?token=')));
-
- return signInForceRedirectUrl?.startsWith(urlToReturnTo) && signUpForceRedirectUrlCheck;
- }
-
- // Check if we're on Vercel SSO (which will redirect to the claim URL)
- // This is acceptable as it's part of the authentication flow
- if (url.hostname.includes('vercel.com') && url.pathname === '/login') {
- const nextParam = url.searchParams.get('next');
- return (
- nextParam?.includes(encodeURIComponent('dashboard.clerk')) &&
- (nextParam?.includes(encodeURIComponent('apps/claim')) ||
- nextParam?.includes(encodeURIComponent('sso-api')))
- );
- }
-
- return false;
- },
- { timeout: 30000 },
- );
+ await newPage.waitForURL(url => {
+ const urlToReturnTo = `${dashboardUrl}apps/claim?token=`;
+
+ const signUpForceRedirectUrl = url.searchParams.get('sign_up_force_redirect_url');
+
+ const signUpForceRedirectUrlCheck =
+ signUpForceRedirectUrl?.startsWith(urlToReturnTo) ||
+ (signUpForceRedirectUrl?.startsWith(`${dashboardUrl}prepare-account`) &&
+ signUpForceRedirectUrl?.includes(encodeURIComponent('apps/claim?token=')));
+
+ return (
+ url.pathname === '/apps/claim/sign-in' &&
+ url.searchParams.get('sign_in_force_redirect_url')?.startsWith(urlToReturnTo) &&
+ signUpForceRedirectUrlCheck
+ );
+ });
});
test('Lands on claimed application with missing explicit keys, expanded by default, click to get keys from dashboard.', async ({
From cf4e0ca75098d052a1fe486f48f10009e49f8930 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Fri, 6 Feb 2026 15:30:14 -0500
Subject: [PATCH 5/7] skip
---
integration/tests/next-quickstart-keyless.test.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/integration/tests/next-quickstart-keyless.test.ts b/integration/tests/next-quickstart-keyless.test.ts
index 77b984e5e24..f3d35de2793 100644
--- a/integration/tests/next-quickstart-keyless.test.ts
+++ b/integration/tests/next-quickstart-keyless.test.ts
@@ -147,7 +147,10 @@ test.describe('Keyless mode @quickstart', () => {
});
});
- test('Signed-in user sees updated prompt content.', async ({ page, context }) => {
+ // Skipped: This test requires creating a user via backend API, which needs CLERK_SECRET_KEY.
+ // Keyless mode is designed to work without keys, so we skip this test for now.
+ // TODO: Revisit when we have a way to test signed-in states in keyless mode without backend API access.
+ test.skip('Signed-in user sees updated prompt content.', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
// Create and sign in a user
From 706c6a97b0667a17446ff4068afc465efea59bb1 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Fri, 6 Feb 2026 15:44:44 -0500
Subject: [PATCH 6/7] Update next-quickstart-keyless.test.ts
---
integration/tests/next-quickstart-keyless.test.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/integration/tests/next-quickstart-keyless.test.ts b/integration/tests/next-quickstart-keyless.test.ts
index f3d35de2793..a3c1f395924 100644
--- a/integration/tests/next-quickstart-keyless.test.ts
+++ b/integration/tests/next-quickstart-keyless.test.ts
@@ -242,7 +242,6 @@ test.describe('Keyless mode @quickstart', () => {
const successContent = u.po.keylessPopover.getSuccessContent();
await expect(successContent.title).toBeVisible();
await expect(successContent.configuredText).toBeVisible();
- await expect(successContent.dashboardLink).toBeVisible();
await u.po.keylessPopover.promptToDismiss().click();
From 84dfd10a8d133be545ce735ef905380b6b29ea69 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Fri, 6 Feb 2026 16:52:00 -0500
Subject: [PATCH 7/7] Update index.tsx
---
.../devPrompts/KeylessPrompt/index.tsx | 254 ++++++++++--------
1 file changed, 147 insertions(+), 107 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/devPrompts/KeylessPrompt/index.tsx b/packages/clerk-js/src/ui/components/devPrompts/KeylessPrompt/index.tsx
index dc73ac95c5a..258a20309be 100644
--- a/packages/clerk-js/src/ui/components/devPrompts/KeylessPrompt/index.tsx
+++ b/packages/clerk-js/src/ui/components/devPrompts/KeylessPrompt/index.tsx
@@ -24,13 +24,114 @@ type KeylessPromptProps = {
onDismiss: (() => Promise) | undefined | null;
};
+export type KeylessPromptState = 'default' | 'signedIn' | 'claimed' | 'success';
+
+export interface SizingConfig {
+ collapsed: {
+ height: string;
+ minWidth: string;
+ };
+ expanded: {
+ height: string;
+ minWidth: string;
+ };
+}
+
+const COLLAPSED_PADDING_LEFT = '0.75rem';
+const EXPANDED_PADDING = '0.625rem 0.75rem 0.75rem 0.75rem';
+
+const contentTextStyles = css`
+ ${basePromptElementStyles};
+ color: #b4b4b4;
+ font-size: 0.8125rem;
+ font-weight: 400;
+ line-height: 1rem;
+`;
+
+const bulletListStyles = css`
+ ${basePromptElementStyles};
+ color: #b4b4b4;
+ font-size: 0.8125rem;
+ font-weight: 400;
+ line-height: 1rem;
+ margin: 0;
+ padding-left: 1.25rem;
+ list-style: disc;
+`;
+
+export const STATE_SIZING_CONFIG: Record = {
+ default: {
+ collapsed: {
+ height: '2.5rem',
+ minWidth: '15.5rem',
+ },
+ expanded: {
+ height: '14.5rem',
+ minWidth: '16.5rem',
+ },
+ },
+ signedIn: {
+ collapsed: {
+ height: '2.5rem',
+ minWidth: '15.75rem',
+ },
+ expanded: {
+ height: '12rem',
+ minWidth: '17rem',
+ },
+ },
+ claimed: {
+ collapsed: {
+ height: '2.5rem',
+ minWidth: '15.5rem',
+ },
+ expanded: {
+ height: 'fit-content',
+ minWidth: '16.5rem',
+ },
+ },
+ success: {
+ collapsed: {
+ height: '2.5rem',
+ minWidth: '15.5rem',
+ },
+ expanded: {
+ height: 'fit-content',
+ minWidth: '16.5rem',
+ },
+ },
+};
+
const buttonIdentifierPrefix = `--clerk-keyless-prompt`;
const buttonIdentifier = `${buttonIdentifierPrefix}-button`;
const contentIdentifier = `${buttonIdentifierPrefix}-content`;
-/**
- * If we cannot reconstruct the url properly, then simply fallback to Clerk Dashboard
- */
+function getButtonLabel(success: boolean, claimed: boolean, isSignedIn: boolean): string {
+ if (success) {
+ return 'Your app is ready';
+ }
+ if (claimed) {
+ return 'Missing environment keys';
+ }
+ if (isSignedIn) {
+ return "You've created your first user";
+ }
+ return 'Configure your application';
+}
+
+function determineState(claimed: boolean, success: boolean, isSignedIn: boolean): KeylessPromptState {
+ if (success) {
+ return 'success';
+ }
+ if (claimed) {
+ return 'claimed';
+ }
+ if (isSignedIn) {
+ return 'signedIn';
+ }
+ return 'default';
+}
+
function withLastActiveFallback(cb: () => string): string {
try {
return cb();
@@ -39,42 +140,48 @@ function withLastActiveFallback(cb: () => string): string {
}
}
-const KeylessPromptInternal = (_props: KeylessPromptProps) => {
+const KeylessPromptInternal = (props: KeylessPromptProps) => {
const { isSignedIn } = useUser();
const [isExpanded, setIsExpanded] = useState(true);
- useEffect(() => {
- if (isSignedIn) {
- setIsExpanded(true);
- }
- }, [isSignedIn]);
-
const environment = useRevalidateEnvironment();
const claimed = Boolean(environment.authConfig.claimedAt);
- const success = typeof _props.onDismiss === 'function' && claimed;
+ const success = typeof props.onDismiss === 'function' && claimed;
const appName = environment.displayConfig.applicationName;
+ const isSignedInBoolean = Boolean(isSignedIn);
+
+ const state = useMemo(
+ () => determineState(claimed, success, isSignedInBoolean),
+ [claimed, success, isSignedInBoolean],
+ );
+ const sizingConfig = useMemo(() => STATE_SIZING_CONFIG[state], [state]);
+
+ useEffect(() => {
+ if (isSignedInBoolean) {
+ setIsExpanded(true);
+ }
+ }, [isSignedInBoolean]);
const isForcedExpanded = claimed || success || isExpanded;
+
const claimUrlToDashboard = useMemo(() => {
if (claimed) {
- return _props.copyKeysUrl;
+ return props.copyKeysUrl;
}
-
- const url = new URL(_props.claimUrl);
- // Clerk Dashboard accepts a `return_url` query param when visiting `/apps/claim`.
+ const url = new URL(props.claimUrl);
url.searchParams.append('return_url', window.location.href);
return url.href;
- }, [claimed, _props.copyKeysUrl, _props.claimUrl]);
+ }, [claimed, props.copyKeysUrl, props.claimUrl]);
const instanceUrlToDashboard = useMemo(() => {
return withLastActiveFallback(() => {
- const redirectUrlParts = handleDashboardUrlParsing(_props.copyKeysUrl);
+ const redirectUrlParts = handleDashboardUrlParsing(props.copyKeysUrl);
const url = new URL(
`${redirectUrlParts.baseDomain}/apps/${redirectUrlParts.appId}/instances/${redirectUrlParts.instanceId}/user-authentication/email-phone-username`,
);
return url.href;
});
- }, [_props.copyKeysUrl]);
+ }, [props.copyKeysUrl]);
const mainCTAStyles = css`
${basePromptElementStyles};
@@ -88,7 +195,7 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => {
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.12px;
- color: ${claimed ? 'white' : success ? 'white' : '#fde047'};
+ color: ${success || claimed ? 'white' : '#fde047'};
text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.32);
white-space: nowrap;
user-select: none;
@@ -110,9 +217,9 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => {
position: 'fixed',
bottom: '1.25rem',
right: '1.25rem',
- height: `${t.sizes.$10}`,
- minWidth: '15.5rem',
- paddingLeft: `${t.space.$3}`,
+ height: sizingConfig.collapsed.height,
+ minWidth: sizingConfig.collapsed.minWidth,
+ paddingLeft: COLLAPSED_PADDING_LEFT,
borderRadius: '1.25rem',
transition: 'all 195ms cubic-bezier(0.2, 0.61, 0.1, 1)',
@@ -124,12 +231,13 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => {
flexDirection: 'column',
alignItems: 'flex-center',
justifyContent: 'flex-center',
- height: claimed || success ? 'fit-content' : isSignedIn ? '12rem' : '14.5rem',
+ height: sizingConfig.expanded.height,
overflow: 'hidden',
width: 'fit-content',
- minWidth: '18.5rem',
+ minWidth: sizingConfig.expanded.minWidth,
+ paddingLeft: undefined,
+ padding: EXPANDED_PADDING,
gap: `${t.space.$1x5}`,
- padding: `${t.space.$2x5} ${t.space.$3} ${t.space.$3} ${t.space.$3}`,
borderRadius: `${t.radii.$xl}`,
transition: 'all 230ms cubic-bezier(0.28, 1, 0.32, 1)',
},
@@ -244,15 +352,7 @@ const KeylessPromptInternal = (_props: KeylessPromptProps) => {
{
cursor: pointer;
`}
>
- {success
- ? 'Your app is ready'
- : claimed
- ? 'Missing environment keys'
- : isSignedIn
- ? "You've created your first user"
- : 'Configure your application'}
+ {getButtonLabel(success, claimed, isSignedInBoolean)}