@digitaldefiance/express-suite-react-components 2.9.1 → 2.9.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 (257) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +10 -6
  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,354 @@
1
+ import {
2
+ Alert,
3
+ AlertTitle,
4
+ Box,
5
+ Button,
6
+ Checkbox,
7
+ Container,
8
+ FormControl,
9
+ FormControlLabel,
10
+ FormHelperText,
11
+ InputLabel,
12
+ Link,
13
+ MenuItem,
14
+ Select,
15
+ TextField,
16
+ Typography,
17
+ } from '@mui/material';
18
+ import { useFormik } from 'formik';
19
+ import { FC, useState } from 'react';
20
+ import * as Yup from 'yup';
21
+ import { SuiteCoreComponentId, SuiteCoreStringKey, Constants } from '@digitaldefiance/suite-core-lib';
22
+ import { useI18n } from '../contexts';
23
+
24
+ export interface RegisterFormValues {
25
+ username: string;
26
+ email: string;
27
+ timezone: string;
28
+ password?: string;
29
+ confirmPassword?: string;
30
+ [key: string]: any;
31
+ }
32
+
33
+ export interface RegisterFormProps {
34
+ onSubmit: (values: RegisterFormValues, usePassword: boolean) => Promise<
35
+ | { success: boolean; message: string; mnemonic?: string }
36
+ | { error: string; errorType?: string; field?: string; errors?: Array<{ path: string; msg: string }> }
37
+ >;
38
+ timezones: string[];
39
+ getInitialTimezone: () => string;
40
+ usernameValidation?: Yup.StringSchema;
41
+ emailValidation?: Yup.StringSchema;
42
+ timezoneValidation?: Yup.StringSchema;
43
+ passwordValidation?: Yup.StringSchema;
44
+ confirmPasswordValidation?: Yup.StringSchema;
45
+ additionalFields?: (formik: any, usePassword: boolean) => React.ReactNode;
46
+ additionalInitialValues?: Record<string, any>;
47
+ additionalValidation?: Record<string, Yup.Schema>;
48
+ labels?: {
49
+ title?: string;
50
+ username?: string;
51
+ email?: string;
52
+ timezone?: string;
53
+ password?: string;
54
+ confirmPassword?: string;
55
+ useMnemonic?: string;
56
+ usePassword?: string;
57
+ registering?: string;
58
+ register?: string;
59
+ successTitle?: string;
60
+ mnemonicSuccess?: string;
61
+ proceedToLogin?: string;
62
+ loginLink?: string;
63
+ };
64
+ }
65
+
66
+ export const RegisterForm: FC<RegisterFormProps> = ({
67
+ onSubmit,
68
+ timezones,
69
+ getInitialTimezone,
70
+ usernameValidation,
71
+ emailValidation,
72
+ timezoneValidation,
73
+ passwordValidation,
74
+ confirmPasswordValidation,
75
+ additionalFields,
76
+ additionalInitialValues = {},
77
+ additionalValidation = {},
78
+ labels = {},
79
+ }) => {
80
+ const { t, tComponent } = useI18n();
81
+ const [apiErrors, setApiErrors] = useState<Record<string, string>>({});
82
+ const [mnemonic, setMnemonic] = useState<string | null>(null);
83
+ const [usePassword, setUsePassword] = useState(true);
84
+ const [registrationSuccess, setRegistrationSuccess] = useState(false);
85
+ const [registering, setRegistering] = useState(false);
86
+
87
+ const validation = {
88
+ username: usernameValidation || Yup.string()
89
+ .min(Constants.UsernameMinLength, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_UsernameMinLengthTemplate))
90
+ .max(Constants.UsernameMaxLength, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_UsernameMaxLengthTemplate))
91
+ .matches(Constants.UsernameRegex, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_UsernameRegexErrorTemplate))
92
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
93
+ email: emailValidation || Yup.string()
94
+ .email(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_InvalidEmail))
95
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
96
+ timezone: timezoneValidation || Yup.string()
97
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_TimezoneRequired))
98
+ .oneOf(timezones, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_TimezoneInvalid)),
99
+ password: passwordValidation || Yup.string()
100
+ .matches(Constants.PasswordRegex, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_PasswordRegexErrorTemplate))
101
+ .min(8, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_PasswordMinLengthTemplate))
102
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
103
+ confirmPassword: confirmPasswordValidation || Yup.string()
104
+ .oneOf([Yup.ref('password')], tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_PasswordMatch))
105
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
106
+ };
107
+
108
+ const formik = useFormik<RegisterFormValues>({
109
+ initialValues: {
110
+ username: '',
111
+ email: '',
112
+ timezone: getInitialTimezone(),
113
+ password: '',
114
+ confirmPassword: '',
115
+ directChallenge: false,
116
+ ...additionalInitialValues,
117
+ },
118
+ enableReinitialize: true,
119
+ validationSchema: Yup.object({
120
+ username: validation.username,
121
+ email: validation.email,
122
+ timezone: validation.timezone,
123
+ ...(usePassword
124
+ ? {
125
+ password: validation.password,
126
+ confirmPassword: validation.confirmPassword,
127
+ }
128
+ : {}),
129
+ ...additionalValidation,
130
+ }),
131
+ onSubmit: async (values, { setSubmitting, setFieldError, setTouched }) => {
132
+ setRegistering(true);
133
+ const registerResult = await onSubmit(values, usePassword);
134
+
135
+ if ('success' in registerResult && registerResult.success) {
136
+ setRegistrationSuccess(true);
137
+ if (!usePassword && registerResult?.mnemonic) {
138
+ setMnemonic(registerResult.mnemonic);
139
+ }
140
+ } else {
141
+ setRegistrationSuccess(false);
142
+ const newApiErrors: Record<string, string> = {};
143
+ const fieldsToTouch: Record<string, boolean> = {};
144
+
145
+ if ('field' in registerResult && registerResult.field) {
146
+ setFieldError(registerResult.field, registerResult.error);
147
+ fieldsToTouch[registerResult.field] = true;
148
+ }
149
+
150
+ if ('errors' in registerResult && registerResult.errors) {
151
+ registerResult.errors.forEach((err) => {
152
+ if (err.path && err.msg) {
153
+ setFieldError(err.path, err.msg);
154
+ fieldsToTouch[err.path] = true;
155
+ }
156
+ });
157
+ }
158
+
159
+ if ('error' in registerResult && registerResult.error && !Object.keys(newApiErrors).length) {
160
+ newApiErrors.general = registerResult.error;
161
+ }
162
+
163
+ setApiErrors(newApiErrors);
164
+ setTouched(fieldsToTouch, false);
165
+ }
166
+ setSubmitting(false);
167
+ setRegistering(false);
168
+ setApiErrors({});
169
+ },
170
+ });
171
+
172
+ return (
173
+ <Container maxWidth="sm">
174
+ <Box sx={{ mt: 8, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
175
+ <Typography variant="h4" component="h1" gutterBottom>
176
+ {labels.title || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Registration)}
177
+ </Typography>
178
+
179
+ <Box component="form" onSubmit={formik.handleSubmit} sx={{ mt: 1, width: '100%' }}>
180
+ <TextField
181
+ fullWidth
182
+ id="username"
183
+ name="username"
184
+ label={labels.username || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Username)}
185
+ value={formik.values.username}
186
+ onChange={formik.handleChange}
187
+ onBlur={formik.handleBlur}
188
+ error={Boolean(formik.touched.username && (formik.errors.username || apiErrors.username))}
189
+ helperText={formik.touched.username && (formik.errors.username || apiErrors.username)}
190
+ margin="normal"
191
+ />
192
+ <TextField
193
+ fullWidth
194
+ id="email"
195
+ name="email"
196
+ label={labels.email || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Email)}
197
+ value={formik.values.email}
198
+ onChange={formik.handleChange}
199
+ onBlur={formik.handleBlur}
200
+ error={Boolean(formik.touched.email && (formik.errors.email || apiErrors.email))}
201
+ helperText={formik.touched.email && (formik.errors.email || apiErrors.email)}
202
+ margin="normal"
203
+ />
204
+ <FormControl fullWidth margin="normal">
205
+ <InputLabel id="timezone-label">{labels.timezone || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Timezone)}</InputLabel>
206
+ <Select
207
+ labelId="timezone-label"
208
+ id="timezone"
209
+ name="timezone"
210
+ value={formik.values.timezone}
211
+ onChange={formik.handleChange}
212
+ onBlur={formik.handleBlur}
213
+ error={formik.touched.timezone && Boolean(formik.errors.timezone)}
214
+ label={labels.timezone || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Timezone)}
215
+ >
216
+ {timezones.map((tz) => (
217
+ <MenuItem key={tz} value={tz}>
218
+ {tz}
219
+ </MenuItem>
220
+ ))}
221
+ </Select>
222
+ {formik.touched.timezone && (formik.errors.timezone || apiErrors.timezone) && (
223
+ <Typography color="error" variant="caption">
224
+ {formik.errors.timezone || apiErrors.timezone}
225
+ </Typography>
226
+ )}
227
+ </FormControl>
228
+
229
+ <Box sx={{ display: 'flex', alignItems: 'center', mt: 2 }}>
230
+ <Button variant="text" onClick={() => setUsePassword(!usePassword)}>
231
+ {usePassword
232
+ ? labels.useMnemonic || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_UseMnemonic)
233
+ : labels.usePassword || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_UsePassword)}
234
+ </Button>
235
+ </Box>
236
+
237
+ {usePassword && (
238
+ <>
239
+ <TextField
240
+ fullWidth
241
+ id="password"
242
+ name="password"
243
+ label={labels.password || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Password)}
244
+ type="password"
245
+ value={formik.values.password}
246
+ onChange={formik.handleChange}
247
+ onBlur={formik.handleBlur}
248
+ error={Boolean(formik.touched.password && formik.errors.password)}
249
+ helperText={formik.touched.password && formik.errors.password}
250
+ margin="normal"
251
+ />
252
+ <TextField
253
+ fullWidth
254
+ id="confirmPassword"
255
+ name="confirmPassword"
256
+ label={labels.confirmPassword || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ConfirmNewPassword)}
257
+ type="password"
258
+ value={formik.values.confirmPassword}
259
+ onChange={formik.handleChange}
260
+ onBlur={formik.handleBlur}
261
+ error={Boolean(formik.touched.confirmPassword && formik.errors.confirmPassword)}
262
+ helperText={formik.touched.confirmPassword && formik.errors.confirmPassword}
263
+ margin="normal"
264
+ />
265
+ </>
266
+ )}
267
+
268
+ <FormControl fullWidth margin="normal">
269
+ <FormControlLabel
270
+ control={
271
+ <Checkbox
272
+ id="directChallenge"
273
+ name="directChallenge"
274
+ checked={formik.values.directChallenge || false}
275
+ onChange={formik.handleChange}
276
+ />
277
+ }
278
+ label={tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Registration_DirectChallengeLabel)}
279
+ />
280
+ <FormHelperText>
281
+ {tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Registration_DirectChallengeHelper)}
282
+ </FormHelperText>
283
+ </FormControl>
284
+
285
+ {additionalFields && additionalFields(formik, usePassword)}
286
+
287
+ {apiErrors.general && (
288
+ <Alert severity="error" sx={{ mt: 2, mb: 2 }}>
289
+ {apiErrors.general}
290
+ </Alert>
291
+ )}
292
+
293
+ <Button
294
+ type="submit"
295
+ fullWidth
296
+ variant="contained"
297
+ color="primary"
298
+ sx={{ mt: 3, mb: 2 }}
299
+ disabled={formik.isSubmitting}
300
+ >
301
+ {registering
302
+ ? labels.registering || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Registration_Registering)
303
+ : labels.register || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Registration_RegisterButton)}
304
+ </Button>
305
+
306
+ {registering && (
307
+ <Alert severity="success" sx={{ mt: 2, mb: 2, whiteSpace: 'pre-wrap' }}>
308
+ <AlertTitle>{labels.registering || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Registration_Registering)}</AlertTitle>
309
+ <Typography variant="body2" component="div">
310
+ {tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Registration_RegisteringMessage)}
311
+ </Typography>
312
+ </Alert>
313
+ )}
314
+
315
+ {mnemonic && (
316
+ <Alert severity="success" sx={{ mt: 2, mb: 2, whiteSpace: 'pre-wrap' }}>
317
+ <AlertTitle>{labels.successTitle || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Registration_SuccessTitle)}</AlertTitle>
318
+ <Typography variant="body2" component="div">
319
+ {labels.mnemonicSuccess || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Registration_MnemonicSuccess)}
320
+ <Typography variant="body1" component="div" sx={{ mt: 1, fontFamily: 'monospace' }}>
321
+ {mnemonic}
322
+ </Typography>
323
+ <Box sx={{ textAlign: 'center' }}>
324
+ <Link href="/login">{labels.proceedToLogin || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.ProceedToLogin)}</Link>
325
+ </Box>
326
+ </Typography>
327
+ </Alert>
328
+ )}
329
+
330
+ {registrationSuccess && (
331
+ <Alert severity="success" sx={{ mt: 2, mb: 2 }}>
332
+ <AlertTitle>{labels.successTitle || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Registration_SuccessTitle)}</AlertTitle>
333
+ <Typography variant="body2" component="div">
334
+ {tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Registration_Success)}
335
+ <Box sx={{ textAlign: 'center' }}>
336
+ <Link href="/login">{labels.proceedToLogin || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.ProceedToLogin)}</Link>
337
+ </Box>
338
+ </Typography>
339
+ </Alert>
340
+ )}
341
+ {!mnemonic && !registrationSuccess && (
342
+ <Box sx={{ textAlign: 'center' }}>
343
+ <Link href="/login" variant="body2">
344
+ {labels.loginLink || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Registration_LoginLink)}
345
+ </Link>
346
+ </Box>
347
+ )}
348
+ </Box>
349
+ </Box>
350
+ </Container>
351
+ );
352
+ };
353
+
354
+ export default RegisterForm;
@@ -0,0 +1,164 @@
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 ResetPasswordFormValues {
9
+ password: string;
10
+ confirmPassword: string;
11
+ }
12
+
13
+ export interface ResetPasswordFormProps {
14
+ token: string | null;
15
+ onSubmit: (token: string, password: string) => Promise<void>;
16
+ passwordValidation?: Yup.StringSchema;
17
+ confirmPasswordValidation?: Yup.StringSchema;
18
+ labels?: {
19
+ title?: string;
20
+ password?: string;
21
+ confirmPassword?: string;
22
+ resetButton?: string;
23
+ successMessage?: string;
24
+ invalidToken?: string;
25
+ };
26
+ }
27
+
28
+ export const ResetPasswordForm: FC<ResetPasswordFormProps> = ({
29
+ token,
30
+ onSubmit,
31
+ passwordValidation,
32
+ confirmPasswordValidation,
33
+ labels = {},
34
+ }) => {
35
+ const { t, tComponent } = useI18n();
36
+ const [success, setSuccess] = useState(false);
37
+ const [apiError, setApiError] = useState<string>('');
38
+
39
+ const validation = {
40
+ password: passwordValidation || Yup.string()
41
+ .min(Constants.PasswordMinLength, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_PasswordMinLengthTemplate))
42
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
43
+ confirmPassword: confirmPasswordValidation || Yup.string()
44
+ .oneOf([Yup.ref('password')], tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_PasswordMatch))
45
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
46
+ };
47
+
48
+ const translatedLabels = {
49
+ title: labels.title || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.PasswordReset_Title),
50
+ password: labels.password || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_NewPassword),
51
+ confirmPassword: labels.confirmPassword || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ConfirmNewPassword),
52
+ resetButton: labels.resetButton || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.PasswordReset_Button),
53
+ successMessage: labels.successMessage || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.PasswordReset_Success),
54
+ invalidToken: labels.invalidToken || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.ForgotPassword_InvalidToken),
55
+ };
56
+
57
+ const formik = useFormik<ResetPasswordFormValues>({
58
+ initialValues: {
59
+ password: '',
60
+ confirmPassword: '',
61
+ },
62
+ validationSchema: Yup.object({
63
+ password: validation.password,
64
+ confirmPassword: validation.confirmPassword,
65
+ }),
66
+ onSubmit: async (values) => {
67
+ if (!token) {
68
+ setApiError(translatedLabels.invalidToken);
69
+ return;
70
+ }
71
+
72
+ try {
73
+ await onSubmit(token, values.password);
74
+ setSuccess(true);
75
+ setApiError('');
76
+ } catch (error: any) {
77
+ setApiError(error.response?.data?.message || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Error_PasswordChange));
78
+ setSuccess(false);
79
+ }
80
+ },
81
+ });
82
+
83
+ if (!token) {
84
+ return (
85
+ <Container maxWidth="sm">
86
+ <Alert severity="error" sx={{ mt: 4 }}>
87
+ {translatedLabels.invalidToken}
88
+ </Alert>
89
+ </Container>
90
+ );
91
+ }
92
+
93
+ return (
94
+ <Container maxWidth="sm">
95
+ <Box
96
+ sx={{
97
+ mt: 8,
98
+ display: 'flex',
99
+ flexDirection: 'column',
100
+ alignItems: 'center',
101
+ }}
102
+ >
103
+ <Typography variant="h4" component="h1" gutterBottom>
104
+ {translatedLabels.title}
105
+ </Typography>
106
+
107
+ <Box component="form" onSubmit={formik.handleSubmit} sx={{ mt: 1, width: '100%' }}>
108
+ <TextField
109
+ fullWidth
110
+ id="password"
111
+ name="password"
112
+ label={translatedLabels.password}
113
+ type="password"
114
+ value={formik.values.password}
115
+ onChange={formik.handleChange}
116
+ onBlur={formik.handleBlur}
117
+ error={Boolean(formik.touched.password && formik.errors.password)}
118
+ helperText={formik.touched.password && formik.errors.password}
119
+ margin="normal"
120
+ />
121
+
122
+ <TextField
123
+ fullWidth
124
+ id="confirmPassword"
125
+ name="confirmPassword"
126
+ label={translatedLabels.confirmPassword}
127
+ type="password"
128
+ value={formik.values.confirmPassword}
129
+ onChange={formik.handleChange}
130
+ onBlur={formik.handleBlur}
131
+ error={Boolean(formik.touched.confirmPassword && formik.errors.confirmPassword)}
132
+ helperText={formik.touched.confirmPassword && formik.errors.confirmPassword}
133
+ margin="normal"
134
+ />
135
+
136
+ {apiError && (
137
+ <Alert severity="error" sx={{ mt: 2, mb: 2 }}>
138
+ {apiError}
139
+ </Alert>
140
+ )}
141
+
142
+ {success && (
143
+ <Alert severity="success" sx={{ mt: 2, mb: 2 }}>
144
+ {translatedLabels.successMessage}
145
+ </Alert>
146
+ )}
147
+
148
+ <Button
149
+ type="submit"
150
+ fullWidth
151
+ variant="contained"
152
+ color="primary"
153
+ sx={{ mt: 3, mb: 2 }}
154
+ disabled={formik.isSubmitting}
155
+ >
156
+ {translatedLabels.resetButton}
157
+ </Button>
158
+ </Box>
159
+ </Box>
160
+ </Container>
161
+ );
162
+ };
163
+
164
+ export default ResetPasswordForm;
@@ -0,0 +1,46 @@
1
+ import { Drawer, List } from '@mui/material';
2
+ import { FC } from 'react';
3
+ import { useNavigate } from 'react-router-dom';
4
+ import { useMenu } from '../contexts/MenuContext';
5
+ import { IMenuOption } from '../interfaces/IMenuOption';
6
+ import { MenuTypes } from '../types/MenuType';
7
+ import { SideMenuListItem } from './SideMenuListItem';
8
+
9
+ interface SideMenuProps {
10
+ isOpen: boolean;
11
+ onClose: () => void;
12
+ }
13
+
14
+ export const SideMenu: FC<SideMenuProps> = ({ isOpen, onClose }) => {
15
+ const { getMenuOptions } = useMenu();
16
+ const navigate = useNavigate();
17
+
18
+ const menuOptions = getMenuOptions(MenuTypes.SideMenu, true);
19
+
20
+ const handleNavigate = (
21
+ link: string | Partial<{ pathname: string; state?: unknown }>
22
+ ) => {
23
+ if (typeof link === 'string') {
24
+ navigate(link);
25
+ } else if (link.pathname) {
26
+ navigate(link.pathname, { state: link.state });
27
+ }
28
+ };
29
+
30
+ return (
31
+ <Drawer anchor="left" open={isOpen} onClose={onClose}>
32
+ <List>
33
+ {menuOptions.map((item: IMenuOption) => (
34
+ <SideMenuListItem
35
+ key={item.id}
36
+ menuItem={item}
37
+ onClose={onClose}
38
+ onNavigate={handleNavigate}
39
+ />
40
+ ))}
41
+ </List>
42
+ </Drawer>
43
+ );
44
+ };
45
+
46
+ export default SideMenu;
@@ -0,0 +1,74 @@
1
+ import {
2
+ Divider,
3
+ ListItemButton,
4
+ ListItemIcon,
5
+ ListItemText,
6
+ } from '@mui/material';
7
+ import { FC, useCallback } from 'react';
8
+ import { IMenuOption } from '../interfaces';
9
+
10
+ export interface SideMenuListItemProps {
11
+ menuItem: IMenuOption;
12
+ onClose: () => void;
13
+ onNavigate?: (
14
+ link: string | Partial<{ pathname: string; state?: unknown }>
15
+ ) => void;
16
+ }
17
+
18
+ export const SideMenuListItem: FC<SideMenuListItemProps> = ({
19
+ menuItem,
20
+ onClose,
21
+ onNavigate,
22
+ }) => {
23
+ const handleMenuItemClick = useCallback(
24
+ (option: IMenuOption) => (event: React.MouseEvent<HTMLElement>) => {
25
+ event.stopPropagation();
26
+ if (option.action) {
27
+ option.action();
28
+ } else if (option.link !== undefined && onNavigate) {
29
+ if (typeof option.link === 'string') {
30
+ onNavigate(option.link);
31
+ } else if (
32
+ typeof option.link === 'object' &&
33
+ 'pathname' in option.link &&
34
+ option.link.pathname
35
+ ) {
36
+ onNavigate({
37
+ pathname: option.link.pathname,
38
+ state: 'state' in option.link ? option.link.state : undefined,
39
+ });
40
+ }
41
+ }
42
+ onClose();
43
+ },
44
+ [onNavigate, onClose]
45
+ );
46
+
47
+ if (menuItem.divider) {
48
+ return <Divider key={menuItem.label} />;
49
+ } else if (menuItem.link) {
50
+ return (
51
+ <ListItemButton key={menuItem.id} onClick={handleMenuItemClick(menuItem)}>
52
+ {menuItem.icon && <ListItemIcon>{menuItem.icon}</ListItemIcon>}
53
+ <ListItemText primary={menuItem.label} />
54
+ </ListItemButton>
55
+ );
56
+ } else if (menuItem.action) {
57
+ const action = menuItem.action;
58
+ return (
59
+ <ListItemButton
60
+ key={menuItem.id}
61
+ onClick={async () => {
62
+ await action();
63
+ onClose();
64
+ }}
65
+ >
66
+ {menuItem.icon && <ListItemIcon>{menuItem.icon}</ListItemIcon>}
67
+ <ListItemText primary={menuItem.label} />
68
+ </ListItemButton>
69
+ );
70
+ }
71
+ return null;
72
+ };
73
+
74
+ export default SideMenuListItem;