@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,904 +0,0 @@
1
- import { CurrencyCode } from '@digitaldefiance/i18n-lib';
2
- import {
3
- SuiteCoreComponentId,
4
- SuiteCoreStringKey,
5
- SuiteCoreStringKeyValue,
6
- } from '@digitaldefiance/suite-core-lib';
7
- import {
8
- Alert,
9
- Box,
10
- Button,
11
- Container,
12
- Divider,
13
- FormControl,
14
- FormControlLabel,
15
- InputLabel,
16
- MenuItem,
17
- Select,
18
- Switch,
19
- TextField,
20
- Typography,
21
- } from '@mui/material';
22
- import { useFormik } from 'formik';
23
- import moment from 'moment-timezone';
24
- import { FC, useCallback, useEffect, useMemo, useState } from 'react';
25
- import * as Yup from 'yup';
26
- import { useI18n } from '../contexts';
27
- import { Constants } from '@digitaldefiance/suite-core-lib';
28
- import { TotpSetupForm } from './TotpSetupForm';
29
-
30
- export interface UserSettingsFormValues {
31
- email: string;
32
- timezone: string;
33
- siteLanguage: string;
34
- currency: string;
35
- darkMode: boolean;
36
- directChallenge: boolean;
37
- displayName?: string;
38
- [key: string]: string | boolean | undefined;
39
- }
40
-
41
- export interface UserSettingsFormProps {
42
- initialValues: UserSettingsFormValues;
43
- onSubmit: (values: UserSettingsFormValues) => Promise<
44
- | { success: boolean; message: string }
45
- | {
46
- error: string;
47
- errorType?: string;
48
- field?: string;
49
- errors?: Array<{ path: string; msg: string }>;
50
- }
51
- >;
52
- languages: Array<{ code: string; label: string }>;
53
- emailValidation?: Yup.StringSchema;
54
- timezoneValidation?: Yup.StringSchema;
55
- siteLanguageValidation?: Yup.StringSchema;
56
- currencyValidation?: Yup.StringSchema;
57
- darkModeValidation?: Yup.BooleanSchema;
58
- directChallengeValidation?: Yup.BooleanSchema;
59
- displayNameValidation?: Yup.StringSchema;
60
- additionalFields?: (
61
- formik: ReturnType<typeof useFormik<UserSettingsFormValues>>
62
- ) => React.ReactNode;
63
- additionalInitialValues?: Record<string, string | boolean>;
64
- additionalValidation?: Record<string, Yup.Schema>;
65
- /**
66
- * Current TOTP 2FA status. When undefined (not passed), no TOTP controls are rendered.
67
- * When false, an "Enable 2FA" button is shown. When true, "Disable 2FA" and "Reset 2FA" buttons are shown.
68
- */
69
- totpEnabled?: boolean;
70
- /**
71
- * Called to initiate TOTP setup (e.g. AuthService.setupTotp()).
72
- * Must return provisioning URI and secret on success, or an error.
73
- */
74
- onTotpSetup?: () => Promise<
75
- | { provisioningUri: string; secret: string }
76
- | { error: string }
77
- >;
78
- /**
79
- * Called to confirm TOTP setup with a 6-digit code (e.g. AuthService.confirmTotp()).
80
- */
81
- onTotpConfirm?: (
82
- code: string
83
- ) => Promise<{ success: boolean } | { error: string }>;
84
- /**
85
- * Called to disable TOTP with a 6-digit code (e.g. AuthService.disableTotp()).
86
- */
87
- onTotpDisable?: (
88
- code: string
89
- ) => Promise<{ success: boolean } | { error: string }>;
90
- /**
91
- * Called to reset TOTP with a 6-digit code (e.g. AuthService.resetTotp()).
92
- * Returns new provisioning URI and secret on success.
93
- */
94
- onTotpReset?: (
95
- code: string
96
- ) => Promise<
97
- | { provisioningUri: string; secret: string }
98
- | { error: string }
99
- >;
100
- labels?: {
101
- title?: string;
102
- email?: string;
103
- emailHelper?: string;
104
- timezone?: string;
105
- siteLanguage?: string;
106
- currency?: string;
107
- darkMode?: string;
108
- directChallenge?: string;
109
- directChallengeHelper?: string;
110
- displayName?: string;
111
- saving?: string;
112
- save?: string;
113
- successMessage?: string;
114
- totpSectionTitle?: string;
115
- totpStatusEnabled?: string;
116
- totpStatusDisabled?: string;
117
- totpEnableButton?: string;
118
- totpDisableButton?: string;
119
- totpResetButton?: string;
120
- totpDisableCodeLabel?: string;
121
- totpDisableSubmitButton?: string;
122
- totpResetCodeLabel?: string;
123
- totpResetSubmitButton?: string;
124
- totpCancelButton?: string;
125
- totpError?: string;
126
- };
127
- }
128
-
129
- export const UserSettingsForm: FC<UserSettingsFormProps> = ({
130
- initialValues,
131
- onSubmit,
132
- languages,
133
- emailValidation,
134
- timezoneValidation,
135
- siteLanguageValidation,
136
- currencyValidation,
137
- darkModeValidation,
138
- directChallengeValidation,
139
- displayNameValidation,
140
- additionalFields,
141
- additionalInitialValues = {},
142
- additionalValidation = {},
143
- totpEnabled,
144
- onTotpSetup,
145
- onTotpConfirm,
146
- onTotpDisable,
147
- onTotpReset,
148
- labels = {},
149
- }) => {
150
- const { tComponent } = useI18n();
151
- const [apiErrors, setApiErrors] = useState<Record<string, string>>({});
152
- const [saving, setSaving] = useState(false);
153
- const [successMessage, setSuccessMessage] = useState<string | null>(null);
154
-
155
- // TOTP management state
156
- const [totpStatus, setTotpStatus] = useState<boolean | undefined>(totpEnabled);
157
-
158
- // Sync totpStatus when the totpEnabled prop transitions from undefined to a
159
- // concrete value (i.e. after the wrapper fetches it asynchronously).
160
- useEffect(() => {
161
- if (totpEnabled !== undefined) {
162
- setTotpStatus(totpEnabled);
163
- }
164
- }, [totpEnabled]);
165
- const [showTotpSetup, setShowTotpSetup] = useState(false);
166
- const [totpProvisioningUri, setTotpProvisioningUri] = useState<string | null>(null);
167
- const [totpSecret, setTotpSecret] = useState<string | null>(null);
168
- const [totpSetupLoading, setTotpSetupLoading] = useState(false);
169
- const [totpError, setTotpError] = useState<string | null>(null);
170
- const [showDisablePrompt, setShowDisablePrompt] = useState(false);
171
- const [disableCode, setDisableCode] = useState('');
172
- const [disableSubmitting, setDisableSubmitting] = useState(false);
173
- const [showResetPrompt, setShowResetPrompt] = useState(false);
174
- const [resetCode, setResetCode] = useState('');
175
- const [resetSubmitting, setResetSubmitting] = useState(false);
176
-
177
- const handleEnableTotp = useCallback(async () => {
178
- if (!onTotpSetup) return;
179
- setTotpSetupLoading(true);
180
- setTotpError(null);
181
- try {
182
- const result = await onTotpSetup();
183
- if ('error' in result) {
184
- setTotpError(result.error);
185
- } else {
186
- setTotpProvisioningUri(result.provisioningUri);
187
- setTotpSecret(result.secret);
188
- setShowTotpSetup(true);
189
- }
190
- } catch {
191
- setTotpError(labels.totpError || 'Failed to set up two-factor authentication');
192
- } finally {
193
- setTotpSetupLoading(false);
194
- }
195
- }, [onTotpSetup, labels.totpError]);
196
-
197
- const handleTotpConfirm = useCallback(
198
- async (code: string): Promise<{ success: boolean } | { error: string }> => {
199
- if (!onTotpConfirm) {
200
- return { error: 'TOTP confirmation is not available' };
201
- }
202
- const result = await onTotpConfirm(code);
203
- if ('success' in result && result.success) {
204
- setTotpStatus(true);
205
- setShowTotpSetup(false);
206
- setTotpProvisioningUri(null);
207
- setTotpSecret(null);
208
- }
209
- return result;
210
- },
211
- [onTotpConfirm]
212
- );
213
-
214
- const handleDisableTotp = useCallback(async () => {
215
- if (!onTotpDisable || !disableCode) return;
216
- setDisableSubmitting(true);
217
- setTotpError(null);
218
- try {
219
- const result = await onTotpDisable(disableCode);
220
- if ('error' in result) {
221
- setTotpError(result.error);
222
- } else {
223
- setTotpStatus(false);
224
- setShowDisablePrompt(false);
225
- setDisableCode('');
226
- }
227
- } catch {
228
- setTotpError(labels.totpError || 'Failed to disable two-factor authentication');
229
- } finally {
230
- setDisableSubmitting(false);
231
- }
232
- }, [onTotpDisable, disableCode, labels.totpError]);
233
-
234
- const handleResetTotp = useCallback(async () => {
235
- if (!onTotpReset || !resetCode) return;
236
- setResetSubmitting(true);
237
- setTotpError(null);
238
- try {
239
- const result = await onTotpReset(resetCode);
240
- if ('error' in result) {
241
- setTotpError(result.error);
242
- } else {
243
- setTotpProvisioningUri(result.provisioningUri);
244
- setTotpSecret(result.secret);
245
- setShowResetPrompt(false);
246
- setResetCode('');
247
- setShowTotpSetup(true);
248
- }
249
- } catch {
250
- setTotpError(labels.totpError || 'Failed to reset two-factor authentication');
251
- } finally {
252
- setResetSubmitting(false);
253
- }
254
- }, [onTotpReset, resetCode, labels.totpError]);
255
-
256
- const handleCancelTotpAction = useCallback(() => {
257
- setShowTotpSetup(false);
258
- setShowDisablePrompt(false);
259
- setShowResetPrompt(false);
260
- setTotpProvisioningUri(null);
261
- setTotpSecret(null);
262
- setDisableCode('');
263
- setResetCode('');
264
- setTotpError(null);
265
- }, []);
266
-
267
- const timezones = useMemo(() => moment.tz.names(), []);
268
- const currencies = useMemo(
269
- () =>
270
- CurrencyCode.getAllData().map((c) => ({
271
- code: c.code,
272
- label: `${c.code} - ${c.currency}`,
273
- })),
274
- []
275
- );
276
-
277
- const validation = {
278
- email:
279
- emailValidation ||
280
- Yup.string()
281
- .email(
282
- tComponent<SuiteCoreStringKeyValue>(
283
- SuiteCoreComponentId,
284
- SuiteCoreStringKey.Validation_InvalidEmail
285
- )
286
- )
287
- .required(
288
- tComponent<SuiteCoreStringKeyValue>(
289
- SuiteCoreComponentId,
290
- SuiteCoreStringKey.Validation_Required
291
- )
292
- ),
293
- timezone:
294
- timezoneValidation ||
295
- Yup.string()
296
- .required(
297
- tComponent<SuiteCoreStringKeyValue>(
298
- SuiteCoreComponentId,
299
- SuiteCoreStringKey.Validation_TimezoneRequired
300
- )
301
- )
302
- .test(
303
- 'valid-timezone',
304
- tComponent<SuiteCoreStringKeyValue>(
305
- SuiteCoreComponentId,
306
- SuiteCoreStringKey.Validation_TimezoneInvalid
307
- ),
308
- (value) => !value || moment.tz.zone(value) !== null
309
- ),
310
- siteLanguage:
311
- siteLanguageValidation ||
312
- Yup.string().required(
313
- tComponent<SuiteCoreStringKeyValue>(
314
- SuiteCoreComponentId,
315
- SuiteCoreStringKey.Validation_Required
316
- )
317
- ),
318
- currency:
319
- currencyValidation ||
320
- Yup.string()
321
- .required(
322
- tComponent<SuiteCoreStringKeyValue>(
323
- SuiteCoreComponentId,
324
- SuiteCoreStringKey.Validation_Required
325
- )
326
- )
327
- .test(
328
- 'valid-currency',
329
- tComponent<SuiteCoreStringKeyValue>(
330
- SuiteCoreComponentId,
331
- SuiteCoreStringKey.Validation_Required
332
- ),
333
- (value) => !value || CurrencyCode.isValid(value)
334
- ),
335
- darkMode:
336
- darkModeValidation ||
337
- Yup.boolean().required(
338
- tComponent<SuiteCoreStringKeyValue>(
339
- SuiteCoreComponentId,
340
- SuiteCoreStringKey.Validation_Required
341
- )
342
- ),
343
- directChallenge:
344
- directChallengeValidation ||
345
- Yup.boolean().required(
346
- tComponent<SuiteCoreStringKeyValue>(
347
- SuiteCoreComponentId,
348
- SuiteCoreStringKey.Validation_Required
349
- )
350
- ),
351
- ...(Constants.EnableDisplayName ? {
352
- displayName: displayNameValidation ||
353
- Yup.string()
354
- .min(
355
- Constants.DisplayNameMinLength,
356
- tComponent<SuiteCoreStringKeyValue>(
357
- SuiteCoreComponentId,
358
- SuiteCoreStringKey.Validation_DisplayNameMinLengthTemplate
359
- )
360
- )
361
- .max(
362
- Constants.DisplayNameMaxLength,
363
- tComponent<SuiteCoreStringKeyValue>(
364
- SuiteCoreComponentId,
365
- SuiteCoreStringKey.Validation_DisplayNameMaxLengthTemplate
366
- )
367
- )
368
- .matches(
369
- Constants.DisplayNameRegex,
370
- tComponent<SuiteCoreStringKeyValue>(
371
- SuiteCoreComponentId,
372
- SuiteCoreStringKey.Validation_DisplayNameRegexErrorTemplate
373
- )
374
- ),
375
- } : {}),
376
- };
377
-
378
- const formik = useFormik<UserSettingsFormValues>({
379
- initialValues: {
380
- ...initialValues,
381
- ...additionalInitialValues,
382
- },
383
- enableReinitialize: true,
384
- validationSchema: Yup.object({
385
- email: validation.email,
386
- timezone: validation.timezone,
387
- siteLanguage: validation.siteLanguage,
388
- currency: validation.currency,
389
- darkMode: validation.darkMode,
390
- directChallenge: validation.directChallenge,
391
- ...(Constants.EnableDisplayName && validation.displayName ? { displayName: validation.displayName } : {}),
392
- ...additionalValidation,
393
- }),
394
- onSubmit: async (values, { setSubmitting, setFieldError, setTouched }) => {
395
- setSaving(true);
396
- setSuccessMessage(null);
397
- const result = await onSubmit(values);
398
-
399
- if ('success' in result && result.success) {
400
- setSuccessMessage(
401
- result.message ||
402
- labels.successMessage ||
403
- tComponent<SuiteCoreStringKeyValue>(
404
- SuiteCoreComponentId,
405
- SuiteCoreStringKey.Settings_SaveSuccess
406
- )
407
- );
408
- setApiErrors({});
409
- } else {
410
- const newApiErrors: Record<string, string> = {};
411
- const fieldsToTouch: Record<string, boolean> = {};
412
-
413
- if ('field' in result && result.field) {
414
- setFieldError(result.field, result.error);
415
- fieldsToTouch[result.field] = true;
416
- }
417
-
418
- if ('errors' in result && result.errors) {
419
- result.errors.forEach((err) => {
420
- if (err.path && err.msg) {
421
- setFieldError(err.path, err.msg);
422
- fieldsToTouch[err.path] = true;
423
- }
424
- });
425
- }
426
-
427
- if (
428
- 'error' in result &&
429
- result.error &&
430
- !Object.keys(newApiErrors).length
431
- ) {
432
- newApiErrors.general = result.error;
433
- }
434
-
435
- setApiErrors(newApiErrors);
436
- setTouched(fieldsToTouch, false);
437
- }
438
- setSubmitting(false);
439
- setSaving(false);
440
- },
441
- });
442
-
443
- return (
444
- <Container maxWidth="sm">
445
- <Box
446
- sx={{
447
- mt: 4,
448
- display: 'flex',
449
- flexDirection: 'column',
450
- alignItems: 'center',
451
- }}
452
- >
453
- <Typography variant="h4" component="h1" gutterBottom>
454
- {labels.title ||
455
- tComponent<SuiteCoreStringKeyValue>(
456
- SuiteCoreComponentId,
457
- SuiteCoreStringKey.Settings_Title
458
- )}
459
- </Typography>
460
-
461
- <Box
462
- component="form"
463
- onSubmit={formik.handleSubmit}
464
- sx={{ mt: 1, width: '100%' }}
465
- >
466
- <TextField
467
- fullWidth
468
- id="email"
469
- name="email"
470
- label={
471
- labels.email ||
472
- tComponent<SuiteCoreStringKeyValue>(
473
- SuiteCoreComponentId,
474
- SuiteCoreStringKey.Common_Email
475
- )
476
- }
477
- value={formik.values.email}
478
- onChange={formik.handleChange}
479
- onBlur={formik.handleBlur}
480
- error={Boolean(
481
- formik.touched.email && (formik.errors.email || apiErrors.email)
482
- )}
483
- helperText={
484
- (formik.touched.email &&
485
- (formik.errors.email || apiErrors.email)) ||
486
- labels.emailHelper ||
487
- tComponent<SuiteCoreStringKeyValue>(
488
- SuiteCoreComponentId,
489
- SuiteCoreStringKey.Settings_EmailHelper
490
- )
491
- }
492
- margin="normal"
493
- />
494
-
495
- {Constants.EnableDisplayName && (
496
- <TextField
497
- fullWidth
498
- id="displayName"
499
- name="displayName"
500
- label={
501
- labels.displayName ||
502
- tComponent<SuiteCoreStringKeyValue>(
503
- SuiteCoreComponentId,
504
- SuiteCoreStringKey.Common_DisplayName
505
- )
506
- }
507
- value={formik.values.displayName ?? ''}
508
- onChange={formik.handleChange}
509
- onBlur={formik.handleBlur}
510
- error={Boolean(
511
- formik.touched.displayName && (formik.errors.displayName || apiErrors.displayName)
512
- )}
513
- helperText={
514
- formik.touched.displayName && (formik.errors.displayName || apiErrors.displayName)
515
- }
516
- margin="normal"
517
- />
518
- )}
519
-
520
- <FormControl fullWidth margin="normal">
521
- <InputLabel id="timezone-label">
522
- {labels.timezone ||
523
- tComponent<SuiteCoreStringKeyValue>(
524
- SuiteCoreComponentId,
525
- SuiteCoreStringKey.Common_Timezone
526
- )}
527
- </InputLabel>
528
- <Select
529
- labelId="timezone-label"
530
- id="timezone"
531
- name="timezone"
532
- value={formik.values.timezone}
533
- onChange={formik.handleChange}
534
- onBlur={formik.handleBlur}
535
- error={formik.touched.timezone && Boolean(formik.errors.timezone)}
536
- label={
537
- labels.timezone ||
538
- tComponent<SuiteCoreStringKeyValue>(
539
- SuiteCoreComponentId,
540
- SuiteCoreStringKey.Common_Timezone
541
- )
542
- }
543
- >
544
- {timezones.map((tz) => (
545
- <MenuItem key={tz} value={tz}>
546
- {tz}
547
- </MenuItem>
548
- ))}
549
- </Select>
550
- {formik.touched.timezone &&
551
- (formik.errors.timezone || apiErrors.timezone) && (
552
- <Typography color="error" variant="caption">
553
- {formik.errors.timezone || apiErrors.timezone}
554
- </Typography>
555
- )}
556
- </FormControl>
557
-
558
- <FormControl fullWidth margin="normal">
559
- <InputLabel id="language-label">
560
- {labels.siteLanguage ||
561
- tComponent<SuiteCoreStringKeyValue>(
562
- SuiteCoreComponentId,
563
- SuiteCoreStringKey.Settings_SiteLanguage
564
- )}
565
- </InputLabel>
566
- <Select
567
- labelId="language-label"
568
- id="siteLanguage"
569
- name="siteLanguage"
570
- value={formik.values.siteLanguage}
571
- onChange={formik.handleChange}
572
- onBlur={formik.handleBlur}
573
- error={
574
- formik.touched.siteLanguage &&
575
- Boolean(formik.errors.siteLanguage)
576
- }
577
- label={
578
- labels.siteLanguage ||
579
- tComponent<SuiteCoreStringKeyValue>(
580
- SuiteCoreComponentId,
581
- SuiteCoreStringKey.Settings_SiteLanguage
582
- )
583
- }
584
- >
585
- {languages.map((lang) => (
586
- <MenuItem key={lang.code} value={lang.code}>
587
- {lang.label}
588
- </MenuItem>
589
- ))}
590
- </Select>
591
- {formik.touched.siteLanguage &&
592
- (formik.errors.siteLanguage || apiErrors.siteLanguage) && (
593
- <Typography color="error" variant="caption">
594
- {formik.errors.siteLanguage || apiErrors.siteLanguage}
595
- </Typography>
596
- )}
597
- </FormControl>
598
-
599
- <FormControl fullWidth margin="normal">
600
- <InputLabel id="currency-label">
601
- {labels.currency ||
602
- tComponent<SuiteCoreStringKeyValue>(
603
- SuiteCoreComponentId,
604
- SuiteCoreStringKey.Settings_Currency
605
- )}
606
- </InputLabel>
607
- <Select
608
- labelId="currency-label"
609
- id="currency"
610
- name="currency"
611
- value={formik.values.currency}
612
- onChange={formik.handleChange}
613
- onBlur={formik.handleBlur}
614
- error={formik.touched.currency && Boolean(formik.errors.currency)}
615
- label={
616
- labels.currency ||
617
- tComponent<SuiteCoreStringKeyValue>(
618
- SuiteCoreComponentId,
619
- SuiteCoreStringKey.Settings_Currency
620
- )
621
- }
622
- >
623
- {currencies.map((curr) => (
624
- <MenuItem key={curr.code} value={curr.code}>
625
- {curr.label}
626
- </MenuItem>
627
- ))}
628
- </Select>
629
- {formik.touched.currency &&
630
- (formik.errors.currency || apiErrors.currency) && (
631
- <Typography color="error" variant="caption">
632
- {formik.errors.currency || apiErrors.currency}
633
- </Typography>
634
- )}
635
- </FormControl>
636
-
637
- <FormControl fullWidth margin="normal">
638
- <FormControlLabel
639
- control={
640
- <Switch
641
- id="darkMode"
642
- name="darkMode"
643
- checked={formik.values.darkMode}
644
- onChange={formik.handleChange}
645
- />
646
- }
647
- label={
648
- labels.darkMode ||
649
- tComponent<SuiteCoreStringKeyValue>(
650
- SuiteCoreComponentId,
651
- SuiteCoreStringKey.Settings_DarkMode
652
- )
653
- }
654
- />
655
- </FormControl>
656
-
657
- <FormControl fullWidth margin="normal">
658
- <FormControlLabel
659
- control={
660
- <Switch
661
- id="directChallenge"
662
- name="directChallenge"
663
- checked={formik.values.directChallenge}
664
- onChange={formik.handleChange}
665
- />
666
- }
667
- label={
668
- labels.directChallenge ||
669
- tComponent<SuiteCoreStringKeyValue>(
670
- SuiteCoreComponentId,
671
- SuiteCoreStringKey.Registration_DirectChallengeLabel
672
- )
673
- }
674
- />
675
- <Typography
676
- variant="caption"
677
- color="text.secondary"
678
- sx={{ ml: 4, mt: -1 }}
679
- >
680
- {labels.directChallengeHelper ||
681
- tComponent<SuiteCoreStringKeyValue>(
682
- SuiteCoreComponentId,
683
- SuiteCoreStringKey.Registration_DirectChallengeHelper
684
- )}
685
- </Typography>
686
- </FormControl>
687
-
688
- {additionalFields && additionalFields(formik)}
689
-
690
- {apiErrors.general && (
691
- <Alert severity="error" sx={{ mt: 2, mb: 2 }}>
692
- {apiErrors.general}
693
- </Alert>
694
- )}
695
-
696
- {successMessage && (
697
- <Alert severity="success" sx={{ mt: 2, mb: 2 }}>
698
- {successMessage}
699
- </Alert>
700
- )}
701
-
702
- <Button
703
- type="submit"
704
- fullWidth
705
- variant="contained"
706
- color="primary"
707
- sx={{ mt: 3, mb: 2 }}
708
- disabled={formik.isSubmitting}
709
- >
710
- {saving
711
- ? labels.saving ||
712
- tComponent<SuiteCoreStringKeyValue>(
713
- SuiteCoreComponentId,
714
- SuiteCoreStringKey.Settings_Saving
715
- )
716
- : labels.save ||
717
- tComponent<SuiteCoreStringKeyValue>(
718
- SuiteCoreComponentId,
719
- SuiteCoreStringKey.Settings_Save
720
- )}
721
- </Button>
722
- </Box>
723
-
724
- {/* TOTP Two-Factor Authentication Management Section — outside the form
725
- to prevent TotpSetupForm's nested <form> from being swallowed by the
726
- outer form (nested <form> elements are invalid HTML). */}
727
- {totpStatus !== undefined && (
728
- <>
729
- <Divider sx={{ mt: 3, mb: 2 }} />
730
- <Typography variant="h6" component="h2" gutterBottom>
731
- {labels.totpSectionTitle || 'Two-Factor Authentication'}
732
- </Typography>
733
-
734
- <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
735
- {totpStatus
736
- ? (labels.totpStatusEnabled || '2FA is currently enabled')
737
- : (labels.totpStatusDisabled || '2FA is currently disabled')}
738
- </Typography>
739
-
740
- {totpError && (
741
- <Alert severity="error" sx={{ mb: 2 }} role="alert">
742
- {totpError}
743
- </Alert>
744
- )}
745
-
746
- {/* Enable 2FA flow */}
747
- {!totpStatus && !showTotpSetup && (
748
- <Button
749
- type="button"
750
- variant="outlined"
751
- onClick={handleEnableTotp}
752
- disabled={totpSetupLoading}
753
- data-testid="enable-totp-button"
754
- >
755
- {totpSetupLoading
756
- ? 'Setting up...'
757
- : (labels.totpEnableButton || 'Enable 2FA')}
758
- </Button>
759
- )}
760
-
761
- {/* TotpSetupForm inline (for enable or reset flows) */}
762
- {showTotpSetup && totpProvisioningUri && totpSecret && (
763
- <Box sx={{ mt: 2 }}>
764
- <TotpSetupForm
765
- provisioningUri={totpProvisioningUri}
766
- secret={totpSecret}
767
- onConfirm={handleTotpConfirm}
768
- />
769
- <Box sx={{ textAlign: 'center', mt: 1 }}>
770
- <Button
771
- type="button"
772
- variant="text"
773
- onClick={handleCancelTotpAction}
774
- data-testid="cancel-totp-setup-button"
775
- >
776
- {labels.totpCancelButton || 'Cancel'}
777
- </Button>
778
- </Box>
779
- </Box>
780
- )}
781
-
782
- {/* Disable 2FA and Reset 2FA buttons */}
783
- {totpStatus && !showTotpSetup && !showDisablePrompt && !showResetPrompt && (
784
- <Box sx={{ display: 'flex', gap: 2 }}>
785
- <Button
786
- type="button"
787
- variant="outlined"
788
- color="error"
789
- onClick={() => { setShowDisablePrompt(true); setTotpError(null); }}
790
- data-testid="disable-totp-button"
791
- >
792
- {labels.totpDisableButton || 'Disable 2FA'}
793
- </Button>
794
- <Button
795
- type="button"
796
- variant="outlined"
797
- onClick={() => { setShowResetPrompt(true); setTotpError(null); }}
798
- data-testid="reset-totp-button"
799
- >
800
- {labels.totpResetButton || 'Reset 2FA'}
801
- </Button>
802
- </Box>
803
- )}
804
-
805
- {/* Disable 2FA code prompt */}
806
- {showDisablePrompt && (
807
- <Box sx={{ mt: 2 }}>
808
- <Typography variant="body2" sx={{ mb: 1 }}>
809
- Enter your current 2FA code to disable two-factor authentication:
810
- </Typography>
811
- <TextField
812
- fullWidth
813
- id="totp-disable-code"
814
- name="totp-disable-code"
815
- label={labels.totpDisableCodeLabel || 'TOTP Code'}
816
- value={disableCode}
817
- onChange={(e) => setDisableCode(e.target.value)}
818
- inputProps={{
819
- 'aria-label': labels.totpDisableCodeLabel || 'TOTP Code',
820
- inputMode: 'numeric',
821
- maxLength: 6,
822
- pattern: '\\d{6}',
823
- }}
824
- autoComplete="one-time-code"
825
- margin="normal"
826
- />
827
- <Box sx={{ display: 'flex', gap: 2, mt: 1 }}>
828
- <Button
829
- type="button"
830
- variant="contained"
831
- color="error"
832
- onClick={handleDisableTotp}
833
- disabled={disableSubmitting || !/^\d{6}$/.test(disableCode)}
834
- data-testid="disable-totp-submit-button"
835
- >
836
- {disableSubmitting
837
- ? 'Disabling...'
838
- : (labels.totpDisableSubmitButton || 'Confirm Disable')}
839
- </Button>
840
- <Button
841
- type="button"
842
- variant="text"
843
- onClick={handleCancelTotpAction}
844
- data-testid="cancel-disable-totp-button"
845
- >
846
- {labels.totpCancelButton || 'Cancel'}
847
- </Button>
848
- </Box>
849
- </Box>
850
- )}
851
-
852
- {/* Reset 2FA code prompt */}
853
- {showResetPrompt && (
854
- <Box sx={{ mt: 2 }}>
855
- <Typography variant="body2" sx={{ mb: 1 }}>
856
- Enter your current 2FA code to reset two-factor authentication:
857
- </Typography>
858
- <TextField
859
- fullWidth
860
- id="totp-reset-code"
861
- name="totp-reset-code"
862
- label={labels.totpResetCodeLabel || 'TOTP Code'}
863
- value={resetCode}
864
- onChange={(e) => setResetCode(e.target.value)}
865
- inputProps={{
866
- 'aria-label': labels.totpResetCodeLabel || 'TOTP Code',
867
- inputMode: 'numeric',
868
- maxLength: 6,
869
- pattern: '\\d{6}',
870
- }}
871
- autoComplete="one-time-code"
872
- margin="normal"
873
- />
874
- <Box sx={{ display: 'flex', gap: 2, mt: 1 }}>
875
- <Button
876
- type="button"
877
- variant="contained"
878
- onClick={handleResetTotp}
879
- disabled={resetSubmitting || !/^\d{6}$/.test(resetCode)}
880
- data-testid="reset-totp-submit-button"
881
- >
882
- {resetSubmitting
883
- ? 'Resetting...'
884
- : (labels.totpResetSubmitButton || 'Confirm Reset')}
885
- </Button>
886
- <Button
887
- type="button"
888
- variant="text"
889
- onClick={handleCancelTotpAction}
890
- data-testid="cancel-reset-totp-button"
891
- >
892
- {labels.totpCancelButton || 'Cancel'}
893
- </Button>
894
- </Box>
895
- </Box>
896
- )}
897
- </>
898
- )}
899
- </Box>
900
- </Container>
901
- );
902
- };
903
-
904
- export default UserSettingsForm;