@hexclave/tanstack-start 1.0.63 → 1.0.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/{admin-app-impl-Cdh1oCee.d.ts → admin-app-impl-Cz4f8VJ9.d.ts} +7 -7
  2. package/dist/{admin-app-impl-Cdh1oCee.d.ts.map → admin-app-impl-Cz4f8VJ9.d.ts.map} +1 -1
  3. package/dist/auth-page-YnM_LtbN.d.ts.map +1 -1
  4. package/dist/cli-auth-confirm-BW3PYpKb.d.ts.map +1 -1
  5. package/dist/components/api-key-dialogs.d.ts +1 -1
  6. package/dist/components/message-cards/no-auth-methods-message-card.d.ts +13 -0
  7. package/dist/components/message-cards/no-auth-methods-message-card.d.ts.map +1 -0
  8. package/dist/components/message-cards/no-auth-methods-message-card.js +75 -0
  9. package/dist/components/message-cards/no-auth-methods-message-card.js.map +1 -0
  10. package/dist/components-page/auth-page.js +7 -2
  11. package/dist/components-page/auth-page.js.map +1 -1
  12. package/dist/components-page/cli-auth-confirm.js +7 -1
  13. package/dist/components-page/cli-auth-confirm.js.map +1 -1
  14. package/dist/components-page/cli-auth-confirm.test.js +42 -0
  15. package/dist/components-page/cli-auth-confirm.test.js.map +1 -1
  16. package/dist/esm/components/api-key-dialogs.d.ts +1 -1
  17. package/dist/esm/components/message-cards/no-auth-methods-message-card.d.ts +13 -0
  18. package/dist/esm/components/message-cards/no-auth-methods-message-card.d.ts.map +1 -0
  19. package/dist/esm/components/message-cards/no-auth-methods-message-card.js +74 -0
  20. package/dist/esm/components/message-cards/no-auth-methods-message-card.js.map +1 -0
  21. package/dist/esm/components-page/auth-page.d.ts.map +1 -1
  22. package/dist/esm/components-page/auth-page.js +7 -2
  23. package/dist/esm/components-page/auth-page.js.map +1 -1
  24. package/dist/esm/components-page/cli-auth-confirm.d.ts.map +1 -1
  25. package/dist/esm/components-page/cli-auth-confirm.js +7 -1
  26. package/dist/esm/components-page/cli-auth-confirm.js.map +1 -1
  27. package/dist/esm/components-page/cli-auth-confirm.test.js +42 -0
  28. package/dist/esm/components-page/cli-auth-confirm.test.js.map +1 -1
  29. package/dist/esm/generated/global-css.d.ts +1 -1
  30. package/dist/esm/generated/global-css.js +1 -1
  31. package/dist/esm/generated/global-css.js.map +1 -1
  32. package/dist/esm/generated/quetzal-translations.d.ts +2 -2
  33. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +4 -4
  34. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +2 -2
  35. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +2 -2
  36. package/dist/generated/global-css.d.ts +1 -1
  37. package/dist/generated/global-css.js +1 -1
  38. package/dist/generated/global-css.js.map +1 -1
  39. package/dist/generated/quetzal-translations.d.ts +1 -1
  40. package/dist/{hexclave-provider-Dxrf-i1f.d.ts → hexclave-provider-CN52q2zh.d.ts} +2 -2
  41. package/dist/{hexclave-provider-Dxrf-i1f.d.ts.map → hexclave-provider-CN52q2zh.d.ts.map} +1 -1
  42. package/dist/index.d.ts +1 -1
  43. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
  44. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -1
  45. package/dist/lib/hexclave-app/apps/implementations/common.js +2 -2
  46. package/dist/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
  47. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  48. package/dist/providers/hexclave-provider.d.ts +1 -1
  49. package/dist/providers/translation-provider.d.ts +1 -1
  50. package/dist/{quetzal-translations-CK45_4Dm.d.ts → quetzal-translations-XeH3zQ9l.d.ts} +3 -3
  51. package/dist/{quetzal-translations-CK45_4Dm.d.ts.map → quetzal-translations-XeH3zQ9l.d.ts.map} +1 -1
  52. package/dist/{translation-provider-DQ3BJepJ.d.ts → translation-provider-WOaZXqAz.d.ts} +2 -2
  53. package/dist/{translation-provider-DQ3BJepJ.d.ts.map → translation-provider-WOaZXqAz.d.ts.map} +1 -1
  54. package/package.json +3 -3
  55. package/src/components/message-cards/no-auth-methods-message-card.tsx +57 -0
  56. package/src/components-page/auth-page.tsx +18 -1
  57. package/src/components-page/cli-auth-confirm.test.tsx +55 -0
  58. package/src/components-page/cli-auth-confirm.tsx +11 -1
@@ -1 +1 @@
1
- {"version":3,"file":"cli-auth-confirm.test.js","names":["vi","hexclaveAppInternalsSymbol","HexclaveContext"],"sources":["../../src/components-page/cli-auth-confirm.test.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n// @vitest-environment jsdom\n\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { hexclaveAppInternalsSymbol } from \"../lib/hexclave-app/common\";\nimport { HexclaveContext } from \"../providers/hexclave-context\";\nimport { useCliAuthConfirmation } from \"./cli-auth-confirm\";\n\nconst previousActEnvironment = Reflect.get(globalThis, \"IS_REACT_ACT_ENVIRONMENT\");\n\nfunction responseJson(data: unknown, init?: ResponseInit) {\n return new Response(JSON.stringify(data), {\n status: init?.status ?? 200,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction createAppTestDouble(options: {\n user: unknown,\n sendRequest: (path: string, requestOptions: RequestInit) => Promise<Response>,\n signInWithTokens?: (tokens: { accessToken: string, refreshToken: string }) => Promise<void>,\n redirectToSignIn?: (options: { replace: true }) => Promise<void>,\n redirectToSignUp?: (options: { replace: true }) => Promise<void>,\n}) {\n const app = {\n useUser: () => options.user,\n redirectToSignIn: options.redirectToSignIn ?? vi.fn(async () => {}),\n redirectToSignUp: options.redirectToSignUp ?? vi.fn(async () => {}),\n [hexclaveAppInternalsSymbol]: {\n sendRequest: options.sendRequest,\n signInWithTokens: options.signInWithTokens ?? vi.fn(async () => {}),\n },\n };\n\n // This test double intentionally implements only the StackClientApp surface\n // that useCliAuthConfirmation touches.\n return app as unknown as StackClientApp<true>;\n}\n\nfunction HookProbe() {\n const cliAuth = useCliAuthConfirmation();\n return (\n <>\n <div data-testid=\"status\">{cliAuth.status}</div>\n <div data-testid=\"error\">{cliAuth.error?.message}</div>\n <button type=\"button\" onClick={() => runAsynchronously(cliAuth.authorize)}>authorize</button>\n <button onClick={cliAuth.retry}>retry</button>\n </>\n );\n}\n\nlet root: Root | null = null;\nlet container: HTMLDivElement | null = null;\n\nasync function renderWithApp(app: StackClientApp<true>) {\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n await act(async () => {\n root?.render(\n <HexclaveContext.Provider value={{ app }}>\n <HookProbe />\n </HexclaveContext.Provider>\n );\n });\n}\n\nfunction getByTestId(testId: string): HTMLElement {\n const element = container?.querySelector(`[data-testid=\"${testId}\"]`);\n if (!(element instanceof HTMLElement)) {\n throw new Error(`Could not find test element ${testId}`);\n }\n return element;\n}\n\nfunction getButton(label: string): HTMLButtonElement {\n const button = [...container?.querySelectorAll(\"button\") ?? []]\n .find((element) => element.textContent === label);\n if (!(button instanceof HTMLButtonElement)) {\n throw new Error(`Could not find button ${label}`);\n }\n return button;\n}\n\ndescribe(\"useCliAuthConfirmation\", () => {\n beforeEach(() => {\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", true);\n });\n\n afterEach(() => {\n act(() => {\n root?.unmount();\n });\n container?.remove();\n root = null;\n container = null;\n vi.restoreAllMocks();\n window.history.replaceState({}, \"\", \"/\");\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", previousActEnvironment);\n });\n\n it(\"completes CLI auth with the current user's refresh token\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const getTokens = vi.fn(async () => ({ refreshToken: \"refresh-token\" }));\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: { currentSession: { getTokens } },\n sendRequest,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(getByTestId(\"status\").textContent).toBe(\"success\");\n expect(getTokens).toHaveBeenCalledOnce();\n expect(sendRequest).toHaveBeenCalledOnce();\n expect(sendRequest.mock.calls[0][0]).toBe(\"/auth/cli/complete\");\n expect(JSON.parse(String(sendRequest.mock.calls[0][1].body))).toMatchInlineSnapshot(`\n {\n \"login_code\": \"login-code\",\n \"refresh_token\": \"refresh-token\",\n }\n `);\n });\n\n it(\"ignores duplicate authorize clicks before React re-renders\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const getTokens = vi.fn(async () => ({ refreshToken: \"refresh-token\" }));\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: { currentSession: { getTokens } },\n sendRequest,\n });\n\n await renderWithApp(app);\n await act(async () => {\n const authorizeButton = getButton(\"authorize\");\n authorizeButton.click();\n authorizeButton.click();\n });\n\n expect(sendRequest).toHaveBeenCalledOnce();\n });\n\n it(\"claims anonymous CLI sessions before redirecting to sign-up\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const signInWithTokens = vi.fn(async (_tokens: { accessToken: string, refreshToken: string }) => {});\n const redirectToSignUp = vi.fn(async (_options: { replace: true }) => {});\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }))\n .mockResolvedValueOnce(responseJson({ cli_session_state: \"anonymous\" }))\n .mockResolvedValueOnce(responseJson({ access_token: \"access-token\", refresh_token: \"refresh-token\" }));\n const app = createAppTestDouble({\n user: null,\n sendRequest,\n signInWithTokens,\n redirectToSignUp,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(redirectToSignUp).toHaveBeenCalledWith({ replace: true });\n expect(signInWithTokens).toHaveBeenCalledWith({\n accessToken: \"access-token\",\n refreshToken: \"refresh-token\",\n });\n expect(sessionStorage.getItem(\"hexclave-cli-auth-confirmed\")).toBe(\"login-code\");\n expect(sendRequest.mock.calls.map(call => JSON.parse(String(call[1].body)))).toMatchInlineSnapshot(`\n [\n {\n \"login_code\": \"login-code\",\n \"mode\": \"check\",\n },\n {\n \"login_code\": \"login-code\",\n \"mode\": \"claim-anon-session\",\n },\n ]\n `);\n });\n\n it(\"reports invalid when the login code is missing\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm\");\n const app = createAppTestDouble({\n user: null,\n sendRequest: vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 })),\n });\n\n await renderWithApp(app);\n\n expect(getByTestId(\"status\").textContent).toBe(\"invalid\");\n });\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli-auth-confirm.test.js","names":["vi","hexclaveAppInternalsSymbol","HexclaveContext"],"sources":["../../src/components-page/cli-auth-confirm.test.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n// @vitest-environment jsdom\n\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { hexclaveAppInternalsSymbol } from \"../lib/hexclave-app/common\";\nimport { HexclaveContext } from \"../providers/hexclave-context\";\nimport { useCliAuthConfirmation } from \"./cli-auth-confirm\";\n\nconst previousActEnvironment = Reflect.get(globalThis, \"IS_REACT_ACT_ENVIRONMENT\");\n\nfunction responseJson(data: unknown, init?: ResponseInit) {\n return new Response(JSON.stringify(data), {\n status: init?.status ?? 200,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction createAppTestDouble(options: {\n user: unknown,\n sendRequest: (path: string, requestOptions: RequestInit) => Promise<Response>,\n signInWithTokens?: (tokens: { accessToken: string, refreshToken: string }) => Promise<void>,\n redirectToSignIn?: (options: { replace: true }) => Promise<void>,\n redirectToSignUp?: (options: { replace: true }) => Promise<void>,\n redirectToOnboarding?: (options: { replace: true }) => Promise<void>,\n}) {\n const app = {\n useUser: () => options.user,\n redirectToSignIn: options.redirectToSignIn ?? vi.fn(async () => {}),\n redirectToSignUp: options.redirectToSignUp ?? vi.fn(async () => {}),\n redirectToOnboarding: options.redirectToOnboarding ?? vi.fn(async () => {}),\n [hexclaveAppInternalsSymbol]: {\n sendRequest: options.sendRequest,\n signInWithTokens: options.signInWithTokens ?? vi.fn(async () => {}),\n },\n };\n\n // This test double intentionally implements only the StackClientApp surface\n // that useCliAuthConfirmation touches.\n return app as unknown as StackClientApp<true>;\n}\n\nfunction HookProbe() {\n const cliAuth = useCliAuthConfirmation();\n return (\n <>\n <div data-testid=\"status\">{cliAuth.status}</div>\n <div data-testid=\"error\">{cliAuth.error?.message}</div>\n <button type=\"button\" onClick={() => runAsynchronously(cliAuth.authorize)}>authorize</button>\n <button onClick={cliAuth.retry}>retry</button>\n </>\n );\n}\n\nlet root: Root | null = null;\nlet container: HTMLDivElement | null = null;\n\nasync function renderWithApp(app: StackClientApp<true>) {\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n await act(async () => {\n root?.render(\n <HexclaveContext.Provider value={{ app }}>\n <HookProbe />\n </HexclaveContext.Provider>\n );\n });\n}\n\nfunction getByTestId(testId: string): HTMLElement {\n const element = container?.querySelector(`[data-testid=\"${testId}\"]`);\n if (!(element instanceof HTMLElement)) {\n throw new Error(`Could not find test element ${testId}`);\n }\n return element;\n}\n\nfunction getButton(label: string): HTMLButtonElement {\n const button = [...container?.querySelectorAll(\"button\") ?? []]\n .find((element) => element.textContent === label);\n if (!(button instanceof HTMLButtonElement)) {\n throw new Error(`Could not find button ${label}`);\n }\n return button;\n}\n\ndescribe(\"useCliAuthConfirmation\", () => {\n beforeEach(() => {\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", true);\n });\n\n afterEach(() => {\n act(() => {\n root?.unmount();\n });\n container?.remove();\n root = null;\n container = null;\n vi.restoreAllMocks();\n sessionStorage.clear();\n window.history.replaceState({}, \"\", \"/\");\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", previousActEnvironment);\n });\n\n it(\"completes CLI auth with the current user's refresh token\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const getTokens = vi.fn(async () => ({ refreshToken: \"refresh-token\" }));\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: { currentSession: { getTokens } },\n sendRequest,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(getByTestId(\"status\").textContent).toBe(\"success\");\n expect(getTokens).toHaveBeenCalledOnce();\n expect(sendRequest).toHaveBeenCalledOnce();\n expect(sendRequest.mock.calls[0][0]).toBe(\"/auth/cli/complete\");\n expect(JSON.parse(String(sendRequest.mock.calls[0][1].body))).toMatchInlineSnapshot(`\n {\n \"login_code\": \"login-code\",\n \"refresh_token\": \"refresh-token\",\n }\n `);\n });\n\n it(\"redirects restricted users to onboarding without completing CLI auth\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const redirectToOnboarding = vi.fn(async (_options: { replace: true }) => {});\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: {\n isRestricted: true,\n isAnonymous: false,\n currentSession: {\n getTokens: vi.fn(async () => ({ refreshToken: \"restricted-refresh-token\" })),\n },\n },\n sendRequest,\n redirectToOnboarding,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(redirectToOnboarding).toHaveBeenCalledWith({ replace: true });\n expect(sessionStorage.getItem(\"hexclave-cli-auth-confirmed\")).toBe(\"login-code\");\n expect(sendRequest).not.toHaveBeenCalled();\n });\n\n it(\"redirects anonymous users to sign-up without completing CLI auth\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const redirectToSignUp = vi.fn(async (_options: { replace: true }) => {});\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: {\n isRestricted: true,\n isAnonymous: true,\n currentSession: {\n getTokens: vi.fn(async () => ({ refreshToken: \"anonymous-refresh-token\" })),\n },\n },\n sendRequest,\n redirectToSignUp,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(redirectToSignUp).toHaveBeenCalledWith({ replace: true });\n expect(sessionStorage.getItem(\"hexclave-cli-auth-confirmed\")).toBe(\"login-code\");\n expect(sendRequest).not.toHaveBeenCalled();\n });\n\n it(\"ignores duplicate authorize clicks before React re-renders\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const getTokens = vi.fn(async () => ({ refreshToken: \"refresh-token\" }));\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: { currentSession: { getTokens } },\n sendRequest,\n });\n\n await renderWithApp(app);\n await act(async () => {\n const authorizeButton = getButton(\"authorize\");\n authorizeButton.click();\n authorizeButton.click();\n });\n\n expect(sendRequest).toHaveBeenCalledOnce();\n });\n\n it(\"claims anonymous CLI sessions before redirecting to sign-up\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const signInWithTokens = vi.fn(async (_tokens: { accessToken: string, refreshToken: string }) => {});\n const redirectToSignUp = vi.fn(async (_options: { replace: true }) => {});\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }))\n .mockResolvedValueOnce(responseJson({ cli_session_state: \"anonymous\" }))\n .mockResolvedValueOnce(responseJson({ access_token: \"access-token\", refresh_token: \"refresh-token\" }));\n const app = createAppTestDouble({\n user: null,\n sendRequest,\n signInWithTokens,\n redirectToSignUp,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(redirectToSignUp).toHaveBeenCalledWith({ replace: true });\n expect(signInWithTokens).toHaveBeenCalledWith({\n accessToken: \"access-token\",\n refreshToken: \"refresh-token\",\n });\n expect(sessionStorage.getItem(\"hexclave-cli-auth-confirmed\")).toBe(\"login-code\");\n expect(sendRequest.mock.calls.map(call => JSON.parse(String(call[1].body)))).toMatchInlineSnapshot(`\n [\n {\n \"login_code\": \"login-code\",\n \"mode\": \"check\",\n },\n {\n \"login_code\": \"login-code\",\n \"mode\": \"claim-anon-session\",\n },\n ]\n `);\n });\n\n it(\"reports invalid when the login code is missing\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm\");\n const app = createAppTestDouble({\n user: null,\n sendRequest: vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 })),\n });\n\n await renderWithApp(app);\n\n expect(getByTestId(\"status\").textContent).toBe(\"invalid\");\n });\n});\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { ApiKey, ApiKeyCreationOptions, ApiKeyType } from "../lib/hexclave-app/a
3
3
  //#region src/components/api-key-dialogs.d.ts
4
4
  declare const neverInMs: number;
5
5
  declare const expiresInOptions: {
6
- readonly [x: number]: "Never" | "1 day" | "7 days" | "30 days" | "90 days" | "1 year";
6
+ readonly [x: number]: "1 day" | "7 days" | "30 days" | "90 days" | "1 year" | "Never";
7
7
  };
8
8
  /**
9
9
  * Dialog for creating a new API key
@@ -0,0 +1,13 @@
1
+ //#region src/components/message-cards/no-auth-methods-message-card.d.ts
2
+ /**
3
+ * Shown instead of the sign-in/sign-up form when a project has no auth methods at all (no password, no
4
+ * OTP, no passkey, no OAuth provider). Without this, the page would look broken: there is nothing the end
5
+ * user can do, and the developer gets no hint about what went wrong or how to fix it.
6
+ */
7
+ declare function NoAuthMethodsMessageCard(props: {
8
+ fullPage?: boolean;
9
+ projectDisplayName?: string;
10
+ }): import("react/jsx-runtime").JSX.Element;
11
+ //#endregion
12
+ export { NoAuthMethodsMessageCard };
13
+ //# sourceMappingURL=no-auth-methods-message-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-auth-methods-message-card.d.ts","names":[],"sources":["../../../../src/components/message-cards/no-auth-methods-message-card.tsx"],"mappings":";;AAsBA;;;;iBAAgB,wBAAA,CAAyB,KAAA;EACvC,QAAA;EACA,kBAAA;AAAA,gCACD,GAAA,CAAA,OAAA"}
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { Typography } from "@hexclave/ui";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { KeyRound } from "lucide-react";
5
+ import { MaybeFullPage } from "../elements/maybe-full-page.js";
6
+ import { useTranslation } from "../../lib/translations.js";
7
+ //#region src/components/message-cards/no-auth-methods-message-card.tsx
8
+ const configSnippet = `auth: {
9
+ password: { allowSignIn: true },
10
+ otp: { allowSignIn: true },
11
+ }`;
12
+ /**
13
+ * Shown instead of the sign-in/sign-up form when a project has no auth methods at all (no password, no
14
+ * OTP, no passkey, no OAuth provider). Without this, the page would look broken: there is nothing the end
15
+ * user can do, and the developer gets no hint about what went wrong or how to fix it.
16
+ */
17
+ function NoAuthMethodsMessageCard(props) {
18
+ const { t } = useTranslation();
19
+ return /* @__PURE__ */ jsx(MaybeFullPage, {
20
+ fullPage: !!props.fullPage,
21
+ children: /* @__PURE__ */ jsxs("div", {
22
+ className: "stack-scope flex flex-col items-stretch",
23
+ style: {
24
+ maxWidth: "380px",
25
+ flexBasis: "380px",
26
+ padding: props.fullPage ? "1rem" : 0
27
+ },
28
+ children: [/* @__PURE__ */ jsxs("div", {
29
+ className: "flex flex-col items-center text-center",
30
+ children: [
31
+ /* @__PURE__ */ jsx("div", {
32
+ className: "mb-4 flex h-11 w-11 items-center justify-center rounded-2xl border border-black/[0.08] dark:border-white/[0.10]",
33
+ children: /* @__PURE__ */ jsx(KeyRound, { className: "h-5 w-5 text-gray-500" })
34
+ }),
35
+ /* @__PURE__ */ jsx(Typography, {
36
+ type: "h2",
37
+ children: t("Sign-in is not available")
38
+ }),
39
+ /* @__PURE__ */ jsx(Typography, {
40
+ type: "label",
41
+ variant: "secondary",
42
+ children: props.projectDisplayName == null ? t("This app has no authentication methods enabled, so nobody can sign in yet.") : t("{projectDisplayName} has no authentication methods enabled, so nobody can sign in yet.", { projectDisplayName: props.projectDisplayName })
43
+ })
44
+ ]
45
+ }), /* @__PURE__ */ jsxs("div", {
46
+ className: "mt-6 rounded-xl border border-black/[0.08] p-4 text-left dark:border-white/[0.10]",
47
+ children: [
48
+ /* @__PURE__ */ jsx(Typography, {
49
+ type: "label",
50
+ children: t("Are you the developer?")
51
+ }),
52
+ /* @__PURE__ */ jsx(Typography, {
53
+ type: "label",
54
+ variant: "secondary",
55
+ children: t("Enable at least one sign-in method in your hexclave.config.ts:")
56
+ }),
57
+ /* @__PURE__ */ jsx("pre", {
58
+ className: "mt-2 overflow-x-auto rounded-lg border border-black/[0.06] p-3 font-mono text-xs leading-relaxed dark:border-white/[0.08]",
59
+ children: configSnippet
60
+ }),
61
+ /* @__PURE__ */ jsx(Typography, {
62
+ type: "label",
63
+ variant: "secondary",
64
+ children: t("Or turn one on in the Hexclave dashboard, under Auth Methods.")
65
+ })
66
+ ]
67
+ })]
68
+ })
69
+ });
70
+ }
71
+ //#endregion
72
+ export { NoAuthMethodsMessageCard };
73
+
74
+ //# sourceMappingURL=no-auth-methods-message-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-auth-methods-message-card.js","names":[],"sources":["../../../../src/components/message-cards/no-auth-methods-message-card.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { Typography } from \"@hexclave/ui\";\nimport { KeyRound } from \"lucide-react\";\nimport { useTranslation } from \"../../lib/translations\";\nimport { MaybeFullPage } from \"../elements/maybe-full-page\";\n\nconst configSnippet = `auth: {\n password: { allowSignIn: true },\n otp: { allowSignIn: true },\n}`;\n\n/**\n * Shown instead of the sign-in/sign-up form when a project has no auth methods at all (no password, no\n * OTP, no passkey, no OAuth provider). Without this, the page would look broken: there is nothing the end\n * user can do, and the developer gets no hint about what went wrong or how to fix it.\n */\nexport function NoAuthMethodsMessageCard(props: {\n fullPage?: boolean,\n projectDisplayName?: string,\n}) {\n const { t } = useTranslation();\n\n return (\n <MaybeFullPage fullPage={!!props.fullPage}>\n <div className='stack-scope flex flex-col items-stretch' style={{ maxWidth: '380px', flexBasis: '380px', padding: props.fullPage ? '1rem' : 0 }}>\n <div className=\"flex flex-col items-center text-center\">\n <div className=\"mb-4 flex h-11 w-11 items-center justify-center rounded-2xl border border-black/[0.08] dark:border-white/[0.10]\">\n <KeyRound className=\"h-5 w-5 text-gray-500\" />\n </div>\n <Typography type='h2'>{t(\"Sign-in is not available\")}</Typography>\n <Typography type='label' variant='secondary'>\n {props.projectDisplayName == null\n ? t(\"This app has no authentication methods enabled, so nobody can sign in yet.\")\n : t(\"{projectDisplayName} has no authentication methods enabled, so nobody can sign in yet.\", { projectDisplayName: props.projectDisplayName })}\n </Typography>\n </div>\n\n <div className=\"mt-6 rounded-xl border border-black/[0.08] p-4 text-left dark:border-white/[0.10]\">\n <Typography type='label'>{t(\"Are you the developer?\")}</Typography>\n <Typography type='label' variant='secondary'>\n {t(\"Enable at least one sign-in method in your hexclave.config.ts:\")}\n </Typography>\n <pre className=\"mt-2 overflow-x-auto rounded-lg border border-black/[0.06] p-3 font-mono text-xs leading-relaxed dark:border-white/[0.08]\">{configSnippet}</pre>\n <Typography type='label' variant='secondary'>\n {t(\"Or turn one on in the Hexclave dashboard, under Auth Methods.\")}\n </Typography>\n </div>\n </div>\n </MaybeFullPage>\n );\n}\n"],"mappings":";;;;;;;AAYA,MAAM,gBAAgB;;;;;;;;;AAUtB,SAAgB,yBAAyB,OAGtC;CACD,MAAM,EAAE,MAAM,eAAe;CAE7B,OACE,oBAAC,eAAD;EAAe,UAAU,CAAC,CAAC,MAAM;YAC/B,qBAAC,OAAD;GAAK,WAAU;GAA0C,OAAO;IAAE,UAAU;IAAS,WAAW;IAAS,SAAS,MAAM,WAAW,SAAS;GAAE;aAA9I,CACE,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,UAAD,EAAU,WAAU,wBAAyB,CAAA;KAC1C,CAAA;KACL,oBAAC,YAAD;MAAY,MAAK;gBAAM,EAAE,0BAA0B;KAAc,CAAA;KACjE,oBAAC,YAAD;MAAY,MAAK;MAAQ,SAAQ;gBAC9B,MAAM,sBAAsB,OACzB,EAAE,4EAA4E,IAC9E,EAAE,0FAA0F,EAAE,oBAAoB,MAAM,mBAAmB,CAAC;KACtI,CAAA;IACT;OAEL,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,YAAD;MAAY,MAAK;gBAAS,EAAE,wBAAwB;KAAc,CAAA;KAClE,oBAAC,YAAD;MAAY,MAAK;MAAQ,SAAQ;gBAC9B,EAAE,gEAAgE;KACzD,CAAA;KACZ,oBAAC,OAAD;MAAK,WAAU;gBAA6H;KAAmB,CAAA;KAC/J,oBAAC,YAAD;MAAY,MAAK;MAAQ,SAAQ;gBAC9B,EAAE,+DAA+D;KACxD,CAAA;IACT;KACF;;CACQ,CAAA;AAEnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth-page.d.ts","names":[],"sources":["../../../src/components-page/auth-page.tsx"],"mappings":";KA2BK,KAAA;EACH,gBAAA;EACA,QAAA;EACA,QAAA;EACA,IAAA;EACA,iBAAA;EACA,SAAA,GAAY,KAAA,CAAM,SAAS;EAC3B,WAAA;IACE,MAAA;MACE,aAAA;MACA,iBAAA;MACA,cAAA;MACA,gBAAA;MACA,cAAA;QACE,EAAA;MAAA;IAAA;EAAA;AAAA;AAAA,iBAWQ,QAAA,CAAS,KAAA,EAAO,KAAK,+BAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"auth-page.d.ts","names":[],"sources":["../../../src/components-page/auth-page.tsx"],"mappings":";KA4BK,KAAA;EACH,gBAAA;EACA,QAAA;EACA,QAAA;EACA,IAAA;EACA,iBAAA;EACA,SAAA,GAAY,KAAA,CAAM,SAAS;EAC3B,WAAA;IACE,MAAA;MACE,aAAA;MACA,iBAAA;MACA,cAAA;MACA,gBAAA;MACA,cAAA;QACE,EAAA;MAAA;IAAA;EAAA;AAAA;AAAA,iBAWQ,QAAA,CAAS,KAAA,EAAO,KAAK,+BAAA,GAAA,CAAA,OAAA"}
@@ -15,6 +15,7 @@ import { SeparatorWithText } from "../components/elements/separator-with-text.js
15
15
  import { StyledLink } from "../components/link.js";
16
16
  import { KnownErrorMessageCard } from "../components/message-cards/known-error-message-card.js";
17
17
  import { MessageCard as MessageCard$1 } from "../components/message-cards/message-card.js";
18
+ import { NoAuthMethodsMessageCard } from "../components/message-cards/no-auth-methods-message-card.js";
18
19
  import { MagicLinkSignIn } from "../components/magic-link-sign-in.js";
19
20
  import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card.js";
20
21
  import { OAuthButtonGroup } from "../components/oauth-button-group.js";
@@ -111,6 +112,10 @@ function Inner(props) {
111
112
  const hasOAuthProviders = project.config.oauthProviders.length > 0;
112
113
  const hasPasskey = project.config.passkeyEnabled === true && props.type === "sign-in";
113
114
  const enableSeparator = (project.config.credentialEnabled || project.config.magicLinkEnabled) && (hasOAuthProviders || hasPasskey);
115
+ if (!(hasOAuthProviders || project.config.passkeyEnabled === true || project.config.credentialEnabled || project.config.magicLinkEnabled)) return /* @__PURE__ */ jsx(NoAuthMethodsMessageCard, {
116
+ fullPage: props.fullPage,
117
+ projectDisplayName: "displayName" in project ? project.displayName : void 0
118
+ });
114
119
  return /* @__PURE__ */ jsx(MaybeFullPage, {
115
120
  fullPage: !!props.fullPage,
116
121
  children: /* @__PURE__ */ jsxs("div", {
@@ -183,9 +188,9 @@ function Inner(props) {
183
188
  })
184
189
  ]
185
190
  }) : project.config.credentialEnabled ? props.type === "sign-up" ? /* @__PURE__ */ jsx(CredentialSignUp, { noPasswordRepeat: props.noPasswordRepeat }) : /* @__PURE__ */ jsx(CredentialSignIn, {}) : project.config.magicLinkEnabled ? /* @__PURE__ */ jsx(MagicLinkSignIn, {}) : !(hasOAuthProviders || hasPasskey) ? /* @__PURE__ */ jsx(Typography, {
186
- variant: "destructive",
191
+ variant: "secondary",
187
192
  className: "text-center",
188
- children: t("No authentication method enabled.")
193
+ children: t("New accounts can't be created with the sign-in methods enabled for this app. Sign in instead.")
189
194
  }) : null,
190
195
  props.extraInfo && /* @__PURE__ */ jsx("div", {
191
196
  className: cn("flex flex-col items-center text-center text-sm text-gray-500", {
@@ -1 +1 @@
1
- {"version":3,"file":"auth-page.js","names":["MessageCard"],"sources":["../../../src/components-page/auth-page.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { KnownError } from '@hexclave/shared';\nimport { captureError } from '@hexclave/shared/dist/utils/errors';\nimport { runAsynchronously } from '@hexclave/shared/dist/utils/promises';\nimport { use } from '@hexclave/shared/dist/utils/react';\nimport { Skeleton, Tabs, TabsContent, TabsList, TabsTrigger, Typography, cn } from '@hexclave/ui';\nimport { Suspense, useMemo } from 'react';\nimport { useStackApp, useUser } from '..';\nimport { CredentialSignIn } from '../components/credential-sign-in';\nimport { CredentialSignUp } from '../components/credential-sign-up';\nimport { MaybeFullPage } from '../components/elements/maybe-full-page';\nimport { SeparatorWithText } from '../components/elements/separator-with-text';\nimport { StyledLink } from '../components/link';\nimport { KnownErrorMessageCard } from '../components/message-cards/known-error-message-card';\nimport { MessageCard } from '../components/message-cards/message-card';\nimport { MagicLinkSignIn } from '../components/magic-link-sign-in';\nimport { PredefinedMessageCard } from '../components/message-cards/predefined-message-card';\nimport { OAuthButtonGroup } from '../components/oauth-button-group';\nimport { PasskeyButton } from '../components/passkey-button';\nimport { useTranslation } from '../lib/translations';\n\ntype Props = {\n noPasswordRepeat?: boolean,\n firstTab?: 'magic-link' | 'password',\n fullPage?: boolean,\n type: 'sign-in' | 'sign-up',\n automaticRedirect?: boolean,\n extraInfo?: React.ReactNode,\n mockProject?: {\n config: {\n signUpEnabled: boolean,\n credentialEnabled: boolean,\n passkeyEnabled: boolean,\n magicLinkEnabled: boolean,\n oauthProviders: {\n id: string,\n }[],\n },\n },\n}\n\ntype AutomaticRedirectResult =\n | { status: \"success\" }\n | { status: \"known-error\", error: KnownError }\n | { status: \"unknown-error\" };\n\nexport function AuthPage(props: Props) {\n return <Suspense fallback={<Fallback {...props} />}>\n <Inner {...props} />\n </Suspense>;\n}\n\nfunction Fallback(props: Props) {\n return (\n <MaybeFullPage fullPage={!!props.fullPage}>\n <div className='stack-scope flex flex-col items-stretch' style={{ maxWidth: '380px', flexBasis: '380px', padding: props.fullPage ? '1rem' : 0 }}>\n <div className=\"text-center mb-6 flex flex-col\">\n <Skeleton className='h-9 w-2/3 self-center' />\n\n <Skeleton className='h-3 w-16 mt-8' />\n <Skeleton className='h-9 w-full mt-1' />\n\n <Skeleton className='h-3 w-24 mt-2' />\n <Skeleton className='h-9 w-full mt-1' />\n\n <Skeleton className='h-9 w-full mt-6' />\n </div>\n </div>\n </MaybeFullPage>\n );\n}\n\nfunction AutomaticRedirect(props: {\n fullPage?: boolean,\n isRestricted: boolean,\n type: 'sign-in' | 'sign-up',\n}) {\n const hexclaveApp = useStackApp();\n const { t } = useTranslation();\n const redirectResultPromise = useMemo(async (): Promise<AutomaticRedirectResult> => {\n try {\n await (\n props.isRestricted\n ? hexclaveApp.redirectToOnboarding({ replace: true })\n : props.type === 'sign-in'\n ? hexclaveApp.redirectToAfterSignIn({ replace: true })\n : hexclaveApp.redirectToAfterSignUp({ replace: true })\n );\n return { status: \"success\" };\n } catch (e) {\n if (KnownError.isKnownError(e)) {\n return { status: \"known-error\", error: e };\n }\n captureError(\"<AuthPage automaticRedirect />\", e);\n return { status: \"unknown-error\" };\n }\n }, [hexclaveApp, props.isRestricted, props.type]);\n\n const redirectResult = use(redirectResultPromise);\n if (redirectResult.status === \"known-error\") {\n return <KnownErrorMessageCard error={redirectResult.error} fullPage={props.fullPage} />;\n }\n if (redirectResult.status === \"unknown-error\") {\n return <PredefinedMessageCard type='unknownError' fullPage={props.fullPage} />;\n }\n return <MessageCard title={t(\"Redirecting...\")} fullPage={props.fullPage} />;\n}\n\nfunction Inner(props: Props) {\n const hexclaveApp = useStackApp();\n const user = useUser({ includeRestricted: true });\n const projectFromHook = hexclaveApp.useProject();\n const project = props.mockProject || projectFromHook;\n const { t } = useTranslation();\n\n if (props.automaticRedirect && user && !props.mockProject) {\n return <Suspense fallback={<MessageCard title={t(\"Redirecting...\")} fullPage={props.fullPage} />}>\n <AutomaticRedirect fullPage={props.fullPage} isRestricted={user.isRestricted} type={props.type} />\n </Suspense>;\n }\n\n if (user && !props.mockProject && !props.automaticRedirect) {\n return <PredefinedMessageCard type='signedIn' fullPage={props.fullPage} />;\n }\n\n if (props.type === 'sign-up' && !project.config.signUpEnabled) {\n return <PredefinedMessageCard type='signUpDisabled' fullPage={props.fullPage} />;\n }\n\n const hasOAuthProviders = project.config.oauthProviders.length > 0;\n const hasPasskey = (project.config.passkeyEnabled === true && props.type === \"sign-in\");\n const enableSeparator = (project.config.credentialEnabled || project.config.magicLinkEnabled) && (hasOAuthProviders || hasPasskey);\n\n return (\n <MaybeFullPage fullPage={!!props.fullPage}>\n <div className='stack-scope flex flex-col items-stretch' style={{ maxWidth: '380px', flexBasis: '380px', padding: props.fullPage ? '1rem' : 0 }}>\n <div className=\"text-center mb-6\">\n <Typography type='h2'>\n {props.type === 'sign-in' ? t(\"Sign in to your account\") : t(\"Create a new account\")}\n </Typography>\n {props.type === 'sign-in' ? (\n project.config.signUpEnabled && (\n <Typography>\n {t(\"Don't have an account?\")}{\" \"}\n <StyledLink href=\"#\" onClick={(e) => {\n runAsynchronously(hexclaveApp.redirectToSignUp());\n e.preventDefault();\n }}>{t(\"Sign up\")}</StyledLink>\n </Typography>\n )\n ) : (\n <Typography>\n {t(\"Already have an account?\")}{\" \"}\n <StyledLink href=\"#\" onClick={(e) => {\n runAsynchronously(hexclaveApp.redirectToSignIn());\n e.preventDefault();\n }}>{t(\"Sign in\")}</StyledLink>\n </Typography>\n )}\n </div>\n {(hasOAuthProviders || hasPasskey) && (\n <div className='gap-4 flex flex-col items-stretch stack-scope'>\n {hasOAuthProviders && <OAuthButtonGroup type={props.type} mockProject={props.mockProject} />}\n {hasPasskey && <PasskeyButton type={props.type} />}\n </div>\n )}\n\n {enableSeparator && <SeparatorWithText text={t('Or continue with')} />}\n {project.config.credentialEnabled && project.config.magicLinkEnabled ? (\n <Tabs defaultValue={props.firstTab || 'magic-link'}>\n <TabsList className={cn('w-full mb-2', {\n 'flex-row-reverse': props.firstTab === 'password'\n })}>\n <TabsTrigger value='magic-link' className='flex-1'>{t(\"Email\")}</TabsTrigger>\n <TabsTrigger value='password' className='flex-1'>{t(\"Email & Password\")}</TabsTrigger>\n </TabsList>\n <TabsContent value='magic-link'>\n <MagicLinkSignIn />\n </TabsContent>\n <TabsContent value='password'>\n {props.type === 'sign-up' ? <CredentialSignUp noPasswordRepeat={props.noPasswordRepeat} /> : <CredentialSignIn />}\n </TabsContent>\n </Tabs>\n ) : project.config.credentialEnabled ? (\n props.type === 'sign-up' ? <CredentialSignUp noPasswordRepeat={props.noPasswordRepeat} /> : <CredentialSignIn />\n ) : project.config.magicLinkEnabled ? (\n <MagicLinkSignIn />\n ) : !(hasOAuthProviders || hasPasskey) ? <Typography variant={\"destructive\"} className=\"text-center\">{t(\"No authentication method enabled.\")}</Typography> : null}\n {props.extraInfo && (\n <div className={cn('flex flex-col items-center text-center text-sm text-gray-500', {\n 'mt-2': project.config.credentialEnabled || project.config.magicLinkEnabled,\n 'mt-6': !(project.config.credentialEnabled || project.config.magicLinkEnabled),\n })}>\n <div>{props.extraInfo}</div>\n </div>\n )}\n </div>\n </MaybeFullPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth-page.js","names":["MessageCard"],"sources":["../../../src/components-page/auth-page.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { KnownError } from '@hexclave/shared';\nimport { captureError } from '@hexclave/shared/dist/utils/errors';\nimport { runAsynchronously } from '@hexclave/shared/dist/utils/promises';\nimport { use } from '@hexclave/shared/dist/utils/react';\nimport { Skeleton, Tabs, TabsContent, TabsList, TabsTrigger, Typography, cn } from '@hexclave/ui';\nimport { Suspense, useMemo } from 'react';\nimport { useStackApp, useUser } from '..';\nimport { CredentialSignIn } from '../components/credential-sign-in';\nimport { CredentialSignUp } from '../components/credential-sign-up';\nimport { MaybeFullPage } from '../components/elements/maybe-full-page';\nimport { SeparatorWithText } from '../components/elements/separator-with-text';\nimport { StyledLink } from '../components/link';\nimport { KnownErrorMessageCard } from '../components/message-cards/known-error-message-card';\nimport { MessageCard } from '../components/message-cards/message-card';\nimport { NoAuthMethodsMessageCard } from '../components/message-cards/no-auth-methods-message-card';\nimport { MagicLinkSignIn } from '../components/magic-link-sign-in';\nimport { PredefinedMessageCard } from '../components/message-cards/predefined-message-card';\nimport { OAuthButtonGroup } from '../components/oauth-button-group';\nimport { PasskeyButton } from '../components/passkey-button';\nimport { useTranslation } from '../lib/translations';\n\ntype Props = {\n noPasswordRepeat?: boolean,\n firstTab?: 'magic-link' | 'password',\n fullPage?: boolean,\n type: 'sign-in' | 'sign-up',\n automaticRedirect?: boolean,\n extraInfo?: React.ReactNode,\n mockProject?: {\n config: {\n signUpEnabled: boolean,\n credentialEnabled: boolean,\n passkeyEnabled: boolean,\n magicLinkEnabled: boolean,\n oauthProviders: {\n id: string,\n }[],\n },\n },\n}\n\ntype AutomaticRedirectResult =\n | { status: \"success\" }\n | { status: \"known-error\", error: KnownError }\n | { status: \"unknown-error\" };\n\nexport function AuthPage(props: Props) {\n return <Suspense fallback={<Fallback {...props} />}>\n <Inner {...props} />\n </Suspense>;\n}\n\nfunction Fallback(props: Props) {\n return (\n <MaybeFullPage fullPage={!!props.fullPage}>\n <div className='stack-scope flex flex-col items-stretch' style={{ maxWidth: '380px', flexBasis: '380px', padding: props.fullPage ? '1rem' : 0 }}>\n <div className=\"text-center mb-6 flex flex-col\">\n <Skeleton className='h-9 w-2/3 self-center' />\n\n <Skeleton className='h-3 w-16 mt-8' />\n <Skeleton className='h-9 w-full mt-1' />\n\n <Skeleton className='h-3 w-24 mt-2' />\n <Skeleton className='h-9 w-full mt-1' />\n\n <Skeleton className='h-9 w-full mt-6' />\n </div>\n </div>\n </MaybeFullPage>\n );\n}\n\nfunction AutomaticRedirect(props: {\n fullPage?: boolean,\n isRestricted: boolean,\n type: 'sign-in' | 'sign-up',\n}) {\n const hexclaveApp = useStackApp();\n const { t } = useTranslation();\n const redirectResultPromise = useMemo(async (): Promise<AutomaticRedirectResult> => {\n try {\n await (\n props.isRestricted\n ? hexclaveApp.redirectToOnboarding({ replace: true })\n : props.type === 'sign-in'\n ? hexclaveApp.redirectToAfterSignIn({ replace: true })\n : hexclaveApp.redirectToAfterSignUp({ replace: true })\n );\n return { status: \"success\" };\n } catch (e) {\n if (KnownError.isKnownError(e)) {\n return { status: \"known-error\", error: e };\n }\n captureError(\"<AuthPage automaticRedirect />\", e);\n return { status: \"unknown-error\" };\n }\n }, [hexclaveApp, props.isRestricted, props.type]);\n\n const redirectResult = use(redirectResultPromise);\n if (redirectResult.status === \"known-error\") {\n return <KnownErrorMessageCard error={redirectResult.error} fullPage={props.fullPage} />;\n }\n if (redirectResult.status === \"unknown-error\") {\n return <PredefinedMessageCard type='unknownError' fullPage={props.fullPage} />;\n }\n return <MessageCard title={t(\"Redirecting...\")} fullPage={props.fullPage} />;\n}\n\nfunction Inner(props: Props) {\n const hexclaveApp = useStackApp();\n const user = useUser({ includeRestricted: true });\n const projectFromHook = hexclaveApp.useProject();\n const project = props.mockProject || projectFromHook;\n const { t } = useTranslation();\n\n if (props.automaticRedirect && user && !props.mockProject) {\n return <Suspense fallback={<MessageCard title={t(\"Redirecting...\")} fullPage={props.fullPage} />}>\n <AutomaticRedirect fullPage={props.fullPage} isRestricted={user.isRestricted} type={props.type} />\n </Suspense>;\n }\n\n if (user && !props.mockProject && !props.automaticRedirect) {\n return <PredefinedMessageCard type='signedIn' fullPage={props.fullPage} />;\n }\n\n if (props.type === 'sign-up' && !project.config.signUpEnabled) {\n return <PredefinedMessageCard type='signUpDisabled' fullPage={props.fullPage} />;\n }\n\n const hasOAuthProviders = project.config.oauthProviders.length > 0;\n const hasPasskey = (project.config.passkeyEnabled === true && props.type === \"sign-in\");\n const enableSeparator = (project.config.credentialEnabled || project.config.magicLinkEnabled) && (hasOAuthProviders || hasPasskey);\n\n // Note that we check passkeys regardless of `props.type` here (unlike `hasPasskey`); a project with only\n // passkeys enabled can't sign up with them, but it is configured correctly, so the sign-up page should\n // keep pointing at the sign-in page instead of claiming that the project is misconfigured.\n const hasAnyAuthMethod = hasOAuthProviders\n || project.config.passkeyEnabled === true\n || project.config.credentialEnabled\n || project.config.magicLinkEnabled;\n\n if (!hasAnyAuthMethod) {\n return <NoAuthMethodsMessageCard fullPage={props.fullPage} projectDisplayName={'displayName' in project ? project.displayName : undefined} />;\n }\n\n return (\n <MaybeFullPage fullPage={!!props.fullPage}>\n <div className='stack-scope flex flex-col items-stretch' style={{ maxWidth: '380px', flexBasis: '380px', padding: props.fullPage ? '1rem' : 0 }}>\n <div className=\"text-center mb-6\">\n <Typography type='h2'>\n {props.type === 'sign-in' ? t(\"Sign in to your account\") : t(\"Create a new account\")}\n </Typography>\n {props.type === 'sign-in' ? (\n project.config.signUpEnabled && (\n <Typography>\n {t(\"Don't have an account?\")}{\" \"}\n <StyledLink href=\"#\" onClick={(e) => {\n runAsynchronously(hexclaveApp.redirectToSignUp());\n e.preventDefault();\n }}>{t(\"Sign up\")}</StyledLink>\n </Typography>\n )\n ) : (\n <Typography>\n {t(\"Already have an account?\")}{\" \"}\n <StyledLink href=\"#\" onClick={(e) => {\n runAsynchronously(hexclaveApp.redirectToSignIn());\n e.preventDefault();\n }}>{t(\"Sign in\")}</StyledLink>\n </Typography>\n )}\n </div>\n {(hasOAuthProviders || hasPasskey) && (\n <div className='gap-4 flex flex-col items-stretch stack-scope'>\n {hasOAuthProviders && <OAuthButtonGroup type={props.type} mockProject={props.mockProject} />}\n {hasPasskey && <PasskeyButton type={props.type} />}\n </div>\n )}\n\n {enableSeparator && <SeparatorWithText text={t('Or continue with')} />}\n {project.config.credentialEnabled && project.config.magicLinkEnabled ? (\n <Tabs defaultValue={props.firstTab || 'magic-link'}>\n <TabsList className={cn('w-full mb-2', {\n 'flex-row-reverse': props.firstTab === 'password'\n })}>\n <TabsTrigger value='magic-link' className='flex-1'>{t(\"Email\")}</TabsTrigger>\n <TabsTrigger value='password' className='flex-1'>{t(\"Email & Password\")}</TabsTrigger>\n </TabsList>\n <TabsContent value='magic-link'>\n <MagicLinkSignIn />\n </TabsContent>\n <TabsContent value='password'>\n {props.type === 'sign-up' ? <CredentialSignUp noPasswordRepeat={props.noPasswordRepeat} /> : <CredentialSignIn />}\n </TabsContent>\n </Tabs>\n ) : project.config.credentialEnabled ? (\n props.type === 'sign-up' ? <CredentialSignUp noPasswordRepeat={props.noPasswordRepeat} /> : <CredentialSignIn />\n ) : project.config.magicLinkEnabled ? (\n <MagicLinkSignIn />\n ) : !(hasOAuthProviders || hasPasskey) ? (\n // Only reachable on the sign-up page of a passkey-only project: the project is configured\n // correctly, there is just no way to create an account with a passkey alone.\n <Typography variant=\"secondary\" className=\"text-center\">{t(\"New accounts can't be created with the sign-in methods enabled for this app. Sign in instead.\")}</Typography>\n ) : null}\n {props.extraInfo && (\n <div className={cn('flex flex-col items-center text-center text-sm text-gray-500', {\n 'mt-2': project.config.credentialEnabled || project.config.magicLinkEnabled,\n 'mt-6': !(project.config.credentialEnabled || project.config.magicLinkEnabled),\n })}>\n <div>{props.extraInfo}</div>\n </div>\n )}\n </div>\n </MaybeFullPage>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cli-auth-confirm.d.ts","names":[],"sources":["../../../src/components-page/cli-auth-confirm.tsx"],"mappings":";KAiEY,yBAAA;AAAA,KAQA,wBAAA;EACV,MAAA,EAAQ,yBAAA;EACR,SAAA;EACA,KAAA,EAAO,KAAA;EACP,SAAA;EACA,SAAA,QAAiB,OAAA;EACjB,KAAA;AAAA;AAAA,iBAGc,sBAAA,IAA0B,wBAAwB;AAAA,iBAgIlD,mBAAA;EAAsB;AAAA;EAAqB,QAAA;AAAA,gCAAoB,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"cli-auth-confirm.d.ts","names":[],"sources":["../../../src/components-page/cli-auth-confirm.tsx"],"mappings":";KAiEY,yBAAA;AAAA,KAQA,wBAAA;EACV,MAAA,EAAQ,yBAAA;EACR,SAAA;EACA,KAAA,EAAO,KAAA;EACP,SAAA;EACA,SAAA,QAAiB,OAAA;EACjB,KAAA;AAAA;AAAA,iBAGc,sBAAA,IAA0B,wBAAwB;AAAA,iBA0IlD,mBAAA;EAAsB;AAAA;EAAqB,QAAA;AAAA,gCAAoB,GAAA,CAAA,OAAA"}
@@ -71,7 +71,7 @@ function useCliAuthConfirmation() {
71
71
  user
72
72
  ]);
73
73
  useEffect(() => {
74
- if (!confirmed || !user || autoCompleteRef.current) return;
74
+ if (!confirmed || !user || user.isRestricted || autoCompleteRef.current) return;
75
75
  autoCompleteRef.current = true;
76
76
  runAsynchronouslyWithAlert(async () => {
77
77
  setStatus("authorizing");
@@ -101,6 +101,12 @@ function useCliAuthConfirmation() {
101
101
  setError(null);
102
102
  setStatus("authorizing");
103
103
  if (user) {
104
+ if (user.isRestricted) {
105
+ markConfirmed(loginCode);
106
+ setStatus("redirecting");
107
+ await (user.isAnonymous ? app.redirectToSignUp({ replace: true }) : app.redirectToOnboarding({ replace: true }));
108
+ return;
109
+ }
104
110
  await completeWithCurrentUser();
105
111
  clearConfirmed();
106
112
  setStatus("success");
@@ -1 +1 @@
1
- {"version":3,"file":"cli-auth-confirm.js","names":[],"sources":["../../../src/components-page/cli-auth-confirm.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { runAsynchronouslyWithAlert } from \"@hexclave/shared/dist/utils/promises\";\nimport { Typography } from \"@hexclave/ui\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { MessageCard } from \"../components/message-cards/message-card\";\nimport { useTranslation } from \"../lib/translations\";\nimport { hexclaveAppInternalsSymbol } from \"../lib/hexclave-app/common\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { useStackApp } from \"../lib/hooks\";\n\nasync function postCliAuthComplete(app: StackClientApp, body: Record<string, unknown>) {\n return await app[hexclaveAppInternalsSymbol].sendRequest(\"/auth/cli/complete\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n}\n\nasync function ensureCliCompleteOk(result: Response) {\n if (!result.ok) {\n throw new Error(`Authorization failed: ${result.status} ${await result.text()}`);\n }\n}\n\nasync function completeCliAuthWithRefreshToken(app: StackClientApp, loginCode: string, refreshToken: string) {\n const result = await postCliAuthComplete(app, { login_code: loginCode, refresh_token: refreshToken });\n await ensureCliCompleteOk(result);\n}\n\n// Hexclave rebrand: sessionStorage key — straight rename (per-tab, low TTL).\nconst CLI_AUTH_CONFIRMED_KEY = \"hexclave-cli-auth-confirmed\";\n\nfunction markConfirmed(loginCode: string) {\n sessionStorage.setItem(CLI_AUTH_CONFIRMED_KEY, loginCode);\n}\n\nfunction isConfirmed(loginCode: string): boolean {\n return sessionStorage.getItem(CLI_AUTH_CONFIRMED_KEY) === loginCode;\n}\n\nfunction clearConfirmed() {\n sessionStorage.removeItem(CLI_AUTH_CONFIRMED_KEY);\n}\n\nfunction getError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n\nfunction getObjectField(data: unknown, fieldName: string): unknown {\n return typeof data === \"object\" && data !== null && fieldName in data\n ? data[fieldName as keyof typeof data]\n : undefined;\n}\n\nfunction getStringField(data: unknown, fieldName: string): string | undefined {\n const value = getObjectField(data, fieldName);\n return typeof value === \"string\" ? value : undefined;\n}\n\nexport type CliAuthConfirmationStatus =\n | \"idle\"\n | \"invalid\"\n | \"authorizing\"\n | \"redirecting\"\n | \"success\"\n | \"error\";\n\nexport type CliAuthConfirmationState = {\n status: CliAuthConfirmationStatus,\n loginCode: string | null,\n error: Error | null,\n isLoading: boolean,\n authorize: () => Promise<void>,\n retry: () => void,\n};\n\nexport function useCliAuthConfirmation(): CliAuthConfirmationState {\n const app = useStackApp();\n const user = app.useUser({ includeRestricted: true });\n const [status, setStatus] = useState<Exclude<CliAuthConfirmationStatus, \"invalid\">>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const autoCompleteRef = useRef(false);\n const authorizeInProgressRef = useRef(false);\n const [loginCode] = useState(() => {\n if (typeof window === 'undefined') return null;\n return new URLSearchParams(window.location.search).get(\"login_code\");\n });\n const [confirmed] = useState(() => {\n if (typeof window === 'undefined') return false;\n return loginCode != null && isConfirmed(loginCode);\n });\n\n const completeWithCurrentUser = useCallback(async () => {\n if (!loginCode) {\n throw new Error(\"Missing login code in URL parameters\");\n }\n if (!user) {\n throw new Error(\"Cannot complete CLI authorization without a signed-in user\");\n }\n const refreshToken = (await user.currentSession.getTokens()).refreshToken;\n if (!refreshToken) {\n throw new Error(\"Could not retrieve session token\");\n }\n await completeCliAuthWithRefreshToken(app, loginCode, refreshToken);\n }, [app, loginCode, user]);\n\n useEffect(() => {\n if (!confirmed || !user || autoCompleteRef.current) {\n return;\n }\n autoCompleteRef.current = true;\n runAsynchronouslyWithAlert(async () => {\n setStatus(\"authorizing\");\n try {\n await completeWithCurrentUser();\n clearConfirmed();\n setStatus(\"success\");\n } catch (err) {\n setError(getError(err));\n setStatus(\"error\");\n }\n });\n }, [confirmed, user, completeWithCurrentUser]);\n\n const authorize = useCallback(async () => {\n if (authorizeInProgressRef.current) {\n return;\n }\n authorizeInProgressRef.current = true;\n\n try {\n if (!loginCode) {\n setError(new Error(\"Missing login code in URL parameters\"));\n setStatus(\"error\");\n return;\n }\n\n setError(null);\n setStatus(\"authorizing\");\n if (user) {\n await completeWithCurrentUser();\n clearConfirmed();\n setStatus(\"success\");\n return;\n }\n\n const checkResult = await postCliAuthComplete(app, { login_code: loginCode, mode: \"check\" });\n if (!checkResult.ok) {\n throw new Error(`Failed to verify login code: ${checkResult.status} ${await checkResult.text()}`);\n }\n const checkData: unknown = await checkResult.json();\n const cliSessionState = getStringField(checkData, \"cli_session_state\") ?? null;\n\n if (cliSessionState === \"anonymous\") {\n const claimResult = await postCliAuthComplete(app, { login_code: loginCode, mode: \"claim-anon-session\" });\n\n if (!claimResult.ok) {\n throw new Error(`Failed to claim anonymous session: ${claimResult.status} ${await claimResult.text()}`);\n }\n\n const tokens: unknown = await claimResult.json();\n const accessToken = getStringField(tokens, \"access_token\");\n const refreshToken = getStringField(tokens, \"refresh_token\");\n if (!accessToken || !refreshToken) {\n throw new Error(\"Anonymous CLI session claim did not return tokens\");\n }\n await app[hexclaveAppInternalsSymbol].signInWithTokens({\n accessToken,\n refreshToken,\n });\n markConfirmed(loginCode);\n setStatus(\"redirecting\");\n await app.redirectToSignUp({ replace: true });\n return;\n }\n\n markConfirmed(loginCode);\n setStatus(\"redirecting\");\n await app.redirectToSignIn({ replace: true });\n } catch (err) {\n setError(getError(err));\n setStatus(\"error\");\n } finally {\n authorizeInProgressRef.current = false;\n }\n }, [app, completeWithCurrentUser, loginCode, user]);\n\n const retry = useCallback(() => {\n setError(null);\n autoCompleteRef.current = false;\n setStatus(\"idle\");\n }, []);\n\n const visibleStatus = loginCode == null ? \"invalid\" : status;\n return {\n status: visibleStatus,\n loginCode,\n error,\n isLoading: visibleStatus === \"authorizing\" || visibleStatus === \"redirecting\",\n authorize,\n retry,\n };\n}\n\nexport function CliAuthConfirmation({ fullPage = true }: { fullPage?: boolean }) {\n const { t } = useTranslation();\n const cliAuth = useCliAuthConfirmation();\n\n if (cliAuth.status === \"success\") {\n return (\n <MessageCard title={t(\"CLI Authorization Successful\")} fullPage={fullPage}>\n <Typography>\n {t(\"The CLI application has been authorized successfully. You can close this window and return to the command line.\")}\n </Typography>\n </MessageCard>\n );\n }\n\n if (cliAuth.status === \"error\") {\n return (\n <MessageCard\n title={t(\"Authorization Failed\")}\n fullPage={fullPage}\n primaryButtonText={t(\"Try Again\")}\n primaryAction={cliAuth.retry}\n >\n <Typography className=\"text-red-600\">\n {t(\"Failed to authorize the CLI application:\")}\n </Typography>\n <Typography className=\"text-red-600\">\n {cliAuth.error?.message}\n </Typography>\n </MessageCard>\n );\n }\n\n if (cliAuth.status === \"invalid\") {\n return (\n <MessageCard title={t(\"Invalid CLI Authorization Link\")} fullPage={fullPage}>\n <Typography className=\"text-red-600\">\n {t(\"This CLI authorization link is missing a login code. Please return to the command line and start the login process again.\")}\n </Typography>\n </MessageCard>\n );\n }\n\n if (cliAuth.status === \"authorizing\" || cliAuth.status === \"redirecting\") {\n return (\n <MessageCard title={t(\"Completing Authorization...\")} fullPage={fullPage}>\n <Typography>\n {t(\"Finishing up the CLI authorization...\")}\n </Typography>\n </MessageCard>\n );\n }\n\n return (\n <MessageCard\n title={t(\"Authorize CLI Application\")}\n fullPage={fullPage}\n primaryButtonText={cliAuth.isLoading ? t(\"Authorizing...\") : t(\"Authorize\")}\n primaryAction={cliAuth.authorize}\n >\n <Typography>\n {t(\"A command line application is requesting access to your account. Click the button below to authorize it.\")}\n </Typography>\n <Typography variant=\"destructive\">\n {t(\"WARNING: Make sure you trust the command line application, as it will gain access to your account. If you did not initiate this request, you can close this page and ignore it. We will never send you this link via email or any other means.\")}\n </Typography>\n </MessageCard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli-auth-confirm.js","names":[],"sources":["../../../src/components-page/cli-auth-confirm.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { runAsynchronouslyWithAlert } from \"@hexclave/shared/dist/utils/promises\";\nimport { Typography } from \"@hexclave/ui\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { MessageCard } from \"../components/message-cards/message-card\";\nimport { useTranslation } from \"../lib/translations\";\nimport { hexclaveAppInternalsSymbol } from \"../lib/hexclave-app/common\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { useStackApp } from \"../lib/hooks\";\n\nasync function postCliAuthComplete(app: StackClientApp, body: Record<string, unknown>) {\n return await app[hexclaveAppInternalsSymbol].sendRequest(\"/auth/cli/complete\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n}\n\nasync function ensureCliCompleteOk(result: Response) {\n if (!result.ok) {\n throw new Error(`Authorization failed: ${result.status} ${await result.text()}`);\n }\n}\n\nasync function completeCliAuthWithRefreshToken(app: StackClientApp, loginCode: string, refreshToken: string) {\n const result = await postCliAuthComplete(app, { login_code: loginCode, refresh_token: refreshToken });\n await ensureCliCompleteOk(result);\n}\n\n// Hexclave rebrand: sessionStorage key — straight rename (per-tab, low TTL).\nconst CLI_AUTH_CONFIRMED_KEY = \"hexclave-cli-auth-confirmed\";\n\nfunction markConfirmed(loginCode: string) {\n sessionStorage.setItem(CLI_AUTH_CONFIRMED_KEY, loginCode);\n}\n\nfunction isConfirmed(loginCode: string): boolean {\n return sessionStorage.getItem(CLI_AUTH_CONFIRMED_KEY) === loginCode;\n}\n\nfunction clearConfirmed() {\n sessionStorage.removeItem(CLI_AUTH_CONFIRMED_KEY);\n}\n\nfunction getError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n\nfunction getObjectField(data: unknown, fieldName: string): unknown {\n return typeof data === \"object\" && data !== null && fieldName in data\n ? data[fieldName as keyof typeof data]\n : undefined;\n}\n\nfunction getStringField(data: unknown, fieldName: string): string | undefined {\n const value = getObjectField(data, fieldName);\n return typeof value === \"string\" ? value : undefined;\n}\n\nexport type CliAuthConfirmationStatus =\n | \"idle\"\n | \"invalid\"\n | \"authorizing\"\n | \"redirecting\"\n | \"success\"\n | \"error\";\n\nexport type CliAuthConfirmationState = {\n status: CliAuthConfirmationStatus,\n loginCode: string | null,\n error: Error | null,\n isLoading: boolean,\n authorize: () => Promise<void>,\n retry: () => void,\n};\n\nexport function useCliAuthConfirmation(): CliAuthConfirmationState {\n const app = useStackApp();\n const user = app.useUser({ includeRestricted: true });\n const [status, setStatus] = useState<Exclude<CliAuthConfirmationStatus, \"invalid\">>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const autoCompleteRef = useRef(false);\n const authorizeInProgressRef = useRef(false);\n const [loginCode] = useState(() => {\n if (typeof window === 'undefined') return null;\n return new URLSearchParams(window.location.search).get(\"login_code\");\n });\n const [confirmed] = useState(() => {\n if (typeof window === 'undefined') return false;\n return loginCode != null && isConfirmed(loginCode);\n });\n\n const completeWithCurrentUser = useCallback(async () => {\n if (!loginCode) {\n throw new Error(\"Missing login code in URL parameters\");\n }\n if (!user) {\n throw new Error(\"Cannot complete CLI authorization without a signed-in user\");\n }\n const refreshToken = (await user.currentSession.getTokens()).refreshToken;\n if (!refreshToken) {\n throw new Error(\"Could not retrieve session token\");\n }\n await completeCliAuthWithRefreshToken(app, loginCode, refreshToken);\n }, [app, loginCode, user]);\n\n useEffect(() => {\n if (!confirmed || !user || user.isRestricted || autoCompleteRef.current) {\n return;\n }\n autoCompleteRef.current = true;\n runAsynchronouslyWithAlert(async () => {\n setStatus(\"authorizing\");\n try {\n await completeWithCurrentUser();\n clearConfirmed();\n setStatus(\"success\");\n } catch (err) {\n setError(getError(err));\n setStatus(\"error\");\n }\n });\n }, [confirmed, user, completeWithCurrentUser]);\n\n const authorize = useCallback(async () => {\n if (authorizeInProgressRef.current) {\n return;\n }\n authorizeInProgressRef.current = true;\n\n try {\n if (!loginCode) {\n setError(new Error(\"Missing login code in URL parameters\"));\n setStatus(\"error\");\n return;\n }\n\n setError(null);\n setStatus(\"authorizing\");\n if (user) {\n if (user.isRestricted) {\n markConfirmed(loginCode);\n setStatus(\"redirecting\");\n await (\n user.isAnonymous\n ? app.redirectToSignUp({ replace: true })\n : app.redirectToOnboarding({ replace: true })\n );\n return;\n }\n await completeWithCurrentUser();\n clearConfirmed();\n setStatus(\"success\");\n return;\n }\n\n const checkResult = await postCliAuthComplete(app, { login_code: loginCode, mode: \"check\" });\n if (!checkResult.ok) {\n throw new Error(`Failed to verify login code: ${checkResult.status} ${await checkResult.text()}`);\n }\n const checkData: unknown = await checkResult.json();\n const cliSessionState = getStringField(checkData, \"cli_session_state\") ?? null;\n\n if (cliSessionState === \"anonymous\") {\n const claimResult = await postCliAuthComplete(app, { login_code: loginCode, mode: \"claim-anon-session\" });\n\n if (!claimResult.ok) {\n throw new Error(`Failed to claim anonymous session: ${claimResult.status} ${await claimResult.text()}`);\n }\n\n const tokens: unknown = await claimResult.json();\n const accessToken = getStringField(tokens, \"access_token\");\n const refreshToken = getStringField(tokens, \"refresh_token\");\n if (!accessToken || !refreshToken) {\n throw new Error(\"Anonymous CLI session claim did not return tokens\");\n }\n await app[hexclaveAppInternalsSymbol].signInWithTokens({\n accessToken,\n refreshToken,\n });\n markConfirmed(loginCode);\n setStatus(\"redirecting\");\n await app.redirectToSignUp({ replace: true });\n return;\n }\n\n markConfirmed(loginCode);\n setStatus(\"redirecting\");\n await app.redirectToSignIn({ replace: true });\n } catch (err) {\n setError(getError(err));\n setStatus(\"error\");\n } finally {\n authorizeInProgressRef.current = false;\n }\n }, [app, completeWithCurrentUser, loginCode, user]);\n\n const retry = useCallback(() => {\n setError(null);\n autoCompleteRef.current = false;\n setStatus(\"idle\");\n }, []);\n\n const visibleStatus = loginCode == null ? \"invalid\" : status;\n return {\n status: visibleStatus,\n loginCode,\n error,\n isLoading: visibleStatus === \"authorizing\" || visibleStatus === \"redirecting\",\n authorize,\n retry,\n };\n}\n\nexport function CliAuthConfirmation({ fullPage = true }: { fullPage?: boolean }) {\n const { t } = useTranslation();\n const cliAuth = useCliAuthConfirmation();\n\n if (cliAuth.status === \"success\") {\n return (\n <MessageCard title={t(\"CLI Authorization Successful\")} fullPage={fullPage}>\n <Typography>\n {t(\"The CLI application has been authorized successfully. You can close this window and return to the command line.\")}\n </Typography>\n </MessageCard>\n );\n }\n\n if (cliAuth.status === \"error\") {\n return (\n <MessageCard\n title={t(\"Authorization Failed\")}\n fullPage={fullPage}\n primaryButtonText={t(\"Try Again\")}\n primaryAction={cliAuth.retry}\n >\n <Typography className=\"text-red-600\">\n {t(\"Failed to authorize the CLI application:\")}\n </Typography>\n <Typography className=\"text-red-600\">\n {cliAuth.error?.message}\n </Typography>\n </MessageCard>\n );\n }\n\n if (cliAuth.status === \"invalid\") {\n return (\n <MessageCard title={t(\"Invalid CLI Authorization Link\")} fullPage={fullPage}>\n <Typography className=\"text-red-600\">\n {t(\"This CLI authorization link is missing a login code. Please return to the command line and start the login process again.\")}\n </Typography>\n </MessageCard>\n );\n }\n\n if (cliAuth.status === \"authorizing\" || cliAuth.status === \"redirecting\") {\n return (\n <MessageCard title={t(\"Completing Authorization...\")} fullPage={fullPage}>\n <Typography>\n {t(\"Finishing up the CLI authorization...\")}\n </Typography>\n </MessageCard>\n );\n }\n\n return (\n <MessageCard\n title={t(\"Authorize CLI Application\")}\n fullPage={fullPage}\n primaryButtonText={cliAuth.isLoading ? t(\"Authorizing...\") : t(\"Authorize\")}\n primaryAction={cliAuth.authorize}\n >\n <Typography>\n {t(\"A command line application is requesting access to your account. Click the button below to authorize it.\")}\n </Typography>\n <Typography variant=\"destructive\">\n {t(\"WARNING: Make sure you trust the command line application, as it will gain access to your account. If you did not initiate this request, you can close this page and ignore it. We will never send you this link via email or any other means.\")}\n </Typography>\n </MessageCard>\n );\n}\n"],"mappings":"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"}
@@ -20,6 +20,7 @@ function createAppTestDouble(options) {
20
20
  useUser: () => options.user,
21
21
  redirectToSignIn: options.redirectToSignIn ?? vi.fn(async () => {}),
22
22
  redirectToSignUp: options.redirectToSignUp ?? vi.fn(async () => {}),
23
+ redirectToOnboarding: options.redirectToOnboarding ?? vi.fn(async () => {}),
23
24
  [hexclaveAppInternalsSymbol]: {
24
25
  sendRequest: options.sendRequest,
25
26
  signInWithTokens: options.signInWithTokens ?? vi.fn(async () => {})
@@ -83,6 +84,7 @@ describe("useCliAuthConfirmation", () => {
83
84
  root = null;
84
85
  container = null;
85
86
  vi.restoreAllMocks();
87
+ sessionStorage.clear();
86
88
  window.history.replaceState({}, "", "/");
87
89
  Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", previousActEnvironment);
88
90
  });
@@ -108,6 +110,46 @@ describe("useCliAuthConfirmation", () => {
108
110
  }
109
111
  `);
110
112
  });
113
+ it("redirects restricted users to onboarding without completing CLI auth", async () => {
114
+ window.history.replaceState({}, "", "/handler/cli-auth-confirm?login_code=login-code");
115
+ const redirectToOnboarding = vi.fn(async (_options) => {});
116
+ const sendRequest = vi.fn(async (_path, _requestOptions) => new Response(null, { status: 200 }));
117
+ await renderWithApp(createAppTestDouble({
118
+ user: {
119
+ isRestricted: true,
120
+ isAnonymous: false,
121
+ currentSession: { getTokens: vi.fn(async () => ({ refreshToken: "restricted-refresh-token" })) }
122
+ },
123
+ sendRequest,
124
+ redirectToOnboarding
125
+ }));
126
+ await act(async () => {
127
+ getButton("authorize").click();
128
+ });
129
+ expect(redirectToOnboarding).toHaveBeenCalledWith({ replace: true });
130
+ expect(sessionStorage.getItem("hexclave-cli-auth-confirmed")).toBe("login-code");
131
+ expect(sendRequest).not.toHaveBeenCalled();
132
+ });
133
+ it("redirects anonymous users to sign-up without completing CLI auth", async () => {
134
+ window.history.replaceState({}, "", "/handler/cli-auth-confirm?login_code=login-code");
135
+ const redirectToSignUp = vi.fn(async (_options) => {});
136
+ const sendRequest = vi.fn(async (_path, _requestOptions) => new Response(null, { status: 200 }));
137
+ await renderWithApp(createAppTestDouble({
138
+ user: {
139
+ isRestricted: true,
140
+ isAnonymous: true,
141
+ currentSession: { getTokens: vi.fn(async () => ({ refreshToken: "anonymous-refresh-token" })) }
142
+ },
143
+ sendRequest,
144
+ redirectToSignUp
145
+ }));
146
+ await act(async () => {
147
+ getButton("authorize").click();
148
+ });
149
+ expect(redirectToSignUp).toHaveBeenCalledWith({ replace: true });
150
+ expect(sessionStorage.getItem("hexclave-cli-auth-confirmed")).toBe("login-code");
151
+ expect(sendRequest).not.toHaveBeenCalled();
152
+ });
111
153
  it("ignores duplicate authorize clicks before React re-renders", async () => {
112
154
  window.history.replaceState({}, "", "/handler/cli-auth-confirm?login_code=login-code");
113
155
  const getTokens = vi.fn(async () => ({ refreshToken: "refresh-token" }));
@@ -1 +1 @@
1
- {"version":3,"file":"cli-auth-confirm.test.js","names":[],"sources":["../../../src/components-page/cli-auth-confirm.test.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n// @vitest-environment jsdom\n\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { hexclaveAppInternalsSymbol } from \"../lib/hexclave-app/common\";\nimport { HexclaveContext } from \"../providers/hexclave-context\";\nimport { useCliAuthConfirmation } from \"./cli-auth-confirm\";\n\nconst previousActEnvironment = Reflect.get(globalThis, \"IS_REACT_ACT_ENVIRONMENT\");\n\nfunction responseJson(data: unknown, init?: ResponseInit) {\n return new Response(JSON.stringify(data), {\n status: init?.status ?? 200,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction createAppTestDouble(options: {\n user: unknown,\n sendRequest: (path: string, requestOptions: RequestInit) => Promise<Response>,\n signInWithTokens?: (tokens: { accessToken: string, refreshToken: string }) => Promise<void>,\n redirectToSignIn?: (options: { replace: true }) => Promise<void>,\n redirectToSignUp?: (options: { replace: true }) => Promise<void>,\n}) {\n const app = {\n useUser: () => options.user,\n redirectToSignIn: options.redirectToSignIn ?? vi.fn(async () => {}),\n redirectToSignUp: options.redirectToSignUp ?? vi.fn(async () => {}),\n [hexclaveAppInternalsSymbol]: {\n sendRequest: options.sendRequest,\n signInWithTokens: options.signInWithTokens ?? vi.fn(async () => {}),\n },\n };\n\n // This test double intentionally implements only the StackClientApp surface\n // that useCliAuthConfirmation touches.\n return app as unknown as StackClientApp<true>;\n}\n\nfunction HookProbe() {\n const cliAuth = useCliAuthConfirmation();\n return (\n <>\n <div data-testid=\"status\">{cliAuth.status}</div>\n <div data-testid=\"error\">{cliAuth.error?.message}</div>\n <button type=\"button\" onClick={() => runAsynchronously(cliAuth.authorize)}>authorize</button>\n <button onClick={cliAuth.retry}>retry</button>\n </>\n );\n}\n\nlet root: Root | null = null;\nlet container: HTMLDivElement | null = null;\n\nasync function renderWithApp(app: StackClientApp<true>) {\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n await act(async () => {\n root?.render(\n <HexclaveContext.Provider value={{ app }}>\n <HookProbe />\n </HexclaveContext.Provider>\n );\n });\n}\n\nfunction getByTestId(testId: string): HTMLElement {\n const element = container?.querySelector(`[data-testid=\"${testId}\"]`);\n if (!(element instanceof HTMLElement)) {\n throw new Error(`Could not find test element ${testId}`);\n }\n return element;\n}\n\nfunction getButton(label: string): HTMLButtonElement {\n const button = [...container?.querySelectorAll(\"button\") ?? []]\n .find((element) => element.textContent === label);\n if (!(button instanceof HTMLButtonElement)) {\n throw new Error(`Could not find button ${label}`);\n }\n return button;\n}\n\ndescribe(\"useCliAuthConfirmation\", () => {\n beforeEach(() => {\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", true);\n });\n\n afterEach(() => {\n act(() => {\n root?.unmount();\n });\n container?.remove();\n root = null;\n container = null;\n vi.restoreAllMocks();\n window.history.replaceState({}, \"\", \"/\");\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", previousActEnvironment);\n });\n\n it(\"completes CLI auth with the current user's refresh token\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const getTokens = vi.fn(async () => ({ refreshToken: \"refresh-token\" }));\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: { currentSession: { getTokens } },\n sendRequest,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(getByTestId(\"status\").textContent).toBe(\"success\");\n expect(getTokens).toHaveBeenCalledOnce();\n expect(sendRequest).toHaveBeenCalledOnce();\n expect(sendRequest.mock.calls[0][0]).toBe(\"/auth/cli/complete\");\n expect(JSON.parse(String(sendRequest.mock.calls[0][1].body))).toMatchInlineSnapshot(`\n {\n \"login_code\": \"login-code\",\n \"refresh_token\": \"refresh-token\",\n }\n `);\n });\n\n it(\"ignores duplicate authorize clicks before React re-renders\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const getTokens = vi.fn(async () => ({ refreshToken: \"refresh-token\" }));\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: { currentSession: { getTokens } },\n sendRequest,\n });\n\n await renderWithApp(app);\n await act(async () => {\n const authorizeButton = getButton(\"authorize\");\n authorizeButton.click();\n authorizeButton.click();\n });\n\n expect(sendRequest).toHaveBeenCalledOnce();\n });\n\n it(\"claims anonymous CLI sessions before redirecting to sign-up\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const signInWithTokens = vi.fn(async (_tokens: { accessToken: string, refreshToken: string }) => {});\n const redirectToSignUp = vi.fn(async (_options: { replace: true }) => {});\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }))\n .mockResolvedValueOnce(responseJson({ cli_session_state: \"anonymous\" }))\n .mockResolvedValueOnce(responseJson({ access_token: \"access-token\", refresh_token: \"refresh-token\" }));\n const app = createAppTestDouble({\n user: null,\n sendRequest,\n signInWithTokens,\n redirectToSignUp,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(redirectToSignUp).toHaveBeenCalledWith({ replace: true });\n expect(signInWithTokens).toHaveBeenCalledWith({\n accessToken: \"access-token\",\n refreshToken: \"refresh-token\",\n });\n expect(sessionStorage.getItem(\"hexclave-cli-auth-confirmed\")).toBe(\"login-code\");\n expect(sendRequest.mock.calls.map(call => JSON.parse(String(call[1].body)))).toMatchInlineSnapshot(`\n [\n {\n \"login_code\": \"login-code\",\n \"mode\": \"check\",\n },\n {\n \"login_code\": \"login-code\",\n \"mode\": \"claim-anon-session\",\n },\n ]\n `);\n });\n\n it(\"reports invalid when the login code is missing\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm\");\n const app = createAppTestDouble({\n user: null,\n sendRequest: vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 })),\n });\n\n await renderWithApp(app);\n\n expect(getByTestId(\"status\").textContent).toBe(\"invalid\");\n });\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli-auth-confirm.test.js","names":[],"sources":["../../../src/components-page/cli-auth-confirm.test.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n// @vitest-environment jsdom\n\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { hexclaveAppInternalsSymbol } from \"../lib/hexclave-app/common\";\nimport { HexclaveContext } from \"../providers/hexclave-context\";\nimport { useCliAuthConfirmation } from \"./cli-auth-confirm\";\n\nconst previousActEnvironment = Reflect.get(globalThis, \"IS_REACT_ACT_ENVIRONMENT\");\n\nfunction responseJson(data: unknown, init?: ResponseInit) {\n return new Response(JSON.stringify(data), {\n status: init?.status ?? 200,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction createAppTestDouble(options: {\n user: unknown,\n sendRequest: (path: string, requestOptions: RequestInit) => Promise<Response>,\n signInWithTokens?: (tokens: { accessToken: string, refreshToken: string }) => Promise<void>,\n redirectToSignIn?: (options: { replace: true }) => Promise<void>,\n redirectToSignUp?: (options: { replace: true }) => Promise<void>,\n redirectToOnboarding?: (options: { replace: true }) => Promise<void>,\n}) {\n const app = {\n useUser: () => options.user,\n redirectToSignIn: options.redirectToSignIn ?? vi.fn(async () => {}),\n redirectToSignUp: options.redirectToSignUp ?? vi.fn(async () => {}),\n redirectToOnboarding: options.redirectToOnboarding ?? vi.fn(async () => {}),\n [hexclaveAppInternalsSymbol]: {\n sendRequest: options.sendRequest,\n signInWithTokens: options.signInWithTokens ?? vi.fn(async () => {}),\n },\n };\n\n // This test double intentionally implements only the StackClientApp surface\n // that useCliAuthConfirmation touches.\n return app as unknown as StackClientApp<true>;\n}\n\nfunction HookProbe() {\n const cliAuth = useCliAuthConfirmation();\n return (\n <>\n <div data-testid=\"status\">{cliAuth.status}</div>\n <div data-testid=\"error\">{cliAuth.error?.message}</div>\n <button type=\"button\" onClick={() => runAsynchronously(cliAuth.authorize)}>authorize</button>\n <button onClick={cliAuth.retry}>retry</button>\n </>\n );\n}\n\nlet root: Root | null = null;\nlet container: HTMLDivElement | null = null;\n\nasync function renderWithApp(app: StackClientApp<true>) {\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n await act(async () => {\n root?.render(\n <HexclaveContext.Provider value={{ app }}>\n <HookProbe />\n </HexclaveContext.Provider>\n );\n });\n}\n\nfunction getByTestId(testId: string): HTMLElement {\n const element = container?.querySelector(`[data-testid=\"${testId}\"]`);\n if (!(element instanceof HTMLElement)) {\n throw new Error(`Could not find test element ${testId}`);\n }\n return element;\n}\n\nfunction getButton(label: string): HTMLButtonElement {\n const button = [...container?.querySelectorAll(\"button\") ?? []]\n .find((element) => element.textContent === label);\n if (!(button instanceof HTMLButtonElement)) {\n throw new Error(`Could not find button ${label}`);\n }\n return button;\n}\n\ndescribe(\"useCliAuthConfirmation\", () => {\n beforeEach(() => {\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", true);\n });\n\n afterEach(() => {\n act(() => {\n root?.unmount();\n });\n container?.remove();\n root = null;\n container = null;\n vi.restoreAllMocks();\n sessionStorage.clear();\n window.history.replaceState({}, \"\", \"/\");\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", previousActEnvironment);\n });\n\n it(\"completes CLI auth with the current user's refresh token\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const getTokens = vi.fn(async () => ({ refreshToken: \"refresh-token\" }));\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: { currentSession: { getTokens } },\n sendRequest,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(getByTestId(\"status\").textContent).toBe(\"success\");\n expect(getTokens).toHaveBeenCalledOnce();\n expect(sendRequest).toHaveBeenCalledOnce();\n expect(sendRequest.mock.calls[0][0]).toBe(\"/auth/cli/complete\");\n expect(JSON.parse(String(sendRequest.mock.calls[0][1].body))).toMatchInlineSnapshot(`\n {\n \"login_code\": \"login-code\",\n \"refresh_token\": \"refresh-token\",\n }\n `);\n });\n\n it(\"redirects restricted users to onboarding without completing CLI auth\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const redirectToOnboarding = vi.fn(async (_options: { replace: true }) => {});\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: {\n isRestricted: true,\n isAnonymous: false,\n currentSession: {\n getTokens: vi.fn(async () => ({ refreshToken: \"restricted-refresh-token\" })),\n },\n },\n sendRequest,\n redirectToOnboarding,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(redirectToOnboarding).toHaveBeenCalledWith({ replace: true });\n expect(sessionStorage.getItem(\"hexclave-cli-auth-confirmed\")).toBe(\"login-code\");\n expect(sendRequest).not.toHaveBeenCalled();\n });\n\n it(\"redirects anonymous users to sign-up without completing CLI auth\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const redirectToSignUp = vi.fn(async (_options: { replace: true }) => {});\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: {\n isRestricted: true,\n isAnonymous: true,\n currentSession: {\n getTokens: vi.fn(async () => ({ refreshToken: \"anonymous-refresh-token\" })),\n },\n },\n sendRequest,\n redirectToSignUp,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(redirectToSignUp).toHaveBeenCalledWith({ replace: true });\n expect(sessionStorage.getItem(\"hexclave-cli-auth-confirmed\")).toBe(\"login-code\");\n expect(sendRequest).not.toHaveBeenCalled();\n });\n\n it(\"ignores duplicate authorize clicks before React re-renders\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const getTokens = vi.fn(async () => ({ refreshToken: \"refresh-token\" }));\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }));\n const app = createAppTestDouble({\n user: { currentSession: { getTokens } },\n sendRequest,\n });\n\n await renderWithApp(app);\n await act(async () => {\n const authorizeButton = getButton(\"authorize\");\n authorizeButton.click();\n authorizeButton.click();\n });\n\n expect(sendRequest).toHaveBeenCalledOnce();\n });\n\n it(\"claims anonymous CLI sessions before redirecting to sign-up\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm?login_code=login-code\");\n const signInWithTokens = vi.fn(async (_tokens: { accessToken: string, refreshToken: string }) => {});\n const redirectToSignUp = vi.fn(async (_options: { replace: true }) => {});\n const sendRequest = vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 }))\n .mockResolvedValueOnce(responseJson({ cli_session_state: \"anonymous\" }))\n .mockResolvedValueOnce(responseJson({ access_token: \"access-token\", refresh_token: \"refresh-token\" }));\n const app = createAppTestDouble({\n user: null,\n sendRequest,\n signInWithTokens,\n redirectToSignUp,\n });\n\n await renderWithApp(app);\n await act(async () => {\n getButton(\"authorize\").click();\n });\n\n expect(redirectToSignUp).toHaveBeenCalledWith({ replace: true });\n expect(signInWithTokens).toHaveBeenCalledWith({\n accessToken: \"access-token\",\n refreshToken: \"refresh-token\",\n });\n expect(sessionStorage.getItem(\"hexclave-cli-auth-confirmed\")).toBe(\"login-code\");\n expect(sendRequest.mock.calls.map(call => JSON.parse(String(call[1].body)))).toMatchInlineSnapshot(`\n [\n {\n \"login_code\": \"login-code\",\n \"mode\": \"check\",\n },\n {\n \"login_code\": \"login-code\",\n \"mode\": \"claim-anon-session\",\n },\n ]\n `);\n });\n\n it(\"reports invalid when the login code is missing\", async () => {\n window.history.replaceState({}, \"\", \"/handler/cli-auth-confirm\");\n const app = createAppTestDouble({\n user: null,\n sendRequest: vi.fn(async (_path: string, _requestOptions: RequestInit) => new Response(null, { status: 200 })),\n });\n\n await renderWithApp(app);\n\n expect(getByTestId(\"status\").textContent).toBe(\"invalid\");\n });\n});\n"],"mappings":"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"}