@digitaldefiance/express-suite-react-components 5.0.2 → 5.0.4

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 (314) hide show
  1. package/package.json +5 -4
  2. package/src/auth/AdminRoute.d.ts +8 -0
  3. package/src/auth/AdminRoute.d.ts.map +1 -0
  4. package/src/auth/AdminRoute.js +28 -0
  5. package/src/auth/Private.d.ts +6 -0
  6. package/src/auth/Private.d.ts.map +1 -0
  7. package/src/auth/Private.js +14 -0
  8. package/src/auth/PrivateRoute.d.ts +8 -0
  9. package/src/auth/PrivateRoute.d.ts.map +1 -0
  10. package/src/auth/PrivateRoute.js +23 -0
  11. package/src/auth/UnAuth.d.ts +6 -0
  12. package/src/auth/UnAuth.d.ts.map +1 -0
  13. package/src/auth/UnAuth.js +14 -0
  14. package/src/auth/UnAuthRoute.d.ts +8 -0
  15. package/src/auth/UnAuthRoute.d.ts.map +1 -0
  16. package/src/auth/UnAuthRoute.js +22 -0
  17. package/src/auth/{index.ts → index.d.ts} +3 -2
  18. package/src/auth/index.d.ts.map +1 -0
  19. package/src/auth/index.js +12 -0
  20. package/src/components/ApiAccess.d.ts +16 -0
  21. package/src/components/ApiAccess.d.ts.map +1 -0
  22. package/src/components/ApiAccess.js +77 -0
  23. package/src/components/BackupCodeLoginForm.d.ts +43 -0
  24. package/src/components/BackupCodeLoginForm.d.ts.map +1 -0
  25. package/src/components/BackupCodeLoginForm.js +139 -0
  26. package/src/components/BackupCodesForm.d.ts +26 -0
  27. package/src/components/BackupCodesForm.d.ts.map +1 -0
  28. package/src/components/BackupCodesForm.js +120 -0
  29. package/src/components/CategoryMenu.d.ts +17 -0
  30. package/src/components/CategoryMenu.d.ts.map +1 -0
  31. package/src/components/CategoryMenu.js +65 -0
  32. package/src/components/ChangePasswordForm.d.ts +26 -0
  33. package/src/components/ChangePasswordForm.d.ts.map +1 -0
  34. package/src/components/ChangePasswordForm.js +78 -0
  35. package/src/components/ConfirmationDialog.d.ts +13 -0
  36. package/src/components/ConfirmationDialog.d.ts.map +1 -0
  37. package/src/components/ConfirmationDialog.js +10 -0
  38. package/src/components/CurrencyCodeSelector.d.ts +9 -0
  39. package/src/components/CurrencyCodeSelector.d.ts.map +1 -0
  40. package/src/components/CurrencyCodeSelector.js +31 -0
  41. package/src/components/CurrencyInput.d.ts +13 -0
  42. package/src/components/CurrencyInput.d.ts.map +1 -0
  43. package/src/components/CurrencyInput.js +22 -0
  44. package/src/components/DashboardPage.d.ts +8 -0
  45. package/src/components/DashboardPage.d.ts.map +1 -0
  46. package/src/components/DashboardPage.js +10 -0
  47. package/src/components/DropdownMenu.d.ts +13 -0
  48. package/src/components/DropdownMenu.d.ts.map +1 -0
  49. package/src/components/DropdownMenu.js +63 -0
  50. package/src/components/ExpirationSecondsSelector.d.ts +13 -0
  51. package/src/components/ExpirationSecondsSelector.d.ts.map +1 -0
  52. package/src/components/ExpirationSecondsSelector.js +32 -0
  53. package/src/components/FakeEmailAdminPanel.d.ts +23 -0
  54. package/src/components/FakeEmailAdminPanel.d.ts.map +1 -0
  55. package/src/components/FakeEmailAdminPanel.js +147 -0
  56. package/src/components/Flag.d.ts +20 -0
  57. package/src/components/Flag.d.ts.map +1 -0
  58. package/src/components/Flag.js +43 -0
  59. package/src/components/ForgotPasswordForm.d.ts +18 -0
  60. package/src/components/ForgotPasswordForm.d.ts.map +1 -0
  61. package/src/components/ForgotPasswordForm.js +61 -0
  62. package/src/components/LoginForm.d.ts +44 -0
  63. package/src/components/LoginForm.d.ts.map +1 -0
  64. package/src/components/LoginForm.js +123 -0
  65. package/src/components/LogoutPage.d.ts +8 -0
  66. package/src/components/LogoutPage.d.ts.map +1 -0
  67. package/src/components/LogoutPage.js +16 -0
  68. package/src/components/RegisterForm.d.ts +100 -0
  69. package/src/components/RegisterForm.d.ts.map +1 -0
  70. package/src/components/RegisterForm.js +306 -0
  71. package/src/components/ResetPasswordForm.d.ts +23 -0
  72. package/src/components/ResetPasswordForm.d.ts.map +1 -0
  73. package/src/components/ResetPasswordForm.js +78 -0
  74. package/src/components/SideMenu.d.ts +8 -0
  75. package/src/components/SideMenu.d.ts.map +1 -0
  76. package/src/components/SideMenu.js +25 -0
  77. package/src/components/SideMenuListItem.d.ts +13 -0
  78. package/src/components/SideMenuListItem.d.ts.map +1 -0
  79. package/src/components/SideMenuListItem.js +44 -0
  80. package/src/components/T.d.ts +7 -0
  81. package/src/components/T.d.ts.map +1 -0
  82. package/src/components/T.js +13 -0
  83. package/src/components/TDiv.d.ts +9 -0
  84. package/src/components/TDiv.d.ts.map +1 -0
  85. package/src/components/TDiv.js +14 -0
  86. package/src/components/TImgAlt.d.ts +10 -0
  87. package/src/components/TImgAlt.d.ts.map +1 -0
  88. package/src/components/TImgAlt.js +14 -0
  89. package/src/components/TopMenu.d.ts +50 -0
  90. package/src/components/TopMenu.d.ts.map +1 -0
  91. package/src/components/TopMenu.js +79 -0
  92. package/src/components/TotpSetupForm.d.ts +24 -0
  93. package/src/components/TotpSetupForm.d.ts.map +1 -0
  94. package/src/components/TotpSetupForm.js +82 -0
  95. package/src/components/TotpVerificationForm.d.ts +23 -0
  96. package/src/components/TotpVerificationForm.d.ts.map +1 -0
  97. package/src/components/TotpVerificationForm.js +66 -0
  98. package/src/components/TranslatedTitle.d.ts +8 -0
  99. package/src/components/TranslatedTitle.d.ts.map +1 -0
  100. package/src/components/TranslatedTitle.js +15 -0
  101. package/src/components/UserLanguageSelector.d.ts +4 -0
  102. package/src/components/UserLanguageSelector.d.ts.map +1 -0
  103. package/src/components/UserLanguageSelector.js +31 -0
  104. package/src/components/UserMenu.d.ts +4 -0
  105. package/src/components/UserMenu.d.ts.map +1 -0
  106. package/src/components/UserMenu.js +12 -0
  107. package/src/components/UserSettingsForm.d.ts +113 -0
  108. package/src/components/UserSettingsForm.d.ts.map +1 -0
  109. package/src/components/UserSettingsForm.js +269 -0
  110. package/src/components/VerifyEmailPage.d.ts +24 -0
  111. package/src/components/VerifyEmailPage.d.ts.map +1 -0
  112. package/src/components/VerifyEmailPage.js +81 -0
  113. package/src/components/{index.ts → index.d.ts} +1 -1
  114. package/src/components/index.d.ts.map +1 -0
  115. package/src/components/index.js +35 -0
  116. package/src/contexts/AuthProvider.d.ts +19 -0
  117. package/src/contexts/AuthProvider.d.ts.map +1 -0
  118. package/src/contexts/AuthProvider.js +514 -0
  119. package/src/contexts/I18nProvider.d.ts +27 -0
  120. package/src/contexts/I18nProvider.d.ts.map +1 -0
  121. package/src/contexts/I18nProvider.js +60 -0
  122. package/src/contexts/MenuContext.d.ts +28 -0
  123. package/src/contexts/MenuContext.d.ts.map +1 -0
  124. package/src/contexts/MenuContext.js +307 -0
  125. package/src/contexts/SuiteConfigProvider.d.ts +44 -0
  126. package/src/contexts/SuiteConfigProvider.d.ts.map +1 -0
  127. package/src/contexts/SuiteConfigProvider.js +43 -0
  128. package/src/contexts/ThemeProvider.d.ts +15 -0
  129. package/src/contexts/ThemeProvider.d.ts.map +1 -0
  130. package/src/contexts/ThemeProvider.js +36 -0
  131. package/src/contexts/auth-context.d.ts +172 -0
  132. package/src/contexts/auth-context.d.ts.map +1 -0
  133. package/src/contexts/auth-context.js +13 -0
  134. package/src/contexts/{index.ts → index.d.ts} +1 -0
  135. package/src/contexts/index.d.ts.map +1 -0
  136. package/src/contexts/index.js +8 -0
  137. package/src/hooks/{index.ts → index.d.ts} +1 -0
  138. package/src/hooks/index.d.ts.map +1 -0
  139. package/src/hooks/index.js +9 -0
  140. package/src/hooks/useAuthenticatedApi.d.ts +34 -0
  141. package/src/hooks/useAuthenticatedApi.d.ts.map +1 -0
  142. package/src/hooks/useAuthenticatedApi.js +46 -0
  143. package/src/hooks/useBackupCodes.d.ts +15 -0
  144. package/src/hooks/useBackupCodes.d.ts.map +1 -0
  145. package/src/hooks/useBackupCodes.js +74 -0
  146. package/src/hooks/useEmailVerification.d.ts +10 -0
  147. package/src/hooks/useEmailVerification.d.ts.map +1 -0
  148. package/src/hooks/useEmailVerification.js +40 -0
  149. package/src/hooks/useExpiringValue.d.ts +14 -0
  150. package/src/hooks/useExpiringValue.d.ts.map +1 -0
  151. package/src/hooks/useExpiringValue.js +53 -0
  152. package/src/hooks/useLocalStorage.d.ts +2 -0
  153. package/src/hooks/useLocalStorage.d.ts.map +1 -0
  154. package/src/hooks/useLocalStorage.js +15 -0
  155. package/src/hooks/useUserSettings.d.ts +48 -0
  156. package/src/hooks/useUserSettings.d.ts.map +1 -0
  157. package/src/hooks/useUserSettings.js +171 -0
  158. package/src/{index.ts → index.d.ts} +1 -1
  159. package/src/index.d.ts.map +1 -0
  160. package/src/index.js +12 -0
  161. package/src/interfaces/IAppConfig.d.ts +8 -0
  162. package/src/interfaces/IAppConfig.d.ts.map +1 -0
  163. package/src/interfaces/IAppConfig.js +2 -0
  164. package/src/interfaces/ICategoryConfig.d.ts +41 -0
  165. package/src/interfaces/ICategoryConfig.d.ts.map +1 -0
  166. package/src/interfaces/ICategoryConfig.js +2 -0
  167. package/src/interfaces/IMenuConfig.d.ts +28 -0
  168. package/src/interfaces/IMenuConfig.d.ts.map +1 -0
  169. package/src/interfaces/IMenuConfig.js +2 -0
  170. package/src/interfaces/IMenuOption.d.ts +82 -0
  171. package/src/interfaces/IMenuOption.d.ts.map +1 -0
  172. package/src/interfaces/IMenuOption.js +2 -0
  173. package/src/interfaces/{index.ts → index.d.ts} +1 -0
  174. package/src/interfaces/index.d.ts.map +1 -0
  175. package/src/interfaces/index.js +7 -0
  176. package/src/services/__mocks__/authService.d.ts +21 -0
  177. package/src/services/__mocks__/authService.d.ts.map +1 -0
  178. package/src/services/__mocks__/authService.js +15 -0
  179. package/src/services/api.d.ts +3 -0
  180. package/src/services/api.d.ts.map +1 -0
  181. package/src/services/api.js +14 -0
  182. package/src/services/authService.d.ts +126 -0
  183. package/src/services/authService.d.ts.map +1 -0
  184. package/src/services/authService.js +471 -0
  185. package/src/services/authenticatedApi.d.ts +3 -0
  186. package/src/services/authenticatedApi.d.ts.map +1 -0
  187. package/src/services/authenticatedApi.js +18 -0
  188. package/src/services/index.d.ts +4 -0
  189. package/src/services/index.d.ts.map +1 -0
  190. package/src/services/index.js +6 -0
  191. package/src/types/MenuType.d.ts +11 -0
  192. package/src/types/MenuType.d.ts.map +1 -0
  193. package/src/types/MenuType.js +12 -0
  194. package/src/types/expirationSeconds.d.ts +3 -0
  195. package/src/types/expirationSeconds.d.ts.map +1 -0
  196. package/src/types/expirationSeconds.js +17 -0
  197. package/src/types/index.d.ts +2 -0
  198. package/src/types/index.d.ts.map +1 -0
  199. package/src/types/index.js +4 -0
  200. package/src/types/translation.d.ts +10 -0
  201. package/src/types/translation.d.ts.map +1 -0
  202. package/src/types/translation.js +9 -0
  203. package/src/wrappers/BackupCodeLoginWrapper.d.ts +8 -0
  204. package/src/wrappers/BackupCodeLoginWrapper.d.ts.map +1 -0
  205. package/src/wrappers/BackupCodeLoginWrapper.js +20 -0
  206. package/src/wrappers/BackupCodesWrapper.d.ts +7 -0
  207. package/src/wrappers/BackupCodesWrapper.d.ts.map +1 -0
  208. package/src/wrappers/BackupCodesWrapper.js +17 -0
  209. package/src/wrappers/ChangePasswordFormWrapper.d.ts +8 -0
  210. package/src/wrappers/ChangePasswordFormWrapper.d.ts.map +1 -0
  211. package/src/wrappers/ChangePasswordFormWrapper.js +21 -0
  212. package/src/wrappers/ForgotPasswordFormWrapper.d.ts +8 -0
  213. package/src/wrappers/ForgotPasswordFormWrapper.d.ts.map +1 -0
  214. package/src/wrappers/ForgotPasswordFormWrapper.js +29 -0
  215. package/src/wrappers/LoginFormWrapper.d.ts +21 -0
  216. package/src/wrappers/LoginFormWrapper.d.ts.map +1 -0
  217. package/src/wrappers/LoginFormWrapper.js +72 -0
  218. package/src/wrappers/LogoutPageWrapper.d.ts +9 -0
  219. package/src/wrappers/LogoutPageWrapper.d.ts.map +1 -0
  220. package/src/wrappers/LogoutPageWrapper.js +21 -0
  221. package/src/wrappers/RegisterFormWrapper.d.ts +20 -0
  222. package/src/wrappers/RegisterFormWrapper.d.ts.map +1 -0
  223. package/src/wrappers/RegisterFormWrapper.js +51 -0
  224. package/src/wrappers/ResetPasswordFormWrapper.d.ts +9 -0
  225. package/src/wrappers/ResetPasswordFormWrapper.d.ts.map +1 -0
  226. package/src/wrappers/ResetPasswordFormWrapper.js +34 -0
  227. package/src/wrappers/UserSettingsFormWrapper.d.ts +29 -0
  228. package/src/wrappers/UserSettingsFormWrapper.d.ts.map +1 -0
  229. package/src/wrappers/UserSettingsFormWrapper.js +95 -0
  230. package/src/wrappers/VerifyEmailPageWrapper.d.ts +8 -0
  231. package/src/wrappers/VerifyEmailPageWrapper.d.ts.map +1 -0
  232. package/src/wrappers/VerifyEmailPageWrapper.js +21 -0
  233. package/src/wrappers/{index.tsx → index.d.ts} +1 -10
  234. package/src/wrappers/index.d.ts.map +1 -0
  235. package/src/wrappers/index.js +24 -0
  236. package/LICENSE +0 -21
  237. package/src/auth/AdminRoute.tsx +0 -54
  238. package/src/auth/Private.tsx +0 -17
  239. package/src/auth/PrivateRoute.tsx +0 -28
  240. package/src/auth/UnAuth.tsx +0 -16
  241. package/src/auth/UnAuthRoute.tsx +0 -30
  242. package/src/components/ApiAccess.tsx +0 -175
  243. package/src/components/BackupCodeLoginForm.tsx +0 -489
  244. package/src/components/BackupCodesForm.tsx +0 -287
  245. package/src/components/CategoryMenu.tsx +0 -155
  246. package/src/components/ChangePasswordForm.tsx +0 -273
  247. package/src/components/ConfirmationDialog.tsx +0 -48
  248. package/src/components/CurrencyCodeSelector.tsx +0 -60
  249. package/src/components/CurrencyInput.tsx +0 -80
  250. package/src/components/DashboardPage.tsx +0 -24
  251. package/src/components/DropdownMenu.tsx +0 -105
  252. package/src/components/ExpirationSecondsSelector.tsx +0 -60
  253. package/src/components/FakeEmailAdminPanel.tsx +0 -374
  254. package/src/components/Flag.tsx +0 -52
  255. package/src/components/ForgotPasswordForm.tsx +0 -174
  256. package/src/components/LoginForm.tsx +0 -468
  257. package/src/components/LogoutPage.tsx +0 -21
  258. package/src/components/RegisterForm.tsx +0 -1169
  259. package/src/components/ResetPasswordForm.tsx +0 -247
  260. package/src/components/SideMenu.tsx +0 -46
  261. package/src/components/SideMenuListItem.tsx +0 -75
  262. package/src/components/T.tsx +0 -24
  263. package/src/components/TDiv.tsx +0 -26
  264. package/src/components/TImgAlt.tsx +0 -28
  265. package/src/components/TopMenu.tsx +0 -252
  266. package/src/components/TotpSetupForm.tsx +0 -188
  267. package/src/components/TotpVerificationForm.tsx +0 -148
  268. package/src/components/TranslatedTitle.tsx +0 -31
  269. package/src/components/UserLanguageSelector.tsx +0 -45
  270. package/src/components/UserMenu.tsx +0 -15
  271. package/src/components/UserSettingsForm.tsx +0 -904
  272. package/src/components/VerifyEmailPage.tsx +0 -222
  273. package/src/components/__tests__/RegisterForm.totp.spec.tsx +0 -377
  274. package/src/components/__tests__/TotpSetupForm.spec.tsx +0 -258
  275. package/src/components/__tests__/TotpVerificationForm.spec.tsx +0 -259
  276. package/src/components/__tests__/UserSettingsForm.totp.spec.tsx +0 -396
  277. package/src/contexts/AuthProvider.spec.tsx +0 -1284
  278. package/src/contexts/AuthProvider.tsx +0 -977
  279. package/src/contexts/I18nProvider.tsx +0 -164
  280. package/src/contexts/MenuContext.tsx +0 -426
  281. package/src/contexts/SuiteConfigProvider.tsx +0 -93
  282. package/src/contexts/ThemeProvider.tsx +0 -67
  283. package/src/hooks/useAuthenticatedApi.tsx +0 -67
  284. package/src/hooks/useBackupCodes.ts +0 -105
  285. package/src/hooks/useEmailVerification.ts +0 -54
  286. package/src/hooks/useExpiringValue.ts +0 -78
  287. package/src/hooks/useLocalStorage.ts +0 -18
  288. package/src/hooks/useUserSettings.ts +0 -271
  289. package/src/interfaces/IAppConfig.ts +0 -7
  290. package/src/interfaces/ICategoryConfig.ts +0 -41
  291. package/src/interfaces/IMenuConfig.ts +0 -28
  292. package/src/interfaces/IMenuOption.ts +0 -79
  293. package/src/services/__mocks__/authService.ts +0 -14
  294. package/src/services/__tests__/authService.totp.property.spec.ts +0 -360
  295. package/src/services/__tests__/authService.totp.spec.ts +0 -439
  296. package/src/services/api.ts +0 -13
  297. package/src/services/authService.ts +0 -692
  298. package/src/services/authenticatedApi.ts +0 -17
  299. package/src/services/index.ts +0 -3
  300. package/src/types/MenuType.ts +0 -15
  301. package/src/types/expirationSeconds.ts +0 -18
  302. package/src/types/index.ts +0 -1
  303. package/src/types/translation.ts +0 -20
  304. package/src/wrappers/BackupCodeLoginWrapper.tsx +0 -34
  305. package/src/wrappers/BackupCodesWrapper.tsx +0 -28
  306. package/src/wrappers/ChangePasswordFormWrapper.tsx +0 -34
  307. package/src/wrappers/ForgotPasswordFormWrapper.tsx +0 -45
  308. package/src/wrappers/LoginFormWrapper.tsx +0 -108
  309. package/src/wrappers/LogoutPageWrapper.tsx +0 -30
  310. package/src/wrappers/RegisterFormWrapper.tsx +0 -109
  311. package/src/wrappers/ResetPasswordFormWrapper.tsx +0 -57
  312. package/src/wrappers/UserSettingsFormWrapper.tsx +0 -140
  313. package/src/wrappers/VerifyEmailPageWrapper.tsx +0 -27
  314. package/src/wrappers/__tests__/LoginFormWrapper.totp.spec.tsx +0 -339
@@ -1,1169 +0,0 @@
1
- import {
2
- Constants,
3
- SuiteCoreComponentId,
4
- SuiteCoreStringKey,
5
- SuiteCoreStringKeyValue,
6
- } from '@digitaldefiance/suite-core-lib';
7
- import {
8
- Alert,
9
- AlertTitle,
10
- Box,
11
- Button,
12
- Checkbox,
13
- Container,
14
- FormControl,
15
- FormControlLabel,
16
- FormHelperText,
17
- InputLabel,
18
- Link,
19
- MenuItem,
20
- Select,
21
- TextField,
22
- Typography,
23
- } from '@mui/material';
24
- import { useFormik } from 'formik';
25
- import { FC, useCallback, useState } from 'react';
26
- import * as Yup from 'yup';
27
- import { generateMnemonic } from 'bip39';
28
- import { useI18n } from '../contexts';
29
- import { TotpSetupForm } from './TotpSetupForm';
30
-
31
- export interface RegisterFormValues {
32
- username: string;
33
- email: string;
34
- displayName?: string;
35
- timezone: string;
36
- password?: string;
37
- confirmPassword?: string;
38
- directChallenge?: boolean;
39
- mnemonic?: string;
40
- [key: string]: string | boolean | undefined;
41
- }
42
-
43
- export interface RegisterFormProps {
44
- onSubmit: (
45
- values: RegisterFormValues,
46
- usePassword: boolean
47
- ) => Promise<
48
- | { success: boolean; message: string; mnemonic?: string }
49
- | {
50
- error: string;
51
- errorType?: string;
52
- field?: string;
53
- errors?: Array<{ path: string; msg: string }>;
54
- }
55
- >;
56
- timezones: string[];
57
- getInitialTimezone: () => string;
58
- usernameValidation?: Yup.StringSchema;
59
- emailValidation?: Yup.StringSchema;
60
- displayNameValidation?: Yup.StringSchema;
61
- timezoneValidation?: Yup.StringSchema;
62
- passwordValidation?: Yup.StringSchema;
63
- confirmPasswordValidation?: Yup.StringSchema;
64
- /**
65
- * When true, display a TOTP setup step after successful registration.
66
- * Defaults to false. When false or not provided, existing flow is unchanged.
67
- */
68
- enableTotpSetup?: boolean;
69
- /**
70
- * Called to initiate TOTP setup (e.g. AuthService.setupTotp()).
71
- * Must return provisioning URI and secret on success, or an error.
72
- */
73
- onTotpSetup?: () => Promise<
74
- { provisioningUri: string; secret: string } | { error: string }
75
- >;
76
- /**
77
- * Called to confirm TOTP setup with a 6-digit code (e.g. AuthService.confirmTotp()).
78
- */
79
- onTotpConfirm?: (
80
- code: string
81
- ) => Promise<{ success: boolean } | { error: string }>;
82
- /**
83
- * Called after the user successfully confirms TOTP during the registration flow.
84
- */
85
- onTotpSetupComplete?: () => void;
86
- /**
87
- * List of email domains that are not allowed during registration.
88
- * For example, the home system's email domain should be disallowed
89
- * so users cannot register with addresses managed by the platform.
90
- */
91
- disallowedEmailDomains?: string[];
92
- additionalFields?: (
93
- formik: ReturnType<typeof useFormik<RegisterFormValues>>,
94
- usePassword: boolean
95
- ) => React.ReactNode;
96
- additionalInitialValues?: Record<string, string | boolean>;
97
- additionalValidation?: Record<string, Yup.Schema>;
98
- labels?: {
99
- title?: string;
100
- username?: string;
101
- email?: string;
102
- displayName?: string;
103
- timezone?: string;
104
- password?: string;
105
- confirmPassword?: string;
106
- useMnemonic?: string;
107
- usePassword?: string;
108
- registering?: string;
109
- register?: string;
110
- successTitle?: string;
111
- mnemonicSuccess?: string;
112
- proceedToLogin?: string;
113
- savedRecoveryPhrase?: string;
114
- loginLink?: string;
115
- mnemonic?: string;
116
- totpSetupTitle?: string;
117
- totpSkipButton?: string;
118
- totpSetupError?: string;
119
- totpSetupSuccess?: string;
120
- passwordAuthInfo?: string;
121
- };
122
- }
123
-
124
- export const RegisterForm: FC<RegisterFormProps> = ({
125
- onSubmit,
126
- timezones,
127
- getInitialTimezone,
128
- usernameValidation,
129
- emailValidation,
130
- displayNameValidation,
131
- timezoneValidation,
132
- passwordValidation,
133
- confirmPasswordValidation,
134
- enableTotpSetup = false,
135
- onTotpSetup,
136
- onTotpConfirm,
137
- onTotpSetupComplete,
138
- disallowedEmailDomains,
139
- additionalFields,
140
- additionalInitialValues = {},
141
- additionalValidation = {},
142
- labels = {},
143
- }) => {
144
- const { tComponent } = useI18n();
145
- const [apiErrors, setApiErrors] = useState<Record<string, string>>({});
146
- const [mnemonic, setMnemonic] = useState<string | null>(null);
147
- const [usePassword, setUsePassword] = useState(true);
148
- const [registrationSuccess, setRegistrationSuccess] = useState(false);
149
- const [registering, setRegistering] = useState(false);
150
- const [showMnemonicInput, setShowMnemonicInput] = useState(false);
151
- const [autoMnemonic, setAutoMnemonic] = useState(() => generateMnemonic(256));
152
- const [showTotpSetup, setShowTotpSetup] = useState(false);
153
- const [totpProvisioningUri, setTotpProvisioningUri] = useState<string | null>(
154
- null
155
- );
156
- const [totpSecret, setTotpSecret] = useState<string | null>(null);
157
- const [totpSetupError, setTotpSetupError] = useState<string | null>(null);
158
- const [totpSetupComplete, setTotpSetupComplete] = useState(false);
159
- const [totpSetupLoading, setTotpSetupLoading] = useState(false);
160
-
161
- const initiateTotpSetup = useCallback(async () => {
162
- if (!onTotpSetup) return;
163
- setTotpSetupLoading(true);
164
- setTotpSetupError(null);
165
- try {
166
- const result = await onTotpSetup();
167
- if ('error' in result) {
168
- setTotpSetupError(result.error);
169
- } else {
170
- setTotpProvisioningUri(result.provisioningUri);
171
- setTotpSecret(result.secret);
172
- }
173
- } catch {
174
- setTotpSetupError(
175
- labels.totpSetupError || 'Failed to set up two-factor authentication'
176
- );
177
- } finally {
178
- setTotpSetupLoading(false);
179
- }
180
- }, [onTotpSetup, labels.totpSetupError]);
181
-
182
- const handleTotpConfirm = useCallback(
183
- async (code: string): Promise<{ success: boolean } | { error: string }> => {
184
- if (!onTotpConfirm) {
185
- return { error: 'TOTP confirmation is not available' };
186
- }
187
- const result = await onTotpConfirm(code);
188
- if ('success' in result && result.success) {
189
- setTotpSetupComplete(true);
190
- setShowTotpSetup(false);
191
- if (onTotpSetupComplete) {
192
- onTotpSetupComplete();
193
- }
194
- }
195
- return result;
196
- },
197
- [onTotpConfirm, onTotpSetupComplete]
198
- );
199
-
200
- const handleSkipTotp = useCallback(() => {
201
- setShowTotpSetup(false);
202
- }, []);
203
-
204
- const validation = {
205
- username:
206
- usernameValidation ||
207
- Yup.string()
208
- .min(
209
- Constants.UsernameMinLength,
210
- tComponent<SuiteCoreStringKeyValue>(
211
- SuiteCoreComponentId,
212
- SuiteCoreStringKey.Validation_UsernameMinLengthTemplate
213
- )
214
- )
215
- .max(
216
- Constants.UsernameMaxLength,
217
- tComponent<SuiteCoreStringKeyValue>(
218
- SuiteCoreComponentId,
219
- SuiteCoreStringKey.Validation_UsernameMaxLengthTemplate
220
- )
221
- )
222
- .matches(
223
- Constants.UsernameRegex,
224
- tComponent<SuiteCoreStringKeyValue>(
225
- SuiteCoreComponentId,
226
- SuiteCoreStringKey.Validation_UsernameRegexErrorTemplate
227
- )
228
- )
229
- .required(
230
- tComponent<SuiteCoreStringKeyValue>(
231
- SuiteCoreComponentId,
232
- SuiteCoreStringKey.Validation_Required
233
- )
234
- ),
235
- email:
236
- emailValidation ||
237
- Yup.string()
238
- .email(
239
- tComponent<SuiteCoreStringKeyValue>(
240
- SuiteCoreComponentId,
241
- SuiteCoreStringKey.Validation_InvalidEmail
242
- )
243
- )
244
- .test('disallowed-domain', '', function (value) {
245
- if (!value || !disallowedEmailDomains?.length) return true;
246
- const atIndex = value.lastIndexOf('@');
247
- if (atIndex <= 0) return true;
248
- const domain = value.slice(atIndex + 1).toLowerCase();
249
- const blocked = disallowedEmailDomains.find(
250
- (d) => d.toLowerCase() === domain
251
- );
252
- if (!blocked) return true;
253
- return this.createError({
254
- message: tComponent<SuiteCoreStringKeyValue>(
255
- SuiteCoreComponentId,
256
- SuiteCoreStringKey.Validation_EmailDomainNotAllowedTemplate,
257
- { domain: blocked }
258
- ),
259
- });
260
- })
261
- .required(
262
- tComponent<SuiteCoreStringKeyValue>(
263
- SuiteCoreComponentId,
264
- SuiteCoreStringKey.Validation_Required
265
- )
266
- ),
267
- displayName: !Constants.EnableDisplayName
268
- ? Yup.string().strip()
269
- : displayNameValidation ||
270
- Yup.string()
271
- .min(
272
- Constants.DisplayNameMinLength,
273
- tComponent<SuiteCoreStringKeyValue>(
274
- SuiteCoreComponentId,
275
- SuiteCoreStringKey.Validation_DisplayNameMinLengthTemplate
276
- )
277
- )
278
- .max(
279
- Constants.DisplayNameMaxLength,
280
- tComponent<SuiteCoreStringKeyValue>(
281
- SuiteCoreComponentId,
282
- SuiteCoreStringKey.Validation_DisplayNameMaxLengthTemplate
283
- )
284
- )
285
- .matches(
286
- Constants.DisplayNameRegex,
287
- tComponent<SuiteCoreStringKeyValue>(
288
- SuiteCoreComponentId,
289
- SuiteCoreStringKey.Validation_DisplayNameRegexErrorTemplate
290
- )
291
- )
292
- .required(
293
- tComponent<SuiteCoreStringKeyValue>(
294
- SuiteCoreComponentId,
295
- SuiteCoreStringKey.Validation_Required
296
- )
297
- ),
298
- directChallenge: Yup.boolean(),
299
- timezone:
300
- timezoneValidation ||
301
- Yup.string()
302
- .required(
303
- tComponent<SuiteCoreStringKeyValue>(
304
- SuiteCoreComponentId,
305
- SuiteCoreStringKey.Validation_TimezoneRequired
306
- )
307
- )
308
- .oneOf(
309
- timezones,
310
- tComponent<SuiteCoreStringKeyValue>(
311
- SuiteCoreComponentId,
312
- SuiteCoreStringKey.Validation_TimezoneInvalid
313
- )
314
- ),
315
- password:
316
- passwordValidation ||
317
- Yup.string()
318
- .matches(
319
- Constants.PasswordRegex,
320
- tComponent<SuiteCoreStringKeyValue>(
321
- SuiteCoreComponentId,
322
- SuiteCoreStringKey.Validation_PasswordRegexErrorTemplate
323
- )
324
- )
325
- .min(
326
- 8,
327
- tComponent<SuiteCoreStringKeyValue>(
328
- SuiteCoreComponentId,
329
- SuiteCoreStringKey.Validation_PasswordMinLengthTemplate
330
- )
331
- )
332
- .required(
333
- tComponent<SuiteCoreStringKeyValue>(
334
- SuiteCoreComponentId,
335
- SuiteCoreStringKey.Validation_Required
336
- )
337
- ),
338
- confirmPassword:
339
- confirmPasswordValidation ||
340
- Yup.string()
341
- .oneOf(
342
- [Yup.ref('password')],
343
- tComponent<SuiteCoreStringKeyValue>(
344
- SuiteCoreComponentId,
345
- SuiteCoreStringKey.Validation_PasswordMatch
346
- )
347
- )
348
- .required(
349
- tComponent<SuiteCoreStringKeyValue>(
350
- SuiteCoreComponentId,
351
- SuiteCoreStringKey.Validation_Required
352
- )
353
- ),
354
- mnemonic: Yup.string()
355
- .optional()
356
- .test(
357
- 'mnemonic-format',
358
- tComponent<SuiteCoreStringKeyValue>(
359
- SuiteCoreComponentId,
360
- SuiteCoreStringKey.Validation_MnemonicRegex
361
- ),
362
- (value) => {
363
- if (!value || value.trim() === '') return true;
364
- return Constants.MnemonicRegex.test(value.trim());
365
- }
366
- ),
367
- };
368
-
369
- const formik = useFormik<RegisterFormValues>({
370
- initialValues: {
371
- username: '',
372
- email: '',
373
- ...(Constants.EnableDisplayName ? { displayName: '' } : {}),
374
- timezone: getInitialTimezone(),
375
- password: '',
376
- confirmPassword: '',
377
- directChallenge: false,
378
- mnemonic: '',
379
- ...additionalInitialValues,
380
- },
381
- enableReinitialize: true,
382
- validationSchema: Yup.object({
383
- username: validation.username,
384
- email: validation.email,
385
- ...(Constants.EnableDisplayName
386
- ? { displayName: validation.displayName }
387
- : {}),
388
- timezone: validation.timezone,
389
- ...(usePassword
390
- ? {
391
- password: validation.password,
392
- confirmPassword: validation.confirmPassword,
393
- }
394
- : {}),
395
- ...(showMnemonicInput ? { mnemonic: validation.mnemonic } : {}),
396
- ...additionalValidation,
397
- }),
398
- onSubmit: async (values, { setSubmitting, setFieldError, setTouched }) => {
399
- setRegistering(true);
400
- setApiErrors({});
401
- try {
402
- const effectiveMnemonic = !showMnemonicInput
403
- ? autoMnemonic
404
- : (values.mnemonic?.trim() || autoMnemonic);
405
- const registerResult = await onSubmit(
406
- { ...values, mnemonic: effectiveMnemonic },
407
- usePassword
408
- );
409
-
410
- if ('success' in registerResult && registerResult.success) {
411
- setRegistrationSuccess(true);
412
- if (registerResult.mnemonic) {
413
- setMnemonic(registerResult.mnemonic);
414
- }
415
- if (enableTotpSetup && onTotpSetup) {
416
- setShowTotpSetup(true);
417
- // Fire-and-forget: initiate TOTP setup in background
418
- // The setup state is managed by initiateTotpSetup
419
- initiateTotpSetup();
420
- }
421
- } else {
422
- setRegistrationSuccess(false);
423
- const newApiErrors: Record<string, string> = {};
424
- const fieldsToTouch: Record<string, boolean> = {};
425
-
426
- if ('field' in registerResult && registerResult.field) {
427
- setFieldError(registerResult.field, registerResult.error);
428
- fieldsToTouch[registerResult.field] = true;
429
- }
430
-
431
- if ('errors' in registerResult && registerResult.errors) {
432
- registerResult.errors.forEach((err) => {
433
- if (err.path && err.msg) {
434
- setFieldError(err.path, err.msg);
435
- fieldsToTouch[err.path] = true;
436
- }
437
- });
438
- }
439
-
440
- if (
441
- 'error' in registerResult &&
442
- registerResult.error &&
443
- !Object.keys(newApiErrors).length
444
- ) {
445
- newApiErrors.general = registerResult.error;
446
- }
447
-
448
- setApiErrors(newApiErrors);
449
- setTouched(fieldsToTouch, false);
450
- }
451
- } catch (err) {
452
- console.error('[RegisterForm] onSubmit error:', err);
453
- setApiErrors({
454
- general: err instanceof Error ? err.message : String(err),
455
- });
456
- }
457
- setSubmitting(false);
458
- setRegistering(false);
459
- },
460
- });
461
-
462
- return (
463
- <Container maxWidth="sm">
464
- <Box
465
- sx={{
466
- mt: 8,
467
- display: 'flex',
468
- flexDirection: 'column',
469
- alignItems: 'center',
470
- }}
471
- >
472
- <Typography variant="h4" component="h1" gutterBottom>
473
- {labels.title ||
474
- tComponent<SuiteCoreStringKeyValue>(
475
- SuiteCoreComponentId,
476
- SuiteCoreStringKey.Common_Registration
477
- )}
478
- </Typography>
479
-
480
- {mnemonic || registrationSuccess ? (
481
- <Box sx={{ mt: 2, width: '100%' }}>
482
- {showTotpSetup && !totpSetupComplete ? (
483
- <Box>
484
- <Typography
485
- variant="h5"
486
- component="h2"
487
- gutterBottom
488
- fontWeight="bold"
489
- sx={{ textAlign: 'center' }}
490
- >
491
- {labels.totpSetupTitle || 'Set Up Two-Factor Authentication'}
492
- </Typography>
493
-
494
- {totpSetupLoading && (
495
- <Typography
496
- variant="body2"
497
- color="text.secondary"
498
- sx={{ textAlign: 'center', mt: 2 }}
499
- >
500
- Loading TOTP setup...
501
- </Typography>
502
- )}
503
-
504
- {totpSetupError && (
505
- <Alert severity="error" sx={{ mt: 2, mb: 2 }}>
506
- {totpSetupError}
507
- </Alert>
508
- )}
509
-
510
- {totpProvisioningUri && totpSecret && (
511
- <TotpSetupForm
512
- provisioningUri={totpProvisioningUri}
513
- secret={totpSecret}
514
- onConfirm={handleTotpConfirm}
515
- />
516
- )}
517
-
518
- <Box sx={{ textAlign: 'center', mt: 2 }}>
519
- <Button
520
- variant="text"
521
- onClick={handleSkipTotp}
522
- data-testid="skip-totp-button"
523
- >
524
- {labels.totpSkipButton || 'Skip'}
525
- </Button>
526
- </Box>
527
- </Box>
528
- ) : totpSetupComplete ? (
529
- <Box>
530
- <Alert severity="success" sx={{ mt: 2, mb: 2 }}>
531
- {labels.totpSetupSuccess ||
532
- 'Two-factor authentication has been enabled successfully.'}
533
- </Alert>
534
- {mnemonic ? (
535
- <>
536
- <Box sx={{ textAlign: 'center', mb: 3 }}>
537
- <Typography
538
- variant="h5"
539
- component="h2"
540
- gutterBottom
541
- fontWeight="bold"
542
- >
543
- {labels.successTitle ||
544
- tComponent<SuiteCoreStringKeyValue>(
545
- SuiteCoreComponentId,
546
- SuiteCoreStringKey.Registration_SuccessTitle
547
- )}
548
- </Typography>
549
- <Typography variant="body2" color="text.secondary">
550
- {labels.mnemonicSuccess ||
551
- tComponent<SuiteCoreStringKeyValue>(
552
- SuiteCoreComponentId,
553
- SuiteCoreStringKey.Registration_MnemonicSuccess
554
- )}
555
- </Typography>
556
- </Box>
557
-
558
- <Box
559
- sx={{
560
- display: 'grid',
561
- gridTemplateColumns: 'repeat(3, 1fr)',
562
- gap: 1.5,
563
- mb: 3,
564
- }}
565
- >
566
- {mnemonic.split(/\s+/).map((word, i) => (
567
- <Box
568
- key={i}
569
- sx={{
570
- display: 'flex',
571
- alignItems: 'center',
572
- justifyContent: 'center',
573
- gap: 0.5,
574
- py: 1,
575
- px: 1.5,
576
- borderRadius: 2,
577
- border: '1px solid',
578
- borderColor: 'divider',
579
- bgcolor: 'action.hover',
580
- }}
581
- >
582
- <Typography
583
- variant="caption"
584
- color="text.secondary"
585
- sx={{ minWidth: '1.5em', textAlign: 'right' }}
586
- >
587
- {i + 1}.
588
- </Typography>
589
- <Typography variant="body2" fontWeight="bold">
590
- {word}
591
- </Typography>
592
- </Box>
593
- ))}
594
- </Box>
595
-
596
- <Box sx={{ textAlign: 'center' }}>
597
- <Button
598
- variant="contained"
599
- color="primary"
600
- size="large"
601
- href="/verify-email"
602
- sx={{ borderRadius: 6, px: 4 }}
603
- >
604
- {labels.savedRecoveryPhrase ||
605
- labels.proceedToLogin ||
606
- tComponent<SuiteCoreStringKeyValue>(
607
- SuiteCoreComponentId,
608
- SuiteCoreStringKey.Registration_SavedRecoveryPhrase
609
- )}
610
- </Button>
611
- </Box>
612
-
613
- <Alert severity="success" sx={{ mt: 3 }}>
614
- <AlertTitle>
615
- {tComponent<SuiteCoreStringKeyValue>(
616
- SuiteCoreComponentId,
617
- SuiteCoreStringKey.Registration_SuccessTitle
618
- )}
619
- </AlertTitle>
620
- <Typography variant="body2" component="div">
621
- {tComponent<SuiteCoreStringKeyValue>(
622
- SuiteCoreComponentId,
623
- SuiteCoreStringKey.Registration_Success
624
- )}
625
- </Typography>
626
- </Alert>
627
- </>
628
- ) : (
629
- <Alert severity="success" sx={{ mt: 2, mb: 2 }}>
630
- <AlertTitle>
631
- {labels.successTitle ||
632
- tComponent<SuiteCoreStringKeyValue>(
633
- SuiteCoreComponentId,
634
- SuiteCoreStringKey.Registration_SuccessTitle
635
- )}
636
- </AlertTitle>
637
- <Typography variant="body2" component="div">
638
- {tComponent<SuiteCoreStringKeyValue>(
639
- SuiteCoreComponentId,
640
- SuiteCoreStringKey.Registration_Success
641
- )}
642
- <Box sx={{ textAlign: 'center', mt: 1 }}>
643
- <Link href="/login">
644
- {labels.proceedToLogin ||
645
- tComponent<SuiteCoreStringKeyValue>(
646
- SuiteCoreComponentId,
647
- SuiteCoreStringKey.ProceedToLogin
648
- )}
649
- </Link>
650
- </Box>
651
- </Typography>
652
- </Alert>
653
- )}
654
- </Box>
655
- ) : (
656
- <>
657
- {mnemonic ? (
658
- <>
659
- <Box sx={{ textAlign: 'center', mb: 3 }}>
660
- <Typography
661
- variant="h5"
662
- component="h2"
663
- gutterBottom
664
- fontWeight="bold"
665
- >
666
- {labels.successTitle ||
667
- tComponent<SuiteCoreStringKeyValue>(
668
- SuiteCoreComponentId,
669
- SuiteCoreStringKey.Registration_SuccessTitle
670
- )}
671
- </Typography>
672
- <Typography variant="body2" color="text.secondary">
673
- {labels.mnemonicSuccess ||
674
- tComponent<SuiteCoreStringKeyValue>(
675
- SuiteCoreComponentId,
676
- SuiteCoreStringKey.Registration_MnemonicSuccess
677
- )}
678
- </Typography>
679
- </Box>
680
-
681
- <Box
682
- sx={{
683
- display: 'grid',
684
- gridTemplateColumns: 'repeat(3, 1fr)',
685
- gap: 1.5,
686
- mb: 3,
687
- }}
688
- >
689
- {mnemonic.split(/\s+/).map((word, i) => (
690
- <Box
691
- key={i}
692
- sx={{
693
- display: 'flex',
694
- alignItems: 'center',
695
- justifyContent: 'center',
696
- gap: 0.5,
697
- py: 1,
698
- px: 1.5,
699
- borderRadius: 2,
700
- border: '1px solid',
701
- borderColor: 'divider',
702
- bgcolor: 'action.hover',
703
- }}
704
- >
705
- <Typography
706
- variant="caption"
707
- color="text.secondary"
708
- sx={{ minWidth: '1.5em', textAlign: 'right' }}
709
- >
710
- {i + 1}.
711
- </Typography>
712
- <Typography variant="body2" fontWeight="bold">
713
- {word}
714
- </Typography>
715
- </Box>
716
- ))}
717
- </Box>
718
-
719
- <Box sx={{ textAlign: 'center' }}>
720
- <Button
721
- variant="contained"
722
- color="primary"
723
- size="large"
724
- href="/verify-email"
725
- sx={{ borderRadius: 6, px: 4 }}
726
- >
727
- {labels.savedRecoveryPhrase ||
728
- labels.proceedToLogin ||
729
- tComponent<SuiteCoreStringKeyValue>(
730
- SuiteCoreComponentId,
731
- SuiteCoreStringKey.Registration_SavedRecoveryPhrase
732
- )}
733
- </Button>
734
- </Box>
735
-
736
- <Alert severity="success" sx={{ mt: 3 }}>
737
- <AlertTitle>
738
- {tComponent<SuiteCoreStringKeyValue>(
739
- SuiteCoreComponentId,
740
- SuiteCoreStringKey.Registration_SuccessTitle
741
- )}
742
- </AlertTitle>
743
- <Typography variant="body2" component="div">
744
- {tComponent<SuiteCoreStringKeyValue>(
745
- SuiteCoreComponentId,
746
- SuiteCoreStringKey.Registration_Success
747
- )}
748
- </Typography>
749
- </Alert>
750
- </>
751
- ) : (
752
- <Alert severity="success" sx={{ mt: 2, mb: 2 }}>
753
- <AlertTitle>
754
- {labels.successTitle ||
755
- tComponent<SuiteCoreStringKeyValue>(
756
- SuiteCoreComponentId,
757
- SuiteCoreStringKey.Registration_SuccessTitle
758
- )}
759
- </AlertTitle>
760
- <Typography variant="body2" component="div">
761
- {tComponent<SuiteCoreStringKeyValue>(
762
- SuiteCoreComponentId,
763
- SuiteCoreStringKey.Registration_Success
764
- )}
765
- <Box sx={{ textAlign: 'center', mt: 1 }}>
766
- <Link href="/login">
767
- {labels.proceedToLogin ||
768
- tComponent<SuiteCoreStringKeyValue>(
769
- SuiteCoreComponentId,
770
- SuiteCoreStringKey.ProceedToLogin
771
- )}
772
- </Link>
773
- </Box>
774
- </Typography>
775
- </Alert>
776
- )}
777
- </>
778
- )}
779
- </Box>
780
- ) : (
781
- <Box
782
- component="form"
783
- onSubmit={formik.handleSubmit}
784
- sx={{ mt: 1, width: '100%' }}
785
- >
786
- <TextField
787
- fullWidth
788
- id="username"
789
- name="username"
790
- label={
791
- labels.username ||
792
- tComponent<SuiteCoreStringKeyValue>(
793
- SuiteCoreComponentId,
794
- SuiteCoreStringKey.Common_Username
795
- )
796
- }
797
- value={formik.values.username}
798
- onChange={formik.handleChange}
799
- onBlur={formik.handleBlur}
800
- error={Boolean(
801
- formik.touched.username &&
802
- (formik.errors.username || apiErrors.username)
803
- )}
804
- helperText={
805
- formik.touched.username &&
806
- (formik.errors.username || apiErrors.username)
807
- }
808
- margin="normal"
809
- />
810
- <TextField
811
- fullWidth
812
- id="email"
813
- name="email"
814
- label={
815
- labels.email ||
816
- tComponent<SuiteCoreStringKeyValue>(
817
- SuiteCoreComponentId,
818
- SuiteCoreStringKey.Common_Email
819
- )
820
- }
821
- value={formik.values.email}
822
- onChange={formik.handleChange}
823
- onBlur={formik.handleBlur}
824
- error={Boolean(
825
- formik.touched.email && (formik.errors.email || apiErrors.email)
826
- )}
827
- helperText={
828
- formik.touched.email && (formik.errors.email || apiErrors.email)
829
- }
830
- margin="normal"
831
- />
832
- {Constants.EnableDisplayName && (
833
- <TextField
834
- fullWidth
835
- id="displayName"
836
- name="displayName"
837
- label={
838
- labels.displayName ||
839
- tComponent<SuiteCoreStringKeyValue>(
840
- SuiteCoreComponentId,
841
- SuiteCoreStringKey.Common_DisplayName
842
- )
843
- }
844
- value={formik.values.displayName}
845
- onChange={formik.handleChange}
846
- onBlur={formik.handleBlur}
847
- error={Boolean(
848
- formik.touched.displayName &&
849
- (formik.errors.displayName || apiErrors.displayName)
850
- )}
851
- helperText={
852
- formik.touched.displayName &&
853
- (formik.errors.displayName || apiErrors.displayName)
854
- }
855
- margin="normal"
856
- />
857
- )}
858
- <FormControl fullWidth margin="normal">
859
- <InputLabel id="timezone-label">
860
- {labels.timezone ||
861
- tComponent<SuiteCoreStringKeyValue>(
862
- SuiteCoreComponentId,
863
- SuiteCoreStringKey.Common_Timezone
864
- )}
865
- </InputLabel>
866
- <Select
867
- labelId="timezone-label"
868
- id="timezone"
869
- name="timezone"
870
- value={formik.values.timezone}
871
- onChange={formik.handleChange}
872
- onBlur={formik.handleBlur}
873
- error={
874
- formik.touched.timezone && Boolean(formik.errors.timezone)
875
- }
876
- label={
877
- labels.timezone ||
878
- tComponent<SuiteCoreStringKeyValue>(
879
- SuiteCoreComponentId,
880
- SuiteCoreStringKey.Common_Timezone
881
- )
882
- }
883
- >
884
- {timezones.map((tz) => (
885
- <MenuItem key={tz} value={tz}>
886
- {tz}
887
- </MenuItem>
888
- ))}
889
- </Select>
890
- {formik.touched.timezone &&
891
- (formik.errors.timezone || apiErrors.timezone) && (
892
- <Typography color="error" variant="caption">
893
- {formik.errors.timezone || apiErrors.timezone}
894
- </Typography>
895
- )}
896
- </FormControl>
897
-
898
- <Box sx={{ display: 'flex', alignItems: 'center', mt: 2 }}>
899
- <Button
900
- variant="text"
901
- onClick={() => setUsePassword(!usePassword)}
902
- >
903
- {usePassword
904
- ? labels.useMnemonic ||
905
- tComponent<SuiteCoreStringKeyValue>(
906
- SuiteCoreComponentId,
907
- SuiteCoreStringKey.Common_UseMnemonic
908
- )
909
- : labels.usePassword ||
910
- tComponent<SuiteCoreStringKeyValue>(
911
- SuiteCoreComponentId,
912
- SuiteCoreStringKey.Common_UsePassword
913
- )}
914
- </Button>
915
- </Box>
916
-
917
- {usePassword && (
918
- <>
919
- <TextField
920
- fullWidth
921
- id="password"
922
- name="password"
923
- label={
924
- labels.password ||
925
- tComponent<SuiteCoreStringKeyValue>(
926
- SuiteCoreComponentId,
927
- SuiteCoreStringKey.Common_Password
928
- )
929
- }
930
- type="password"
931
- value={formik.values.password}
932
- onChange={formik.handleChange}
933
- onBlur={formik.handleBlur}
934
- error={Boolean(
935
- formik.touched.password && formik.errors.password
936
- )}
937
- helperText={formik.touched.password && formik.errors.password}
938
- margin="normal"
939
- />
940
- <TextField
941
- fullWidth
942
- id="confirmPassword"
943
- name="confirmPassword"
944
- label={
945
- labels.confirmPassword ||
946
- tComponent<SuiteCoreStringKeyValue>(
947
- SuiteCoreComponentId,
948
- SuiteCoreStringKey.Common_ConfirmNewPassword
949
- )
950
- }
951
- type="password"
952
- value={formik.values.confirmPassword}
953
- onChange={formik.handleChange}
954
- onBlur={formik.handleBlur}
955
- error={Boolean(
956
- formik.touched.confirmPassword &&
957
- formik.errors.confirmPassword
958
- )}
959
- helperText={
960
- formik.touched.confirmPassword &&
961
- formik.errors.confirmPassword
962
- }
963
- margin="normal"
964
- />
965
- <Alert severity="info" sx={{ mt: 1 }}>
966
- {labels?.passwordAuthInfo ||
967
- tComponent<SuiteCoreStringKeyValue>(
968
- SuiteCoreComponentId,
969
- SuiteCoreStringKey.Registration_PasswordAuthInfo
970
- )}
971
- </Alert>
972
- </>
973
- )}
974
-
975
- <FormControl fullWidth margin="normal">
976
- <FormControlLabel
977
- control={
978
- <Checkbox
979
- id="directChallenge"
980
- name="directChallenge"
981
- checked={formik.values.directChallenge || false}
982
- onChange={formik.handleChange}
983
- />
984
- }
985
- label={tComponent<SuiteCoreStringKeyValue>(
986
- SuiteCoreComponentId,
987
- SuiteCoreStringKey.Registration_DirectChallengeLabel
988
- )}
989
- />
990
- <FormHelperText>
991
- {tComponent<SuiteCoreStringKeyValue>(
992
- SuiteCoreComponentId,
993
- SuiteCoreStringKey.Registration_DirectChallengeHelper
994
- )}
995
- </FormHelperText>
996
- </FormControl>
997
-
998
- {additionalFields && additionalFields(formik, usePassword)}
999
-
1000
- {!showMnemonicInput && (
1001
- <Box sx={{ mt: 2 }}>
1002
- <Typography variant="subtitle2" gutterBottom>
1003
- {labels.mnemonic || 'Your Recovery Phrase — save this before registering'}
1004
- </Typography>
1005
- <Box
1006
- sx={{
1007
- display: 'grid',
1008
- gridTemplateColumns: 'repeat(3, 1fr)',
1009
- gap: 1,
1010
- mb: 1,
1011
- }}
1012
- >
1013
- {autoMnemonic.split(/\s+/).map((word, i) => (
1014
- <Box
1015
- key={i}
1016
- sx={{
1017
- display: 'flex',
1018
- alignItems: 'center',
1019
- gap: 0.5,
1020
- py: 0.5,
1021
- px: 1,
1022
- borderRadius: 1,
1023
- border: '1px solid',
1024
- borderColor: 'divider',
1025
- bgcolor: 'action.hover',
1026
- }}
1027
- >
1028
- <Typography
1029
- variant="caption"
1030
- color="text.secondary"
1031
- sx={{ minWidth: '1.5em', textAlign: 'right' }}
1032
- >
1033
- {i + 1}.
1034
- </Typography>
1035
- <Typography variant="body2" fontWeight="bold">
1036
- {word}
1037
- </Typography>
1038
- </Box>
1039
- ))}
1040
- </Box>
1041
- <Alert severity="warning" sx={{ mb: 1 }}>
1042
- Write down these words in order. You will need them to recover your account.
1043
- </Alert>
1044
- <Button
1045
- variant="text"
1046
- size="small"
1047
- onClick={() => setAutoMnemonic(generateMnemonic(256))}
1048
- >
1049
- Regenerate
1050
- </Button>
1051
- </Box>
1052
- )}
1053
-
1054
- <Box sx={{ mt: 1 }}>
1055
- <Button
1056
- variant="text"
1057
- size="small"
1058
- onClick={() => setShowMnemonicInput(!showMnemonicInput)}
1059
- aria-expanded={showMnemonicInput}
1060
- aria-controls="mnemonic-input"
1061
- >
1062
- {showMnemonicInput
1063
- ? tComponent<SuiteCoreStringKeyValue>(
1064
- SuiteCoreComponentId,
1065
- SuiteCoreStringKey.Common_ClearMnemonic
1066
- )
1067
- : tComponent<SuiteCoreStringKeyValue>(
1068
- SuiteCoreComponentId,
1069
- SuiteCoreStringKey.Common_Mnemonic
1070
- )}
1071
- </Button>
1072
- </Box>
1073
-
1074
- {showMnemonicInput && (
1075
- <TextField
1076
- fullWidth
1077
- id="mnemonic-input"
1078
- name="mnemonic"
1079
- label={
1080
- labels.mnemonic ||
1081
- tComponent<SuiteCoreStringKeyValue>(
1082
- SuiteCoreComponentId,
1083
- SuiteCoreStringKey.Common_Mnemonic
1084
- )
1085
- }
1086
- value={formik.values.mnemonic}
1087
- onChange={formik.handleChange}
1088
- onBlur={formik.handleBlur}
1089
- error={Boolean(
1090
- formik.touched.mnemonic &&
1091
- (formik.errors.mnemonic || apiErrors.mnemonic)
1092
- )}
1093
- helperText={
1094
- formik.touched.mnemonic &&
1095
- (formik.errors.mnemonic || apiErrors.mnemonic)
1096
- }
1097
- margin="normal"
1098
- multiline
1099
- minRows={2}
1100
- />
1101
- )}
1102
-
1103
- {apiErrors.general && (
1104
- <Alert severity="error" sx={{ mt: 2, mb: 2 }}>
1105
- {apiErrors.general}
1106
- </Alert>
1107
- )}
1108
-
1109
- <Button
1110
- type="submit"
1111
- fullWidth
1112
- variant="contained"
1113
- color="primary"
1114
- sx={{ mt: 3, mb: 2 }}
1115
- disabled={formik.isSubmitting}
1116
- >
1117
- {registering
1118
- ? labels.registering ||
1119
- tComponent<SuiteCoreStringKeyValue>(
1120
- SuiteCoreComponentId,
1121
- SuiteCoreStringKey.Registration_Registering
1122
- )
1123
- : labels.register ||
1124
- tComponent<SuiteCoreStringKeyValue>(
1125
- SuiteCoreComponentId,
1126
- SuiteCoreStringKey.Registration_RegisterButton
1127
- )}
1128
- </Button>
1129
-
1130
- {registering && (
1131
- <Alert
1132
- severity="success"
1133
- sx={{ mt: 2, mb: 2, whiteSpace: 'pre-wrap' }}
1134
- >
1135
- <AlertTitle>
1136
- {labels.registering ||
1137
- tComponent<SuiteCoreStringKeyValue>(
1138
- SuiteCoreComponentId,
1139
- SuiteCoreStringKey.Registration_Registering
1140
- )}
1141
- </AlertTitle>
1142
- <Typography variant="body2" component="div">
1143
- {tComponent<SuiteCoreStringKeyValue>(
1144
- SuiteCoreComponentId,
1145
- SuiteCoreStringKey.Registration_RegisteringMessage
1146
- )}
1147
- </Typography>
1148
- </Alert>
1149
- )}
1150
-
1151
- {!registrationSuccess && (
1152
- <Box sx={{ textAlign: 'center' }}>
1153
- <Link href="/login" variant="body2">
1154
- {labels.loginLink ||
1155
- tComponent<SuiteCoreStringKeyValue>(
1156
- SuiteCoreComponentId,
1157
- SuiteCoreStringKey.Registration_LoginLink
1158
- )}
1159
- </Link>
1160
- </Box>
1161
- )}
1162
- </Box>
1163
- )}
1164
- </Box>
1165
- </Container>
1166
- );
1167
- };
1168
-
1169
- export default RegisterForm;