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

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 (312) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -0
  3. package/package.json +3 -4
  4. package/src/auth/AdminRoute.tsx +54 -0
  5. package/src/auth/Private.tsx +17 -0
  6. package/src/auth/PrivateRoute.tsx +28 -0
  7. package/src/auth/UnAuth.tsx +16 -0
  8. package/src/auth/UnAuthRoute.tsx +30 -0
  9. package/src/auth/{index.d.ts → index.ts} +2 -3
  10. package/src/components/ApiAccess.tsx +175 -0
  11. package/src/components/BackupCodeLoginForm.tsx +489 -0
  12. package/src/components/BackupCodesForm.tsx +287 -0
  13. package/src/components/CategoryMenu.tsx +155 -0
  14. package/src/components/ChangePasswordForm.tsx +273 -0
  15. package/src/components/ConfirmationDialog.tsx +48 -0
  16. package/src/components/CurrencyCodeSelector.tsx +60 -0
  17. package/src/components/CurrencyInput.tsx +80 -0
  18. package/src/components/DashboardPage.tsx +24 -0
  19. package/src/components/DropdownMenu.tsx +105 -0
  20. package/src/components/ExpirationSecondsSelector.tsx +60 -0
  21. package/src/components/FakeEmailAdminPanel.tsx +374 -0
  22. package/src/components/Flag.tsx +52 -0
  23. package/src/components/ForgotPasswordForm.tsx +174 -0
  24. package/src/components/LoginForm.tsx +468 -0
  25. package/src/components/LogoutPage.tsx +21 -0
  26. package/src/components/RegisterForm.tsx +1169 -0
  27. package/src/components/ResetPasswordForm.tsx +247 -0
  28. package/src/components/SideMenu.tsx +46 -0
  29. package/src/components/SideMenuListItem.tsx +75 -0
  30. package/src/components/T.tsx +24 -0
  31. package/src/components/TDiv.tsx +26 -0
  32. package/src/components/TImgAlt.tsx +28 -0
  33. package/src/components/TopMenu.tsx +252 -0
  34. package/src/components/TotpSetupForm.tsx +188 -0
  35. package/src/components/TotpVerificationForm.tsx +148 -0
  36. package/src/components/TranslatedTitle.tsx +31 -0
  37. package/src/components/UserLanguageSelector.tsx +45 -0
  38. package/src/components/UserMenu.tsx +15 -0
  39. package/src/components/UserSettingsForm.tsx +904 -0
  40. package/src/components/VerifyEmailPage.tsx +222 -0
  41. package/src/components/__tests__/RegisterForm.totp.spec.tsx +377 -0
  42. package/src/components/__tests__/TotpSetupForm.spec.tsx +258 -0
  43. package/src/components/__tests__/TotpVerificationForm.spec.tsx +259 -0
  44. package/src/components/__tests__/UserSettingsForm.totp.spec.tsx +396 -0
  45. package/src/components/{index.d.ts → index.ts} +1 -1
  46. package/src/contexts/AuthProvider.spec.tsx +1284 -0
  47. package/src/contexts/AuthProvider.tsx +977 -0
  48. package/src/contexts/I18nProvider.tsx +164 -0
  49. package/src/contexts/MenuContext.tsx +426 -0
  50. package/src/contexts/SuiteConfigProvider.tsx +93 -0
  51. package/src/contexts/ThemeProvider.tsx +67 -0
  52. package/src/contexts/{index.d.ts → index.ts} +0 -1
  53. package/src/hooks/{index.d.ts → index.ts} +0 -1
  54. package/src/hooks/useAuthenticatedApi.tsx +67 -0
  55. package/src/hooks/useBackupCodes.ts +105 -0
  56. package/src/hooks/useEmailVerification.ts +54 -0
  57. package/src/hooks/useExpiringValue.ts +78 -0
  58. package/src/hooks/useLocalStorage.ts +18 -0
  59. package/src/hooks/useUserSettings.ts +271 -0
  60. package/src/{index.d.ts → index.ts} +1 -1
  61. package/src/interfaces/IAppConfig.ts +7 -0
  62. package/src/interfaces/ICategoryConfig.ts +41 -0
  63. package/src/interfaces/IMenuConfig.ts +28 -0
  64. package/src/interfaces/IMenuOption.ts +79 -0
  65. package/src/interfaces/{index.d.ts → index.ts} +0 -1
  66. package/src/services/__mocks__/authService.ts +14 -0
  67. package/src/services/__tests__/authService.totp.property.spec.ts +360 -0
  68. package/src/services/__tests__/authService.totp.spec.ts +439 -0
  69. package/src/services/api.ts +13 -0
  70. package/src/services/authService.ts +692 -0
  71. package/src/services/authenticatedApi.ts +17 -0
  72. package/src/services/index.ts +3 -0
  73. package/src/types/MenuType.ts +15 -0
  74. package/src/types/expirationSeconds.ts +18 -0
  75. package/src/types/index.ts +1 -0
  76. package/src/types/translation.ts +20 -0
  77. package/src/wrappers/BackupCodeLoginWrapper.tsx +34 -0
  78. package/src/wrappers/BackupCodesWrapper.tsx +28 -0
  79. package/src/wrappers/ChangePasswordFormWrapper.tsx +34 -0
  80. package/src/wrappers/ForgotPasswordFormWrapper.tsx +45 -0
  81. package/src/wrappers/LoginFormWrapper.tsx +108 -0
  82. package/src/wrappers/LogoutPageWrapper.tsx +30 -0
  83. package/src/wrappers/RegisterFormWrapper.tsx +109 -0
  84. package/src/wrappers/ResetPasswordFormWrapper.tsx +57 -0
  85. package/src/wrappers/UserSettingsFormWrapper.tsx +140 -0
  86. package/src/wrappers/VerifyEmailPageWrapper.tsx +27 -0
  87. package/src/wrappers/__tests__/LoginFormWrapper.totp.spec.tsx +339 -0
  88. package/src/wrappers/{index.d.ts → index.tsx} +10 -1
  89. package/src/auth/AdminRoute.d.ts +0 -8
  90. package/src/auth/AdminRoute.d.ts.map +0 -1
  91. package/src/auth/AdminRoute.js +0 -28
  92. package/src/auth/Private.d.ts +0 -6
  93. package/src/auth/Private.d.ts.map +0 -1
  94. package/src/auth/Private.js +0 -14
  95. package/src/auth/PrivateRoute.d.ts +0 -8
  96. package/src/auth/PrivateRoute.d.ts.map +0 -1
  97. package/src/auth/PrivateRoute.js +0 -23
  98. package/src/auth/UnAuth.d.ts +0 -6
  99. package/src/auth/UnAuth.d.ts.map +0 -1
  100. package/src/auth/UnAuth.js +0 -14
  101. package/src/auth/UnAuthRoute.d.ts +0 -8
  102. package/src/auth/UnAuthRoute.d.ts.map +0 -1
  103. package/src/auth/UnAuthRoute.js +0 -22
  104. package/src/auth/index.d.ts.map +0 -1
  105. package/src/auth/index.js +0 -12
  106. package/src/components/ApiAccess.d.ts +0 -16
  107. package/src/components/ApiAccess.d.ts.map +0 -1
  108. package/src/components/ApiAccess.js +0 -77
  109. package/src/components/BackupCodeLoginForm.d.ts +0 -43
  110. package/src/components/BackupCodeLoginForm.d.ts.map +0 -1
  111. package/src/components/BackupCodeLoginForm.js +0 -139
  112. package/src/components/BackupCodesForm.d.ts +0 -26
  113. package/src/components/BackupCodesForm.d.ts.map +0 -1
  114. package/src/components/BackupCodesForm.js +0 -120
  115. package/src/components/CategoryMenu.d.ts +0 -17
  116. package/src/components/CategoryMenu.d.ts.map +0 -1
  117. package/src/components/CategoryMenu.js +0 -65
  118. package/src/components/ChangePasswordForm.d.ts +0 -26
  119. package/src/components/ChangePasswordForm.d.ts.map +0 -1
  120. package/src/components/ChangePasswordForm.js +0 -78
  121. package/src/components/ConfirmationDialog.d.ts +0 -13
  122. package/src/components/ConfirmationDialog.d.ts.map +0 -1
  123. package/src/components/ConfirmationDialog.js +0 -10
  124. package/src/components/CurrencyCodeSelector.d.ts +0 -9
  125. package/src/components/CurrencyCodeSelector.d.ts.map +0 -1
  126. package/src/components/CurrencyCodeSelector.js +0 -31
  127. package/src/components/CurrencyInput.d.ts +0 -13
  128. package/src/components/CurrencyInput.d.ts.map +0 -1
  129. package/src/components/CurrencyInput.js +0 -22
  130. package/src/components/DashboardPage.d.ts +0 -8
  131. package/src/components/DashboardPage.d.ts.map +0 -1
  132. package/src/components/DashboardPage.js +0 -10
  133. package/src/components/DropdownMenu.d.ts +0 -13
  134. package/src/components/DropdownMenu.d.ts.map +0 -1
  135. package/src/components/DropdownMenu.js +0 -63
  136. package/src/components/ExpirationSecondsSelector.d.ts +0 -13
  137. package/src/components/ExpirationSecondsSelector.d.ts.map +0 -1
  138. package/src/components/ExpirationSecondsSelector.js +0 -32
  139. package/src/components/FakeEmailAdminPanel.d.ts +0 -23
  140. package/src/components/FakeEmailAdminPanel.d.ts.map +0 -1
  141. package/src/components/FakeEmailAdminPanel.js +0 -147
  142. package/src/components/Flag.d.ts +0 -20
  143. package/src/components/Flag.d.ts.map +0 -1
  144. package/src/components/Flag.js +0 -43
  145. package/src/components/ForgotPasswordForm.d.ts +0 -18
  146. package/src/components/ForgotPasswordForm.d.ts.map +0 -1
  147. package/src/components/ForgotPasswordForm.js +0 -61
  148. package/src/components/LoginForm.d.ts +0 -44
  149. package/src/components/LoginForm.d.ts.map +0 -1
  150. package/src/components/LoginForm.js +0 -123
  151. package/src/components/LogoutPage.d.ts +0 -8
  152. package/src/components/LogoutPage.d.ts.map +0 -1
  153. package/src/components/LogoutPage.js +0 -16
  154. package/src/components/RegisterForm.d.ts +0 -100
  155. package/src/components/RegisterForm.d.ts.map +0 -1
  156. package/src/components/RegisterForm.js +0 -306
  157. package/src/components/ResetPasswordForm.d.ts +0 -23
  158. package/src/components/ResetPasswordForm.d.ts.map +0 -1
  159. package/src/components/ResetPasswordForm.js +0 -78
  160. package/src/components/SideMenu.d.ts +0 -8
  161. package/src/components/SideMenu.d.ts.map +0 -1
  162. package/src/components/SideMenu.js +0 -25
  163. package/src/components/SideMenuListItem.d.ts +0 -13
  164. package/src/components/SideMenuListItem.d.ts.map +0 -1
  165. package/src/components/SideMenuListItem.js +0 -44
  166. package/src/components/T.d.ts +0 -7
  167. package/src/components/T.d.ts.map +0 -1
  168. package/src/components/T.js +0 -13
  169. package/src/components/TDiv.d.ts +0 -9
  170. package/src/components/TDiv.d.ts.map +0 -1
  171. package/src/components/TDiv.js +0 -14
  172. package/src/components/TImgAlt.d.ts +0 -10
  173. package/src/components/TImgAlt.d.ts.map +0 -1
  174. package/src/components/TImgAlt.js +0 -14
  175. package/src/components/TopMenu.d.ts +0 -50
  176. package/src/components/TopMenu.d.ts.map +0 -1
  177. package/src/components/TopMenu.js +0 -79
  178. package/src/components/TotpSetupForm.d.ts +0 -24
  179. package/src/components/TotpSetupForm.d.ts.map +0 -1
  180. package/src/components/TotpSetupForm.js +0 -82
  181. package/src/components/TotpVerificationForm.d.ts +0 -23
  182. package/src/components/TotpVerificationForm.d.ts.map +0 -1
  183. package/src/components/TotpVerificationForm.js +0 -66
  184. package/src/components/TranslatedTitle.d.ts +0 -8
  185. package/src/components/TranslatedTitle.d.ts.map +0 -1
  186. package/src/components/TranslatedTitle.js +0 -15
  187. package/src/components/UserLanguageSelector.d.ts +0 -4
  188. package/src/components/UserLanguageSelector.d.ts.map +0 -1
  189. package/src/components/UserLanguageSelector.js +0 -31
  190. package/src/components/UserMenu.d.ts +0 -4
  191. package/src/components/UserMenu.d.ts.map +0 -1
  192. package/src/components/UserMenu.js +0 -12
  193. package/src/components/UserSettingsForm.d.ts +0 -113
  194. package/src/components/UserSettingsForm.d.ts.map +0 -1
  195. package/src/components/UserSettingsForm.js +0 -269
  196. package/src/components/VerifyEmailPage.d.ts +0 -24
  197. package/src/components/VerifyEmailPage.d.ts.map +0 -1
  198. package/src/components/VerifyEmailPage.js +0 -81
  199. package/src/components/index.d.ts.map +0 -1
  200. package/src/components/index.js +0 -35
  201. package/src/contexts/AuthProvider.d.ts +0 -186
  202. package/src/contexts/AuthProvider.d.ts.map +0 -1
  203. package/src/contexts/AuthProvider.js +0 -515
  204. package/src/contexts/I18nProvider.d.ts +0 -27
  205. package/src/contexts/I18nProvider.d.ts.map +0 -1
  206. package/src/contexts/I18nProvider.js +0 -60
  207. package/src/contexts/MenuContext.d.ts +0 -20
  208. package/src/contexts/MenuContext.d.ts.map +0 -1
  209. package/src/contexts/MenuContext.js +0 -275
  210. package/src/contexts/SuiteConfigProvider.d.ts +0 -44
  211. package/src/contexts/SuiteConfigProvider.d.ts.map +0 -1
  212. package/src/contexts/SuiteConfigProvider.js +0 -43
  213. package/src/contexts/ThemeProvider.d.ts +0 -15
  214. package/src/contexts/ThemeProvider.d.ts.map +0 -1
  215. package/src/contexts/ThemeProvider.js +0 -36
  216. package/src/contexts/index.d.ts.map +0 -1
  217. package/src/contexts/index.js +0 -8
  218. package/src/hooks/index.d.ts.map +0 -1
  219. package/src/hooks/index.js +0 -9
  220. package/src/hooks/useAuthenticatedApi.d.ts +0 -34
  221. package/src/hooks/useAuthenticatedApi.d.ts.map +0 -1
  222. package/src/hooks/useAuthenticatedApi.js +0 -46
  223. package/src/hooks/useBackupCodes.d.ts +0 -15
  224. package/src/hooks/useBackupCodes.d.ts.map +0 -1
  225. package/src/hooks/useBackupCodes.js +0 -74
  226. package/src/hooks/useEmailVerification.d.ts +0 -10
  227. package/src/hooks/useEmailVerification.d.ts.map +0 -1
  228. package/src/hooks/useEmailVerification.js +0 -40
  229. package/src/hooks/useExpiringValue.d.ts +0 -14
  230. package/src/hooks/useExpiringValue.d.ts.map +0 -1
  231. package/src/hooks/useExpiringValue.js +0 -53
  232. package/src/hooks/useLocalStorage.d.ts +0 -2
  233. package/src/hooks/useLocalStorage.d.ts.map +0 -1
  234. package/src/hooks/useLocalStorage.js +0 -15
  235. package/src/hooks/useUserSettings.d.ts +0 -48
  236. package/src/hooks/useUserSettings.d.ts.map +0 -1
  237. package/src/hooks/useUserSettings.js +0 -171
  238. package/src/index.d.ts.map +0 -1
  239. package/src/index.js +0 -12
  240. package/src/interfaces/IAppConfig.d.ts +0 -8
  241. package/src/interfaces/IAppConfig.d.ts.map +0 -1
  242. package/src/interfaces/IAppConfig.js +0 -2
  243. package/src/interfaces/ICategoryConfig.d.ts +0 -41
  244. package/src/interfaces/ICategoryConfig.d.ts.map +0 -1
  245. package/src/interfaces/ICategoryConfig.js +0 -2
  246. package/src/interfaces/IMenuConfig.d.ts +0 -28
  247. package/src/interfaces/IMenuConfig.d.ts.map +0 -1
  248. package/src/interfaces/IMenuConfig.js +0 -2
  249. package/src/interfaces/IMenuOption.d.ts +0 -82
  250. package/src/interfaces/IMenuOption.d.ts.map +0 -1
  251. package/src/interfaces/IMenuOption.js +0 -2
  252. package/src/interfaces/index.d.ts.map +0 -1
  253. package/src/interfaces/index.js +0 -7
  254. package/src/services/__mocks__/authService.d.ts +0 -21
  255. package/src/services/__mocks__/authService.d.ts.map +0 -1
  256. package/src/services/__mocks__/authService.js +0 -15
  257. package/src/services/api.d.ts +0 -3
  258. package/src/services/api.d.ts.map +0 -1
  259. package/src/services/api.js +0 -14
  260. package/src/services/authService.d.ts +0 -126
  261. package/src/services/authService.d.ts.map +0 -1
  262. package/src/services/authService.js +0 -471
  263. package/src/services/authenticatedApi.d.ts +0 -3
  264. package/src/services/authenticatedApi.d.ts.map +0 -1
  265. package/src/services/authenticatedApi.js +0 -18
  266. package/src/services/index.d.ts +0 -4
  267. package/src/services/index.d.ts.map +0 -1
  268. package/src/services/index.js +0 -6
  269. package/src/types/MenuType.d.ts +0 -11
  270. package/src/types/MenuType.d.ts.map +0 -1
  271. package/src/types/MenuType.js +0 -12
  272. package/src/types/expirationSeconds.d.ts +0 -3
  273. package/src/types/expirationSeconds.d.ts.map +0 -1
  274. package/src/types/expirationSeconds.js +0 -17
  275. package/src/types/index.d.ts +0 -2
  276. package/src/types/index.d.ts.map +0 -1
  277. package/src/types/index.js +0 -4
  278. package/src/types/translation.d.ts +0 -10
  279. package/src/types/translation.d.ts.map +0 -1
  280. package/src/types/translation.js +0 -9
  281. package/src/wrappers/BackupCodeLoginWrapper.d.ts +0 -8
  282. package/src/wrappers/BackupCodeLoginWrapper.d.ts.map +0 -1
  283. package/src/wrappers/BackupCodeLoginWrapper.js +0 -20
  284. package/src/wrappers/BackupCodesWrapper.d.ts +0 -7
  285. package/src/wrappers/BackupCodesWrapper.d.ts.map +0 -1
  286. package/src/wrappers/BackupCodesWrapper.js +0 -17
  287. package/src/wrappers/ChangePasswordFormWrapper.d.ts +0 -8
  288. package/src/wrappers/ChangePasswordFormWrapper.d.ts.map +0 -1
  289. package/src/wrappers/ChangePasswordFormWrapper.js +0 -21
  290. package/src/wrappers/ForgotPasswordFormWrapper.d.ts +0 -8
  291. package/src/wrappers/ForgotPasswordFormWrapper.d.ts.map +0 -1
  292. package/src/wrappers/ForgotPasswordFormWrapper.js +0 -29
  293. package/src/wrappers/LoginFormWrapper.d.ts +0 -21
  294. package/src/wrappers/LoginFormWrapper.d.ts.map +0 -1
  295. package/src/wrappers/LoginFormWrapper.js +0 -72
  296. package/src/wrappers/LogoutPageWrapper.d.ts +0 -9
  297. package/src/wrappers/LogoutPageWrapper.d.ts.map +0 -1
  298. package/src/wrappers/LogoutPageWrapper.js +0 -21
  299. package/src/wrappers/RegisterFormWrapper.d.ts +0 -20
  300. package/src/wrappers/RegisterFormWrapper.d.ts.map +0 -1
  301. package/src/wrappers/RegisterFormWrapper.js +0 -51
  302. package/src/wrappers/ResetPasswordFormWrapper.d.ts +0 -9
  303. package/src/wrappers/ResetPasswordFormWrapper.d.ts.map +0 -1
  304. package/src/wrappers/ResetPasswordFormWrapper.js +0 -34
  305. package/src/wrappers/UserSettingsFormWrapper.d.ts +0 -29
  306. package/src/wrappers/UserSettingsFormWrapper.d.ts.map +0 -1
  307. package/src/wrappers/UserSettingsFormWrapper.js +0 -95
  308. package/src/wrappers/VerifyEmailPageWrapper.d.ts +0 -8
  309. package/src/wrappers/VerifyEmailPageWrapper.d.ts.map +0 -1
  310. package/src/wrappers/VerifyEmailPageWrapper.js +0 -21
  311. package/src/wrappers/index.d.ts.map +0 -1
  312. package/src/wrappers/index.js +0 -24
@@ -0,0 +1,287 @@
1
+ import {
2
+ Constants,
3
+ SuiteCoreComponentId,
4
+ SuiteCoreStringKey,
5
+ SuiteCoreStringKeyValue,
6
+ } from '@digitaldefiance/suite-core-lib';
7
+ import {
8
+ Alert,
9
+ Box,
10
+ Button,
11
+ Container,
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 BackupCodesFormValues {
21
+ password?: string;
22
+ mnemonic?: string;
23
+ }
24
+
25
+ export interface BackupCodesFormProps {
26
+ onSubmit: (values: BackupCodesFormValues) => Promise<{
27
+ message: string;
28
+ backupCodes: string[];
29
+ }>;
30
+ backupCodesRemaining?: number | null;
31
+ mnemonicValidation?: Yup.StringSchema;
32
+ passwordValidation?: Yup.StringSchema;
33
+ labels?: {
34
+ title?: string;
35
+ codesRemaining?: string;
36
+ mnemonic?: string;
37
+ password?: string;
38
+ generateButton?: string;
39
+ successTitle?: string;
40
+ };
41
+ }
42
+
43
+ export const BackupCodesForm: FC<BackupCodesFormProps> = ({
44
+ onSubmit,
45
+ backupCodesRemaining = null,
46
+ mnemonicValidation,
47
+ passwordValidation,
48
+ labels = {},
49
+ }) => {
50
+ const { tComponent } = useI18n();
51
+ const [apiError, setApiError] = useState<string | null>(null);
52
+ const [apiSuccess, setApiSuccess] = useState<string | null>(null);
53
+ const [backupCodes, setBackupCodes] = useState<string[] | null>(null);
54
+
55
+ const validation = {
56
+ mnemonic:
57
+ mnemonicValidation ||
58
+ Yup.string()
59
+ .trim()
60
+ .matches(
61
+ Constants.MnemonicRegex,
62
+ tComponent<SuiteCoreStringKeyValue>(
63
+ SuiteCoreComponentId,
64
+ SuiteCoreStringKey.Validation_MnemonicRegex
65
+ )
66
+ )
67
+ .optional(),
68
+ password:
69
+ passwordValidation ||
70
+ Yup.string()
71
+ .trim()
72
+ .matches(
73
+ Constants.PasswordRegex,
74
+ tComponent<SuiteCoreStringKeyValue>(
75
+ SuiteCoreComponentId,
76
+ SuiteCoreStringKey.Validation_PasswordRegexErrorTemplate
77
+ )
78
+ )
79
+ .optional(),
80
+ };
81
+
82
+ const translatedLabels = {
83
+ codesRemaining:
84
+ labels.codesRemaining ||
85
+ tComponent<SuiteCoreStringKeyValue>(
86
+ SuiteCoreComponentId,
87
+ SuiteCoreStringKey.BackupCodeRecovery_CodesRemainingTemplate
88
+ ),
89
+ mnemonic:
90
+ labels.mnemonic ||
91
+ tComponent<SuiteCoreStringKeyValue>(
92
+ SuiteCoreComponentId,
93
+ SuiteCoreStringKey.Common_Mnemonic
94
+ ),
95
+ password:
96
+ labels.password ||
97
+ tComponent<SuiteCoreStringKeyValue>(
98
+ SuiteCoreComponentId,
99
+ SuiteCoreStringKey.Common_Password
100
+ ),
101
+ generateButton:
102
+ labels.generateButton ||
103
+ tComponent<SuiteCoreStringKeyValue>(
104
+ SuiteCoreComponentId,
105
+ SuiteCoreStringKey.BackupCodeRecovery_GenerateNewCodes
106
+ ),
107
+ successTitle:
108
+ labels.successTitle ||
109
+ tComponent<SuiteCoreStringKeyValue>(
110
+ SuiteCoreComponentId,
111
+ SuiteCoreStringKey.BackupCodeRecovery_YourNewCodes
112
+ ),
113
+ xorError: tComponent<SuiteCoreStringKeyValue>(
114
+ SuiteCoreComponentId,
115
+ SuiteCoreStringKey.Validation_MnemonicOrPasswordRequired
116
+ ),
117
+ unexpectedError: tComponent<SuiteCoreStringKeyValue>(
118
+ SuiteCoreComponentId,
119
+ SuiteCoreStringKey.Common_UnexpectedError
120
+ ),
121
+ };
122
+
123
+ const validationSchema = Yup.object({
124
+ mnemonic: validation.mnemonic,
125
+ password: validation.password,
126
+ })
127
+ .test(
128
+ 'xor-mnemonic-password-mnemonic',
129
+ translatedLabels.xorError,
130
+ function (value) {
131
+ const mnemonic = value?.mnemonic?.trim() ?? '';
132
+ const password = value?.password?.trim() ?? '';
133
+ const hasMnemonic = mnemonic.length > 0;
134
+ const hasPassword = password.length > 0;
135
+
136
+ if (!hasMnemonic && !hasPassword) {
137
+ return this.createError({
138
+ path: 'mnemonic',
139
+ message: translatedLabels.xorError,
140
+ });
141
+ }
142
+ if (hasMnemonic && hasPassword) {
143
+ return this.createError({
144
+ path: 'mnemonic',
145
+ message: translatedLabels.xorError,
146
+ });
147
+ }
148
+ return true;
149
+ }
150
+ )
151
+ .test(
152
+ 'xor-mnemonic-password-password',
153
+ translatedLabels.xorError,
154
+ function (value) {
155
+ const mnemonic = value?.mnemonic?.trim() ?? '';
156
+ const password = value?.password?.trim() ?? '';
157
+ const hasMnemonic = mnemonic.length > 0;
158
+ const hasPassword = password.length > 0;
159
+
160
+ if (!hasMnemonic && !hasPassword) {
161
+ return this.createError({
162
+ path: 'password',
163
+ message: translatedLabels.xorError,
164
+ });
165
+ }
166
+ if (hasMnemonic && hasPassword) {
167
+ return this.createError({
168
+ path: 'password',
169
+ message: translatedLabels.xorError,
170
+ });
171
+ }
172
+ return true;
173
+ }
174
+ );
175
+
176
+ const formik = useFormik<BackupCodesFormValues>({
177
+ initialValues: {
178
+ password: '',
179
+ mnemonic: '',
180
+ },
181
+ validationSchema,
182
+ onSubmit: async (values, { setSubmitting }) => {
183
+ try {
184
+ const result = await onSubmit(values);
185
+ if (result && result.backupCodes) {
186
+ setApiSuccess(translatedLabels.successTitle);
187
+ setBackupCodes(result.backupCodes);
188
+ }
189
+ if (result && result.message) {
190
+ setApiSuccess(result.message);
191
+ }
192
+ setApiError(null);
193
+ } catch (e: unknown) {
194
+ setApiSuccess(null);
195
+ const error = e as { response?: { data?: { message?: string } } };
196
+ setApiError(
197
+ error.response?.data?.message ?? translatedLabels.unexpectedError
198
+ );
199
+ } finally {
200
+ setSubmitting(false);
201
+ }
202
+ },
203
+ });
204
+
205
+ return (
206
+ <Container component="main" maxWidth="xs">
207
+ <Box>
208
+ <Typography component="h1" variant="h5">
209
+ {translatedLabels.codesRemaining.replace(
210
+ '{count}',
211
+ String(backupCodesRemaining ?? 0)
212
+ )}
213
+ </Typography>
214
+ </Box>
215
+ <Box
216
+ component="form"
217
+ onSubmit={formik.handleSubmit}
218
+ sx={{ mt: 1, width: '100%' }}
219
+ >
220
+ <TextField
221
+ margin="normal"
222
+ fullWidth
223
+ id="mnemonic"
224
+ label={translatedLabels.mnemonic}
225
+ type="password"
226
+ name="mnemonic"
227
+ autoComplete="mnemonic"
228
+ autoFocus
229
+ value={formik.values.mnemonic}
230
+ onChange={formik.handleChange}
231
+ onBlur={formik.handleBlur}
232
+ error={
233
+ (formik.touched.mnemonic || formik.submitCount > 0) &&
234
+ Boolean(formik.errors.mnemonic)
235
+ }
236
+ helperText={
237
+ (formik.touched.mnemonic || formik.submitCount > 0) &&
238
+ formik.errors.mnemonic
239
+ }
240
+ />
241
+ <TextField
242
+ margin="normal"
243
+ fullWidth
244
+ name="password"
245
+ label={translatedLabels.password}
246
+ type="password"
247
+ id="password"
248
+ value={formik.values.password}
249
+ onChange={formik.handleChange}
250
+ onBlur={formik.handleBlur}
251
+ error={
252
+ (formik.touched.password || formik.submitCount > 0) &&
253
+ Boolean(formik.errors.password)
254
+ }
255
+ helperText={
256
+ (formik.touched.password || formik.submitCount > 0) &&
257
+ formik.errors.password
258
+ }
259
+ />
260
+ <Button type="submit" fullWidth variant="contained" sx={{ mt: 2 }}>
261
+ {translatedLabels.generateButton}
262
+ </Button>
263
+ </Box>
264
+ {apiError && (
265
+ <Alert severity="error" sx={{ mt: 2, mb: 2 }}>
266
+ {apiError}
267
+ </Alert>
268
+ )}
269
+ {backupCodes && apiSuccess && (
270
+ <Box sx={{ mt: 2, mb: 2 }}>
271
+ <Typography component="h2" variant="h6">
272
+ {apiSuccess}
273
+ </Typography>
274
+ <ul>
275
+ {backupCodes.map((code, index) => (
276
+ <li key={index}>
277
+ <pre>{code}</pre>
278
+ </li>
279
+ ))}
280
+ </ul>
281
+ </Box>
282
+ )}
283
+ </Container>
284
+ );
285
+ };
286
+
287
+ export default BackupCodesForm;
@@ -0,0 +1,155 @@
1
+ import {
2
+ Box,
3
+ Fade,
4
+ IconButton,
5
+ Popover,
6
+ Tooltip,
7
+ Typography,
8
+ } from '@mui/material';
9
+ import {
10
+ FC,
11
+ MouseEvent,
12
+ ReactElement,
13
+ useCallback,
14
+ useMemo,
15
+ useState,
16
+ } from 'react';
17
+ import { ICategoryConfig } from '../interfaces/ICategoryConfig';
18
+ import { IMenuConfig } from '../interfaces/IMenuConfig';
19
+ import { DropdownMenu } from './DropdownMenu';
20
+
21
+ export interface CategoryMenuProps {
22
+ category: ICategoryConfig;
23
+ menus: IMenuConfig[];
24
+ }
25
+
26
+ /**
27
+ * Renders an AppBar icon button that opens a popover containing a grid of
28
+ * icon tiles. Each tile is a {@link DropdownMenu} for one of the supplied
29
+ * `menus`, preserving its existing sub-menu behaviour. Use this together with
30
+ * `IMenuConfig.category` to collapse many top-bar icons into a small number
31
+ * of category containers (a "mega-menu" pattern).
32
+ */
33
+ export const CategoryMenu: FC<CategoryMenuProps> = ({ category, menus }) => {
34
+ const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
35
+
36
+ const handleOpen = useCallback((event: MouseEvent<HTMLElement>) => {
37
+ setAnchorEl(event.currentTarget);
38
+ }, []);
39
+
40
+ const handleClose = useCallback(() => {
41
+ setAnchorEl(null);
42
+ }, []);
43
+
44
+ const columns = category.columns ?? 3;
45
+ const showLabels = category.showLabels ?? true;
46
+ const showHeader = category.showHeader ?? false;
47
+ const hideWhenEmpty = category.hideWhenEmpty ?? true;
48
+
49
+ const visibleMenus = useMemo(
50
+ () => menus.filter((m) => !m.isUserMenu),
51
+ [menus]
52
+ );
53
+
54
+ if (visibleMenus.length === 0 && hideWhenEmpty) {
55
+ return null;
56
+ }
57
+
58
+ const trigger = (
59
+ <IconButton
60
+ color="inherit"
61
+ onClick={handleOpen}
62
+ aria-label={category.label ?? category.id}
63
+ aria-haspopup="true"
64
+ aria-expanded={Boolean(anchorEl) || undefined}
65
+ >
66
+ {category.icon}
67
+ </IconButton>
68
+ );
69
+
70
+ return (
71
+ <Box>
72
+ {category.label ? (
73
+ <Tooltip title={category.label}>{trigger}</Tooltip>
74
+ ) : (
75
+ trigger
76
+ )}
77
+ <Popover
78
+ anchorEl={anchorEl}
79
+ open={Boolean(anchorEl)}
80
+ onClose={handleClose}
81
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
82
+ transformOrigin={{ vertical: 'top', horizontal: 'right' }}
83
+ TransitionComponent={Fade}
84
+ >
85
+ <Box sx={{ p: 1.5, minWidth: 64 * columns + 24 }}>
86
+ {showHeader && (category.header || category.label) && (
87
+ <Typography
88
+ variant="overline"
89
+ sx={{
90
+ display: 'block',
91
+ px: 1,
92
+ pb: 1,
93
+ color: 'text.secondary',
94
+ lineHeight: 1.2,
95
+ }}
96
+ >
97
+ {category.header ?? category.label}
98
+ </Typography>
99
+ )}
100
+ <Box
101
+ sx={{
102
+ display: 'grid',
103
+ gridTemplateColumns: `repeat(${columns}, minmax(72px, 1fr))`,
104
+ gap: 1,
105
+ }}
106
+ >
107
+ {visibleMenus.map((menu, index) => {
108
+ const tileLabel = menu.label ?? String(menu.menuType);
109
+ return (
110
+ <Box
111
+ key={`${category.id}-tile-${menu.menuType}-${index}`}
112
+ sx={{
113
+ display: 'flex',
114
+ flexDirection: 'column',
115
+ alignItems: 'center',
116
+ justifyContent: 'flex-start',
117
+ px: 0.5,
118
+ py: 0.5,
119
+ borderRadius: 1,
120
+ '&:hover': {
121
+ backgroundColor: 'action.hover',
122
+ },
123
+ }}
124
+ >
125
+ <DropdownMenu
126
+ menuType={menu.menuType}
127
+ menuIcon={menu.menuIcon as ReactElement}
128
+ action={menu.action}
129
+ hideWhenEmpty={menu.hideWhenEmpty}
130
+ />
131
+ {showLabels && (
132
+ <Typography
133
+ variant="caption"
134
+ sx={{
135
+ mt: -0.5,
136
+ textAlign: 'center',
137
+ lineHeight: 1.1,
138
+ color: 'text.primary',
139
+ userSelect: 'none',
140
+ }}
141
+ >
142
+ {tileLabel}
143
+ </Typography>
144
+ )}
145
+ </Box>
146
+ );
147
+ })}
148
+ </Box>
149
+ </Box>
150
+ </Popover>
151
+ </Box>
152
+ );
153
+ };
154
+
155
+ export default CategoryMenu;
@@ -0,0 +1,273 @@
1
+ import {
2
+ Constants,
3
+ SuiteCoreComponentId,
4
+ SuiteCoreStringKey,
5
+ SuiteCoreStringKeyValue,
6
+ } from '@digitaldefiance/suite-core-lib';
7
+ import {
8
+ Alert,
9
+ Box,
10
+ Button,
11
+ Container,
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 ChangePasswordFormValues {
21
+ currentPassword: string;
22
+ newPassword: string;
23
+ confirmPassword: string;
24
+ }
25
+
26
+ export interface ChangePasswordFormProps {
27
+ onSubmit: (
28
+ values: ChangePasswordFormValues
29
+ ) => Promise<{ success?: boolean; error?: string }>;
30
+ titleText?: string;
31
+ currentPasswordLabel?: string;
32
+ newPasswordLabel?: string;
33
+ confirmPasswordLabel?: string;
34
+ submitButtonText?: string;
35
+ submittingButtonText?: string;
36
+ successMessage?: string;
37
+ currentPasswordValidation?: Yup.StringSchema;
38
+ newPasswordValidation?: Yup.StringSchema;
39
+ confirmPasswordValidation?: Yup.StringSchema;
40
+ }
41
+
42
+ export const ChangePasswordForm: FC<ChangePasswordFormProps> = ({
43
+ onSubmit,
44
+ titleText,
45
+ currentPasswordLabel,
46
+ newPasswordLabel,
47
+ confirmPasswordLabel,
48
+ submitButtonText,
49
+ submittingButtonText,
50
+ successMessage,
51
+ currentPasswordValidation,
52
+ newPasswordValidation,
53
+ confirmPasswordValidation,
54
+ }) => {
55
+ const { tComponent } = useI18n();
56
+ const [success, setSuccess] = useState(false);
57
+ const [apiError, setApiError] = useState<string>('');
58
+
59
+ const validation = {
60
+ currentPassword:
61
+ currentPasswordValidation ||
62
+ Yup.string().required(
63
+ tComponent<SuiteCoreStringKeyValue>(
64
+ SuiteCoreComponentId,
65
+ SuiteCoreStringKey.Validation_Required
66
+ )
67
+ ),
68
+ newPassword:
69
+ newPasswordValidation ||
70
+ Yup.string()
71
+ .min(
72
+ Constants.PasswordMinLength,
73
+ tComponent<SuiteCoreStringKeyValue>(
74
+ SuiteCoreComponentId,
75
+ SuiteCoreStringKey.Validation_PasswordMinLengthTemplate
76
+ )
77
+ )
78
+ .required(
79
+ tComponent<SuiteCoreStringKeyValue>(
80
+ SuiteCoreComponentId,
81
+ SuiteCoreStringKey.Validation_Required
82
+ )
83
+ ),
84
+ confirmPassword:
85
+ confirmPasswordValidation ||
86
+ Yup.string()
87
+ .oneOf(
88
+ [Yup.ref('newPassword')],
89
+ tComponent<SuiteCoreStringKeyValue>(
90
+ SuiteCoreComponentId,
91
+ SuiteCoreStringKey.Validation_PasswordMatch
92
+ )
93
+ )
94
+ .required(
95
+ tComponent<SuiteCoreStringKeyValue>(
96
+ SuiteCoreComponentId,
97
+ SuiteCoreStringKey.Validation_Required
98
+ )
99
+ ),
100
+ };
101
+
102
+ const labels = {
103
+ title:
104
+ titleText ||
105
+ tComponent<SuiteCoreStringKeyValue>(
106
+ SuiteCoreComponentId,
107
+ SuiteCoreStringKey.Common_ChangePassword
108
+ ),
109
+ currentPassword:
110
+ currentPasswordLabel ||
111
+ tComponent<SuiteCoreStringKeyValue>(
112
+ SuiteCoreComponentId,
113
+ SuiteCoreStringKey.Common_CurrentPassword
114
+ ),
115
+ newPassword:
116
+ newPasswordLabel ||
117
+ tComponent<SuiteCoreStringKeyValue>(
118
+ SuiteCoreComponentId,
119
+ SuiteCoreStringKey.Common_NewPassword
120
+ ),
121
+ confirmPassword:
122
+ confirmPasswordLabel ||
123
+ tComponent<SuiteCoreStringKeyValue>(
124
+ SuiteCoreComponentId,
125
+ SuiteCoreStringKey.Common_ConfirmNewPassword
126
+ ),
127
+ submitButton:
128
+ submitButtonText ||
129
+ tComponent<SuiteCoreStringKeyValue>(
130
+ SuiteCoreComponentId,
131
+ SuiteCoreStringKey.Common_ChangePassword
132
+ ),
133
+ submittingButton:
134
+ submittingButtonText ||
135
+ tComponent<SuiteCoreStringKeyValue>(
136
+ SuiteCoreComponentId,
137
+ SuiteCoreStringKey.Common_ChangingPassword
138
+ ),
139
+ success:
140
+ successMessage ||
141
+ tComponent<SuiteCoreStringKeyValue>(
142
+ SuiteCoreComponentId,
143
+ SuiteCoreStringKey.PasswordChange_Success
144
+ ),
145
+ };
146
+
147
+ const formik = useFormik<ChangePasswordFormValues>({
148
+ initialValues: {
149
+ currentPassword: '',
150
+ newPassword: '',
151
+ confirmPassword: '',
152
+ },
153
+ validationSchema: Yup.object({
154
+ currentPassword: validation.currentPassword,
155
+ newPassword: validation.newPassword,
156
+ confirmPassword: validation.confirmPassword,
157
+ }),
158
+ onSubmit: async (values, { resetForm }) => {
159
+ const result = await onSubmit(values);
160
+
161
+ if ('success' in result) {
162
+ setSuccess(true);
163
+ setApiError('');
164
+ resetForm();
165
+ } else if ('error' in result && result.error) {
166
+ setApiError(result.error);
167
+ setSuccess(false);
168
+ }
169
+ },
170
+ });
171
+
172
+ return (
173
+ <Container maxWidth="sm">
174
+ <Box
175
+ sx={{
176
+ mt: 8,
177
+ display: 'flex',
178
+ flexDirection: 'column',
179
+ alignItems: 'center',
180
+ }}
181
+ >
182
+ <Typography variant="h4" component="h1" gutterBottom>
183
+ {labels.title}
184
+ </Typography>
185
+
186
+ <Box
187
+ component="form"
188
+ onSubmit={formik.handleSubmit}
189
+ sx={{ mt: 1, width: '100%' }}
190
+ >
191
+ <TextField
192
+ fullWidth
193
+ id="currentPassword"
194
+ name="currentPassword"
195
+ label={labels.currentPassword}
196
+ type="password"
197
+ value={formik.values.currentPassword}
198
+ onChange={formik.handleChange}
199
+ onBlur={formik.handleBlur}
200
+ error={Boolean(
201
+ formik.touched.currentPassword && formik.errors.currentPassword
202
+ )}
203
+ helperText={
204
+ formik.touched.currentPassword && formik.errors.currentPassword
205
+ }
206
+ margin="normal"
207
+ />
208
+
209
+ <TextField
210
+ fullWidth
211
+ id="newPassword"
212
+ name="newPassword"
213
+ label={labels.newPassword}
214
+ type="password"
215
+ value={formik.values.newPassword}
216
+ onChange={formik.handleChange}
217
+ onBlur={formik.handleBlur}
218
+ error={Boolean(
219
+ formik.touched.newPassword && formik.errors.newPassword
220
+ )}
221
+ helperText={formik.touched.newPassword && formik.errors.newPassword}
222
+ margin="normal"
223
+ />
224
+
225
+ <TextField
226
+ fullWidth
227
+ id="confirmPassword"
228
+ name="confirmPassword"
229
+ label={labels.confirmPassword}
230
+ type="password"
231
+ value={formik.values.confirmPassword}
232
+ onChange={formik.handleChange}
233
+ onBlur={formik.handleBlur}
234
+ error={Boolean(
235
+ formik.touched.confirmPassword && formik.errors.confirmPassword
236
+ )}
237
+ helperText={
238
+ formik.touched.confirmPassword && formik.errors.confirmPassword
239
+ }
240
+ margin="normal"
241
+ />
242
+
243
+ {apiError && (
244
+ <Alert severity="error" sx={{ mt: 2, mb: 2 }}>
245
+ {apiError}
246
+ </Alert>
247
+ )}
248
+
249
+ {success && (
250
+ <Alert severity="success" sx={{ mt: 2, mb: 2 }}>
251
+ {labels.success}
252
+ </Alert>
253
+ )}
254
+
255
+ <Button
256
+ type="submit"
257
+ fullWidth
258
+ variant="contained"
259
+ color="primary"
260
+ sx={{ mt: 3, mb: 2 }}
261
+ disabled={formik.isSubmitting}
262
+ >
263
+ {formik.isSubmitting
264
+ ? labels.submittingButton
265
+ : labels.submitButton}
266
+ </Button>
267
+ </Box>
268
+ </Box>
269
+ </Container>
270
+ );
271
+ };
272
+
273
+ export default ChangePasswordForm;