@hexclave/next 1.0.123 → 1.0.126

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 (169) hide show
  1. package/dist/{admin-app-impl-MncU_Oxy.d.ts → admin-app-impl-CjDjJ3PS.d.ts} +29 -13
  2. package/dist/admin-app-impl-CjDjJ3PS.d.ts.map +1 -0
  3. package/dist/clickmap/clickmap-core.d.ts +1 -1
  4. package/dist/clickmap/index.d.ts +1 -1
  5. package/dist/{client-app-B0cTytS5.d.ts → client-app-jAjrgwSh.d.ts} +34 -6
  6. package/dist/client-app-jAjrgwSh.d.ts.map +1 -0
  7. package/dist/{common-B2gkWlXX.d.ts → common-BjAI7WW6.d.ts} +15 -3
  8. package/dist/common-BjAI7WW6.d.ts.map +1 -0
  9. package/dist/{common-BK_sydxF.d.ts → common-DWfeER0s.d.ts} +2 -2
  10. package/dist/{common-BK_sydxF.d.ts.map → common-DWfeER0s.d.ts.map} +1 -1
  11. package/dist/components/message-cards/predefined-message-card.js +4 -2
  12. package/dist/components/message-cards/predefined-message-card.js.map +1 -1
  13. package/dist/components/selected-team-switcher.d.ts +1 -1
  14. package/dist/components/team-icon.d.ts +1 -1
  15. package/dist/components/team-switcher.d.ts +1 -1
  16. package/dist/components/user-button.js +9 -9
  17. package/dist/components/user-button.js.map +1 -1
  18. package/dist/components/user-button.test.d.ts +1 -0
  19. package/dist/components/user-button.test.js +71 -0
  20. package/dist/components/user-button.test.js.map +1 -0
  21. package/dist/components-page/account-settings/payments/payments-page.d.ts +1 -1
  22. package/dist/components-page/account-settings/payments/payments-panel.d.ts +1 -1
  23. package/dist/components-page/account-settings/settings/sign-out-section.d.ts +1 -1
  24. package/dist/components-page/account-settings/settings/sign-out-section.d.ts.map +1 -1
  25. package/dist/components-page/account-settings/settings/sign-out-section.js +4 -1
  26. package/dist/components-page/account-settings/settings/sign-out-section.js.map +1 -1
  27. package/dist/components-page/account-settings/teams/leave-team-section.d.ts +1 -1
  28. package/dist/components-page/account-settings/teams/team-api-keys-section.d.ts +1 -1
  29. package/dist/components-page/account-settings/teams/team-display-name-section.d.ts +1 -1
  30. package/dist/components-page/account-settings/teams/team-member-invitation-section.d.ts +1 -1
  31. package/dist/components-page/account-settings/teams/team-member-list-section.d.ts +1 -1
  32. package/dist/components-page/account-settings/teams/team-page.d.ts +1 -1
  33. package/dist/components-page/account-settings/teams/team-profile-image-section.d.ts +1 -1
  34. package/dist/components-page/account-settings/teams/team-profile-user-section.d.ts +1 -1
  35. package/dist/components-page/hexclave-handler-client.d.ts +1 -1
  36. package/dist/components-page/hexclave-handler.d.ts +1 -1
  37. package/dist/components-page/onboarding.js +15 -13
  38. package/dist/components-page/onboarding.js.map +1 -1
  39. package/dist/components-page/onboarding.test.js +2 -1
  40. package/dist/components-page/onboarding.test.js.map +1 -1
  41. package/dist/dev-tool/dev-tool-core.d.ts +1 -1
  42. package/dist/dev-tool/index.d.ts +1 -1
  43. package/dist/esm/components/message-cards/predefined-message-card.js +4 -2
  44. package/dist/esm/components/message-cards/predefined-message-card.js.map +1 -1
  45. package/dist/esm/components/user-button.js +9 -9
  46. package/dist/esm/components/user-button.js.map +1 -1
  47. package/dist/esm/components/user-button.test.d.ts +1 -0
  48. package/dist/esm/components/user-button.test.js +70 -0
  49. package/dist/esm/components/user-button.test.js.map +1 -0
  50. package/dist/esm/components-page/account-settings/settings/sign-out-section.d.ts +1 -1
  51. package/dist/esm/components-page/account-settings/settings/sign-out-section.d.ts.map +1 -1
  52. package/dist/esm/components-page/account-settings/settings/sign-out-section.js +4 -1
  53. package/dist/esm/components-page/account-settings/settings/sign-out-section.js.map +1 -1
  54. package/dist/esm/components-page/onboarding.js +15 -13
  55. package/dist/esm/components-page/onboarding.js.map +1 -1
  56. package/dist/esm/components-page/onboarding.test.js +2 -1
  57. package/dist/esm/components-page/onboarding.test.js.map +1 -1
  58. package/dist/esm/generated/quetzal-translations.d.ts +2 -2
  59. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +4 -4
  60. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  61. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +18 -2
  62. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  63. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.external-token-store.test.d.ts +1 -0
  64. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.external-token-store.test.js +382 -0
  65. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.external-token-store.test.js.map +1 -0
  66. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.init-analytics.test.d.ts +1 -0
  67. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.init-analytics.test.js +66 -0
  68. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.init-analytics.test.js.map +1 -0
  69. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +66 -14
  70. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  71. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +2 -2
  72. package/dist/esm/lib/hexclave-app/apps/implementations/external-token-store-session-adapter.d.ts +31 -0
  73. package/dist/esm/lib/hexclave-app/apps/implementations/external-token-store-session-adapter.d.ts.map +1 -0
  74. package/dist/esm/lib/hexclave-app/apps/implementations/external-token-store-session-adapter.js +115 -0
  75. package/dist/esm/lib/hexclave-app/apps/implementations/external-token-store-session-adapter.js.map +1 -0
  76. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts +16 -1
  77. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
  78. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  79. package/dist/esm/lib/hexclave-app/common.d.ts +14 -2
  80. package/dist/esm/lib/hexclave-app/common.d.ts.map +1 -1
  81. package/dist/esm/lib/hexclave-app/common.js +17 -1
  82. package/dist/esm/lib/hexclave-app/common.js.map +1 -1
  83. package/dist/esm/lib/hexclave-app/index.d.ts +2 -2
  84. package/dist/esm/lib/hexclave-app/index.js +2 -2
  85. package/dist/esm/lib/hexclave-app/users/index.d.ts +14 -1
  86. package/dist/esm/lib/hexclave-app/users/index.d.ts.map +1 -1
  87. package/dist/esm/lib/hexclave-app/users/index.js.map +1 -1
  88. package/dist/generated/quetzal-translations.d.ts +1 -1
  89. package/dist/{hexclave-provider-XVO5TbFD.d.ts → hexclave-provider-Br8j6WHN.d.ts} +3 -3
  90. package/dist/{hexclave-provider-XVO5TbFD.d.ts.map → hexclave-provider-Br8j6WHN.d.ts.map} +1 -1
  91. package/dist/{hooks-nqzRtl9S.d.ts → hooks-DyC8t1Sh.d.ts} +3 -3
  92. package/dist/{hooks-nqzRtl9S.d.ts.map → hooks-DyC8t1Sh.d.ts.map} +1 -1
  93. package/dist/{index-DbiyXho6.d.ts → index-CQ4kC6Cu.d.ts} +2 -2
  94. package/dist/{index-DbiyXho6.d.ts.map → index-CQ4kC6Cu.d.ts.map} +1 -1
  95. package/dist/{index-BDzsJvkD.d.ts → index-CWaynElx.d.ts} +3 -3
  96. package/dist/{index-BDzsJvkD.d.ts.map → index-CWaynElx.d.ts.map} +1 -1
  97. package/dist/{index-BSr2pd22.d.ts → index-FsS-NA9c.d.ts} +2 -2
  98. package/dist/{index-BSr2pd22.d.ts.map → index-FsS-NA9c.d.ts.map} +1 -1
  99. package/dist/index.d.ts +7 -7
  100. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
  101. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -1
  102. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.external-token-store.test.d.ts +1 -0
  103. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.external-token-store.test.js +381 -0
  104. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.external-token-store.test.js.map +1 -0
  105. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.init-analytics.test.d.ts +1 -0
  106. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.init-analytics.test.js +65 -0
  107. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.init-analytics.test.js.map +1 -0
  108. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +65 -13
  109. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  110. package/dist/lib/hexclave-app/apps/implementations/common.d.ts +1 -1
  111. package/dist/lib/hexclave-app/apps/implementations/common.js +2 -2
  112. package/dist/lib/hexclave-app/apps/implementations/external-token-store-session-adapter.d.ts +31 -0
  113. package/dist/lib/hexclave-app/apps/implementations/external-token-store-session-adapter.d.ts.map +1 -0
  114. package/dist/lib/hexclave-app/apps/implementations/external-token-store-session-adapter.js +117 -0
  115. package/dist/lib/hexclave-app/apps/implementations/external-token-store-session-adapter.js.map +1 -0
  116. package/dist/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
  117. package/dist/lib/hexclave-app/apps/implementations/redirect-page-urls.d.ts +1 -1
  118. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  119. package/dist/lib/hexclave-app/apps/index.d.ts +1 -1
  120. package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts +1 -1
  121. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -1
  122. package/dist/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  123. package/dist/lib/hexclave-app/apps/interfaces/server-app.d.ts +1 -1
  124. package/dist/lib/hexclave-app/common.d.ts +2 -2
  125. package/dist/lib/hexclave-app/common.js +19 -0
  126. package/dist/lib/hexclave-app/common.js.map +1 -1
  127. package/dist/lib/hexclave-app/customers/index.d.ts +1 -1
  128. package/dist/lib/hexclave-app/data-vault/index.d.ts +1 -1
  129. package/dist/lib/hexclave-app/index.d.ts +4 -4
  130. package/dist/lib/hexclave-app/index.js +18 -0
  131. package/dist/lib/hexclave-app/projects/index.d.ts +1 -1
  132. package/dist/lib/hexclave-app/teams/index.d.ts +1 -1
  133. package/dist/lib/hexclave-app/url-targets.d.ts +1 -1
  134. package/dist/lib/hexclave-app/users/index.d.ts +1 -1
  135. package/dist/lib/hexclave-app/users/index.js.map +1 -1
  136. package/dist/lib/hooks.d.ts +1 -1
  137. package/dist/providers/hexclave-context.d.ts +1 -1
  138. package/dist/providers/hexclave-provider-client.d.ts +1 -1
  139. package/dist/providers/hexclave-provider.d.ts +1 -1
  140. package/dist/providers/translation-provider.d.ts +1 -1
  141. package/dist/pushed-config-error-overlay/index.d.ts +1 -1
  142. package/dist/{quetzal-translations-CwNB-iql.d.ts → quetzal-translations-XeH3zQ9l.d.ts} +3 -3
  143. package/dist/{quetzal-translations-CwNB-iql.d.ts.map → quetzal-translations-XeH3zQ9l.d.ts.map} +1 -1
  144. package/dist/{redirect-page-urls-ofcg3AYr.d.ts → redirect-page-urls-CnGvn0UJ.d.ts} +2 -2
  145. package/dist/{redirect-page-urls-ofcg3AYr.d.ts.map → redirect-page-urls-CnGvn0UJ.d.ts.map} +1 -1
  146. package/dist/tanstack-start-server-context.d.ts +804 -485
  147. package/dist/tanstack-start-server-context.d.ts.map +1 -1
  148. package/dist/{translation-provider-C0uPBd41.d.ts → translation-provider-WOaZXqAz.d.ts} +2 -2
  149. package/dist/{translation-provider-C0uPBd41.d.ts.map → translation-provider-WOaZXqAz.d.ts.map} +1 -1
  150. package/dist/{url-targets-CWO0mjTr.d.ts → url-targets-DbhzuQtY.d.ts} +2 -2
  151. package/dist/{url-targets-CWO0mjTr.d.ts.map → url-targets-DbhzuQtY.d.ts.map} +1 -1
  152. package/package.json +4 -4
  153. package/src/components/message-cards/predefined-message-card.tsx +4 -2
  154. package/src/components/user-button.test.tsx +87 -0
  155. package/src/components/user-button.tsx +9 -9
  156. package/src/components-page/account-settings/settings/sign-out-section.tsx +9 -1
  157. package/src/components-page/onboarding.test.tsx +1 -0
  158. package/src/components-page/onboarding.tsx +16 -13
  159. package/src/lib/hexclave-app/apps/implementations/client-app-impl.external-token-store.test.ts +425 -0
  160. package/src/lib/hexclave-app/apps/implementations/client-app-impl.init-analytics.test.ts +79 -0
  161. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +124 -13
  162. package/src/lib/hexclave-app/apps/implementations/external-token-store-session-adapter.ts +190 -0
  163. package/src/lib/hexclave-app/apps/interfaces/client-app.ts +17 -0
  164. package/src/lib/hexclave-app/common.ts +33 -0
  165. package/src/lib/hexclave-app/index.ts +4 -0
  166. package/src/lib/hexclave-app/users/index.ts +13 -1
  167. package/dist/admin-app-impl-MncU_Oxy.d.ts.map +0 -1
  168. package/dist/client-app-B0cTytS5.d.ts.map +0 -1
  169. package/dist/common-B2gkWlXX.d.ts.map +0 -1
@@ -0,0 +1,70 @@
1
+ import { act } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { createRoot } from "react-dom/client";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { UserButton } from "./user-button.js";
6
+ //#region src/components/user-button.test.tsx
7
+ // @vitest-environment jsdom
8
+ const previousActEnvironment = Reflect.get(globalThis, "IS_REACT_ACT_ENVIRONMENT");
9
+ const state = vi.hoisted(() => ({
10
+ app: null,
11
+ user: null
12
+ }));
13
+ vi.mock("..", () => ({
14
+ useStackApp: () => state.app,
15
+ useUser: () => state.user
16
+ }));
17
+ vi.mock("../lib/translations", () => ({ useTranslation: () => ({ t: (value) => value }) }));
18
+ vi.mock("./elements/user-avatar", () => ({ UserAvatar: () => /* @__PURE__ */ jsx("div", { "data-testid": "user-avatar" }) }));
19
+ vi.mock("@hexclave/ui", () => ({
20
+ DropdownMenu: (props) => /* @__PURE__ */ jsx("div", { children: props.children }),
21
+ DropdownMenuContent: (props) => /* @__PURE__ */ jsx("div", { children: props.children }),
22
+ DropdownMenuItem: (props) => /* @__PURE__ */ jsx("button", {
23
+ onClick: props.onClick,
24
+ children: props.children
25
+ }),
26
+ DropdownMenuLabel: (props) => /* @__PURE__ */ jsx("div", { children: props.children }),
27
+ DropdownMenuSeparator: () => /* @__PURE__ */ jsx("hr", {}),
28
+ DropdownMenuTrigger: (props) => /* @__PURE__ */ jsx("div", { children: props.children }),
29
+ Skeleton: () => /* @__PURE__ */ jsx("div", {}),
30
+ Typography: (props) => /* @__PURE__ */ jsx("div", { children: props.children })
31
+ }));
32
+ describe("UserButton", () => {
33
+ let root = null;
34
+ let container = null;
35
+ beforeEach(() => {
36
+ Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true);
37
+ });
38
+ afterEach(() => {
39
+ act(() => root?.unmount());
40
+ container?.remove();
41
+ root = null;
42
+ container = null;
43
+ state.app = null;
44
+ state.user = null;
45
+ Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", previousActEnvironment);
46
+ });
47
+ it("hides external-auth sign out while keeping account settings", async () => {
48
+ state.app = {
49
+ isExternalAuthApp: () => true,
50
+ redirectToAccountSettings: vi.fn(async () => {})
51
+ };
52
+ state.user = {
53
+ displayName: "External user",
54
+ primaryEmail: "user@example.com",
55
+ signOut: vi.fn(async () => {})
56
+ };
57
+ container = document.createElement("div");
58
+ document.body.append(container);
59
+ root = createRoot(container);
60
+ await act(async () => {
61
+ root?.render(/* @__PURE__ */ jsx(UserButton, {}));
62
+ });
63
+ expect(container.textContent).toContain("Account settings");
64
+ expect(container.textContent).not.toContain("Sign out");
65
+ });
66
+ });
67
+ //#endregion
68
+ export {};
69
+
70
+ //# sourceMappingURL=user-button.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-button.test.js","names":[],"sources":["../../../src/components/user-button.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 React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { UserButton } from \"./user-button\";\n\ntype MockState = {\n app: { isExternalAuthApp: () => boolean, redirectToAccountSettings: () => Promise<void> } | null,\n user: { displayName: string, primaryEmail: string, signOut: () => Promise<void> } | null,\n};\n\nconst previousActEnvironment = Reflect.get(globalThis, \"IS_REACT_ACT_ENVIRONMENT\");\n\nconst state = vi.hoisted<MockState>(() => ({\n app: null,\n user: null,\n}));\n\nvi.mock(\"..\", () => ({\n useStackApp: () => state.app,\n useUser: () => state.user,\n}));\n\nvi.mock(\"../lib/translations\", () => ({\n useTranslation: () => ({ t: (value: string) => value }),\n}));\n\nvi.mock(\"./elements/user-avatar\", () => ({\n UserAvatar: () => <div data-testid=\"user-avatar\" />,\n}));\n\nvi.mock(\"@hexclave/ui\", () => ({\n DropdownMenu: (props: { children: React.ReactNode }) => <div>{props.children}</div>,\n DropdownMenuContent: (props: { children: React.ReactNode }) => <div>{props.children}</div>,\n DropdownMenuItem: (props: { children: React.ReactNode, onClick: () => void }) => <button onClick={props.onClick}>{props.children}</button>,\n DropdownMenuLabel: (props: { children: React.ReactNode }) => <div>{props.children}</div>,\n DropdownMenuSeparator: () => <hr />,\n DropdownMenuTrigger: (props: { children: React.ReactNode }) => <div>{props.children}</div>,\n Skeleton: () => <div />,\n Typography: (props: { children: React.ReactNode }) => <div>{props.children}</div>,\n}));\n\ndescribe(\"UserButton\", () => {\n let root: Root | null = null;\n let container: HTMLDivElement | null = null;\n\n beforeEach(() => {\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", true);\n });\n\n afterEach(() => {\n act(() => root?.unmount());\n container?.remove();\n root = null;\n container = null;\n state.app = null;\n state.user = null;\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", previousActEnvironment);\n });\n\n it(\"hides external-auth sign out while keeping account settings\", async () => {\n state.app = {\n isExternalAuthApp: () => true,\n redirectToAccountSettings: vi.fn(async () => {}),\n };\n state.user = {\n displayName: \"External user\",\n primaryEmail: \"user@example.com\",\n signOut: vi.fn(async () => {}),\n };\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n\n await act(async () => {\n root?.render(<UserButton />);\n });\n\n expect(container.textContent).toContain(\"Account settings\");\n expect(container.textContent).not.toContain(\"Sign out\");\n });\n});\n"],"mappings":";;;;;;;AAgBA,MAAM,yBAAyB,QAAQ,IAAI,YAAY,0BAA0B;AAEjF,MAAM,QAAQ,GAAG,eAA0B;CACzC,KAAK;CACL,MAAM;AACR,EAAE;AAEF,GAAG,KAAK,aAAa;CACnB,mBAAmB,MAAM;CACzB,eAAe,MAAM;AACvB,EAAE;AAEF,GAAG,KAAK,8BAA8B,EACpC,uBAAuB,EAAE,IAAI,UAAkB,MAAM,GACvD,EAAE;AAEF,GAAG,KAAK,iCAAiC,EACvC,kBAAkB,oBAAC,OAAD,EAAK,eAAY,cAAe,CAAA,EACpD,EAAE;AAEF,GAAG,KAAK,uBAAuB;CAC7B,eAAe,UAAyC,oBAAC,OAAD,EAAA,UAAM,MAAM,SAAc,CAAA;CAClF,sBAAsB,UAAyC,oBAAC,OAAD,EAAA,UAAM,MAAM,SAAc,CAAA;CACzF,mBAAmB,UAA8D,oBAAC,UAAD;EAAQ,SAAS,MAAM;YAAU,MAAM;CAAiB,CAAA;CACzI,oBAAoB,UAAyC,oBAAC,OAAD,EAAA,UAAM,MAAM,SAAc,CAAA;CACvF,6BAA6B,oBAAC,MAAD,CAAK,CAAA;CAClC,sBAAsB,UAAyC,oBAAC,OAAD,EAAA,UAAM,MAAM,SAAc,CAAA;CACzF,gBAAgB,oBAAC,OAAD,CAAM,CAAA;CACtB,aAAa,UAAyC,oBAAC,OAAD,EAAA,UAAM,MAAM,SAAc,CAAA;AAClF,EAAE;AAEF,SAAS,oBAAoB;CAC3B,IAAI,OAAoB;CACxB,IAAI,YAAmC;CAEvC,iBAAiB;EACf,QAAQ,IAAI,YAAY,4BAA4B,IAAI;CAC1D,CAAC;CAED,gBAAgB;EACd,UAAU,MAAM,QAAQ,CAAC;EACzB,WAAW,OAAO;EAClB,OAAO;EACP,YAAY;EACZ,MAAM,MAAM;EACZ,MAAM,OAAO;EACb,QAAQ,IAAI,YAAY,4BAA4B,sBAAsB;CAC5E,CAAC;CAED,GAAG,+DAA+D,YAAY;EAC5E,MAAM,MAAM;GACV,yBAAyB;GACzB,2BAA2B,GAAG,GAAG,YAAY,CAAC,CAAC;EACjD;EACA,MAAM,OAAO;GACX,aAAa;GACb,cAAc;GACd,SAAS,GAAG,GAAG,YAAY,CAAC,CAAC;EAC/B;EACA,YAAY,SAAS,cAAc,KAAK;EACxC,SAAS,KAAK,OAAO,SAAS;EAC9B,OAAO,WAAW,SAAS;EAE3B,MAAM,IAAI,YAAY;GACpB,MAAM,OAAO,oBAAC,YAAD,CAAa,CAAA,CAAC;EAC7B,CAAC;EAED,OAAO,UAAU,WAAW,CAAC,CAAC,UAAU,kBAAkB;EAC1D,OAAO,UAAU,WAAW,CAAC,CAAC,IAAI,UAAU,UAAU;CACxD,CAAC;AACH,CAAC"}
@@ -1,7 +1,7 @@
1
1
  //#region src/components-page/account-settings/settings/sign-out-section.d.ts
2
2
  declare function SignOutSection(props?: {
3
3
  mockMode?: boolean;
4
- }): import("react/jsx-runtime").JSX.Element;
4
+ }): import("react/jsx-runtime").JSX.Element | null;
5
5
  //#endregion
6
6
  export { SignOutSection };
7
7
  //# sourceMappingURL=sign-out-section.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sign-out-section.d.ts","names":[],"sources":["../../../../../src/components-page/account-settings/settings/sign-out-section.tsx"],"mappings":";iBASgB,cAAA,CAAe,KAAA;EAAU,QAAA;AAAA,gCAAoB,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"sign-out-section.d.ts","names":[],"sources":["../../../../../src/components-page/account-settings/settings/sign-out-section.tsx"],"mappings":";iBAUgB,cAAA,CAAe,KAAA;EAAU,QAAA;AAAA,gCAAoB,GAAA,CAAA,OAAA"}
@@ -3,10 +3,13 @@ import { jsx } from "react/jsx-runtime";
3
3
  import { useUser } from "../../../lib/hooks.js";
4
4
  import { useTranslation } from "../../../lib/translations.js";
5
5
  import { Section } from "../section.js";
6
+ import { useStackApp as useStackApp$1 } from "../../../index.js";
6
7
  //#region src/components-page/account-settings/settings/sign-out-section.tsx
7
8
  function SignOutSection(props) {
8
9
  const { t } = useTranslation();
9
- const user = useUser({ or: props?.mockMode ? "return-null" : "throw" });
10
+ const app = useStackApp$1();
11
+ const user = useUser({ or: props?.mockMode || app.isExternalAuthApp() ? "return-null" : "throw" });
12
+ if (app.isExternalAuthApp()) return null;
10
13
  const handleSignOut = async () => {
11
14
  if (props?.mockMode) {
12
15
  alert("Mock mode: Sign out clicked");
@@ -1 +1 @@
1
- {"version":3,"file":"sign-out-section.js","names":[],"sources":["../../../../../src/components-page/account-settings/settings/sign-out-section.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//===========================================\nimport { Button } from \"@hexclave/ui\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { Section } from \"../section\";\n\nexport function SignOutSection(props?: { mockMode?: boolean }) {\n const { t } = useTranslation();\n const user = useUser({ or: props?.mockMode ? \"return-null\" : \"throw\" });\n\n const handleSignOut = async () => {\n if (props?.mockMode) {\n // Mock mode - just show an alert or do nothing\n alert(\"Mock mode: Sign out clicked\");\n return;\n }\n if (user) {\n await user.signOut();\n }\n };\n\n return (\n <Section\n title={t(\"Sign out\")}\n description={t(\"End your current session\")}\n >\n <div>\n <Button\n variant='secondary'\n onClick={handleSignOut}\n >\n {t(\"Sign out\")}\n </Button>\n </div>\n </Section>\n );\n}\n"],"mappings":";;;;;;AASA,SAAgB,eAAe,OAAgC;CAC7D,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,OAAO,QAAQ,EAAE,IAAI,OAAO,WAAW,gBAAgB,QAAQ,CAAC;CAEtE,MAAM,gBAAgB,YAAY;EAChC,IAAI,OAAO,UAAU;GAEnB,MAAM,6BAA6B;GACnC;EACF;EACA,IAAI,MACF,MAAM,KAAK,QAAQ;CAEvB;CAEA,OACE,oBAAC,SAAD;EACE,OAAO,EAAE,UAAU;EACnB,aAAa,EAAE,0BAA0B;YAEzC,oBAAC,OAAD,EAAA,UACE,oBAAC,QAAD;GACE,SAAQ;GACR,SAAS;aAER,EAAE,UAAU;EACP,CAAA,EACL,CAAA;CACE,CAAA;AAEb"}
1
+ {"version":3,"file":"sign-out-section.js","names":["useStackApp"],"sources":["../../../../../src/components-page/account-settings/settings/sign-out-section.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//===========================================\nimport { Button } from \"@hexclave/ui\";\nimport { useStackApp } from \"../../..\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { Section } from \"../section\";\n\nexport function SignOutSection(props?: { mockMode?: boolean }) {\n const { t } = useTranslation();\n const app = useStackApp();\n // External-auth apps render nothing here, and the provider can clear its session (so Hexclave has no user)\n // before this re-renders, so don't throw for them.\n const user = useUser({ or: props?.mockMode || app.isExternalAuthApp() ? \"return-null\" : \"throw\" });\n\n if (app.isExternalAuthApp()) {\n return null;\n }\n\n const handleSignOut = async () => {\n if (props?.mockMode) {\n // Mock mode - just show an alert or do nothing\n alert(\"Mock mode: Sign out clicked\");\n return;\n }\n if (user) {\n await user.signOut();\n }\n };\n\n return (\n <Section\n title={t(\"Sign out\")}\n description={t(\"End your current session\")}\n >\n <div>\n <Button\n variant='secondary'\n onClick={handleSignOut}\n >\n {t(\"Sign out\")}\n </Button>\n </div>\n </Section>\n );\n}\n"],"mappings":";;;;;;;AAUA,SAAgB,eAAe,OAAgC;CAC7D,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,MAAMA,cAAY;CAGxB,MAAM,OAAO,QAAQ,EAAE,IAAI,OAAO,YAAY,IAAI,kBAAkB,IAAI,gBAAgB,QAAQ,CAAC;CAEjG,IAAI,IAAI,kBAAkB,GACxB,OAAO;CAGT,MAAM,gBAAgB,YAAY;EAChC,IAAI,OAAO,UAAU;GAEnB,MAAM,6BAA6B;GACnC;EACF;EACA,IAAI,MACF,MAAM,KAAK,QAAQ;CAEvB;CAEA,OACE,oBAAC,SAAD;EACE,OAAO,EAAE,UAAU;EACnB,aAAa,EAAE,0BAA0B;YAEzC,oBAAC,OAAD,EAAA,UACE,oBAAC,QAAD;GACE,SAAQ;GACR,SAAS;aAER,EAAE,UAAU;EACP,CAAA,EACL,CAAA;CACE,CAAA;AAEb"}
@@ -18,6 +18,7 @@ function Onboarding(props) {
18
18
  or: "return-null",
19
19
  includeRestricted: true
20
20
  });
21
+ const canSignOut = !hexclaveApp.isExternalAuthApp();
21
22
  if (user && !user.isRestricted) {
22
23
  runAsynchronously(hexclaveApp.redirectToAfterSignIn());
23
24
  return null;
@@ -38,19 +39,19 @@ function Onboarding(props) {
38
39
  if (restrictedReason?.type === "restricted_by_administrator") return /* @__PURE__ */ jsx(MessageCard$1, {
39
40
  title: t("Your account has been restricted"),
40
41
  fullPage: !!props.fullPage,
41
- secondaryButtonText: t("Sign out"),
42
- secondaryAction: async () => {
42
+ secondaryButtonText: canSignOut ? t("Sign out") : void 0,
43
+ secondaryAction: canSignOut ? async () => {
43
44
  await user.signOut();
44
- },
45
+ } : void 0,
45
46
  children: /* @__PURE__ */ jsx("p", { children: user.restrictedByAdminReason ?? t("An administrator has restricted your account. Please reach out to support if you believe this is an error.") })
46
47
  });
47
48
  return /* @__PURE__ */ jsx(MessageCard$1, {
48
49
  title: t("Complete your account setup"),
49
50
  fullPage: !!props.fullPage,
50
- secondaryButtonText: t("Sign out"),
51
- secondaryAction: async () => {
51
+ secondaryButtonText: canSignOut ? t("Sign out") : void 0,
52
+ secondaryAction: canSignOut ? async () => {
52
53
  await user.signOut();
53
- },
54
+ } : void 0,
54
55
  children: /* @__PURE__ */ jsx("p", { children: t("You have not yet completed your account setup. Please reach out to support if you believe this is an error.") })
55
56
  });
56
57
  }
@@ -60,7 +61,7 @@ function AddEmailForm(props) {
60
61
  or: "throw",
61
62
  includeRestricted: true
62
63
  });
63
- useStackApp();
64
+ const canSignOut = !useStackApp().isExternalAuthApp();
64
65
  const [loading, setLoading] = useState(false);
65
66
  const { register, handleSubmit, formState: { errors } } = useForm({ resolver: yupResolver(yupObject({ email: strictEmailSchema(t("Please enter a valid email address")).defined().nonEmpty(t("Email is required")) })) });
66
67
  const onSubmit = async (data) => {
@@ -75,10 +76,10 @@ function AddEmailForm(props) {
75
76
  return /* @__PURE__ */ jsxs(MessageCard$1, {
76
77
  title: t("Add your email address"),
77
78
  fullPage: !!props.fullPage,
78
- secondaryButtonText: t("Sign out"),
79
- secondaryAction: async () => {
79
+ secondaryButtonText: canSignOut ? t("Sign out") : void 0,
80
+ secondaryAction: canSignOut ? async () => {
80
81
  await user.signOut();
81
- },
82
+ } : void 0,
82
83
  children: [/* @__PURE__ */ jsx(Typography, {
83
84
  className: "mb-4",
84
85
  children: t("Please add an email address to complete your account setup. We'll send you a verification email.")
@@ -106,6 +107,7 @@ function AddEmailForm(props) {
106
107
  function VerifyEmailScreen(props) {
107
108
  const { t } = useTranslation();
108
109
  const { user, email } = props;
110
+ const canSignOut = !useStackApp().isExternalAuthApp();
109
111
  const [changingEmail, setChangingEmail] = useState(false);
110
112
  if (changingEmail) return /* @__PURE__ */ jsx(AddEmailForm, {
111
113
  fullPage: props.fullPage,
@@ -118,10 +120,10 @@ function VerifyEmailScreen(props) {
118
120
  primaryAction: async () => {
119
121
  await user.sendVerificationEmail();
120
122
  },
121
- secondaryButtonText: t("Sign out"),
122
- secondaryAction: async () => {
123
+ secondaryButtonText: canSignOut ? t("Sign out") : void 0,
124
+ secondaryAction: canSignOut ? async () => {
123
125
  await user.signOut();
124
- },
126
+ } : void 0,
125
127
  children: /* @__PURE__ */ jsxs(Typography, { children: [
126
128
  t("Please verify your email address "),
127
129
  /* @__PURE__ */ jsx("span", {
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding.js","names":["MessageCard"],"sources":["../../../src/components-page/onboarding.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 { yupResolver } from \"@hookform/resolvers/yup\";\nimport { strictEmailSchema, yupObject } from \"@hexclave/shared/dist/schema-fields\";\nimport { runAsynchronously, runAsynchronouslyWithAlert } from \"@hexclave/shared/dist/utils/promises\";\nimport { Button, Input, Typography } from \"@hexclave/ui\";\nimport { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { useStackApp, useUser } from \"..\";\nimport { FormWarningText } from \"../components/elements/form-warning\";\nimport { MessageCard } from \"../components/message-cards/message-card\";\nimport { useTranslation } from \"../lib/translations\";\n\nexport function Onboarding(props: {\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const hexclaveApp = useStackApp();\n const user = useUser({ or: \"return-null\", includeRestricted: true });\n\n // If user is not restricted anymore, redirect to the intended destination\n // redirectToAfterSignIn automatically checks for after_auth_return_to in the URL\n if (user && !user.isRestricted) {\n runAsynchronously(hexclaveApp.redirectToAfterSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // If user is anonymous or not logged in, redirect to sign-in\n if (!user || user.isAnonymous) {\n runAsynchronously(hexclaveApp.redirectToSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // User is restricted - show appropriate onboarding step based on restricted reason\n const restrictedReason = user.restrictedReason;\n\n if (restrictedReason?.type === \"email_not_verified\") {\n // Check if user has a primary email\n const hasPrimaryEmail = !!user.primaryEmail;\n\n if (!hasPrimaryEmail) {\n // User needs to add an email first\n return <AddEmailForm fullPage={props.fullPage} />;\n }\n\n // User has email but it's not verified\n return <VerifyEmailScreen user={user} email={user.primaryEmail} fullPage={props.fullPage} />;\n }\n\n if (restrictedReason?.type === \"restricted_by_administrator\") {\n return (\n <MessageCard\n title={t(\"Your account has been restricted\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n {/* The public reason is set by the project's administrators (sign-up rules or manually), so it's the most\n helpful thing we can show; the generic sentence is only a fallback for when they didn't provide one. */}\n <p>{user.restrictedByAdminReason ?? t(\"An administrator has restricted your account. Please reach out to support if you believe this is an error.\")}</p>\n </MessageCard>\n );\n }\n\n // Unknown restricted reason - show generic message\n return (\n <MessageCard\n title={t(\"Complete your account setup\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <p>{t(\"You have not yet completed your account setup. Please reach out to support if you believe this is an error.\")}</p>\n </MessageCard>\n );\n}\n\nfunction AddEmailForm(props: {\n fullPage?: boolean,\n onEmailAdded?: () => void,\n}) {\n const { t } = useTranslation();\n const user = useUser({ or: \"throw\", includeRestricted: true });\n const hexclaveApp = useStackApp();\n const [loading, setLoading] = useState(false);\n\n const emailSchema = yupObject({\n email: strictEmailSchema(t('Please enter a valid email address'))\n .defined()\n .nonEmpty(t('Email is required')),\n });\n\n const { register, handleSubmit, formState: { errors } } = useForm({\n resolver: yupResolver(emailSchema)\n });\n\n const onSubmit = async (data: yup.InferType<typeof emailSchema>) => {\n setLoading(true);\n try {\n await user.update({ primaryEmail: data.email });\n props.onEmailAdded?.();\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <MessageCard\n title={t(\"Add your email address\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <Typography className=\"mb-4\">\n {t(\"Please add an email address to complete your account setup. We'll send you a verification email.\")}\n </Typography>\n <form\n onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}\n noValidate\n className='flex flex-col gap-2'\n >\n <Input\n {...register(\"email\")}\n placeholder={t(\"Enter your email\")}\n type=\"email\"\n />\n {errors.email && <FormWarningText text={errors.email.message} />}\n <Button type=\"submit\" loading={loading} className=\"w-full\">\n {t(\"Continue\")}\n </Button>\n </form>\n </MessageCard>\n );\n}\n\nfunction VerifyEmailScreen(props: {\n user: NonNullable<ReturnType<typeof useUser>>,\n email: string,\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const { user, email } = props;\n const [changingEmail, setChangingEmail] = useState(false);\n\n if (changingEmail) {\n return <AddEmailForm fullPage={props.fullPage} onEmailAdded={() => setChangingEmail(false)} />;\n }\n\n return (\n <MessageCard\n title={t(\"Please check your email inbox\")}\n fullPage={!!props.fullPage}\n primaryButtonText={t(\"Resend verification email\")}\n primaryAction={async () => {\n await user.sendVerificationEmail();\n }}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <Typography>\n {t(\"Please verify your email address \")}\n <span className=\"font-semibold\">{email}</span>\n {\" (\"}\n <button\n type=\"button\"\n className=\"text-primary hover:underline\"\n onClick={() => setChangingEmail(true)}\n >\n {t(\"change\")}\n </button>\n {\"). \"}\n {t(\"Click the button below to resend the verification link.\")}\n </Typography>\n </MessageCard>\n );\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"onboarding.js","names":["MessageCard"],"sources":["../../../src/components-page/onboarding.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 { yupResolver } from \"@hookform/resolvers/yup\";\nimport { strictEmailSchema, yupObject } from \"@hexclave/shared/dist/schema-fields\";\nimport { runAsynchronously, runAsynchronouslyWithAlert } from \"@hexclave/shared/dist/utils/promises\";\nimport { Button, Input, Typography } from \"@hexclave/ui\";\nimport { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { useStackApp, useUser } from \"..\";\nimport { FormWarningText } from \"../components/elements/form-warning\";\nimport { MessageCard } from \"../components/message-cards/message-card\";\nimport { useTranslation } from \"../lib/translations\";\n\nexport function Onboarding(props: {\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const hexclaveApp = useStackApp();\n const user = useUser({ or: \"return-null\", includeRestricted: true });\n const canSignOut = !hexclaveApp.isExternalAuthApp();\n\n // If user is not restricted anymore, redirect to the intended destination\n // redirectToAfterSignIn automatically checks for after_auth_return_to in the URL\n if (user && !user.isRestricted) {\n runAsynchronously(hexclaveApp.redirectToAfterSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // If user is anonymous or not logged in, redirect to sign-in\n if (!user || user.isAnonymous) {\n runAsynchronously(hexclaveApp.redirectToSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // User is restricted - show appropriate onboarding step based on restricted reason\n const restrictedReason = user.restrictedReason;\n\n if (restrictedReason?.type === \"email_not_verified\") {\n // Check if user has a primary email\n const hasPrimaryEmail = !!user.primaryEmail;\n\n if (!hasPrimaryEmail) {\n // User needs to add an email first\n return <AddEmailForm fullPage={props.fullPage} />;\n }\n\n // User has email but it's not verified\n return <VerifyEmailScreen user={user} email={user.primaryEmail} fullPage={props.fullPage} />;\n }\n\n if (restrictedReason?.type === \"restricted_by_administrator\") {\n return (\n <MessageCard\n title={t(\"Your account has been restricted\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={canSignOut ? t(\"Sign out\") : undefined}\n secondaryAction={canSignOut ? async () => {\n await user.signOut();\n } : undefined}\n >\n {/* The public reason is set by the project's administrators (sign-up rules or manually), so it's the most\n helpful thing we can show; the generic sentence is only a fallback for when they didn't provide one. */}\n <p>{user.restrictedByAdminReason ?? t(\"An administrator has restricted your account. Please reach out to support if you believe this is an error.\")}</p>\n </MessageCard>\n );\n }\n\n // Unknown restricted reason - show generic message\n return (\n <MessageCard\n title={t(\"Complete your account setup\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={canSignOut ? t(\"Sign out\") : undefined}\n secondaryAction={canSignOut ? async () => {\n await user.signOut();\n } : undefined}\n >\n <p>{t(\"You have not yet completed your account setup. Please reach out to support if you believe this is an error.\")}</p>\n </MessageCard>\n );\n}\n\nfunction AddEmailForm(props: {\n fullPage?: boolean,\n onEmailAdded?: () => void,\n}) {\n const { t } = useTranslation();\n const user = useUser({ or: \"throw\", includeRestricted: true });\n const hexclaveApp = useStackApp();\n const canSignOut = !hexclaveApp.isExternalAuthApp();\n const [loading, setLoading] = useState(false);\n\n const emailSchema = yupObject({\n email: strictEmailSchema(t('Please enter a valid email address'))\n .defined()\n .nonEmpty(t('Email is required')),\n });\n\n const { register, handleSubmit, formState: { errors } } = useForm({\n resolver: yupResolver(emailSchema)\n });\n\n const onSubmit = async (data: yup.InferType<typeof emailSchema>) => {\n setLoading(true);\n try {\n await user.update({ primaryEmail: data.email });\n props.onEmailAdded?.();\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <MessageCard\n title={t(\"Add your email address\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={canSignOut ? t(\"Sign out\") : undefined}\n secondaryAction={canSignOut ? async () => {\n await user.signOut();\n } : undefined}\n >\n <Typography className=\"mb-4\">\n {t(\"Please add an email address to complete your account setup. We'll send you a verification email.\")}\n </Typography>\n <form\n onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}\n noValidate\n className='flex flex-col gap-2'\n >\n <Input\n {...register(\"email\")}\n placeholder={t(\"Enter your email\")}\n type=\"email\"\n />\n {errors.email && <FormWarningText text={errors.email.message} />}\n <Button type=\"submit\" loading={loading} className=\"w-full\">\n {t(\"Continue\")}\n </Button>\n </form>\n </MessageCard>\n );\n}\n\nfunction VerifyEmailScreen(props: {\n user: NonNullable<ReturnType<typeof useUser>>,\n email: string,\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const { user, email } = props;\n const hexclaveApp = useStackApp();\n const canSignOut = !hexclaveApp.isExternalAuthApp();\n const [changingEmail, setChangingEmail] = useState(false);\n\n if (changingEmail) {\n return <AddEmailForm fullPage={props.fullPage} onEmailAdded={() => setChangingEmail(false)} />;\n }\n\n return (\n <MessageCard\n title={t(\"Please check your email inbox\")}\n fullPage={!!props.fullPage}\n primaryButtonText={t(\"Resend verification email\")}\n primaryAction={async () => {\n await user.sendVerificationEmail();\n }}\n secondaryButtonText={canSignOut ? t(\"Sign out\") : undefined}\n secondaryAction={canSignOut ? async () => {\n await user.signOut();\n } : undefined}\n >\n <Typography>\n {t(\"Please verify your email address \")}\n <span className=\"font-semibold\">{email}</span>\n {\" (\"}\n <button\n type=\"button\"\n className=\"text-primary hover:underline\"\n onClick={() => setChangingEmail(true)}\n >\n {t(\"change\")}\n </button>\n {\"). \"}\n {t(\"Click the button below to resend the verification link.\")}\n </Typography>\n </MessageCard>\n );\n}\n"],"mappings":"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"}
@@ -34,7 +34,8 @@ const previousActEnvironment = Reflect.get(globalThis, "IS_REACT_ACT_ENVIRONMENT
34
34
  function createAppTestDouble() {
35
35
  return {
36
36
  redirectToSignIn: vi.fn(async () => {}),
37
- redirectToAfterSignIn: vi.fn(async () => {})
37
+ redirectToAfterSignIn: vi.fn(async () => {}),
38
+ isExternalAuthApp: vi.fn(() => false)
38
39
  };
39
40
  }
40
41
  function createUserTestDouble(user) {
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding.test.js","names":[],"sources":["../../../src/components-page/onboarding.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 React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { CurrentUser } from \"../lib/hexclave-app\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { TranslationProviderClient } from \"../providers/translation-provider-client\";\nimport { Onboarding } from \"./onboarding\";\n\nconst appMockState = vi.hoisted(() => ({\n app: null as unknown,\n user: null as unknown,\n}));\n\nvi.mock(\"..\", () => ({\n useStackApp: () => {\n if (appMockState.app == null) {\n throw new Error(\"Expected test app to be set before rendering.\");\n }\n return appMockState.app;\n },\n useUser: () => appMockState.user,\n}));\n\nvi.mock(\"@hexclave/ui\", async () => {\n const React = await import(\"react\");\n return {\n Button: (props: { children: React.ReactNode, onClick?: () => void }) => (\n <button type=\"button\" onClick={props.onClick}>{props.children}</button>\n ),\n Input: (props: Record<string, unknown>) => <input {...props} />,\n Typography: (props: { children: React.ReactNode }) => <div>{props.children}</div>,\n cn: (...classes: (string | false | null | undefined)[]) => classes.filter(Boolean).join(\" \"),\n };\n});\n\nconst previousActEnvironment = Reflect.get(globalThis, \"IS_REACT_ACT_ENVIRONMENT\");\n\nfunction createAppTestDouble() {\n const app = {\n redirectToSignIn: vi.fn(async () => {}),\n redirectToAfterSignIn: vi.fn(async () => {}),\n };\n\n // This test double intentionally implements only the StackClientApp surface that Onboarding touches.\n return app as unknown as StackClientApp<true> & {\n redirectToSignIn: ReturnType<typeof vi.fn>,\n redirectToAfterSignIn: ReturnType<typeof vi.fn>,\n };\n}\n\nfunction createUserTestDouble(user: Partial<CurrentUser>) {\n // Same as the app test double: only the surface Onboarding touches.\n return {\n isAnonymous: false,\n isRestricted: true,\n restrictedReason: null,\n restrictedByAdmin: false,\n restrictedByAdminReason: null,\n primaryEmail: \"user@example.com\",\n signOut: vi.fn(async () => {}),\n ...user,\n } as unknown as CurrentUser;\n}\n\nlet root: Root | null = null;\nlet container: HTMLDivElement | null = null;\n\nasync function renderOnboarding(options: {\n app: StackClientApp<true>,\n user: unknown,\n}) {\n appMockState.app = options.app;\n appMockState.user = options.user;\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n await act(async () => {\n root?.render(\n <TranslationProviderClient quetzalKeys={new Map()} quetzalLocale={new Map()}>\n <Onboarding />\n </TranslationProviderClient>\n );\n });\n return container;\n}\n\ndescribe(\"Onboarding\", () => {\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 appMockState.app = null;\n appMockState.user = null;\n vi.restoreAllMocks();\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", previousActEnvironment);\n });\n\n it(\"redirects signed-out users to sign-in\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({ app, user: null });\n\n expect(app.redirectToSignIn).toHaveBeenCalled();\n expect(container.textContent).toBe(\"\");\n });\n\n it(\"redirects anonymous users to sign-in\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({ app, user: createUserTestDouble({ isAnonymous: true, restrictedReason: { type: \"anonymous\" } }) });\n\n expect(app.redirectToSignIn).toHaveBeenCalled();\n expect(container.textContent).toBe(\"\");\n });\n\n it(\"redirects users that are no longer restricted to their destination\", async () => {\n const app = createAppTestDouble();\n\n await renderOnboarding({ app, user: createUserTestDouble({ isRestricted: false }) });\n\n expect(app.redirectToAfterSignIn).toHaveBeenCalled();\n expect(app.redirectToSignIn).not.toHaveBeenCalled();\n });\n\n it(\"shows the administrator's public reason to admin-restricted users\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n user: createUserTestDouble({\n restrictedReason: { type: \"restricted_by_administrator\" },\n restrictedByAdmin: true,\n restrictedByAdminReason: \"Your sign-up was flagged by our fraud prevention rules.\",\n }),\n });\n\n expect(container.textContent).toContain(\"Your account has been restricted\");\n expect(container.textContent).toContain(\"Your sign-up was flagged by our fraud prevention rules.\");\n expect(container.textContent).not.toContain(\"Complete your account setup\");\n });\n\n it(\"falls back to a generic restricted message when no public reason is set\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n user: createUserTestDouble({\n restrictedReason: { type: \"restricted_by_administrator\" },\n restrictedByAdmin: true,\n restrictedByAdminReason: null,\n }),\n });\n\n expect(container.textContent).toContain(\"Your account has been restricted\");\n expect(container.textContent).toContain(\"An administrator has restricted your account.\");\n });\n\n it(\"shows the setup-required message for unknown restricted reasons\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n // The reason types are a closed union, so an unknown reason can only come from a newer backend.\n user: createUserTestDouble({ restrictedReason: { type: \"some_future_reason\" } as unknown as CurrentUser[\"restrictedReason\"] }),\n });\n\n expect(container.textContent).toContain(\"Complete your account setup\");\n });\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"onboarding.test.js","names":[],"sources":["../../../src/components-page/onboarding.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 React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { CurrentUser } from \"../lib/hexclave-app\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { TranslationProviderClient } from \"../providers/translation-provider-client\";\nimport { Onboarding } from \"./onboarding\";\n\nconst appMockState = vi.hoisted(() => ({\n app: null as unknown,\n user: null as unknown,\n}));\n\nvi.mock(\"..\", () => ({\n useStackApp: () => {\n if (appMockState.app == null) {\n throw new Error(\"Expected test app to be set before rendering.\");\n }\n return appMockState.app;\n },\n useUser: () => appMockState.user,\n}));\n\nvi.mock(\"@hexclave/ui\", async () => {\n const React = await import(\"react\");\n return {\n Button: (props: { children: React.ReactNode, onClick?: () => void }) => (\n <button type=\"button\" onClick={props.onClick}>{props.children}</button>\n ),\n Input: (props: Record<string, unknown>) => <input {...props} />,\n Typography: (props: { children: React.ReactNode }) => <div>{props.children}</div>,\n cn: (...classes: (string | false | null | undefined)[]) => classes.filter(Boolean).join(\" \"),\n };\n});\n\nconst previousActEnvironment = Reflect.get(globalThis, \"IS_REACT_ACT_ENVIRONMENT\");\n\nfunction createAppTestDouble() {\n const app = {\n redirectToSignIn: vi.fn(async () => {}),\n redirectToAfterSignIn: vi.fn(async () => {}),\n isExternalAuthApp: vi.fn(() => false),\n };\n\n // This test double intentionally implements only the StackClientApp surface that Onboarding touches.\n return app as unknown as StackClientApp<true> & {\n redirectToSignIn: ReturnType<typeof vi.fn>,\n redirectToAfterSignIn: ReturnType<typeof vi.fn>,\n };\n}\n\nfunction createUserTestDouble(user: Partial<CurrentUser>) {\n // Same as the app test double: only the surface Onboarding touches.\n return {\n isAnonymous: false,\n isRestricted: true,\n restrictedReason: null,\n restrictedByAdmin: false,\n restrictedByAdminReason: null,\n primaryEmail: \"user@example.com\",\n signOut: vi.fn(async () => {}),\n ...user,\n } as unknown as CurrentUser;\n}\n\nlet root: Root | null = null;\nlet container: HTMLDivElement | null = null;\n\nasync function renderOnboarding(options: {\n app: StackClientApp<true>,\n user: unknown,\n}) {\n appMockState.app = options.app;\n appMockState.user = options.user;\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n await act(async () => {\n root?.render(\n <TranslationProviderClient quetzalKeys={new Map()} quetzalLocale={new Map()}>\n <Onboarding />\n </TranslationProviderClient>\n );\n });\n return container;\n}\n\ndescribe(\"Onboarding\", () => {\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 appMockState.app = null;\n appMockState.user = null;\n vi.restoreAllMocks();\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", previousActEnvironment);\n });\n\n it(\"redirects signed-out users to sign-in\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({ app, user: null });\n\n expect(app.redirectToSignIn).toHaveBeenCalled();\n expect(container.textContent).toBe(\"\");\n });\n\n it(\"redirects anonymous users to sign-in\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({ app, user: createUserTestDouble({ isAnonymous: true, restrictedReason: { type: \"anonymous\" } }) });\n\n expect(app.redirectToSignIn).toHaveBeenCalled();\n expect(container.textContent).toBe(\"\");\n });\n\n it(\"redirects users that are no longer restricted to their destination\", async () => {\n const app = createAppTestDouble();\n\n await renderOnboarding({ app, user: createUserTestDouble({ isRestricted: false }) });\n\n expect(app.redirectToAfterSignIn).toHaveBeenCalled();\n expect(app.redirectToSignIn).not.toHaveBeenCalled();\n });\n\n it(\"shows the administrator's public reason to admin-restricted users\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n user: createUserTestDouble({\n restrictedReason: { type: \"restricted_by_administrator\" },\n restrictedByAdmin: true,\n restrictedByAdminReason: \"Your sign-up was flagged by our fraud prevention rules.\",\n }),\n });\n\n expect(container.textContent).toContain(\"Your account has been restricted\");\n expect(container.textContent).toContain(\"Your sign-up was flagged by our fraud prevention rules.\");\n expect(container.textContent).not.toContain(\"Complete your account setup\");\n });\n\n it(\"falls back to a generic restricted message when no public reason is set\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n user: createUserTestDouble({\n restrictedReason: { type: \"restricted_by_administrator\" },\n restrictedByAdmin: true,\n restrictedByAdminReason: null,\n }),\n });\n\n expect(container.textContent).toContain(\"Your account has been restricted\");\n expect(container.textContent).toContain(\"An administrator has restricted your account.\");\n });\n\n it(\"shows the setup-required message for unknown restricted reasons\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n // The reason types are a closed union, so an unknown reason can only come from a newer backend.\n user: createUserTestDouble({ restrictedReason: { type: \"some_future_reason\" } as unknown as CurrentUser[\"restrictedReason\"] }),\n });\n\n expect(container.textContent).toContain(\"Complete your account setup\");\n });\n});\n"],"mappings":"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"}