@stackframe/react 2.7.20

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 (316) hide show
  1. package/CHANGELOG.md +1415 -0
  2. package/LICENSE +7 -0
  3. package/README.md +26 -0
  4. package/dist/components/credential-sign-in.d.mts +5 -0
  5. package/dist/components/credential-sign-in.d.ts +5 -0
  6. package/dist/components/credential-sign-in.js +103 -0
  7. package/dist/components/credential-sign-in.js.map +1 -0
  8. package/dist/components/credential-sign-up.d.mts +7 -0
  9. package/dist/components/credential-sign-up.d.ts +7 -0
  10. package/dist/components/credential-sign-up.js +138 -0
  11. package/dist/components/credential-sign-up.js.map +1 -0
  12. package/dist/components/elements/form-warning.d.mts +7 -0
  13. package/dist/components/elements/form-warning.d.ts +7 -0
  14. package/dist/components/elements/form-warning.js +39 -0
  15. package/dist/components/elements/form-warning.js.map +1 -0
  16. package/dist/components/elements/maybe-full-page.d.mts +11 -0
  17. package/dist/components/elements/maybe-full-page.d.ts +11 -0
  18. package/dist/components/elements/maybe-full-page.js +74 -0
  19. package/dist/components/elements/maybe-full-page.js.map +1 -0
  20. package/dist/components/elements/separator-with-text.d.mts +7 -0
  21. package/dist/components/elements/separator-with-text.d.ts +7 -0
  22. package/dist/components/elements/separator-with-text.js +41 -0
  23. package/dist/components/elements/separator-with-text.js.map +1 -0
  24. package/dist/components/elements/sidebar-layout.d.mts +19 -0
  25. package/dist/components/elements/sidebar-layout.d.ts +19 -0
  26. package/dist/components/elements/sidebar-layout.js +126 -0
  27. package/dist/components/elements/sidebar-layout.js.map +1 -0
  28. package/dist/components/elements/ssr-layout-effect.d.mts +8 -0
  29. package/dist/components/elements/ssr-layout-effect.d.ts +8 -0
  30. package/dist/components/elements/ssr-layout-effect.js +47 -0
  31. package/dist/components/elements/ssr-layout-effect.js.map +1 -0
  32. package/dist/components/elements/user-avatar.d.mts +13 -0
  33. package/dist/components/elements/user-avatar.d.ts +13 -0
  34. package/dist/components/elements/user-avatar.js +41 -0
  35. package/dist/components/elements/user-avatar.js.map +1 -0
  36. package/dist/components/iframe-preventer.d.mts +8 -0
  37. package/dist/components/iframe-preventer.d.ts +8 -0
  38. package/dist/components/iframe-preventer.js +52 -0
  39. package/dist/components/iframe-preventer.js.map +1 -0
  40. package/dist/components/link.d.mts +14 -0
  41. package/dist/components/link.d.ts +14 -0
  42. package/dist/components/link.js +51 -0
  43. package/dist/components/link.js.map +1 -0
  44. package/dist/components/magic-link-sign-in.d.mts +5 -0
  45. package/dist/components/magic-link-sign-in.d.ts +5 -0
  46. package/dist/components/magic-link-sign-in.js +151 -0
  47. package/dist/components/magic-link-sign-in.js.map +1 -0
  48. package/dist/components/message-cards/known-error-message-card.d.mts +9 -0
  49. package/dist/components/message-cards/known-error-message-card.d.ts +9 -0
  50. package/dist/components/message-cards/known-error-message-card.js +61 -0
  51. package/dist/components/message-cards/known-error-message-card.js.map +1 -0
  52. package/dist/components/message-cards/message-card.d.mts +14 -0
  53. package/dist/components/message-cards/message-card.d.ts +14 -0
  54. package/dist/components/message-cards/message-card.js +45 -0
  55. package/dist/components/message-cards/message-card.js.map +1 -0
  56. package/dist/components/message-cards/predefined-message-card.d.mts +8 -0
  57. package/dist/components/message-cards/predefined-message-card.d.ts +8 -0
  58. package/dist/components/message-cards/predefined-message-card.js +107 -0
  59. package/dist/components/message-cards/predefined-message-card.js.map +1 -0
  60. package/dist/components/oauth-button-group.d.mts +14 -0
  61. package/dist/components/oauth-button-group.d.ts +14 -0
  62. package/dist/components/oauth-button-group.js +43 -0
  63. package/dist/components/oauth-button-group.js.map +1 -0
  64. package/dist/components/oauth-button.d.mts +8 -0
  65. package/dist/components/oauth-button.d.ts +8 -0
  66. package/dist/components/oauth-button.js +210 -0
  67. package/dist/components/oauth-button.js.map +1 -0
  68. package/dist/components/passkey-button.d.mts +7 -0
  69. package/dist/components/passkey-button.d.ts +7 -0
  70. package/dist/components/passkey-button.js +58 -0
  71. package/dist/components/passkey-button.js.map +1 -0
  72. package/dist/components/profile-image-editor.d.mts +11 -0
  73. package/dist/components/profile-image-editor.d.ts +11 -0
  74. package/dist/components/profile-image-editor.js +162 -0
  75. package/dist/components/profile-image-editor.js.map +1 -0
  76. package/dist/components/selected-team-switcher.d.mts +21 -0
  77. package/dist/components/selected-team-switcher.d.ts +21 -0
  78. package/dist/components/selected-team-switcher.js +119 -0
  79. package/dist/components/selected-team-switcher.js.map +1 -0
  80. package/dist/components/team-icon.d.mts +18 -0
  81. package/dist/components/team-icon.d.ts +18 -0
  82. package/dist/components/team-icon.js +39 -0
  83. package/dist/components/team-icon.js.map +1 -0
  84. package/dist/components/user-button.d.mts +15 -0
  85. package/dist/components/user-button.d.ts +15 -0
  86. package/dist/components/user-button.js +120 -0
  87. package/dist/components/user-button.js.map +1 -0
  88. package/dist/components-page/account-settings.d.mts +24 -0
  89. package/dist/components-page/account-settings.d.ts +24 -0
  90. package/dist/components-page/account-settings.js +1095 -0
  91. package/dist/components-page/account-settings.js.map +1 -0
  92. package/dist/components-page/auth-page.d.mts +24 -0
  93. package/dist/components-page/auth-page.d.ts +24 -0
  94. package/dist/components-page/auth-page.js +123 -0
  95. package/dist/components-page/auth-page.js.map +1 -0
  96. package/dist/components-page/email-verification.d.mts +8 -0
  97. package/dist/components-page/email-verification.d.ts +8 -0
  98. package/dist/components-page/email-verification.js +100 -0
  99. package/dist/components-page/email-verification.js.map +1 -0
  100. package/dist/components-page/error-page.d.mts +8 -0
  101. package/dist/components-page/error-page.d.ts +8 -0
  102. package/dist/components-page/error-page.js +97 -0
  103. package/dist/components-page/error-page.js.map +1 -0
  104. package/dist/components-page/forgot-password.d.mts +10 -0
  105. package/dist/components-page/forgot-password.d.ts +10 -0
  106. package/dist/components-page/forgot-password.js +117 -0
  107. package/dist/components-page/forgot-password.js.map +1 -0
  108. package/dist/components-page/magic-link-callback.d.mts +8 -0
  109. package/dist/components-page/magic-link-callback.d.ts +8 -0
  110. package/dist/components-page/magic-link-callback.js +110 -0
  111. package/dist/components-page/magic-link-callback.js.map +1 -0
  112. package/dist/components-page/oauth-callback.d.mts +7 -0
  113. package/dist/components-page/oauth-callback.d.ts +7 -0
  114. package/dist/components-page/oauth-callback.js +75 -0
  115. package/dist/components-page/oauth-callback.js.map +1 -0
  116. package/dist/components-page/password-reset.d.mts +12 -0
  117. package/dist/components-page/password-reset.d.ts +12 -0
  118. package/dist/components-page/password-reset.js +179 -0
  119. package/dist/components-page/password-reset.js.map +1 -0
  120. package/dist/components-page/sign-in.d.mts +10 -0
  121. package/dist/components-page/sign-in.d.ts +10 -0
  122. package/dist/components-page/sign-in.js +44 -0
  123. package/dist/components-page/sign-in.js.map +1 -0
  124. package/dist/components-page/sign-out.d.mts +7 -0
  125. package/dist/components-page/sign-out.d.ts +7 -0
  126. package/dist/components-page/sign-out.js +57 -0
  127. package/dist/components-page/sign-out.js.map +1 -0
  128. package/dist/components-page/sign-up.d.mts +11 -0
  129. package/dist/components-page/sign-up.d.ts +11 -0
  130. package/dist/components-page/sign-up.js +47 -0
  131. package/dist/components-page/sign-up.js.map +1 -0
  132. package/dist/components-page/stack-handler.d.mts +51 -0
  133. package/dist/components-page/stack-handler.d.ts +51 -0
  134. package/dist/components-page/stack-handler.js +244 -0
  135. package/dist/components-page/stack-handler.js.map +1 -0
  136. package/dist/components-page/team-creation.d.mts +7 -0
  137. package/dist/components-page/team-creation.d.ts +7 -0
  138. package/dist/components-page/team-creation.js +92 -0
  139. package/dist/components-page/team-creation.js.map +1 -0
  140. package/dist/components-page/team-invitation.d.mts +8 -0
  141. package/dist/components-page/team-invitation.d.ts +8 -0
  142. package/dist/components-page/team-invitation.js +144 -0
  143. package/dist/components-page/team-invitation.js.map +1 -0
  144. package/dist/esm/components/credential-sign-in.js +79 -0
  145. package/dist/esm/components/credential-sign-in.js.map +1 -0
  146. package/dist/esm/components/credential-sign-up.js +104 -0
  147. package/dist/esm/components/credential-sign-up.js.map +1 -0
  148. package/dist/esm/components/elements/form-warning.js +15 -0
  149. package/dist/esm/components/elements/form-warning.js.map +1 -0
  150. package/dist/esm/components/elements/maybe-full-page.js +50 -0
  151. package/dist/esm/components/elements/maybe-full-page.js.map +1 -0
  152. package/dist/esm/components/elements/separator-with-text.js +17 -0
  153. package/dist/esm/components/elements/separator-with-text.js.map +1 -0
  154. package/dist/esm/components/elements/sidebar-layout.js +102 -0
  155. package/dist/esm/components/elements/sidebar-layout.js.map +1 -0
  156. package/dist/esm/components/elements/ssr-layout-effect.js +23 -0
  157. package/dist/esm/components/elements/ssr-layout-effect.js.map +1 -0
  158. package/dist/esm/components/elements/user-avatar.js +16 -0
  159. package/dist/esm/components/elements/user-avatar.js.map +1 -0
  160. package/dist/esm/components/iframe-preventer.js +28 -0
  161. package/dist/esm/components/iframe-preventer.js.map +1 -0
  162. package/dist/esm/components/link.js +26 -0
  163. package/dist/esm/components/link.js.map +1 -0
  164. package/dist/esm/components/magic-link-sign-in.js +127 -0
  165. package/dist/esm/components/magic-link-sign-in.js.map +1 -0
  166. package/dist/esm/components/message-cards/known-error-message-card.js +37 -0
  167. package/dist/esm/components/message-cards/known-error-message-card.js.map +1 -0
  168. package/dist/esm/components/message-cards/message-card.js +21 -0
  169. package/dist/esm/components/message-cards/message-card.js.map +1 -0
  170. package/dist/esm/components/message-cards/predefined-message-card.js +83 -0
  171. package/dist/esm/components/message-cards/predefined-message-card.js.map +1 -0
  172. package/dist/esm/components/oauth-button-group.js +19 -0
  173. package/dist/esm/components/oauth-button-group.js.map +1 -0
  174. package/dist/esm/components/oauth-button.js +176 -0
  175. package/dist/esm/components/oauth-button.js.map +1 -0
  176. package/dist/esm/components/passkey-button.js +34 -0
  177. package/dist/esm/components/passkey-button.js.map +1 -0
  178. package/dist/esm/components/profile-image-editor.js +126 -0
  179. package/dist/esm/components/profile-image-editor.js.map +1 -0
  180. package/dist/esm/components/selected-team-switcher.js +107 -0
  181. package/dist/esm/components/selected-team-switcher.js.map +1 -0
  182. package/dist/esm/components/team-icon.js +14 -0
  183. package/dist/esm/components/team-icon.js.map +1 -0
  184. package/dist/esm/components/user-button.js +96 -0
  185. package/dist/esm/components/user-button.js.map +1 -0
  186. package/dist/esm/components-page/account-settings.js +1058 -0
  187. package/dist/esm/components-page/account-settings.js.map +1 -0
  188. package/dist/esm/components-page/auth-page.js +99 -0
  189. package/dist/esm/components-page/auth-page.js.map +1 -0
  190. package/dist/esm/components-page/email-verification.js +66 -0
  191. package/dist/esm/components-page/email-verification.js.map +1 -0
  192. package/dist/esm/components-page/error-page.js +73 -0
  193. package/dist/esm/components-page/error-page.js.map +1 -0
  194. package/dist/esm/components-page/forgot-password.js +92 -0
  195. package/dist/esm/components-page/forgot-password.js.map +1 -0
  196. package/dist/esm/components-page/magic-link-callback.js +76 -0
  197. package/dist/esm/components-page/magic-link-callback.js.map +1 -0
  198. package/dist/esm/components-page/oauth-callback.js +51 -0
  199. package/dist/esm/components-page/oauth-callback.js.map +1 -0
  200. package/dist/esm/components-page/password-reset.js +145 -0
  201. package/dist/esm/components-page/password-reset.js.map +1 -0
  202. package/dist/esm/components-page/sign-in.js +19 -0
  203. package/dist/esm/components-page/sign-in.js.map +1 -0
  204. package/dist/esm/components-page/sign-out.js +23 -0
  205. package/dist/esm/components-page/sign-out.js.map +1 -0
  206. package/dist/esm/components-page/sign-up.js +23 -0
  207. package/dist/esm/components-page/sign-up.js.map +1 -0
  208. package/dist/esm/components-page/stack-handler.js +223 -0
  209. package/dist/esm/components-page/stack-handler.js.map +1 -0
  210. package/dist/esm/components-page/team-creation.js +68 -0
  211. package/dist/esm/components-page/team-creation.js.map +1 -0
  212. package/dist/esm/components-page/team-invitation.js +110 -0
  213. package/dist/esm/components-page/team-invitation.js.map +1 -0
  214. package/dist/esm/generated/global-css.js +6 -0
  215. package/dist/esm/generated/global-css.js.map +1 -0
  216. package/dist/esm/generated/quetzal-translations.js +2397 -0
  217. package/dist/esm/generated/quetzal-translations.js.map +1 -0
  218. package/dist/esm/global.d.js +1 -0
  219. package/dist/esm/global.d.js.map +1 -0
  220. package/dist/esm/index.js +46 -0
  221. package/dist/esm/index.js.map +1 -0
  222. package/dist/esm/lib/auth.js +98 -0
  223. package/dist/esm/lib/auth.js.map +1 -0
  224. package/dist/esm/lib/cookie.js +244 -0
  225. package/dist/esm/lib/cookie.js.map +1 -0
  226. package/dist/esm/lib/hooks.js +30 -0
  227. package/dist/esm/lib/hooks.js.map +1 -0
  228. package/dist/esm/lib/stack-app.js +2398 -0
  229. package/dist/esm/lib/stack-app.js.map +1 -0
  230. package/dist/esm/lib/translations.js +23 -0
  231. package/dist/esm/lib/translations.js.map +1 -0
  232. package/dist/esm/providers/stack-provider-client.js +29 -0
  233. package/dist/esm/providers/stack-provider-client.js.map +1 -0
  234. package/dist/esm/providers/stack-provider.js +25 -0
  235. package/dist/esm/providers/stack-provider.js.map +1 -0
  236. package/dist/esm/providers/theme-provider.js +71 -0
  237. package/dist/esm/providers/theme-provider.js.map +1 -0
  238. package/dist/esm/providers/translation-provider-client.js +18 -0
  239. package/dist/esm/providers/translation-provider-client.js.map +1 -0
  240. package/dist/esm/providers/translation-provider.js +18 -0
  241. package/dist/esm/providers/translation-provider.js.map +1 -0
  242. package/dist/esm/utils/browser-script.js +112 -0
  243. package/dist/esm/utils/browser-script.js.map +1 -0
  244. package/dist/esm/utils/constants.js +66 -0
  245. package/dist/esm/utils/constants.js.map +1 -0
  246. package/dist/esm/utils/url.js +30 -0
  247. package/dist/esm/utils/url.js.map +1 -0
  248. package/dist/generated/global-css.d.mts +3 -0
  249. package/dist/generated/global-css.d.ts +3 -0
  250. package/dist/generated/global-css.js +31 -0
  251. package/dist/generated/global-css.js.map +1 -0
  252. package/dist/generated/quetzal-translations.d.mts +4 -0
  253. package/dist/generated/quetzal-translations.d.ts +4 -0
  254. package/dist/generated/quetzal-translations.js +2423 -0
  255. package/dist/generated/quetzal-translations.js.map +1 -0
  256. package/dist/global.d.d.mts +2 -0
  257. package/dist/global.d.d.ts +2 -0
  258. package/dist/global.d.js +2 -0
  259. package/dist/global.d.js.map +1 -0
  260. package/dist/index.d.mts +41 -0
  261. package/dist/index.d.ts +41 -0
  262. package/dist/index.js +103 -0
  263. package/dist/index.js.map +1 -0
  264. package/dist/lib/auth.d.mts +33 -0
  265. package/dist/lib/auth.d.ts +33 -0
  266. package/dist/lib/auth.js +125 -0
  267. package/dist/lib/auth.js.map +1 -0
  268. package/dist/lib/cookie.d.mts +33 -0
  269. package/dist/lib/cookie.d.ts +33 -0
  270. package/dist/lib/cookie.js +291 -0
  271. package/dist/lib/cookie.js.map +1 -0
  272. package/dist/lib/hooks.d.mts +41 -0
  273. package/dist/lib/hooks.d.ts +41 -0
  274. package/dist/lib/hooks.js +56 -0
  275. package/dist/lib/hooks.js.map +1 -0
  276. package/dist/lib/stack-app.d.mts +775 -0
  277. package/dist/lib/stack-app.d.ts +775 -0
  278. package/dist/lib/stack-app.js +2438 -0
  279. package/dist/lib/stack-app.js.map +1 -0
  280. package/dist/lib/translations.d.mts +5 -0
  281. package/dist/lib/translations.d.ts +5 -0
  282. package/dist/lib/translations.js +58 -0
  283. package/dist/lib/translations.js.map +1 -0
  284. package/dist/providers/stack-provider-client.d.mts +27 -0
  285. package/dist/providers/stack-provider-client.d.ts +27 -0
  286. package/dist/providers/stack-provider-client.js +65 -0
  287. package/dist/providers/stack-provider-client.js.map +1 -0
  288. package/dist/providers/stack-provider.d.mts +30 -0
  289. package/dist/providers/stack-provider.d.ts +30 -0
  290. package/dist/providers/stack-provider.js +46 -0
  291. package/dist/providers/stack-provider.js.map +1 -0
  292. package/dist/providers/theme-provider.d.mts +40 -0
  293. package/dist/providers/theme-provider.d.ts +40 -0
  294. package/dist/providers/theme-provider.js +105 -0
  295. package/dist/providers/theme-provider.js.map +1 -0
  296. package/dist/providers/translation-provider-client.d.mts +14 -0
  297. package/dist/providers/translation-provider-client.d.ts +14 -0
  298. package/dist/providers/translation-provider-client.js +43 -0
  299. package/dist/providers/translation-provider-client.js.map +1 -0
  300. package/dist/providers/translation-provider.d.mts +10 -0
  301. package/dist/providers/translation-provider.d.ts +10 -0
  302. package/dist/providers/translation-provider.js +43 -0
  303. package/dist/providers/translation-provider.js.map +1 -0
  304. package/dist/utils/browser-script.d.mts +7 -0
  305. package/dist/utils/browser-script.d.ts +7 -0
  306. package/dist/utils/browser-script.js +137 -0
  307. package/dist/utils/browser-script.js.map +1 -0
  308. package/dist/utils/constants.d.mts +79 -0
  309. package/dist/utils/constants.d.ts +79 -0
  310. package/dist/utils/constants.js +99 -0
  311. package/dist/utils/constants.js.map +1 -0
  312. package/dist/utils/url.d.mts +4 -0
  313. package/dist/utils/url.d.ts +4 -0
  314. package/dist/utils/url.js +56 -0
  315. package/dist/utils/url.js.map +1 -0
  316. package/package.json +96 -0
@@ -0,0 +1,1058 @@
1
+ "use client";
2
+ "use client";
3
+
4
+ // src/components-page/account-settings.tsx
5
+ import { yupResolver } from "@hookform/resolvers/yup";
6
+ import { KnownErrors } from "@stackframe/stack-shared";
7
+ import { getPasswordError } from "@stackframe/stack-shared/dist/helpers/password";
8
+ import { useAsyncCallback } from "@stackframe/stack-shared/dist/hooks/use-async-callback";
9
+ import { passwordSchema as schemaFieldsPasswordSchema, strictEmailSchema, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
10
+ import { generateRandomValues } from "@stackframe/stack-shared/dist/utils/crypto";
11
+ import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
12
+ import { runAsynchronously, runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
13
+ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActionCell, Badge, Button, Input, Label, PasswordInput, Separator, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
14
+ import { Edit, Trash, icons } from "lucide-react";
15
+ import { TOTPController, createTOTPKeyURI } from "oslo/otp";
16
+ import * as QRCode from "qrcode";
17
+ import React, { Suspense, useEffect, useState } from "react";
18
+ import { useForm } from "react-hook-form";
19
+ import * as yup from "yup";
20
+ import { MessageCard, useStackApp, useUser } from "..";
21
+ import { FormWarningText } from "../components/elements/form-warning";
22
+ import { MaybeFullPage } from "../components/elements/maybe-full-page";
23
+ import { SidebarLayout } from "../components/elements/sidebar-layout";
24
+ import { UserAvatar } from "../components/elements/user-avatar";
25
+ import { ProfileImageEditor } from "../components/profile-image-editor";
26
+ import { TeamIcon } from "../components/team-icon";
27
+ import { useTranslation } from "../lib/translations";
28
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
29
+ var Icon = ({ name }) => {
30
+ const LucideIcon = icons[name];
31
+ return /* @__PURE__ */ jsx(LucideIcon, { className: "mr-2 h-4 w-4" });
32
+ };
33
+ function AccountSettings(props) {
34
+ const { t } = useTranslation();
35
+ const user = useUser({ or: "redirect" });
36
+ const teams = user.useTeams();
37
+ const stackApp = useStackApp();
38
+ const project = stackApp.useProject();
39
+ return /* @__PURE__ */ jsx(MaybeFullPage, { fullPage: !!props.fullPage, children: /* @__PURE__ */ jsx("div", { className: "self-stretch flex-grow w-full", children: /* @__PURE__ */ jsx(
40
+ SidebarLayout,
41
+ {
42
+ items: [
43
+ {
44
+ title: t("My Profile"),
45
+ type: "item",
46
+ id: "profile",
47
+ icon: /* @__PURE__ */ jsx(Icon, { name: "Contact" }),
48
+ content: /* @__PURE__ */ jsx(ProfilePage, {})
49
+ },
50
+ {
51
+ title: t("Emails & Auth"),
52
+ type: "item",
53
+ id: "auth",
54
+ icon: /* @__PURE__ */ jsx(Icon, { name: "ShieldCheck" }),
55
+ content: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(EmailsAndAuthPageSkeleton, {}), children: /* @__PURE__ */ jsx(EmailsAndAuthPage, {}) })
56
+ },
57
+ {
58
+ title: t("Settings"),
59
+ type: "item",
60
+ id: "settings",
61
+ icon: /* @__PURE__ */ jsx(Icon, { name: "Settings" }),
62
+ content: /* @__PURE__ */ jsx(SettingsPage, {})
63
+ },
64
+ ...props.extraItems?.map((item) => ({
65
+ title: item.title,
66
+ type: "item",
67
+ id: item.id,
68
+ icon: (() => {
69
+ const iconName = item.iconName;
70
+ if (iconName) {
71
+ return /* @__PURE__ */ jsx(Icon, { name: iconName });
72
+ } else if (item.icon) {
73
+ return item.icon;
74
+ }
75
+ return null;
76
+ })(),
77
+ content: item.content
78
+ })) || [],
79
+ ...teams.length > 0 || project.config.clientTeamCreationEnabled ? [{
80
+ title: t("Teams"),
81
+ type: "divider"
82
+ }] : [],
83
+ ...teams.map((team) => ({
84
+ title: /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center w-full", children: [
85
+ /* @__PURE__ */ jsx(TeamIcon, { team }),
86
+ /* @__PURE__ */ jsx(Typography, { className: "max-w-[320px] md:w-[90%] truncate", children: team.displayName })
87
+ ] }),
88
+ type: "item",
89
+ id: `team-${team.id}`,
90
+ content: /* @__PURE__ */ jsx(TeamPage, { team })
91
+ })),
92
+ ...project.config.clientTeamCreationEnabled ? [{
93
+ title: t("Create a team"),
94
+ icon: /* @__PURE__ */ jsx(Icon, { name: "CirclePlus" }),
95
+ type: "item",
96
+ id: "team-creation",
97
+ content: /* @__PURE__ */ jsx(TeamCreation, {})
98
+ }] : []
99
+ ].filter((p) => p.type === "divider" || p.content),
100
+ title: t("Account Settings")
101
+ }
102
+ ) }) });
103
+ }
104
+ function Section(props) {
105
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-2", children: [
106
+ /* @__PURE__ */ jsxs("div", { className: "sm:flex-1 flex flex-col justify-center", children: [
107
+ /* @__PURE__ */ jsx(Typography, { className: "font-medium", children: props.title }),
108
+ props.description && /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "footnote", children: props.description })
109
+ ] }),
110
+ /* @__PURE__ */ jsx("div", { className: "sm:flex-1 sm:items-end flex flex-col gap-2 ", children: props.children })
111
+ ] });
112
+ }
113
+ function PageLayout(props) {
114
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6", children: [
115
+ /* @__PURE__ */ jsx(Separator, {}),
116
+ React.Children.map(props.children, (child) => child && /* @__PURE__ */ jsxs(Fragment, { children: [
117
+ child,
118
+ /* @__PURE__ */ jsx(Separator, {})
119
+ ] }))
120
+ ] });
121
+ }
122
+ function ProfilePage() {
123
+ const { t } = useTranslation();
124
+ const user = useUser({ or: "redirect" });
125
+ return /* @__PURE__ */ jsxs(PageLayout, { children: [
126
+ /* @__PURE__ */ jsx(
127
+ Section,
128
+ {
129
+ title: t("User name"),
130
+ description: t("This is a display name and is not used for authentication"),
131
+ children: /* @__PURE__ */ jsx(
132
+ EditableText,
133
+ {
134
+ value: user.displayName || "",
135
+ onSave: async (newDisplayName) => {
136
+ await user.update({ displayName: newDisplayName });
137
+ }
138
+ }
139
+ )
140
+ }
141
+ ),
142
+ /* @__PURE__ */ jsx(
143
+ Section,
144
+ {
145
+ title: t("Profile image"),
146
+ description: t("Upload your own image as your avatar"),
147
+ children: /* @__PURE__ */ jsx(
148
+ ProfileImageEditor,
149
+ {
150
+ user,
151
+ onProfileImageUrlChange: async (profileImageUrl) => {
152
+ await user.update({ profileImageUrl });
153
+ }
154
+ }
155
+ )
156
+ }
157
+ )
158
+ ] });
159
+ }
160
+ function EmailsSection() {
161
+ const { t } = useTranslation();
162
+ const user = useUser({ or: "redirect" });
163
+ const contactChannels = user.useContactChannels();
164
+ const [addingEmail, setAddingEmail] = useState(contactChannels.length === 0);
165
+ const [addingEmailLoading, setAddingEmailLoading] = useState(false);
166
+ const [addedEmail, setAddedEmail] = useState(null);
167
+ const isLastEmail = contactChannels.filter((x) => x.usedForAuth && x.type === "email").length === 1;
168
+ useEffect(() => {
169
+ if (addedEmail) {
170
+ runAsynchronously(async () => {
171
+ const cc = contactChannels.find((x) => x.value === addedEmail);
172
+ if (cc && !cc.isVerified) {
173
+ await cc.sendVerificationEmail();
174
+ }
175
+ setAddedEmail(null);
176
+ });
177
+ }
178
+ }, [contactChannels, addedEmail]);
179
+ const emailSchema = yupObject({
180
+ email: strictEmailSchema(t("Please enter a valid email address")).notOneOf(contactChannels.map((x) => x.value), t("Email already exists")).defined().nonEmpty(t("Email is required"))
181
+ });
182
+ const { register, handleSubmit, formState: { errors }, reset } = useForm({
183
+ resolver: yupResolver(emailSchema)
184
+ });
185
+ const onSubmit = async (data) => {
186
+ setAddingEmailLoading(true);
187
+ try {
188
+ await user.createContactChannel({ type: "email", value: data.email, usedForAuth: false });
189
+ setAddedEmail(data.email);
190
+ } finally {
191
+ setAddingEmailLoading(false);
192
+ }
193
+ setAddingEmail(false);
194
+ reset();
195
+ };
196
+ return /* @__PURE__ */ jsxs("div", { children: [
197
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row justify-between mb-4 gap-4", children: [
198
+ /* @__PURE__ */ jsx(Typography, { className: "font-medium", children: t("Emails") }),
199
+ addingEmail ? /* @__PURE__ */ jsxs(
200
+ "form",
201
+ {
202
+ onSubmit: (e) => {
203
+ e.preventDefault();
204
+ runAsynchronously(handleSubmit(onSubmit));
205
+ },
206
+ className: "flex flex-col",
207
+ children: [
208
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
209
+ /* @__PURE__ */ jsx(
210
+ Input,
211
+ {
212
+ ...register("email"),
213
+ placeholder: t("Enter email")
214
+ }
215
+ ),
216
+ /* @__PURE__ */ jsx(Button, { type: "submit", loading: addingEmailLoading, children: t("Add") }),
217
+ /* @__PURE__ */ jsx(
218
+ Button,
219
+ {
220
+ variant: "secondary",
221
+ onClick: () => {
222
+ setAddingEmail(false);
223
+ reset();
224
+ },
225
+ children: t("Cancel")
226
+ }
227
+ )
228
+ ] }),
229
+ errors.email && /* @__PURE__ */ jsx(FormWarningText, { text: errors.email.message })
230
+ ]
231
+ }
232
+ ) : /* @__PURE__ */ jsx("div", { className: "flex md:justify-end", children: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setAddingEmail(true), children: t("Add an email") }) })
233
+ ] }),
234
+ contactChannels.length > 0 ? /* @__PURE__ */ jsx("div", { className: "border rounded-md", children: /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsx(TableBody, { children: contactChannels.filter((x) => x.type === "email").sort((a, b) => {
235
+ if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1;
236
+ if (a.isVerified !== b.isVerified) return a.isVerified ? -1 : 1;
237
+ return 0;
238
+ }).map((x) => /* @__PURE__ */ jsxs(TableRow, { children: [
239
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row gap-2 md:gap-4", children: [
240
+ x.value,
241
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
242
+ x.isPrimary ? /* @__PURE__ */ jsx(Badge, { children: t("Primary") }) : null,
243
+ !x.isVerified ? /* @__PURE__ */ jsx(Badge, { variant: "destructive", children: t("Unverified") }) : null,
244
+ x.usedForAuth ? /* @__PURE__ */ jsx(Badge, { variant: "outline", children: t("Used for sign-in") }) : null
245
+ ] })
246
+ ] }) }),
247
+ /* @__PURE__ */ jsx(TableCell, { className: "flex justify-end", children: /* @__PURE__ */ jsx(ActionCell, { items: [
248
+ ...!x.isVerified ? [{
249
+ item: t("Send verification email"),
250
+ onClick: async () => {
251
+ await x.sendVerificationEmail();
252
+ }
253
+ }] : [],
254
+ ...!x.isPrimary && x.isVerified ? [{
255
+ item: t("Set as primary"),
256
+ onClick: async () => {
257
+ await x.update({ isPrimary: true });
258
+ }
259
+ }] : !x.isPrimary ? [{
260
+ item: t("Set as primary"),
261
+ onClick: async () => {
262
+ },
263
+ disabled: true,
264
+ disabledTooltip: t("Please verify your email first")
265
+ }] : [],
266
+ ...!x.usedForAuth && x.isVerified ? [{
267
+ item: t("Use for sign-in"),
268
+ onClick: async () => {
269
+ try {
270
+ await x.update({ usedForAuth: true });
271
+ } catch (e) {
272
+ if (e instanceof KnownErrors.ContactChannelAlreadyUsedForAuthBySomeoneElse) {
273
+ alert(t("This email is already used for sign-in by another user."));
274
+ }
275
+ }
276
+ }
277
+ }] : [],
278
+ ...x.usedForAuth && !isLastEmail ? [{
279
+ item: t("Stop using for sign-in"),
280
+ onClick: async () => {
281
+ await x.update({ usedForAuth: false });
282
+ }
283
+ }] : x.usedForAuth ? [{
284
+ item: t("Stop using for sign-in"),
285
+ onClick: async () => {
286
+ },
287
+ disabled: true,
288
+ disabledTooltip: t("You can not remove your last sign-in email")
289
+ }] : [],
290
+ ...!isLastEmail || !x.usedForAuth ? [{
291
+ item: t("Remove"),
292
+ onClick: async () => {
293
+ await x.delete();
294
+ },
295
+ danger: true
296
+ }] : [{
297
+ item: t("Remove"),
298
+ onClick: async () => {
299
+ },
300
+ disabled: true,
301
+ disabledTooltip: t("You can not remove your last sign-in email")
302
+ }]
303
+ ] }) })
304
+ ] }, x.id)) }) }) }) : null
305
+ ] });
306
+ }
307
+ function EmailsAndAuthPage() {
308
+ const passwordSection = usePasswordSection();
309
+ const mfaSection = useMfaSection();
310
+ const otpSection = useOtpSection();
311
+ const passkeySection = usePasskeySection();
312
+ return /* @__PURE__ */ jsxs(PageLayout, { children: [
313
+ /* @__PURE__ */ jsx(EmailsSection, {}),
314
+ passwordSection,
315
+ passkeySection,
316
+ otpSection,
317
+ mfaSection
318
+ ] });
319
+ }
320
+ function EmailsAndAuthPageSkeleton() {
321
+ return /* @__PURE__ */ jsxs(PageLayout, { children: [
322
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
323
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
324
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
325
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" })
326
+ ] });
327
+ }
328
+ function usePasskeySection() {
329
+ const { t } = useTranslation();
330
+ const user = useUser({ or: "throw" });
331
+ const stackApp = useStackApp();
332
+ const project = stackApp.useProject();
333
+ const contactChannels = user.useContactChannels();
334
+ const hasPasskey = user.passkeyAuthEnabled;
335
+ const isLastAuth = user.passkeyAuthEnabled && !user.hasPassword && user.oauthProviders.length === 0 && !user.otpAuthEnabled;
336
+ const [showConfirmationModal, setShowConfirmationModal] = useState(false);
337
+ const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
338
+ if (!project.config.passkeyEnabled) {
339
+ return null;
340
+ }
341
+ const handleDeletePasskey = async () => {
342
+ await user.update({ passkeyAuthEnabled: false });
343
+ setShowConfirmationModal(false);
344
+ };
345
+ const handleAddNewPasskey = async () => {
346
+ await user.registerPasskey();
347
+ };
348
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Section, { title: t("Passkey"), description: hasPasskey ? t("Passkey registered") : t("Register a passkey"), children: /* @__PURE__ */ jsxs("div", { className: "flex md:justify-end gap-2", children: [
349
+ !hasValidEmail && /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("To enable Passkey sign-in, please add a verified sign-in email.") }),
350
+ hasValidEmail && hasPasskey && isLastAuth && /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("Passkey sign-in is enabled and cannot be disabled as it is currently the only sign-in method") }),
351
+ !hasPasskey && hasValidEmail && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { onClick: handleAddNewPasskey, variant: "secondary", children: t("Add new passkey") }) }),
352
+ hasValidEmail && hasPasskey && !isLastAuth && !showConfirmationModal && /* @__PURE__ */ jsx(
353
+ Button,
354
+ {
355
+ variant: "secondary",
356
+ onClick: () => setShowConfirmationModal(true),
357
+ children: t("Delete Passkey")
358
+ }
359
+ ),
360
+ hasValidEmail && hasPasskey && !isLastAuth && showConfirmationModal && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
361
+ /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to disable Passkey sign-in? You will not be able to sign in with your passkey anymore.") }),
362
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
363
+ /* @__PURE__ */ jsx(
364
+ Button,
365
+ {
366
+ variant: "destructive",
367
+ onClick: handleDeletePasskey,
368
+ children: t("Disable")
369
+ }
370
+ ),
371
+ /* @__PURE__ */ jsx(
372
+ Button,
373
+ {
374
+ variant: "secondary",
375
+ onClick: () => setShowConfirmationModal(false),
376
+ children: t("Cancel")
377
+ }
378
+ )
379
+ ] })
380
+ ] })
381
+ ] }) }) });
382
+ }
383
+ function useOtpSection() {
384
+ const { t } = useTranslation();
385
+ const user = useUser({ or: "throw" });
386
+ const project = useStackApp().useProject();
387
+ const contactChannels = user.useContactChannels();
388
+ const isLastAuth = user.otpAuthEnabled && !user.hasPassword && user.oauthProviders.length === 0 && !user.passkeyAuthEnabled;
389
+ const [disabling, setDisabling] = useState(false);
390
+ const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
391
+ if (!project.config.magicLinkEnabled) {
392
+ return null;
393
+ }
394
+ const handleDisableOTP = async () => {
395
+ await user.update({ otpAuthEnabled: false });
396
+ setDisabling(false);
397
+ };
398
+ return /* @__PURE__ */ jsx(Section, { title: t("OTP sign-in"), description: user.otpAuthEnabled ? t("OTP/magic link sign-in is currently enabled.") : t("Enable sign-in via magic link or OTP sent to your sign-in emails."), children: /* @__PURE__ */ jsx("div", { className: "flex md:justify-end", children: hasValidEmail ? user.otpAuthEnabled ? !isLastAuth ? !disabling ? /* @__PURE__ */ jsx(
399
+ Button,
400
+ {
401
+ variant: "secondary",
402
+ onClick: () => setDisabling(true),
403
+ children: t("Disable OTP")
404
+ }
405
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
406
+ /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to disable OTP sign-in? You will not be able to sign in with only emails anymore.") }),
407
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
408
+ /* @__PURE__ */ jsx(
409
+ Button,
410
+ {
411
+ variant: "destructive",
412
+ onClick: handleDisableOTP,
413
+ children: t("Disable")
414
+ }
415
+ ),
416
+ /* @__PURE__ */ jsx(
417
+ Button,
418
+ {
419
+ variant: "secondary",
420
+ onClick: () => setDisabling(false),
421
+ children: t("Cancel")
422
+ }
423
+ )
424
+ ] })
425
+ ] }) : /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("OTP sign-in is enabled and cannot be disabled as it is currently the only sign-in method") }) : /* @__PURE__ */ jsx(
426
+ Button,
427
+ {
428
+ variant: "secondary",
429
+ onClick: async () => {
430
+ await user.update({ otpAuthEnabled: true });
431
+ },
432
+ children: t("Enable OTP")
433
+ }
434
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("To enable OTP sign-in, please add a verified sign-in email.") }) }) });
435
+ }
436
+ function SettingsPage() {
437
+ const deleteAccountSection = useDeleteAccountSection();
438
+ const signOutSection = useSignOutSection();
439
+ return /* @__PURE__ */ jsxs(PageLayout, { children: [
440
+ deleteAccountSection,
441
+ signOutSection
442
+ ] });
443
+ }
444
+ function usePasswordSection() {
445
+ const { t } = useTranslation();
446
+ const user = useUser({ or: "throw" });
447
+ const contactChannels = user.useContactChannels();
448
+ const [changingPassword, setChangingPassword] = useState(false);
449
+ const [loading, setLoading] = useState(false);
450
+ const passwordSchema = yupObject({
451
+ oldPassword: user.hasPassword ? schemaFieldsPasswordSchema.defined().nonEmpty(t("Please enter your old password")) : yupString(),
452
+ newPassword: schemaFieldsPasswordSchema.defined().nonEmpty(t("Please enter your password")).test({
453
+ name: "is-valid-password",
454
+ test: (value, ctx) => {
455
+ const error = getPasswordError(value);
456
+ if (error) {
457
+ return ctx.createError({ message: error.message });
458
+ } else {
459
+ return true;
460
+ }
461
+ }
462
+ }),
463
+ newPasswordRepeat: yupString().nullable().oneOf([yup.ref("newPassword"), "", null], t("Passwords do not match")).defined().nonEmpty(t("Please repeat your password"))
464
+ });
465
+ const { register, handleSubmit, setError, formState: { errors }, clearErrors, reset } = useForm({
466
+ resolver: yupResolver(passwordSchema)
467
+ });
468
+ const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.usedForAuth).length > 0;
469
+ const onSubmit = async (data) => {
470
+ setLoading(true);
471
+ try {
472
+ const { oldPassword, newPassword } = data;
473
+ const error = user.hasPassword ? await user.updatePassword({ oldPassword, newPassword }) : await user.setPassword({ password: newPassword });
474
+ if (error) {
475
+ setError("oldPassword", { type: "manual", message: t("Incorrect password") });
476
+ } else {
477
+ reset();
478
+ setChangingPassword(false);
479
+ }
480
+ } finally {
481
+ setLoading(false);
482
+ }
483
+ };
484
+ const registerPassword = register("newPassword");
485
+ const registerPasswordRepeat = register("newPasswordRepeat");
486
+ return /* @__PURE__ */ jsx(
487
+ Section,
488
+ {
489
+ title: t("Password"),
490
+ description: user.hasPassword ? t("Update your password") : t("Set a password for your account"),
491
+ children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4", children: !changingPassword ? hasValidEmail ? /* @__PURE__ */ jsx(
492
+ Button,
493
+ {
494
+ variant: "secondary",
495
+ onClick: () => setChangingPassword(true),
496
+ children: user.hasPassword ? t("Update password") : t("Set password")
497
+ }
498
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("To set a password, please add a sign-in email.") }) : /* @__PURE__ */ jsxs(
499
+ "form",
500
+ {
501
+ onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
502
+ noValidate: true,
503
+ children: [
504
+ user.hasPassword && /* @__PURE__ */ jsxs(Fragment, { children: [
505
+ /* @__PURE__ */ jsx(Label, { htmlFor: "old-password", className: "mb-1", children: t("Old password") }),
506
+ /* @__PURE__ */ jsx(
507
+ Input,
508
+ {
509
+ id: "old-password",
510
+ type: "password",
511
+ autoComplete: "current-password",
512
+ ...register("oldPassword")
513
+ }
514
+ ),
515
+ /* @__PURE__ */ jsx(FormWarningText, { text: errors.oldPassword?.message?.toString() })
516
+ ] }),
517
+ /* @__PURE__ */ jsx(Label, { htmlFor: "new-password", className: "mt-4 mb-1", children: t("New password") }),
518
+ /* @__PURE__ */ jsx(
519
+ PasswordInput,
520
+ {
521
+ id: "new-password",
522
+ autoComplete: "new-password",
523
+ ...registerPassword,
524
+ onChange: (e) => {
525
+ clearErrors("newPassword");
526
+ clearErrors("newPasswordRepeat");
527
+ runAsynchronously(registerPassword.onChange(e));
528
+ }
529
+ }
530
+ ),
531
+ /* @__PURE__ */ jsx(FormWarningText, { text: errors.newPassword?.message?.toString() }),
532
+ /* @__PURE__ */ jsx(Label, { htmlFor: "repeat-password", className: "mt-4 mb-1", children: t("Repeat new password") }),
533
+ /* @__PURE__ */ jsx(
534
+ PasswordInput,
535
+ {
536
+ id: "repeat-password",
537
+ autoComplete: "new-password",
538
+ ...registerPasswordRepeat,
539
+ onChange: (e) => {
540
+ clearErrors("newPassword");
541
+ clearErrors("newPasswordRepeat");
542
+ runAsynchronously(registerPasswordRepeat.onChange(e));
543
+ }
544
+ }
545
+ ),
546
+ /* @__PURE__ */ jsx(FormWarningText, { text: errors.newPasswordRepeat?.message?.toString() }),
547
+ /* @__PURE__ */ jsxs("div", { className: "mt-6 flex gap-4", children: [
548
+ /* @__PURE__ */ jsx(Button, { type: "submit", loading, children: user.hasPassword ? t("Update Password") : t("Set Password") }),
549
+ /* @__PURE__ */ jsx(
550
+ Button,
551
+ {
552
+ variant: "secondary",
553
+ onClick: () => {
554
+ setChangingPassword(false);
555
+ reset();
556
+ },
557
+ children: t("Cancel")
558
+ }
559
+ )
560
+ ] })
561
+ ]
562
+ }
563
+ ) })
564
+ }
565
+ );
566
+ }
567
+ function useMfaSection() {
568
+ const { t } = useTranslation();
569
+ const project = useStackApp().useProject();
570
+ const user = useUser({ or: "throw" });
571
+ const [generatedSecret, setGeneratedSecret] = useState(null);
572
+ const [qrCodeUrl, setQrCodeUrl] = useState(null);
573
+ const [mfaCode, setMfaCode] = useState("");
574
+ const [isMaybeWrong, setIsMaybeWrong] = useState(false);
575
+ const isEnabled = user.isMultiFactorRequired;
576
+ const [handleSubmit, isLoading] = useAsyncCallback(async () => {
577
+ await user.update({
578
+ totpMultiFactorSecret: generatedSecret
579
+ });
580
+ setGeneratedSecret(null);
581
+ setQrCodeUrl(null);
582
+ setMfaCode("");
583
+ }, [generatedSecret, user]);
584
+ useEffect(() => {
585
+ setIsMaybeWrong(false);
586
+ runAsynchronouslyWithAlert(async () => {
587
+ if (generatedSecret && await new TOTPController().verify(mfaCode, generatedSecret)) {
588
+ await handleSubmit();
589
+ }
590
+ setIsMaybeWrong(true);
591
+ });
592
+ }, [mfaCode, generatedSecret, handleSubmit]);
593
+ return /* @__PURE__ */ jsx(
594
+ Section,
595
+ {
596
+ title: t("Multi-factor authentication"),
597
+ description: isEnabled ? t("Multi-factor authentication is currently enabled.") : t("Multi-factor authentication is currently disabled."),
598
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
599
+ !isEnabled && generatedSecret && /* @__PURE__ */ jsxs(Fragment, { children: [
600
+ /* @__PURE__ */ jsx(Typography, { children: t("Scan this QR code with your authenticator app:") }),
601
+ /* @__PURE__ */ jsx("img", { width: 200, height: 200, src: qrCodeUrl ?? throwErr("TOTP QR code failed to generate"), alt: t("TOTP multi-factor authentication QR code") }),
602
+ /* @__PURE__ */ jsx(Typography, { children: t("Then, enter your six-digit MFA code:") }),
603
+ /* @__PURE__ */ jsx(
604
+ Input,
605
+ {
606
+ value: mfaCode,
607
+ onChange: (e) => {
608
+ setIsMaybeWrong(false);
609
+ setMfaCode(e.target.value);
610
+ },
611
+ placeholder: "123456",
612
+ maxLength: 6,
613
+ disabled: isLoading
614
+ }
615
+ ),
616
+ isMaybeWrong && mfaCode.length === 6 && /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Incorrect code. Please try again.") }),
617
+ /* @__PURE__ */ jsx("div", { className: "flex", children: /* @__PURE__ */ jsx(
618
+ Button,
619
+ {
620
+ variant: "secondary",
621
+ onClick: () => {
622
+ setGeneratedSecret(null);
623
+ setQrCodeUrl(null);
624
+ setMfaCode("");
625
+ },
626
+ children: t("Cancel")
627
+ }
628
+ ) })
629
+ ] }),
630
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: isEnabled ? /* @__PURE__ */ jsx(
631
+ Button,
632
+ {
633
+ variant: "secondary",
634
+ onClick: async () => {
635
+ await user.update({
636
+ totpMultiFactorSecret: null
637
+ });
638
+ },
639
+ children: t("Disable MFA")
640
+ }
641
+ ) : !generatedSecret && /* @__PURE__ */ jsx(
642
+ Button,
643
+ {
644
+ variant: "secondary",
645
+ onClick: async () => {
646
+ const secret = generateRandomValues(new Uint8Array(20));
647
+ setQrCodeUrl(await generateTotpQrCode(project, user, secret));
648
+ setGeneratedSecret(secret);
649
+ },
650
+ children: t("Enable MFA")
651
+ }
652
+ ) })
653
+ ] })
654
+ }
655
+ );
656
+ }
657
+ async function generateTotpQrCode(project, user, secret) {
658
+ const uri = createTOTPKeyURI(project.displayName, user.primaryEmail ?? user.id, secret);
659
+ return await QRCode.toDataURL(uri);
660
+ }
661
+ function useSignOutSection() {
662
+ const { t } = useTranslation();
663
+ const user = useUser({ or: "throw" });
664
+ return /* @__PURE__ */ jsx(
665
+ Section,
666
+ {
667
+ title: t("Sign out"),
668
+ description: t("End your current session"),
669
+ children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
670
+ Button,
671
+ {
672
+ variant: "secondary",
673
+ onClick: () => user.signOut(),
674
+ children: t("Sign out")
675
+ }
676
+ ) })
677
+ }
678
+ );
679
+ }
680
+ function TeamPage(props) {
681
+ const teamUserProfileSection = useTeamUserProfileSection(props);
682
+ const teamProfileImageSection = useTeamProfileImageSection(props);
683
+ const teamDisplayNameSection = useTeamDisplayNameSection(props);
684
+ const leaveTeamSection = useLeaveTeamSection(props);
685
+ const memberInvitationSection = useMemberInvitationSection(props);
686
+ const memberListSection = useMemberListSection(props);
687
+ return /* @__PURE__ */ jsxs(PageLayout, { children: [
688
+ teamUserProfileSection,
689
+ memberListSection,
690
+ memberInvitationSection,
691
+ teamProfileImageSection,
692
+ teamDisplayNameSection,
693
+ leaveTeamSection
694
+ ] });
695
+ }
696
+ function useLeaveTeamSection(props) {
697
+ const { t } = useTranslation();
698
+ const user = useUser({ or: "redirect" });
699
+ const [leaving, setLeaving] = useState(false);
700
+ return /* @__PURE__ */ jsx(
701
+ Section,
702
+ {
703
+ title: t("Leave Team"),
704
+ description: t("leave this team and remove your team profile"),
705
+ children: !leaving ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
706
+ Button,
707
+ {
708
+ variant: "secondary",
709
+ onClick: () => setLeaving(true),
710
+ children: t("Leave team")
711
+ }
712
+ ) }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
713
+ /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to leave the team?") }),
714
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
715
+ /* @__PURE__ */ jsx(
716
+ Button,
717
+ {
718
+ variant: "destructive",
719
+ onClick: async () => {
720
+ await user.leaveTeam(props.team);
721
+ window.location.reload();
722
+ },
723
+ children: t("Leave")
724
+ }
725
+ ),
726
+ /* @__PURE__ */ jsx(
727
+ Button,
728
+ {
729
+ variant: "secondary",
730
+ onClick: () => setLeaving(false),
731
+ children: t("Cancel")
732
+ }
733
+ )
734
+ ] })
735
+ ] })
736
+ }
737
+ );
738
+ }
739
+ function useTeamProfileImageSection(props) {
740
+ const { t } = useTranslation();
741
+ const user = useUser({ or: "redirect" });
742
+ const updateTeamPermission = user.usePermission(props.team, "$update_team");
743
+ if (!updateTeamPermission) {
744
+ return null;
745
+ }
746
+ return /* @__PURE__ */ jsx(
747
+ Section,
748
+ {
749
+ title: t("Team profile image"),
750
+ description: t("Upload an image for your team"),
751
+ children: /* @__PURE__ */ jsx(
752
+ ProfileImageEditor,
753
+ {
754
+ user: props.team,
755
+ onProfileImageUrlChange: async (profileImageUrl) => {
756
+ await props.team.update({ profileImageUrl });
757
+ }
758
+ }
759
+ )
760
+ }
761
+ );
762
+ }
763
+ function useTeamDisplayNameSection(props) {
764
+ const { t } = useTranslation();
765
+ const user = useUser({ or: "redirect" });
766
+ const updateTeamPermission = user.usePermission(props.team, "$update_team");
767
+ if (!updateTeamPermission) {
768
+ return null;
769
+ }
770
+ return /* @__PURE__ */ jsx(
771
+ Section,
772
+ {
773
+ title: t("Team display name"),
774
+ description: t("Change the display name of your team"),
775
+ children: /* @__PURE__ */ jsx(
776
+ EditableText,
777
+ {
778
+ value: props.team.displayName,
779
+ onSave: async (newDisplayName) => await props.team.update({ displayName: newDisplayName })
780
+ }
781
+ )
782
+ }
783
+ );
784
+ }
785
+ function useTeamUserProfileSection(props) {
786
+ const { t } = useTranslation();
787
+ const user = useUser({ or: "redirect" });
788
+ const profile = user.useTeamProfile(props.team);
789
+ return /* @__PURE__ */ jsx(
790
+ Section,
791
+ {
792
+ title: t("Team user name"),
793
+ description: t("Overwrite your user display name in this team"),
794
+ children: /* @__PURE__ */ jsx(
795
+ EditableText,
796
+ {
797
+ value: profile.displayName || "",
798
+ onSave: async (newDisplayName) => {
799
+ await profile.update({ displayName: newDisplayName });
800
+ }
801
+ }
802
+ )
803
+ }
804
+ );
805
+ }
806
+ function useMemberInvitationSection(props) {
807
+ const { t } = useTranslation();
808
+ const invitationSchema = yupObject({
809
+ email: strictEmailSchema(t("Please enter a valid email address")).defined().nonEmpty(t("Please enter an email address"))
810
+ });
811
+ const user = useUser({ or: "redirect" });
812
+ const inviteMemberPermission = user.usePermission(props.team, "$invite_members");
813
+ const readMemberPermission = user.usePermission(props.team, "$read_members");
814
+ const removeMemberPermission = user.usePermission(props.team, "$remove_members");
815
+ if (!inviteMemberPermission) {
816
+ return null;
817
+ }
818
+ let invitationsToShow = [];
819
+ if (readMemberPermission) {
820
+ invitationsToShow = props.team.useInvitations();
821
+ }
822
+ const { register, handleSubmit, formState: { errors }, watch } = useForm({
823
+ resolver: yupResolver(invitationSchema)
824
+ });
825
+ const [loading, setLoading] = useState(false);
826
+ const [invitedEmail, setInvitedEmail] = useState(null);
827
+ const onSubmit = async (data) => {
828
+ setLoading(true);
829
+ try {
830
+ await props.team.inviteUser({ email: data.email });
831
+ setInvitedEmail(data.email);
832
+ } finally {
833
+ setLoading(false);
834
+ }
835
+ };
836
+ useEffect(() => {
837
+ setInvitedEmail(null);
838
+ }, [watch("email")]);
839
+ return /* @__PURE__ */ jsxs("div", { children: [
840
+ /* @__PURE__ */ jsx(
841
+ Section,
842
+ {
843
+ title: t("Invite member"),
844
+ description: t("Invite a user to your team through email"),
845
+ children: /* @__PURE__ */ jsxs(
846
+ "form",
847
+ {
848
+ onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
849
+ noValidate: true,
850
+ className: "w-full",
851
+ children: [
852
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row w-full", children: [
853
+ /* @__PURE__ */ jsx(
854
+ Input,
855
+ {
856
+ placeholder: t("Email"),
857
+ ...register("email")
858
+ }
859
+ ),
860
+ /* @__PURE__ */ jsx(Button, { type: "submit", loading, children: t("Invite User") })
861
+ ] }),
862
+ /* @__PURE__ */ jsx(FormWarningText, { text: errors.email?.message?.toString() }),
863
+ invitedEmail && /* @__PURE__ */ jsxs(Typography, { type: "label", variant: "secondary", children: [
864
+ "Invited ",
865
+ invitedEmail
866
+ ] })
867
+ ]
868
+ }
869
+ )
870
+ }
871
+ ),
872
+ invitationsToShow.length > 0 && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Table, { className: "mt-6", children: [
873
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
874
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[200px]", children: t("Outstanding invitations") }),
875
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[60px]", children: t("Expires") }),
876
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[36px] max-w-[36px]" })
877
+ ] }) }),
878
+ /* @__PURE__ */ jsx(TableBody, { children: invitationsToShow.map((invitation, i) => /* @__PURE__ */ jsxs(TableRow, { children: [
879
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { children: invitation.recipientEmail }) }),
880
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { variant: "secondary", children: invitation.expiresAt.toLocaleString() }) }),
881
+ /* @__PURE__ */ jsx(TableCell, { align: "right", className: "max-w-[36px]", children: removeMemberPermission && /* @__PURE__ */ jsx(Button, { onClick: async () => await invitation.revoke(), size: "icon", variant: "ghost", children: /* @__PURE__ */ jsx(Trash, { className: "w-4 h-4" }) }) })
882
+ ] }, invitation.id)) })
883
+ ] }) })
884
+ ] });
885
+ }
886
+ function useMemberListSection(props) {
887
+ const { t } = useTranslation();
888
+ const user = useUser({ or: "redirect" });
889
+ const readMemberPermission = user.usePermission(props.team, "$read_members");
890
+ const inviteMemberPermission = user.usePermission(props.team, "$invite_members");
891
+ if (!readMemberPermission && !inviteMemberPermission) {
892
+ return null;
893
+ }
894
+ const users = props.team.useUsers();
895
+ if (!readMemberPermission) {
896
+ return null;
897
+ }
898
+ return /* @__PURE__ */ jsxs("div", { children: [
899
+ /* @__PURE__ */ jsx(Typography, { className: "font-medium mb-2", children: t("Members") }),
900
+ /* @__PURE__ */ jsx("div", { className: "border rounded-md", children: /* @__PURE__ */ jsxs(Table, { children: [
901
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
902
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[100px]", children: t("User") }),
903
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[200px]", children: t("Name") })
904
+ ] }) }),
905
+ /* @__PURE__ */ jsx(TableBody, { children: users.map(({ id, teamProfile }, i) => /* @__PURE__ */ jsxs(TableRow, { children: [
906
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(UserAvatar, { user: teamProfile }) }),
907
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { children: teamProfile.displayName }) })
908
+ ] }, id)) })
909
+ ] }) })
910
+ ] });
911
+ }
912
+ function TeamCreation() {
913
+ const { t } = useTranslation();
914
+ const teamCreationSchema = yupObject({
915
+ displayName: yupString().defined().nonEmpty(t("Please enter a team name"))
916
+ });
917
+ const { register, handleSubmit, formState: { errors } } = useForm({
918
+ resolver: yupResolver(teamCreationSchema)
919
+ });
920
+ const app = useStackApp();
921
+ const project = app.useProject();
922
+ const user = useUser({ or: "redirect" });
923
+ const navigate = app.useNavigate();
924
+ const [loading, setLoading] = useState(false);
925
+ if (!project.config.clientTeamCreationEnabled) {
926
+ return /* @__PURE__ */ jsx(MessageCard, { title: t("Team creation is not enabled") });
927
+ }
928
+ const onSubmit = async (data) => {
929
+ setLoading(true);
930
+ let team;
931
+ try {
932
+ team = await user.createTeam({ displayName: data.displayName });
933
+ } finally {
934
+ setLoading(false);
935
+ }
936
+ navigate(`#team-${team.id}`);
937
+ };
938
+ return /* @__PURE__ */ jsx(PageLayout, { children: /* @__PURE__ */ jsx(Section, { title: t("Create a Team"), description: t("Enter a display name for your new team"), children: /* @__PURE__ */ jsxs(
939
+ "form",
940
+ {
941
+ onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
942
+ noValidate: true,
943
+ className: "flex gap-2 flex-col sm:flex-row",
944
+ children: [
945
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1", children: [
946
+ /* @__PURE__ */ jsx(
947
+ Input,
948
+ {
949
+ id: "displayName",
950
+ type: "text",
951
+ ...register("displayName")
952
+ }
953
+ ),
954
+ /* @__PURE__ */ jsx(FormWarningText, { text: errors.displayName?.message?.toString() })
955
+ ] }),
956
+ /* @__PURE__ */ jsx(Button, { type: "submit", loading, children: t("Create") })
957
+ ]
958
+ }
959
+ ) }) });
960
+ }
961
+ function useDeleteAccountSection() {
962
+ const { t } = useTranslation();
963
+ const user = useUser({ or: "redirect" });
964
+ const app = useStackApp();
965
+ const project = app.useProject();
966
+ const [deleting, setDeleting] = useState(false);
967
+ if (!project.config.clientUserDeletionEnabled) {
968
+ return null;
969
+ }
970
+ return /* @__PURE__ */ jsx(
971
+ Section,
972
+ {
973
+ title: t("Delete Account"),
974
+ description: t("Permanently remove your account and all associated data"),
975
+ children: /* @__PURE__ */ jsx("div", { className: "stack-scope flex flex-col items-stretch", children: /* @__PURE__ */ jsx(Accordion, { type: "single", collapsible: true, className: "w-full", children: /* @__PURE__ */ jsxs(AccordionItem, { value: "item-1", children: [
976
+ /* @__PURE__ */ jsx(AccordionTrigger, { children: t("Danger zone") }),
977
+ /* @__PURE__ */ jsx(AccordionContent, { children: !deleting ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
978
+ Button,
979
+ {
980
+ variant: "destructive",
981
+ onClick: () => setDeleting(true),
982
+ children: t("Delete account")
983
+ }
984
+ ) }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
985
+ /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to delete your account? This action is IRREVERSIBLE and will delete ALL associated data.") }),
986
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
987
+ /* @__PURE__ */ jsx(
988
+ Button,
989
+ {
990
+ variant: "destructive",
991
+ onClick: async () => {
992
+ await user.delete();
993
+ await app.redirectToHome();
994
+ },
995
+ children: t("Delete Account")
996
+ }
997
+ ),
998
+ /* @__PURE__ */ jsx(
999
+ Button,
1000
+ {
1001
+ variant: "secondary",
1002
+ onClick: () => setDeleting(false),
1003
+ children: t("Cancel")
1004
+ }
1005
+ )
1006
+ ] })
1007
+ ] }) })
1008
+ ] }) }) })
1009
+ }
1010
+ );
1011
+ }
1012
+ function EditableText(props) {
1013
+ const [editing, setEditing] = useState(false);
1014
+ const [editingValue, setEditingValue] = useState(props.value);
1015
+ const { t } = useTranslation();
1016
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
1017
+ /* @__PURE__ */ jsx(
1018
+ Input,
1019
+ {
1020
+ value: editingValue,
1021
+ onChange: (e) => setEditingValue(e.target.value)
1022
+ }
1023
+ ),
1024
+ /* @__PURE__ */ jsx(
1025
+ Button,
1026
+ {
1027
+ size: "sm",
1028
+ onClick: async () => {
1029
+ await props.onSave?.(editingValue);
1030
+ setEditing(false);
1031
+ },
1032
+ children: t("Save")
1033
+ }
1034
+ ),
1035
+ /* @__PURE__ */ jsx(
1036
+ Button,
1037
+ {
1038
+ size: "sm",
1039
+ variant: "secondary",
1040
+ onClick: () => {
1041
+ setEditingValue(props.value);
1042
+ setEditing(false);
1043
+ },
1044
+ children: t("Cancel")
1045
+ }
1046
+ )
1047
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1048
+ /* @__PURE__ */ jsx(Typography, { children: props.value }),
1049
+ /* @__PURE__ */ jsx(Button, { onClick: () => setEditing(true), size: "icon", variant: "ghost", children: /* @__PURE__ */ jsx(Edit, { className: "w-4 h-4" }) })
1050
+ ] }) });
1051
+ }
1052
+ export {
1053
+ AccountSettings,
1054
+ EditableText,
1055
+ TeamCreation,
1056
+ useDeleteAccountSection
1057
+ };
1058
+ //# sourceMappingURL=account-settings.js.map