@digitaldefiance/express-suite-react-components 2.9.36 → 2.9.38

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 (257) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +4 -5
  3. package/src/auth/Private.tsx +17 -0
  4. package/src/auth/PrivateRoute.tsx +28 -0
  5. package/src/auth/UnAuth.tsx +16 -0
  6. package/src/auth/UnAuthRoute.tsx +30 -0
  7. package/src/auth/{index.d.ts → index.ts} +1 -2
  8. package/src/components/ApiAccess.tsx +174 -0
  9. package/src/components/BackupCodeLoginForm.tsx +488 -0
  10. package/src/components/BackupCodesForm.tsx +286 -0
  11. package/src/components/ChangePasswordForm.tsx +272 -0
  12. package/src/components/ConfirmationDialog.tsx +48 -0
  13. package/src/components/CurrencyCodeSelector.tsx +60 -0
  14. package/src/components/CurrencyInput.tsx +80 -0
  15. package/src/components/DashboardPage.tsx +24 -0
  16. package/src/components/DropdownMenu.tsx +92 -0
  17. package/src/components/ExpirationSecondsSelector.tsx +60 -0
  18. package/src/components/Flag.tsx +52 -0
  19. package/src/components/ForgotPasswordForm.tsx +173 -0
  20. package/src/components/LoginForm.tsx +455 -0
  21. package/src/components/LogoutPage.tsx +21 -0
  22. package/src/components/RegisterForm.tsx +602 -0
  23. package/src/components/ResetPasswordForm.tsx +246 -0
  24. package/src/components/SideMenu.tsx +46 -0
  25. package/src/components/SideMenuListItem.tsx +74 -0
  26. package/src/components/TopMenu.tsx +145 -0
  27. package/src/components/TranslatedTitle.tsx +29 -0
  28. package/src/components/UserLanguageSelector.tsx +45 -0
  29. package/src/components/UserMenu.tsx +15 -0
  30. package/src/components/UserSettingsForm.tsx +505 -0
  31. package/src/components/VerifyEmailPage.tsx +184 -0
  32. package/src/components/{index.d.ts → index.ts} +1 -1
  33. package/src/contexts/AuthProvider.spec.tsx +1195 -0
  34. package/src/contexts/AuthProvider.tsx +924 -0
  35. package/src/contexts/I18nProvider.tsx +114 -0
  36. package/src/contexts/MenuContext.tsx +398 -0
  37. package/src/contexts/SuiteConfigProvider.tsx +93 -0
  38. package/src/contexts/ThemeProvider.tsx +67 -0
  39. package/src/contexts/{index.d.ts → index.ts} +0 -1
  40. package/src/hooks/{index.d.ts → index.ts} +0 -1
  41. package/src/hooks/useBackupCodes.ts +105 -0
  42. package/src/hooks/useEmailVerification.ts +49 -0
  43. package/src/hooks/useExpiringValue.ts +78 -0
  44. package/src/hooks/useLocalStorage.ts +18 -0
  45. package/src/hooks/useUserSettings.ts +269 -0
  46. package/src/{index.d.ts → index.ts} +1 -1
  47. package/src/interfaces/IAppConfig.ts +5 -0
  48. package/src/interfaces/IMenuConfig.ts +11 -0
  49. package/src/interfaces/IMenuOption.ts +55 -0
  50. package/src/interfaces/index.ts +3 -0
  51. package/src/services/__mocks__/authService.ts +14 -0
  52. package/src/services/api.ts +13 -0
  53. package/src/services/authService.ts +500 -0
  54. package/src/services/authenticatedApi.ts +17 -0
  55. package/src/services/index.ts +3 -0
  56. package/src/types/MenuType.ts +15 -0
  57. package/src/types/expirationSeconds.ts +18 -0
  58. package/src/types/index.ts +1 -0
  59. package/src/types/translation.ts +20 -0
  60. package/src/wrappers/BackupCodeLoginWrapper.tsx +34 -0
  61. package/src/wrappers/BackupCodesWrapper.tsx +28 -0
  62. package/src/wrappers/ChangePasswordFormWrapper.tsx +34 -0
  63. package/src/wrappers/LoginFormWrapper.tsx +59 -0
  64. package/src/wrappers/LogoutPageWrapper.tsx +30 -0
  65. package/src/wrappers/RegisterFormWrapper.tsx +61 -0
  66. package/src/wrappers/UserSettingsFormWrapper.tsx +39 -0
  67. package/src/wrappers/VerifyEmailPageWrapper.tsx +27 -0
  68. package/src/wrappers/{index.d.ts → index.tsx} +8 -1
  69. package/src/auth/Private.d.ts +0 -6
  70. package/src/auth/Private.d.ts.map +0 -1
  71. package/src/auth/Private.js +0 -14
  72. package/src/auth/PrivateRoute.d.ts +0 -8
  73. package/src/auth/PrivateRoute.d.ts.map +0 -1
  74. package/src/auth/PrivateRoute.js +0 -23
  75. package/src/auth/UnAuth.d.ts +0 -6
  76. package/src/auth/UnAuth.d.ts.map +0 -1
  77. package/src/auth/UnAuth.js +0 -14
  78. package/src/auth/UnAuthRoute.d.ts +0 -8
  79. package/src/auth/UnAuthRoute.d.ts.map +0 -1
  80. package/src/auth/UnAuthRoute.js +0 -22
  81. package/src/auth/index.d.ts.map +0 -1
  82. package/src/auth/index.js +0 -10
  83. package/src/components/ApiAccess.d.ts +0 -16
  84. package/src/components/ApiAccess.d.ts.map +0 -1
  85. package/src/components/ApiAccess.js +0 -77
  86. package/src/components/BackupCodeLoginForm.d.ts +0 -43
  87. package/src/components/BackupCodeLoginForm.d.ts.map +0 -1
  88. package/src/components/BackupCodeLoginForm.js +0 -139
  89. package/src/components/BackupCodesForm.d.ts +0 -26
  90. package/src/components/BackupCodesForm.d.ts.map +0 -1
  91. package/src/components/BackupCodesForm.js +0 -120
  92. package/src/components/ChangePasswordForm.d.ts +0 -26
  93. package/src/components/ChangePasswordForm.d.ts.map +0 -1
  94. package/src/components/ChangePasswordForm.js +0 -78
  95. package/src/components/ConfirmationDialog.d.ts +0 -13
  96. package/src/components/ConfirmationDialog.d.ts.map +0 -1
  97. package/src/components/ConfirmationDialog.js +0 -10
  98. package/src/components/CurrencyCodeSelector.d.ts +0 -9
  99. package/src/components/CurrencyCodeSelector.d.ts.map +0 -1
  100. package/src/components/CurrencyCodeSelector.js +0 -31
  101. package/src/components/CurrencyInput.d.ts +0 -13
  102. package/src/components/CurrencyInput.d.ts.map +0 -1
  103. package/src/components/CurrencyInput.js +0 -22
  104. package/src/components/DashboardPage.d.ts +0 -8
  105. package/src/components/DashboardPage.d.ts.map +0 -1
  106. package/src/components/DashboardPage.js +0 -10
  107. package/src/components/DropdownMenu.d.ts +0 -9
  108. package/src/components/DropdownMenu.d.ts.map +0 -1
  109. package/src/components/DropdownMenu.js +0 -56
  110. package/src/components/ExpirationSecondsSelector.d.ts +0 -13
  111. package/src/components/ExpirationSecondsSelector.d.ts.map +0 -1
  112. package/src/components/ExpirationSecondsSelector.js +0 -32
  113. package/src/components/Flag.d.ts +0 -20
  114. package/src/components/Flag.d.ts.map +0 -1
  115. package/src/components/Flag.js +0 -43
  116. package/src/components/ForgotPasswordForm.d.ts +0 -18
  117. package/src/components/ForgotPasswordForm.d.ts.map +0 -1
  118. package/src/components/ForgotPasswordForm.js +0 -61
  119. package/src/components/LoginForm.d.ts +0 -44
  120. package/src/components/LoginForm.d.ts.map +0 -1
  121. package/src/components/LoginForm.js +0 -122
  122. package/src/components/LogoutPage.d.ts +0 -8
  123. package/src/components/LogoutPage.d.ts.map +0 -1
  124. package/src/components/LogoutPage.js +0 -16
  125. package/src/components/RegisterForm.d.ts +0 -56
  126. package/src/components/RegisterForm.d.ts.map +0 -1
  127. package/src/components/RegisterForm.js +0 -140
  128. package/src/components/ResetPasswordForm.d.ts +0 -23
  129. package/src/components/ResetPasswordForm.d.ts.map +0 -1
  130. package/src/components/ResetPasswordForm.js +0 -78
  131. package/src/components/SideMenu.d.ts +0 -8
  132. package/src/components/SideMenu.d.ts.map +0 -1
  133. package/src/components/SideMenu.js +0 -25
  134. package/src/components/SideMenuListItem.d.ts +0 -13
  135. package/src/components/SideMenuListItem.d.ts.map +0 -1
  136. package/src/components/SideMenuListItem.js +0 -44
  137. package/src/components/TopMenu.d.ts +0 -24
  138. package/src/components/TopMenu.d.ts.map +0 -1
  139. package/src/components/TopMenu.js +0 -35
  140. package/src/components/TranslatedTitle.d.ts +0 -7
  141. package/src/components/TranslatedTitle.d.ts.map +0 -1
  142. package/src/components/TranslatedTitle.js +0 -15
  143. package/src/components/UserLanguageSelector.d.ts +0 -4
  144. package/src/components/UserLanguageSelector.d.ts.map +0 -1
  145. package/src/components/UserLanguageSelector.js +0 -31
  146. package/src/components/UserMenu.d.ts +0 -4
  147. package/src/components/UserMenu.d.ts.map +0 -1
  148. package/src/components/UserMenu.js +0 -12
  149. package/src/components/UserSettingsForm.d.ts +0 -57
  150. package/src/components/UserSettingsForm.d.ts.map +0 -1
  151. package/src/components/UserSettingsForm.js +0 -126
  152. package/src/components/VerifyEmailPage.d.ts +0 -23
  153. package/src/components/VerifyEmailPage.d.ts.map +0 -1
  154. package/src/components/VerifyEmailPage.js +0 -70
  155. package/src/components/index.d.ts.map +0 -1
  156. package/src/components/index.js +0 -28
  157. package/src/contexts/AuthProvider.d.ts +0 -152
  158. package/src/contexts/AuthProvider.d.ts.map +0 -1
  159. package/src/contexts/AuthProvider.js +0 -502
  160. package/src/contexts/I18nProvider.d.ts +0 -16
  161. package/src/contexts/I18nProvider.d.ts.map +0 -1
  162. package/src/contexts/I18nProvider.js +0 -46
  163. package/src/contexts/MenuContext.d.ts +0 -20
  164. package/src/contexts/MenuContext.d.ts.map +0 -1
  165. package/src/contexts/MenuContext.js +0 -273
  166. package/src/contexts/SuiteConfigProvider.d.ts +0 -44
  167. package/src/contexts/SuiteConfigProvider.d.ts.map +0 -1
  168. package/src/contexts/SuiteConfigProvider.js +0 -43
  169. package/src/contexts/ThemeProvider.d.ts +0 -15
  170. package/src/contexts/ThemeProvider.d.ts.map +0 -1
  171. package/src/contexts/ThemeProvider.js +0 -36
  172. package/src/contexts/index.d.ts.map +0 -1
  173. package/src/contexts/index.js +0 -8
  174. package/src/hooks/index.d.ts.map +0 -1
  175. package/src/hooks/index.js +0 -8
  176. package/src/hooks/useBackupCodes.d.ts +0 -15
  177. package/src/hooks/useBackupCodes.d.ts.map +0 -1
  178. package/src/hooks/useBackupCodes.js +0 -74
  179. package/src/hooks/useEmailVerification.d.ts +0 -10
  180. package/src/hooks/useEmailVerification.d.ts.map +0 -1
  181. package/src/hooks/useEmailVerification.js +0 -40
  182. package/src/hooks/useExpiringValue.d.ts +0 -14
  183. package/src/hooks/useExpiringValue.d.ts.map +0 -1
  184. package/src/hooks/useExpiringValue.js +0 -53
  185. package/src/hooks/useLocalStorage.d.ts +0 -2
  186. package/src/hooks/useLocalStorage.d.ts.map +0 -1
  187. package/src/hooks/useLocalStorage.js +0 -15
  188. package/src/hooks/useUserSettings.d.ts +0 -48
  189. package/src/hooks/useUserSettings.d.ts.map +0 -1
  190. package/src/hooks/useUserSettings.js +0 -169
  191. package/src/index.d.ts.map +0 -1
  192. package/src/index.js +0 -12
  193. package/src/interfaces/IAppConfig.d.ts +0 -6
  194. package/src/interfaces/IAppConfig.d.ts.map +0 -1
  195. package/src/interfaces/IAppConfig.js +0 -2
  196. package/src/interfaces/IMenuConfig.d.ts +0 -11
  197. package/src/interfaces/IMenuConfig.d.ts.map +0 -1
  198. package/src/interfaces/IMenuConfig.js +0 -2
  199. package/src/interfaces/IMenuOption.d.ts +0 -58
  200. package/src/interfaces/IMenuOption.d.ts.map +0 -1
  201. package/src/interfaces/IMenuOption.js +0 -2
  202. package/src/interfaces/index.d.ts +0 -4
  203. package/src/interfaces/index.d.ts.map +0 -1
  204. package/src/interfaces/index.js +0 -6
  205. package/src/services/__mocks__/authService.d.ts +0 -21
  206. package/src/services/__mocks__/authService.d.ts.map +0 -1
  207. package/src/services/__mocks__/authService.js +0 -15
  208. package/src/services/api.d.ts +0 -3
  209. package/src/services/api.d.ts.map +0 -1
  210. package/src/services/api.js +0 -14
  211. package/src/services/authService.d.ts +0 -72
  212. package/src/services/authService.d.ts.map +0 -1
  213. package/src/services/authService.js +0 -335
  214. package/src/services/authenticatedApi.d.ts +0 -3
  215. package/src/services/authenticatedApi.d.ts.map +0 -1
  216. package/src/services/authenticatedApi.js +0 -18
  217. package/src/services/index.d.ts +0 -4
  218. package/src/services/index.d.ts.map +0 -1
  219. package/src/services/index.js +0 -6
  220. package/src/types/MenuType.d.ts +0 -11
  221. package/src/types/MenuType.d.ts.map +0 -1
  222. package/src/types/MenuType.js +0 -12
  223. package/src/types/expirationSeconds.d.ts +0 -3
  224. package/src/types/expirationSeconds.d.ts.map +0 -1
  225. package/src/types/expirationSeconds.js +0 -17
  226. package/src/types/index.d.ts +0 -2
  227. package/src/types/index.d.ts.map +0 -1
  228. package/src/types/index.js +0 -4
  229. package/src/types/translation.d.ts +0 -10
  230. package/src/types/translation.d.ts.map +0 -1
  231. package/src/types/translation.js +0 -9
  232. package/src/wrappers/BackupCodeLoginWrapper.d.ts +0 -8
  233. package/src/wrappers/BackupCodeLoginWrapper.d.ts.map +0 -1
  234. package/src/wrappers/BackupCodeLoginWrapper.js +0 -20
  235. package/src/wrappers/BackupCodesWrapper.d.ts +0 -7
  236. package/src/wrappers/BackupCodesWrapper.d.ts.map +0 -1
  237. package/src/wrappers/BackupCodesWrapper.js +0 -17
  238. package/src/wrappers/ChangePasswordFormWrapper.d.ts +0 -8
  239. package/src/wrappers/ChangePasswordFormWrapper.d.ts.map +0 -1
  240. package/src/wrappers/ChangePasswordFormWrapper.js +0 -21
  241. package/src/wrappers/LoginFormWrapper.d.ts +0 -9
  242. package/src/wrappers/LoginFormWrapper.d.ts.map +0 -1
  243. package/src/wrappers/LoginFormWrapper.js +0 -43
  244. package/src/wrappers/LogoutPageWrapper.d.ts +0 -9
  245. package/src/wrappers/LogoutPageWrapper.d.ts.map +0 -1
  246. package/src/wrappers/LogoutPageWrapper.js +0 -21
  247. package/src/wrappers/RegisterFormWrapper.d.ts +0 -9
  248. package/src/wrappers/RegisterFormWrapper.d.ts.map +0 -1
  249. package/src/wrappers/RegisterFormWrapper.js +0 -31
  250. package/src/wrappers/UserSettingsFormWrapper.d.ts +0 -8
  251. package/src/wrappers/UserSettingsFormWrapper.d.ts.map +0 -1
  252. package/src/wrappers/UserSettingsFormWrapper.js +0 -24
  253. package/src/wrappers/VerifyEmailPageWrapper.d.ts +0 -8
  254. package/src/wrappers/VerifyEmailPageWrapper.d.ts.map +0 -1
  255. package/src/wrappers/VerifyEmailPageWrapper.js +0 -20
  256. package/src/wrappers/index.d.ts.map +0 -1
  257. package/src/wrappers/index.js +0 -20
@@ -0,0 +1,488 @@
1
+ import {
2
+ Constants,
3
+ SuiteCoreComponentId,
4
+ SuiteCoreStringKey,
5
+ } from '@digitaldefiance/suite-core-lib';
6
+ import {
7
+ Box,
8
+ Button,
9
+ Checkbox,
10
+ Container,
11
+ FormControlLabel,
12
+ TextField,
13
+ Typography,
14
+ } from '@mui/material';
15
+ import { useFormik } from 'formik';
16
+ import { FC, useState } from 'react';
17
+ import * as Yup from 'yup';
18
+ import { useI18n } from '../contexts';
19
+
20
+ export interface BackupCodeLoginFormValues {
21
+ email: string;
22
+ username: string;
23
+ code: string;
24
+ newPassword?: string;
25
+ confirmNewPassword?: string;
26
+ recoverMnemonic: boolean;
27
+ }
28
+
29
+ export interface BackupCodeLoginFormProps {
30
+ onSubmit: (
31
+ identifier: string,
32
+ code: string,
33
+ isEmail: boolean,
34
+ recoverMnemonic: boolean,
35
+ newPassword?: string
36
+ ) => Promise<
37
+ | { token: string; codeCount: number; mnemonic?: string; message?: string }
38
+ | { error: string; status?: number }
39
+ >;
40
+ onNavigate?: (path: string, state?: Record<string, unknown>) => void;
41
+ isAuthenticated?: boolean;
42
+ validationSchema?: Yup.ObjectSchema<BackupCodeLoginFormValues>;
43
+ labels?: {
44
+ title?: string;
45
+ email?: string;
46
+ username?: string;
47
+ code?: string;
48
+ newPassword?: string;
49
+ confirmPassword?: string;
50
+ recoverMnemonic?: string;
51
+ login?: string;
52
+ useUsername?: string;
53
+ useEmail?: string;
54
+ dashboard?: string;
55
+ generateNewCodes?: string;
56
+ mnemonicLabel?: string;
57
+ codesRemaining?: string;
58
+ };
59
+ }
60
+
61
+ export const BackupCodeLoginForm: FC<BackupCodeLoginFormProps> = ({
62
+ onSubmit,
63
+ onNavigate,
64
+ isAuthenticated = false,
65
+ validationSchema,
66
+ labels = {},
67
+ }) => {
68
+ const { tComponent } = useI18n();
69
+ const [loginType, setLoginType] = useState<'email' | 'username'>('email');
70
+ const [loginError, setLoginError] = useState<string | null>(null);
71
+ const [recoveredMnemonic, setRecoveredMnemonic] = useState<string | null>(
72
+ null
73
+ );
74
+ const [successMessage, setSuccessMessage] = useState<string | null>(null);
75
+ const [codesRemaining, setCodesRemaining] = useState<number | null>(null);
76
+
77
+ const translatedLabels = {
78
+ title:
79
+ labels.title ||
80
+ tComponent<SuiteCoreStringKey>(
81
+ SuiteCoreComponentId,
82
+ SuiteCoreStringKey.BackupCodeRecovery_Title
83
+ ),
84
+ email:
85
+ labels.email ||
86
+ tComponent<SuiteCoreStringKey>(
87
+ SuiteCoreComponentId,
88
+ SuiteCoreStringKey.Common_Email
89
+ ),
90
+ username:
91
+ labels.username ||
92
+ tComponent<SuiteCoreStringKey>(
93
+ SuiteCoreComponentId,
94
+ SuiteCoreStringKey.Common_Username
95
+ ),
96
+ code:
97
+ labels.code ||
98
+ tComponent<SuiteCoreStringKey>(
99
+ SuiteCoreComponentId,
100
+ SuiteCoreStringKey.Common_BackupCode
101
+ ),
102
+ newPassword:
103
+ labels.newPassword ||
104
+ tComponent<SuiteCoreStringKey>(
105
+ SuiteCoreComponentId,
106
+ SuiteCoreStringKey.Common_NewPassword
107
+ ),
108
+ confirmPassword:
109
+ labels.confirmPassword ||
110
+ tComponent<SuiteCoreStringKey>(
111
+ SuiteCoreComponentId,
112
+ SuiteCoreStringKey.Common_ConfirmNewPassword
113
+ ),
114
+ recoverMnemonic:
115
+ labels.recoverMnemonic ||
116
+ tComponent<SuiteCoreStringKey>(
117
+ SuiteCoreComponentId,
118
+ SuiteCoreStringKey.BackupCodeRecovery_RecoverMnemonic
119
+ ),
120
+ login:
121
+ labels.login ||
122
+ tComponent<SuiteCoreStringKey>(
123
+ SuiteCoreComponentId,
124
+ SuiteCoreStringKey.BackupCodeRecovery_Login
125
+ ),
126
+ useUsername:
127
+ labels.useUsername ||
128
+ tComponent<SuiteCoreStringKey>(
129
+ SuiteCoreComponentId,
130
+ SuiteCoreStringKey.Login_UseUsername
131
+ ),
132
+ useEmail:
133
+ labels.useEmail ||
134
+ tComponent<SuiteCoreStringKey>(
135
+ SuiteCoreComponentId,
136
+ SuiteCoreStringKey.Login_UseEmailAddress
137
+ ),
138
+ dashboard:
139
+ labels.dashboard ||
140
+ tComponent<SuiteCoreStringKey>(
141
+ SuiteCoreComponentId,
142
+ SuiteCoreStringKey.Common_Dashboard
143
+ ),
144
+ generateNewCodes:
145
+ labels.generateNewCodes ||
146
+ tComponent<SuiteCoreStringKey>(
147
+ SuiteCoreComponentId,
148
+ SuiteCoreStringKey.BackupCodeRecovery_GenerateNewCodes
149
+ ),
150
+ mnemonicLabel:
151
+ labels.mnemonicLabel ||
152
+ tComponent<SuiteCoreStringKey>(
153
+ SuiteCoreComponentId,
154
+ SuiteCoreStringKey.Common_Mnemonic
155
+ ),
156
+ codesRemaining:
157
+ labels.codesRemaining ||
158
+ tComponent<SuiteCoreStringKey>(
159
+ SuiteCoreComponentId,
160
+ SuiteCoreStringKey.BackupCodeRecovery_CodesRemainingTemplate
161
+ ),
162
+ unexpectedError: tComponent<SuiteCoreStringKey>(
163
+ SuiteCoreComponentId,
164
+ SuiteCoreStringKey.Common_UnexpectedError
165
+ ),
166
+ };
167
+
168
+ const yupFieldValidation = {
169
+ email: Yup.string()
170
+ .email(
171
+ tComponent<SuiteCoreStringKey>(
172
+ SuiteCoreComponentId,
173
+ SuiteCoreStringKey.Validation_InvalidEmail
174
+ )
175
+ )
176
+ .required(
177
+ tComponent<SuiteCoreStringKey>(
178
+ SuiteCoreComponentId,
179
+ SuiteCoreStringKey.Validation_Required
180
+ )
181
+ ),
182
+ username: Yup.string()
183
+ .matches(
184
+ Constants.UsernameRegex,
185
+ tComponent<SuiteCoreStringKey>(
186
+ SuiteCoreComponentId,
187
+ SuiteCoreStringKey.Validation_UsernameRegexErrorTemplate
188
+ )
189
+ )
190
+ .required(
191
+ tComponent<SuiteCoreStringKey>(
192
+ SuiteCoreComponentId,
193
+ SuiteCoreStringKey.Validation_Required
194
+ )
195
+ ),
196
+ code: Yup.string()
197
+ .required(
198
+ tComponent<SuiteCoreStringKey>(
199
+ SuiteCoreComponentId,
200
+ SuiteCoreStringKey.Validation_Required
201
+ )
202
+ )
203
+ .matches(
204
+ Constants.BACKUP_CODES.DisplayRegex,
205
+ tComponent<SuiteCoreStringKey>(
206
+ SuiteCoreComponentId,
207
+ SuiteCoreStringKey.Validation_InvalidBackupCode
208
+ )
209
+ ),
210
+ password: Yup.string().matches(
211
+ Constants.PasswordRegex,
212
+ tComponent<SuiteCoreStringKey>(
213
+ SuiteCoreComponentId,
214
+ SuiteCoreStringKey.Validation_PasswordRegexErrorTemplate
215
+ )
216
+ ),
217
+ confirmPassword: Yup.string().oneOf(
218
+ [Yup.ref('newPassword')],
219
+ tComponent<SuiteCoreStringKey>(
220
+ SuiteCoreComponentId,
221
+ SuiteCoreStringKey.Validation_PasswordMatch
222
+ )
223
+ ),
224
+ };
225
+
226
+ const formik = useFormik<BackupCodeLoginFormValues>({
227
+ initialValues: {
228
+ email: '',
229
+ username: '',
230
+ code: '',
231
+ newPassword: '',
232
+ confirmNewPassword: '',
233
+ recoverMnemonic: false,
234
+ },
235
+ validationSchema:
236
+ validationSchema ??
237
+ Yup.object({
238
+ [loginType]:
239
+ loginType === 'email'
240
+ ? yupFieldValidation.email
241
+ : yupFieldValidation.username,
242
+ code: yupFieldValidation.code,
243
+ newPassword: yupFieldValidation.password,
244
+ confirmNewPassword: yupFieldValidation.confirmPassword,
245
+ }),
246
+ enableReinitialize: true,
247
+ onSubmit: async (values, { setSubmitting }) => {
248
+ try {
249
+ const loginResult = await onSubmit(
250
+ loginType === 'email' ? values.email : values.username,
251
+ values.code,
252
+ loginType === 'email',
253
+ values.recoverMnemonic,
254
+ values.newPassword && values.newPassword.length > 0
255
+ ? values.newPassword
256
+ : undefined
257
+ );
258
+ if ('error' in loginResult) {
259
+ setLoginError(loginResult.error);
260
+ setCodesRemaining(null);
261
+ setRecoveredMnemonic(null);
262
+ return;
263
+ }
264
+ setLoginError(null);
265
+ if (loginResult.codeCount) {
266
+ setCodesRemaining(loginResult.codeCount);
267
+ }
268
+ if (loginResult.mnemonic) {
269
+ setRecoveredMnemonic(loginResult.mnemonic);
270
+ }
271
+ if (loginResult.message) {
272
+ setSuccessMessage(loginResult.message);
273
+ }
274
+ } catch {
275
+ setLoginError(translatedLabels.unexpectedError);
276
+ } finally {
277
+ setSubmitting(false);
278
+ }
279
+ },
280
+ });
281
+
282
+ if (
283
+ isAuthenticated &&
284
+ recoveredMnemonic === null &&
285
+ codesRemaining === null
286
+ ) {
287
+ onNavigate?.('/dashboard');
288
+ return null;
289
+ }
290
+
291
+ return (
292
+ <Container component="main" maxWidth="xs">
293
+ <Box
294
+ sx={{
295
+ marginTop: 8,
296
+ display: 'flex',
297
+ flexDirection: 'column',
298
+ alignItems: 'center',
299
+ }}
300
+ >
301
+ <Typography component="h1" variant="h5">
302
+ {translatedLabels.title}
303
+ </Typography>
304
+ <Box
305
+ component="form"
306
+ onSubmit={formik.handleSubmit}
307
+ sx={{ mt: 1, width: '100%' }}
308
+ >
309
+ {!isAuthenticated && (
310
+ <>
311
+ <TextField
312
+ margin="normal"
313
+ fullWidth
314
+ id={loginType}
315
+ label={
316
+ loginType === 'email'
317
+ ? translatedLabels.email
318
+ : translatedLabels.username
319
+ }
320
+ name={loginType}
321
+ autoComplete={loginType === 'email' ? 'email' : 'username'}
322
+ autoFocus
323
+ value={
324
+ loginType === 'email'
325
+ ? formik.values.email
326
+ : formik.values.username
327
+ }
328
+ onChange={formik.handleChange}
329
+ onBlur={formik.handleBlur}
330
+ error={
331
+ formik.touched[loginType] && Boolean(formik.errors[loginType])
332
+ }
333
+ helperText={
334
+ formik.touched[loginType] && formik.errors[loginType]
335
+ }
336
+ disabled={isAuthenticated}
337
+ />
338
+ <TextField
339
+ margin="normal"
340
+ required
341
+ fullWidth
342
+ name="code"
343
+ label={translatedLabels.code}
344
+ id="code"
345
+ value={formik.values.code}
346
+ onChange={formik.handleChange}
347
+ onBlur={formik.handleBlur}
348
+ error={formik.touched.code && Boolean(formik.errors.code)}
349
+ helperText={formik.touched.code && formik.errors.code}
350
+ disabled={isAuthenticated}
351
+ />
352
+ <TextField
353
+ margin="normal"
354
+ fullWidth
355
+ name="newPassword"
356
+ label={translatedLabels.newPassword}
357
+ type="password"
358
+ id="newPassword"
359
+ value={formik.values.newPassword}
360
+ onChange={formik.handleChange}
361
+ onBlur={formik.handleBlur}
362
+ error={
363
+ formik.touched.newPassword &&
364
+ Boolean(formik.errors.newPassword)
365
+ }
366
+ helperText={
367
+ formik.touched.newPassword && formik.errors.newPassword
368
+ }
369
+ disabled={isAuthenticated}
370
+ />
371
+ <TextField
372
+ margin="normal"
373
+ fullWidth
374
+ name="confirmNewPassword"
375
+ label={translatedLabels.confirmPassword}
376
+ type="password"
377
+ id="confirmNewPassword"
378
+ value={formik.values.confirmNewPassword}
379
+ onChange={formik.handleChange}
380
+ onBlur={formik.handleBlur}
381
+ error={
382
+ formik.touched.confirmNewPassword &&
383
+ Boolean(formik.errors.confirmNewPassword)
384
+ }
385
+ helperText={
386
+ formik.touched.confirmNewPassword &&
387
+ formik.errors.confirmNewPassword
388
+ }
389
+ disabled={isAuthenticated}
390
+ />
391
+ <FormControlLabel
392
+ control={
393
+ <Checkbox
394
+ checked={formik.values.recoverMnemonic}
395
+ onChange={formik.handleChange}
396
+ onBlur={formik.handleBlur}
397
+ name="recoverMnemonic"
398
+ />
399
+ }
400
+ label={translatedLabels.recoverMnemonic}
401
+ disabled={isAuthenticated}
402
+ />
403
+ </>
404
+ )}
405
+ {successMessage && (
406
+ <Typography color="success.main" variant="body2" sx={{ mt: 1 }}>
407
+ {successMessage}
408
+ </Typography>
409
+ )}
410
+ {recoveredMnemonic && (
411
+ <Box sx={{ mt: 2, p: 2, bgcolor: 'grey.100', borderRadius: 1 }}>
412
+ <Typography variant="subtitle2" gutterBottom>
413
+ {translatedLabels.mnemonicLabel}:
414
+ </Typography>
415
+ <Typography variant="body2" sx={{ fontFamily: 'monospace' }}>
416
+ {recoveredMnemonic}
417
+ </Typography>
418
+ <Button
419
+ fullWidth
420
+ variant="contained"
421
+ sx={{ mt: 2 }}
422
+ onClick={() => onNavigate?.('/dashboard')}
423
+ >
424
+ {translatedLabels.dashboard}
425
+ </Button>
426
+ </Box>
427
+ )}
428
+ {codesRemaining !== null && (
429
+ <Box sx={{ mt: 2 }}>
430
+ <Typography variant="body2">
431
+ {translatedLabels.codesRemaining.replace(
432
+ '{count}',
433
+ String(codesRemaining)
434
+ )}
435
+ </Typography>
436
+ <Button
437
+ fullWidth
438
+ variant="contained"
439
+ sx={{ mt: 2 }}
440
+ onClick={() =>
441
+ onNavigate?.('/backup-codes', { codeCount: codesRemaining })
442
+ }
443
+ >
444
+ {translatedLabels.generateNewCodes}
445
+ </Button>
446
+ </Box>
447
+ )}
448
+ {loginError && (
449
+ <Typography color="error" variant="body2" sx={{ mt: 1 }}>
450
+ {loginError}
451
+ </Typography>
452
+ )}
453
+ {!isAuthenticated && (
454
+ <>
455
+ <Button
456
+ type="submit"
457
+ fullWidth
458
+ variant="contained"
459
+ sx={{ mt: 3, mb: 2 }}
460
+ disabled={formik.isSubmitting}
461
+ >
462
+ {translatedLabels.login}
463
+ </Button>
464
+ <Box sx={{ display: 'flex', justifyContent: 'center' }}>
465
+ <Button
466
+ fullWidth
467
+ variant="text"
468
+ onClick={() => {
469
+ const newType =
470
+ loginType === 'email' ? 'username' : 'email';
471
+ formik.setFieldValue(loginType, '');
472
+ setLoginType(newType);
473
+ }}
474
+ >
475
+ {loginType === 'email'
476
+ ? translatedLabels.useUsername
477
+ : translatedLabels.useEmail}
478
+ </Button>
479
+ </Box>
480
+ </>
481
+ )}
482
+ </Box>
483
+ </Box>
484
+ </Container>
485
+ );
486
+ };
487
+
488
+ export default BackupCodeLoginForm;