@digitaldefiance/express-suite-react-components 2.9.1 → 2.9.3

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 +11 -7
  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 +134 -0
  9. package/src/components/BackupCodeLoginForm.tsx +314 -0
  10. package/src/components/BackupCodesForm.tsx +198 -0
  11. package/src/components/ChangePasswordForm.tsx +182 -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 +65 -0
  18. package/src/components/Flag.tsx +53 -0
  19. package/src/components/ForgotPasswordForm.tsx +120 -0
  20. package/src/components/LoginForm.tsx +307 -0
  21. package/src/components/LogoutPage.tsx +21 -0
  22. package/src/components/RegisterForm.tsx +354 -0
  23. package/src/components/ResetPasswordForm.tsx +164 -0
  24. package/src/components/SideMenu.tsx +46 -0
  25. package/src/components/SideMenuListItem.tsx +74 -0
  26. package/src/components/TopMenu.tsx +134 -0
  27. package/src/components/TranslatedTitle.tsx +22 -0
  28. package/src/components/UserLanguageSelector.tsx +45 -0
  29. package/src/components/UserMenu.tsx +15 -0
  30. package/src/components/UserSettingsForm.tsx +328 -0
  31. package/src/components/VerifyEmailPage.tsx +133 -0
  32. package/src/components/{index.d.ts → index.ts} +1 -1
  33. package/src/contexts/AuthProvider.spec.tsx +1060 -0
  34. package/src/contexts/AuthProvider.tsx +741 -0
  35. package/src/contexts/I18nProvider.tsx +85 -0
  36. package/src/contexts/MenuContext.tsx +310 -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 +85 -0
  42. package/src/hooks/useEmailVerification.ts +39 -0
  43. package/src/hooks/useExpiringValue.ts +78 -0
  44. package/src/hooks/useLocalStorage.ts +18 -0
  45. package/src/hooks/useUserSettings.ts +216 -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 +422 -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 +35 -0
  61. package/src/wrappers/BackupCodesWrapper.tsx +28 -0
  62. package/src/wrappers/ChangePasswordFormWrapper.tsx +31 -0
  63. package/src/wrappers/LoginFormWrapper.tsx +59 -0
  64. package/src/wrappers/LogoutPageWrapper.tsx +30 -0
  65. package/src/wrappers/RegisterFormWrapper.tsx +48 -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 -70
  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 -106
  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 -108
  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 -66
  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 -54
  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 -99
  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 -54
  126. package/src/components/RegisterForm.d.ts.map +0 -1
  127. package/src/components/RegisterForm.js +0 -105
  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 -68
  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 -36
  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 -56
  150. package/src/components/UserSettingsForm.d.ts.map +0 -1
  151. package/src/components/UserSettingsForm.js +0 -93
  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 -61
  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 -446
  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 -244
  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 -70
  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 -36
  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 -46
  189. package/src/hooks/useUserSettings.d.ts.map +0 -1
  190. package/src/hooks/useUserSettings.js +0 -152
  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 -347
  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 -21
  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 -26
  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,198 @@
1
+ import { Alert, Box, Button, Container, TextField, Typography } from '@mui/material';
2
+ import { useFormik } from 'formik';
3
+ import { FC, useState } from 'react';
4
+ import * as Yup from 'yup';
5
+ import { Constants, SuiteCoreComponentId, SuiteCoreStringKey } from '@digitaldefiance/suite-core-lib';
6
+ import { useI18n } from '../contexts';
7
+
8
+ export interface BackupCodesFormValues {
9
+ password?: string;
10
+ mnemonic?: string;
11
+ }
12
+
13
+ export interface BackupCodesFormProps {
14
+ onSubmit: (values: BackupCodesFormValues) => Promise<{
15
+ message: string;
16
+ backupCodes: string[];
17
+ }>;
18
+ backupCodesRemaining?: number | null;
19
+ mnemonicValidation?: Yup.StringSchema;
20
+ passwordValidation?: Yup.StringSchema;
21
+ labels?: {
22
+ title?: string;
23
+ codesRemaining?: string;
24
+ mnemonic?: string;
25
+ password?: string;
26
+ generateButton?: string;
27
+ successTitle?: string;
28
+ };
29
+ }
30
+
31
+ export const BackupCodesForm: FC<BackupCodesFormProps> = ({
32
+ onSubmit,
33
+ backupCodesRemaining = null,
34
+ mnemonicValidation,
35
+ passwordValidation,
36
+ labels = {},
37
+ }) => {
38
+ const { t, tComponent } = useI18n();
39
+ const [apiError, setApiError] = useState<string | null>(null);
40
+ const [apiSuccess, setApiSuccess] = useState<string | null>(null);
41
+ const [backupCodes, setBackupCodes] = useState<string[] | null>(null);
42
+
43
+ const validation = {
44
+ mnemonic: mnemonicValidation || Yup.string()
45
+ .trim()
46
+ .matches(Constants.MnemonicRegex, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_MnemonicRegex))
47
+ .optional(),
48
+ password: passwordValidation || Yup.string()
49
+ .trim()
50
+ .matches(Constants.PasswordRegex, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_PasswordRegexErrorTemplate))
51
+ .optional(),
52
+ };
53
+
54
+ const translatedLabels = {
55
+ codesRemaining: labels.codesRemaining || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.BackupCodeRecovery_CodesRemainingTemplate),
56
+ mnemonic: labels.mnemonic || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Mnemonic),
57
+ password: labels.password || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Password),
58
+ generateButton: labels.generateButton || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.BackupCodeRecovery_GenerateNewCodes),
59
+ successTitle: labels.successTitle || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.BackupCodeRecovery_YourNewCodes),
60
+ xorError: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_MnemonicOrPasswordRequired),
61
+ unexpectedError: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_UnexpectedError),
62
+ };
63
+
64
+ const validationSchema = Yup.object({
65
+ mnemonic: validation.mnemonic,
66
+ password: validation.password,
67
+ })
68
+ .test('xor-mnemonic-password-mnemonic', translatedLabels.xorError, function (value) {
69
+ const mnemonic = value?.mnemonic?.trim() ?? '';
70
+ const password = value?.password?.trim() ?? '';
71
+ const hasMnemonic = mnemonic.length > 0;
72
+ const hasPassword = password.length > 0;
73
+
74
+ if (!hasMnemonic && !hasPassword) {
75
+ return this.createError({
76
+ path: 'mnemonic',
77
+ message: translatedLabels.xorError,
78
+ });
79
+ }
80
+ if (hasMnemonic && hasPassword) {
81
+ return this.createError({
82
+ path: 'mnemonic',
83
+ message: translatedLabels.xorError,
84
+ });
85
+ }
86
+ return true;
87
+ })
88
+ .test('xor-mnemonic-password-password', translatedLabels.xorError, function (value) {
89
+ const mnemonic = value?.mnemonic?.trim() ?? '';
90
+ const password = value?.password?.trim() ?? '';
91
+ const hasMnemonic = mnemonic.length > 0;
92
+ const hasPassword = password.length > 0;
93
+
94
+ if (!hasMnemonic && !hasPassword) {
95
+ return this.createError({
96
+ path: 'password',
97
+ message: translatedLabels.xorError,
98
+ });
99
+ }
100
+ if (hasMnemonic && hasPassword) {
101
+ return this.createError({
102
+ path: 'password',
103
+ message: translatedLabels.xorError,
104
+ });
105
+ }
106
+ return true;
107
+ });
108
+
109
+ const formik = useFormik<BackupCodesFormValues>({
110
+ initialValues: {
111
+ password: '',
112
+ mnemonic: '',
113
+ },
114
+ validationSchema,
115
+ onSubmit: async (values, { setSubmitting }) => {
116
+ try {
117
+ const result = await onSubmit(values);
118
+ if (result && result.backupCodes) {
119
+ setApiSuccess(translatedLabels.successTitle);
120
+ setBackupCodes(result.backupCodes);
121
+ }
122
+ if (result && result.message) {
123
+ setApiSuccess(result.message);
124
+ }
125
+ setApiError(null);
126
+ } catch (e: any) {
127
+ setApiSuccess(null);
128
+ setApiError(e.response?.data?.message ?? translatedLabels.unexpectedError);
129
+ } finally {
130
+ setSubmitting(false);
131
+ }
132
+ },
133
+ });
134
+
135
+ return (
136
+ <Container component="main" maxWidth="xs">
137
+ <Box>
138
+ <Typography component="h1" variant="h5">
139
+ {translatedLabels.codesRemaining.replace('{count}', String(backupCodesRemaining ?? 0))}
140
+ </Typography>
141
+ </Box>
142
+ <Box component="form" onSubmit={formik.handleSubmit} sx={{ mt: 1, width: '100%' }}>
143
+ <TextField
144
+ margin="normal"
145
+ fullWidth
146
+ id="mnemonic"
147
+ label={translatedLabels.mnemonic}
148
+ type="password"
149
+ name="mnemonic"
150
+ autoComplete="mnemonic"
151
+ autoFocus
152
+ value={formik.values.mnemonic}
153
+ onChange={formik.handleChange}
154
+ onBlur={formik.handleBlur}
155
+ error={(formik.touched.mnemonic || formik.submitCount > 0) && Boolean(formik.errors.mnemonic)}
156
+ helperText={(formik.touched.mnemonic || formik.submitCount > 0) && formik.errors.mnemonic}
157
+ />
158
+ <TextField
159
+ margin="normal"
160
+ fullWidth
161
+ name="password"
162
+ label={translatedLabels.password}
163
+ type="password"
164
+ id="password"
165
+ value={formik.values.password}
166
+ onChange={formik.handleChange}
167
+ onBlur={formik.handleBlur}
168
+ error={(formik.touched.password || formik.submitCount > 0) && Boolean(formik.errors.password)}
169
+ helperText={(formik.touched.password || formik.submitCount > 0) && formik.errors.password}
170
+ />
171
+ <Button type="submit" fullWidth variant="contained" sx={{ mt: 2 }}>
172
+ {translatedLabels.generateButton}
173
+ </Button>
174
+ </Box>
175
+ {apiError && (
176
+ <Alert severity="error" sx={{ mt: 2, mb: 2 }}>
177
+ {apiError}
178
+ </Alert>
179
+ )}
180
+ {backupCodes && apiSuccess && (
181
+ <Box sx={{ mt: 2, mb: 2 }}>
182
+ <Typography component="h2" variant="h6">
183
+ {apiSuccess}
184
+ </Typography>
185
+ <ul>
186
+ {backupCodes.map((code, index) => (
187
+ <li key={index}>
188
+ <pre>{code}</pre>
189
+ </li>
190
+ ))}
191
+ </ul>
192
+ </Box>
193
+ )}
194
+ </Container>
195
+ );
196
+ };
197
+
198
+ export default BackupCodesForm;
@@ -0,0 +1,182 @@
1
+ import {
2
+ Alert,
3
+ Box,
4
+ Button,
5
+ Container,
6
+ TextField,
7
+ Typography,
8
+ } from '@mui/material';
9
+ import { useFormik } from 'formik';
10
+ import { FC, useState } from 'react';
11
+ import * as Yup from 'yup';
12
+ import { Constants, SuiteCoreComponentId, SuiteCoreStringKey } from '@digitaldefiance/suite-core-lib';
13
+ import { useI18n } from '../contexts';
14
+
15
+ export interface ChangePasswordFormValues {
16
+ currentPassword: string;
17
+ newPassword: string;
18
+ confirmPassword: string;
19
+ }
20
+
21
+ export interface ChangePasswordFormProps {
22
+ onSubmit: (values: ChangePasswordFormValues) => Promise<{ success?: boolean; error?: string }>;
23
+ titleText?: string;
24
+ currentPasswordLabel?: string;
25
+ newPasswordLabel?: string;
26
+ confirmPasswordLabel?: string;
27
+ submitButtonText?: string;
28
+ submittingButtonText?: string;
29
+ successMessage?: string;
30
+ currentPasswordValidation?: Yup.StringSchema;
31
+ newPasswordValidation?: Yup.StringSchema;
32
+ confirmPasswordValidation?: Yup.StringSchema;
33
+ }
34
+
35
+ export const ChangePasswordForm: FC<ChangePasswordFormProps> = ({
36
+ onSubmit,
37
+ titleText,
38
+ currentPasswordLabel,
39
+ newPasswordLabel,
40
+ confirmPasswordLabel,
41
+ submitButtonText,
42
+ submittingButtonText,
43
+ successMessage,
44
+ currentPasswordValidation,
45
+ newPasswordValidation,
46
+ confirmPasswordValidation,
47
+ }) => {
48
+ const { t, tComponent } = useI18n();
49
+ const [success, setSuccess] = useState(false);
50
+ const [apiError, setApiError] = useState<string>('');
51
+
52
+ const validation = {
53
+ currentPassword: currentPasswordValidation || Yup.string().required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
54
+ newPassword: newPasswordValidation || Yup.string()
55
+ .min(Constants.PasswordMinLength, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_PasswordMinLengthTemplate))
56
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
57
+ confirmPassword: confirmPasswordValidation || Yup.string()
58
+ .oneOf([Yup.ref('newPassword')], tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_PasswordMatch))
59
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
60
+ };
61
+
62
+ const labels = {
63
+ title: titleText || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ChangePassword),
64
+ currentPassword: currentPasswordLabel || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_CurrentPassword),
65
+ newPassword: newPasswordLabel || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_NewPassword),
66
+ confirmPassword: confirmPasswordLabel || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ConfirmNewPassword),
67
+ submitButton: submitButtonText || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ChangePassword),
68
+ submittingButton: submittingButtonText || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ChangingPassword),
69
+ success: successMessage || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.PasswordChange_Success),
70
+ };
71
+
72
+ const formik = useFormik<ChangePasswordFormValues>({
73
+ initialValues: {
74
+ currentPassword: '',
75
+ newPassword: '',
76
+ confirmPassword: '',
77
+ },
78
+ validationSchema: Yup.object({
79
+ currentPassword: validation.currentPassword,
80
+ newPassword: validation.newPassword,
81
+ confirmPassword: validation.confirmPassword,
82
+ }),
83
+ onSubmit: async (values, { resetForm }) => {
84
+ const result = await onSubmit(values);
85
+
86
+ if ('success' in result) {
87
+ setSuccess(true);
88
+ setApiError('');
89
+ resetForm();
90
+ } else if ('error' in result && result.error) {
91
+ setApiError(result.error);
92
+ setSuccess(false);
93
+ }
94
+ },
95
+ });
96
+
97
+ return (
98
+ <Container maxWidth="sm">
99
+ <Box
100
+ sx={{
101
+ mt: 8,
102
+ display: 'flex',
103
+ flexDirection: 'column',
104
+ alignItems: 'center',
105
+ }}
106
+ >
107
+ <Typography variant="h4" component="h1" gutterBottom>
108
+ {labels.title}
109
+ </Typography>
110
+
111
+ <Box component="form" onSubmit={formik.handleSubmit} sx={{ mt: 1, width: '100%' }}>
112
+ <TextField
113
+ fullWidth
114
+ id="currentPassword"
115
+ name="currentPassword"
116
+ label={labels.currentPassword}
117
+ type="password"
118
+ value={formik.values.currentPassword}
119
+ onChange={formik.handleChange}
120
+ onBlur={formik.handleBlur}
121
+ error={Boolean(formik.touched.currentPassword && formik.errors.currentPassword)}
122
+ helperText={formik.touched.currentPassword && formik.errors.currentPassword}
123
+ margin="normal"
124
+ />
125
+
126
+ <TextField
127
+ fullWidth
128
+ id="newPassword"
129
+ name="newPassword"
130
+ label={labels.newPassword}
131
+ type="password"
132
+ value={formik.values.newPassword}
133
+ onChange={formik.handleChange}
134
+ onBlur={formik.handleBlur}
135
+ error={Boolean(formik.touched.newPassword && formik.errors.newPassword)}
136
+ helperText={formik.touched.newPassword && formik.errors.newPassword}
137
+ margin="normal"
138
+ />
139
+
140
+ <TextField
141
+ fullWidth
142
+ id="confirmPassword"
143
+ name="confirmPassword"
144
+ label={labels.confirmPassword}
145
+ type="password"
146
+ value={formik.values.confirmPassword}
147
+ onChange={formik.handleChange}
148
+ onBlur={formik.handleBlur}
149
+ error={Boolean(formik.touched.confirmPassword && formik.errors.confirmPassword)}
150
+ helperText={formik.touched.confirmPassword && formik.errors.confirmPassword}
151
+ margin="normal"
152
+ />
153
+
154
+ {apiError && (
155
+ <Alert severity="error" sx={{ mt: 2, mb: 2 }}>
156
+ {apiError}
157
+ </Alert>
158
+ )}
159
+
160
+ {success && (
161
+ <Alert severity="success" sx={{ mt: 2, mb: 2 }}>
162
+ {labels.success}
163
+ </Alert>
164
+ )}
165
+
166
+ <Button
167
+ type="submit"
168
+ fullWidth
169
+ variant="contained"
170
+ color="primary"
171
+ sx={{ mt: 3, mb: 2 }}
172
+ disabled={formik.isSubmitting}
173
+ >
174
+ {formik.isSubmitting ? labels.submittingButton : labels.submitButton}
175
+ </Button>
176
+ </Box>
177
+ </Box>
178
+ </Container>
179
+ );
180
+ };
181
+
182
+ export default ChangePasswordForm;
@@ -0,0 +1,48 @@
1
+ import {
2
+ Button,
3
+ Dialog,
4
+ DialogActions,
5
+ DialogContent,
6
+ DialogContentText,
7
+ DialogTitle,
8
+ } from '@mui/material';
9
+ import React from 'react';
10
+
11
+ export interface ConfirmationDialogProps {
12
+ open: boolean;
13
+ title: string;
14
+ message: string;
15
+ confirmText?: string;
16
+ cancelText?: string;
17
+ onConfirm: () => void;
18
+ onCancel: () => void;
19
+ }
20
+
21
+ export const ConfirmationDialog: React.FC<ConfirmationDialogProps> = ({
22
+ open,
23
+ title,
24
+ message,
25
+ confirmText = 'Confirm',
26
+ cancelText = 'Cancel',
27
+ onConfirm,
28
+ onCancel,
29
+ }) => {
30
+ return (
31
+ <Dialog open={open} onClose={onCancel}>
32
+ <DialogTitle>{title}</DialogTitle>
33
+ <DialogContent>
34
+ <DialogContentText>{message}</DialogContentText>
35
+ </DialogContent>
36
+ <DialogActions>
37
+ <Button onClick={onCancel} color="primary">
38
+ {cancelText}
39
+ </Button>
40
+ <Button onClick={onConfirm} color="primary">
41
+ {confirmText}
42
+ </Button>
43
+ </DialogActions>
44
+ </Dialog>
45
+ );
46
+ };
47
+
48
+ export default ConfirmationDialog;
@@ -0,0 +1,60 @@
1
+ import { CurrencyCode } from '@digitaldefiance/i18n-lib';
2
+ import { MenuItem, TextField } from '@mui/material';
3
+ import { Field, FieldProps } from 'formik';
4
+ import { ChangeEvent, FC } from 'react';
5
+
6
+ export interface CurrencyCodeSelectorProps {
7
+ name: string;
8
+ label: string;
9
+ onCurrencyChange?: (code: string) => void;
10
+ }
11
+
12
+ export const CurrencyCodeSelector: FC<CurrencyCodeSelectorProps> = ({
13
+ name,
14
+ label,
15
+ onCurrencyChange,
16
+ }) => {
17
+ return (
18
+ <Field name={name}>
19
+ {({ field, form }: FieldProps) => (
20
+ <TextField
21
+ select
22
+ fullWidth
23
+ label={label}
24
+ {...field}
25
+ onChange={(event: ChangeEvent<HTMLInputElement>) => {
26
+ const selectedCode = event.target.value;
27
+ form.setFieldValue(name, selectedCode);
28
+ onCurrencyChange?.(selectedCode);
29
+ }}
30
+ error={form.touched[name] && Boolean(form.errors[name])}
31
+ helperText={form.touched[name] && (form.errors[name] as string)}
32
+ sx={{
33
+ '& .MuiSelect-select': {
34
+ paddingRight: '32px',
35
+ },
36
+ '& .MuiOutlinedInput-root': {
37
+ '& fieldset': {
38
+ borderColor: 'rgba(0, 0, 0, 0.23)',
39
+ },
40
+ '&:hover fieldset': {
41
+ borderColor: 'rgba(0, 0, 0, 0.87)',
42
+ },
43
+ '&.Mui-focused fieldset': {
44
+ borderColor: 'primary.main',
45
+ },
46
+ },
47
+ }}
48
+ >
49
+ {CurrencyCode.getAll().map((code: string) => (
50
+ <MenuItem key={code} value={code}>
51
+ {code}
52
+ </MenuItem>
53
+ ))}
54
+ </TextField>
55
+ )}
56
+ </Field>
57
+ );
58
+ };
59
+
60
+ export default CurrencyCodeSelector;
@@ -0,0 +1,80 @@
1
+ import { TextField } from '@mui/material';
2
+ import { NumericFormat } from 'react-number-format';
3
+ import { getCurrencyFormat } from '@digitaldefiance/i18n-lib';
4
+
5
+ export interface CurrencyInputProps {
6
+ value: number;
7
+ onChange: (value: number) => void;
8
+ currencyCode?: string;
9
+ locale?: string;
10
+ label: string;
11
+ error?: boolean;
12
+ helperText?: string;
13
+ name: string;
14
+ }
15
+
16
+ export const CurrencyInput: React.FC<CurrencyInputProps> = ({
17
+ value,
18
+ onChange,
19
+ currencyCode = 'USD',
20
+ locale = 'en-US',
21
+ label,
22
+ error,
23
+ helperText,
24
+ name,
25
+ }) => {
26
+ const format = getCurrencyFormat(locale, currencyCode);
27
+
28
+ if (format.position === 'infix') {
29
+ const [whole, decimal] = value.toString().split('.');
30
+ const displayValue = `${whole}${format.symbol}${format.decimalSeparator}${
31
+ decimal || '00'
32
+ }`;
33
+
34
+ return (
35
+ <NumericFormat
36
+ customInput={TextField}
37
+ fullWidth
38
+ margin="normal"
39
+ label={label}
40
+ value={displayValue}
41
+ thousandSeparator={format.groupSeparator}
42
+ decimalSeparator={format.decimalSeparator}
43
+ decimalScale={2}
44
+ fixedDecimalScale
45
+ valueIsNumericString
46
+ onValueChange={(values) => {
47
+ onChange(values.floatValue || 0);
48
+ }}
49
+ error={error}
50
+ helperText={helperText}
51
+ name={name}
52
+ />
53
+ );
54
+ }
55
+
56
+ return (
57
+ <NumericFormat
58
+ customInput={TextField}
59
+ fullWidth
60
+ margin="normal"
61
+ label={label}
62
+ value={value}
63
+ thousandSeparator={format.groupSeparator}
64
+ decimalSeparator={format.decimalSeparator}
65
+ decimalScale={2}
66
+ fixedDecimalScale
67
+ prefix={format.position === 'prefix' ? format.symbol + ' ' : undefined}
68
+ suffix={format.position === 'postfix' ? ' ' + format.symbol : undefined}
69
+ valueIsNumericString
70
+ onValueChange={(values) => {
71
+ onChange(values.floatValue || 0);
72
+ }}
73
+ error={error}
74
+ helperText={helperText}
75
+ name={name}
76
+ />
77
+ );
78
+ };
79
+
80
+ export default CurrencyInput;
@@ -0,0 +1,24 @@
1
+ import { Box, Container, Typography } from '@mui/material';
2
+ import { FC, ReactNode } from 'react';
3
+
4
+ export interface DashboardPageProps {
5
+ title?: string;
6
+ children?: ReactNode;
7
+ }
8
+
9
+ export const DashboardPage: FC<DashboardPageProps> = ({ title = 'Dashboard', children }) => {
10
+ return (
11
+ <Container maxWidth="md">
12
+ <Box my={4}>
13
+ <Typography variant="h4" component="h1" gutterBottom align="center">
14
+ {title}
15
+ </Typography>
16
+ <Box display="flex" justifyContent="center" mt={3}>
17
+ {children}
18
+ </Box>
19
+ </Box>
20
+ </Container>
21
+ );
22
+ };
23
+
24
+ export default DashboardPage;
@@ -0,0 +1,92 @@
1
+ import { Box, Fade, IconButton, Menu, MenuItem } from '@mui/material';
2
+ import { FC, MouseEvent, ReactElement, useCallback, useState } from 'react';
3
+ import { useNavigate } from 'react-router-dom';
4
+ import { MenuType } from '../types/MenuType';
5
+ import { IMenuOption } from '../interfaces/IMenuOption';
6
+ import { useMenu } from '../contexts/MenuContext';
7
+
8
+ interface DropdownMenuProps {
9
+ menuType: MenuType;
10
+ menuIcon: ReactElement;
11
+ }
12
+
13
+ export const DropdownMenu: FC<DropdownMenuProps> = ({ menuType, menuIcon }) => {
14
+ const { getMenuOptions } = useMenu();
15
+ const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
16
+ const navigate = useNavigate();
17
+ const handleClose = useCallback(() => {
18
+ setAnchorEl(null);
19
+ }, []);
20
+ const handleMenuItemClick = useCallback(
21
+ (option: IMenuOption) => (event: React.MouseEvent<HTMLElement>) => {
22
+ event.stopPropagation();
23
+ if (option.action) {
24
+ option.action();
25
+ } else if (option.link !== undefined) {
26
+ if (
27
+ typeof option.link === 'object' &&
28
+ 'pathname' in option.link &&
29
+ 'state' in option.link
30
+ ) {
31
+ navigate(option.link.pathname, { state: option.link.state });
32
+ } else {
33
+ navigate(option.link);
34
+ }
35
+ }
36
+ handleClose(); // Call handleClose after handling the click
37
+ },
38
+ [navigate, handleClose], // Add handleClose to the dependency array
39
+ );
40
+
41
+ const handleClick = useCallback((event: MouseEvent<HTMLElement>) => {
42
+ setAnchorEl(event.currentTarget);
43
+ }, []);
44
+
45
+ const menuItems = getMenuOptions(menuType, false);
46
+
47
+ if (menuItems.length === 0) {
48
+ return null;
49
+ }
50
+
51
+ return (
52
+ <Box>
53
+ <IconButton color="inherit" onClick={handleClick}>
54
+ {menuIcon}
55
+ </IconButton>
56
+ <Menu
57
+ anchorEl={anchorEl}
58
+ open={Boolean(anchorEl)}
59
+ onClose={handleClose}
60
+ TransitionComponent={Fade}
61
+ sx={{
62
+ '& .MuiPopover-paper': {
63
+ opacity: 0.5,
64
+ overflow: 'visible',
65
+ },
66
+ }}
67
+ >
68
+ {menuItems.map((option) => (
69
+ <MenuItem
70
+ key={option.id}
71
+ component="li"
72
+ onClick={handleMenuItemClick(option)}
73
+ sx={{
74
+ display: 'flex',
75
+ alignItems: 'center',
76
+ '& > svg': {
77
+ marginRight: 2,
78
+ width: 24,
79
+ height: 24,
80
+ },
81
+ }}
82
+ >
83
+ {option.icon}
84
+ {option.label}
85
+ </MenuItem>
86
+ ))}
87
+ </Menu>
88
+ </Box>
89
+ );
90
+ };
91
+
92
+ export default DropdownMenu;