@digitaldefiance/express-suite-react-components 2.8.5 → 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 (258) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -0
  3. package/package.json +11 -7
  4. package/src/auth/Private.tsx +17 -0
  5. package/src/auth/PrivateRoute.tsx +28 -0
  6. package/src/auth/UnAuth.tsx +16 -0
  7. package/src/auth/UnAuthRoute.tsx +30 -0
  8. package/src/auth/{index.d.ts → index.ts} +1 -2
  9. package/src/components/ApiAccess.tsx +134 -0
  10. package/src/components/BackupCodeLoginForm.tsx +314 -0
  11. package/src/components/BackupCodesForm.tsx +198 -0
  12. package/src/components/ChangePasswordForm.tsx +182 -0
  13. package/src/components/ConfirmationDialog.tsx +48 -0
  14. package/src/components/CurrencyCodeSelector.tsx +60 -0
  15. package/src/components/CurrencyInput.tsx +80 -0
  16. package/src/components/DashboardPage.tsx +24 -0
  17. package/src/components/DropdownMenu.tsx +92 -0
  18. package/src/components/ExpirationSecondsSelector.tsx +65 -0
  19. package/src/components/Flag.tsx +53 -0
  20. package/src/components/ForgotPasswordForm.tsx +120 -0
  21. package/src/components/LoginForm.tsx +307 -0
  22. package/src/components/LogoutPage.tsx +21 -0
  23. package/src/components/RegisterForm.tsx +354 -0
  24. package/src/components/ResetPasswordForm.tsx +164 -0
  25. package/src/components/SideMenu.tsx +46 -0
  26. package/src/components/SideMenuListItem.tsx +74 -0
  27. package/src/components/TopMenu.tsx +134 -0
  28. package/src/components/TranslatedTitle.tsx +22 -0
  29. package/src/components/UserLanguageSelector.tsx +45 -0
  30. package/src/components/UserMenu.tsx +15 -0
  31. package/src/components/UserSettingsForm.tsx +328 -0
  32. package/src/components/VerifyEmailPage.tsx +133 -0
  33. package/src/components/{index.d.ts → index.ts} +1 -1
  34. package/src/contexts/AuthProvider.spec.tsx +1060 -0
  35. package/src/contexts/AuthProvider.tsx +741 -0
  36. package/src/contexts/I18nProvider.tsx +85 -0
  37. package/src/contexts/MenuContext.tsx +310 -0
  38. package/src/contexts/SuiteConfigProvider.tsx +93 -0
  39. package/src/contexts/ThemeProvider.tsx +67 -0
  40. package/src/contexts/{index.d.ts → index.ts} +0 -1
  41. package/src/hooks/{index.d.ts → index.ts} +0 -1
  42. package/src/hooks/useBackupCodes.ts +85 -0
  43. package/src/hooks/useEmailVerification.ts +39 -0
  44. package/src/hooks/useExpiringValue.ts +78 -0
  45. package/src/hooks/useLocalStorage.ts +18 -0
  46. package/src/hooks/useUserSettings.ts +216 -0
  47. package/src/{index.d.ts → index.ts} +1 -1
  48. package/src/interfaces/IAppConfig.ts +5 -0
  49. package/src/interfaces/IMenuConfig.ts +11 -0
  50. package/src/interfaces/IMenuOption.ts +55 -0
  51. package/src/interfaces/index.ts +3 -0
  52. package/src/services/__mocks__/authService.ts +14 -0
  53. package/src/services/api.ts +13 -0
  54. package/src/services/authService.ts +422 -0
  55. package/src/services/authenticatedApi.ts +17 -0
  56. package/src/services/index.ts +3 -0
  57. package/src/types/MenuType.ts +15 -0
  58. package/src/types/expirationSeconds.ts +18 -0
  59. package/src/types/index.ts +1 -0
  60. package/src/types/translation.ts +20 -0
  61. package/src/wrappers/BackupCodeLoginWrapper.tsx +35 -0
  62. package/src/wrappers/BackupCodesWrapper.tsx +28 -0
  63. package/src/wrappers/ChangePasswordFormWrapper.tsx +31 -0
  64. package/src/wrappers/LoginFormWrapper.tsx +59 -0
  65. package/src/wrappers/LogoutPageWrapper.tsx +30 -0
  66. package/src/wrappers/RegisterFormWrapper.tsx +48 -0
  67. package/src/wrappers/UserSettingsFormWrapper.tsx +39 -0
  68. package/src/wrappers/VerifyEmailPageWrapper.tsx +27 -0
  69. package/src/wrappers/{index.d.ts → index.tsx} +8 -1
  70. package/src/auth/Private.d.ts +0 -6
  71. package/src/auth/Private.d.ts.map +0 -1
  72. package/src/auth/Private.js +0 -14
  73. package/src/auth/PrivateRoute.d.ts +0 -8
  74. package/src/auth/PrivateRoute.d.ts.map +0 -1
  75. package/src/auth/PrivateRoute.js +0 -23
  76. package/src/auth/UnAuth.d.ts +0 -6
  77. package/src/auth/UnAuth.d.ts.map +0 -1
  78. package/src/auth/UnAuth.js +0 -14
  79. package/src/auth/UnAuthRoute.d.ts +0 -8
  80. package/src/auth/UnAuthRoute.d.ts.map +0 -1
  81. package/src/auth/UnAuthRoute.js +0 -22
  82. package/src/auth/index.d.ts.map +0 -1
  83. package/src/auth/index.js +0 -10
  84. package/src/components/ApiAccess.d.ts +0 -16
  85. package/src/components/ApiAccess.d.ts.map +0 -1
  86. package/src/components/ApiAccess.js +0 -70
  87. package/src/components/BackupCodeLoginForm.d.ts +0 -43
  88. package/src/components/BackupCodeLoginForm.d.ts.map +0 -1
  89. package/src/components/BackupCodeLoginForm.js +0 -106
  90. package/src/components/BackupCodesForm.d.ts +0 -26
  91. package/src/components/BackupCodesForm.d.ts.map +0 -1
  92. package/src/components/BackupCodesForm.js +0 -108
  93. package/src/components/ChangePasswordForm.d.ts +0 -26
  94. package/src/components/ChangePasswordForm.d.ts.map +0 -1
  95. package/src/components/ChangePasswordForm.js +0 -66
  96. package/src/components/ConfirmationDialog.d.ts +0 -13
  97. package/src/components/ConfirmationDialog.d.ts.map +0 -1
  98. package/src/components/ConfirmationDialog.js +0 -10
  99. package/src/components/CurrencyCodeSelector.d.ts +0 -9
  100. package/src/components/CurrencyCodeSelector.d.ts.map +0 -1
  101. package/src/components/CurrencyCodeSelector.js +0 -31
  102. package/src/components/CurrencyInput.d.ts +0 -13
  103. package/src/components/CurrencyInput.d.ts.map +0 -1
  104. package/src/components/CurrencyInput.js +0 -22
  105. package/src/components/DashboardPage.d.ts +0 -8
  106. package/src/components/DashboardPage.d.ts.map +0 -1
  107. package/src/components/DashboardPage.js +0 -10
  108. package/src/components/DropdownMenu.d.ts +0 -9
  109. package/src/components/DropdownMenu.d.ts.map +0 -1
  110. package/src/components/DropdownMenu.js +0 -56
  111. package/src/components/ExpirationSecondsSelector.d.ts +0 -13
  112. package/src/components/ExpirationSecondsSelector.d.ts.map +0 -1
  113. package/src/components/ExpirationSecondsSelector.js +0 -32
  114. package/src/components/Flag.d.ts +0 -20
  115. package/src/components/Flag.d.ts.map +0 -1
  116. package/src/components/Flag.js +0 -43
  117. package/src/components/ForgotPasswordForm.d.ts +0 -18
  118. package/src/components/ForgotPasswordForm.d.ts.map +0 -1
  119. package/src/components/ForgotPasswordForm.js +0 -54
  120. package/src/components/LoginForm.d.ts +0 -44
  121. package/src/components/LoginForm.d.ts.map +0 -1
  122. package/src/components/LoginForm.js +0 -99
  123. package/src/components/LogoutPage.d.ts +0 -8
  124. package/src/components/LogoutPage.d.ts.map +0 -1
  125. package/src/components/LogoutPage.js +0 -16
  126. package/src/components/RegisterForm.d.ts +0 -54
  127. package/src/components/RegisterForm.d.ts.map +0 -1
  128. package/src/components/RegisterForm.js +0 -105
  129. package/src/components/ResetPasswordForm.d.ts +0 -23
  130. package/src/components/ResetPasswordForm.d.ts.map +0 -1
  131. package/src/components/ResetPasswordForm.js +0 -68
  132. package/src/components/SideMenu.d.ts +0 -8
  133. package/src/components/SideMenu.d.ts.map +0 -1
  134. package/src/components/SideMenu.js +0 -25
  135. package/src/components/SideMenuListItem.d.ts +0 -13
  136. package/src/components/SideMenuListItem.d.ts.map +0 -1
  137. package/src/components/SideMenuListItem.js +0 -44
  138. package/src/components/TopMenu.d.ts +0 -24
  139. package/src/components/TopMenu.d.ts.map +0 -1
  140. package/src/components/TopMenu.js +0 -36
  141. package/src/components/TranslatedTitle.d.ts +0 -7
  142. package/src/components/TranslatedTitle.d.ts.map +0 -1
  143. package/src/components/TranslatedTitle.js +0 -15
  144. package/src/components/UserLanguageSelector.d.ts +0 -4
  145. package/src/components/UserLanguageSelector.d.ts.map +0 -1
  146. package/src/components/UserLanguageSelector.js +0 -31
  147. package/src/components/UserMenu.d.ts +0 -4
  148. package/src/components/UserMenu.d.ts.map +0 -1
  149. package/src/components/UserMenu.js +0 -12
  150. package/src/components/UserSettingsForm.d.ts +0 -56
  151. package/src/components/UserSettingsForm.d.ts.map +0 -1
  152. package/src/components/UserSettingsForm.js +0 -93
  153. package/src/components/VerifyEmailPage.d.ts +0 -23
  154. package/src/components/VerifyEmailPage.d.ts.map +0 -1
  155. package/src/components/VerifyEmailPage.js +0 -61
  156. package/src/components/index.d.ts.map +0 -1
  157. package/src/components/index.js +0 -28
  158. package/src/contexts/AuthProvider.d.ts +0 -152
  159. package/src/contexts/AuthProvider.d.ts.map +0 -1
  160. package/src/contexts/AuthProvider.js +0 -446
  161. package/src/contexts/I18nProvider.d.ts +0 -16
  162. package/src/contexts/I18nProvider.d.ts.map +0 -1
  163. package/src/contexts/I18nProvider.js +0 -46
  164. package/src/contexts/MenuContext.d.ts +0 -20
  165. package/src/contexts/MenuContext.d.ts.map +0 -1
  166. package/src/contexts/MenuContext.js +0 -244
  167. package/src/contexts/SuiteConfigProvider.d.ts +0 -44
  168. package/src/contexts/SuiteConfigProvider.d.ts.map +0 -1
  169. package/src/contexts/SuiteConfigProvider.js +0 -43
  170. package/src/contexts/ThemeProvider.d.ts +0 -15
  171. package/src/contexts/ThemeProvider.d.ts.map +0 -1
  172. package/src/contexts/ThemeProvider.js +0 -36
  173. package/src/contexts/index.d.ts.map +0 -1
  174. package/src/contexts/index.js +0 -8
  175. package/src/hooks/index.d.ts.map +0 -1
  176. package/src/hooks/index.js +0 -8
  177. package/src/hooks/useBackupCodes.d.ts +0 -15
  178. package/src/hooks/useBackupCodes.d.ts.map +0 -1
  179. package/src/hooks/useBackupCodes.js +0 -70
  180. package/src/hooks/useEmailVerification.d.ts +0 -10
  181. package/src/hooks/useEmailVerification.d.ts.map +0 -1
  182. package/src/hooks/useEmailVerification.js +0 -36
  183. package/src/hooks/useExpiringValue.d.ts +0 -14
  184. package/src/hooks/useExpiringValue.d.ts.map +0 -1
  185. package/src/hooks/useExpiringValue.js +0 -53
  186. package/src/hooks/useLocalStorage.d.ts +0 -2
  187. package/src/hooks/useLocalStorage.d.ts.map +0 -1
  188. package/src/hooks/useLocalStorage.js +0 -15
  189. package/src/hooks/useUserSettings.d.ts +0 -46
  190. package/src/hooks/useUserSettings.d.ts.map +0 -1
  191. package/src/hooks/useUserSettings.js +0 -152
  192. package/src/index.d.ts.map +0 -1
  193. package/src/index.js +0 -12
  194. package/src/interfaces/IAppConfig.d.ts +0 -6
  195. package/src/interfaces/IAppConfig.d.ts.map +0 -1
  196. package/src/interfaces/IAppConfig.js +0 -2
  197. package/src/interfaces/IMenuConfig.d.ts +0 -11
  198. package/src/interfaces/IMenuConfig.d.ts.map +0 -1
  199. package/src/interfaces/IMenuConfig.js +0 -2
  200. package/src/interfaces/IMenuOption.d.ts +0 -58
  201. package/src/interfaces/IMenuOption.d.ts.map +0 -1
  202. package/src/interfaces/IMenuOption.js +0 -2
  203. package/src/interfaces/index.d.ts +0 -4
  204. package/src/interfaces/index.d.ts.map +0 -1
  205. package/src/interfaces/index.js +0 -6
  206. package/src/services/__mocks__/authService.d.ts +0 -21
  207. package/src/services/__mocks__/authService.d.ts.map +0 -1
  208. package/src/services/__mocks__/authService.js +0 -15
  209. package/src/services/api.d.ts +0 -3
  210. package/src/services/api.d.ts.map +0 -1
  211. package/src/services/api.js +0 -14
  212. package/src/services/authService.d.ts +0 -72
  213. package/src/services/authService.d.ts.map +0 -1
  214. package/src/services/authService.js +0 -347
  215. package/src/services/authenticatedApi.d.ts +0 -3
  216. package/src/services/authenticatedApi.d.ts.map +0 -1
  217. package/src/services/authenticatedApi.js +0 -18
  218. package/src/services/index.d.ts +0 -4
  219. package/src/services/index.d.ts.map +0 -1
  220. package/src/services/index.js +0 -6
  221. package/src/types/MenuType.d.ts +0 -11
  222. package/src/types/MenuType.d.ts.map +0 -1
  223. package/src/types/MenuType.js +0 -12
  224. package/src/types/expirationSeconds.d.ts +0 -3
  225. package/src/types/expirationSeconds.d.ts.map +0 -1
  226. package/src/types/expirationSeconds.js +0 -17
  227. package/src/types/index.d.ts +0 -2
  228. package/src/types/index.d.ts.map +0 -1
  229. package/src/types/index.js +0 -4
  230. package/src/types/translation.d.ts +0 -10
  231. package/src/types/translation.d.ts.map +0 -1
  232. package/src/types/translation.js +0 -9
  233. package/src/wrappers/BackupCodeLoginWrapper.d.ts +0 -8
  234. package/src/wrappers/BackupCodeLoginWrapper.d.ts.map +0 -1
  235. package/src/wrappers/BackupCodeLoginWrapper.js +0 -21
  236. package/src/wrappers/BackupCodesWrapper.d.ts +0 -7
  237. package/src/wrappers/BackupCodesWrapper.d.ts.map +0 -1
  238. package/src/wrappers/BackupCodesWrapper.js +0 -17
  239. package/src/wrappers/ChangePasswordFormWrapper.d.ts +0 -8
  240. package/src/wrappers/ChangePasswordFormWrapper.d.ts.map +0 -1
  241. package/src/wrappers/ChangePasswordFormWrapper.js +0 -21
  242. package/src/wrappers/LoginFormWrapper.d.ts +0 -9
  243. package/src/wrappers/LoginFormWrapper.d.ts.map +0 -1
  244. package/src/wrappers/LoginFormWrapper.js +0 -43
  245. package/src/wrappers/LogoutPageWrapper.d.ts +0 -9
  246. package/src/wrappers/LogoutPageWrapper.d.ts.map +0 -1
  247. package/src/wrappers/LogoutPageWrapper.js +0 -21
  248. package/src/wrappers/RegisterFormWrapper.d.ts +0 -9
  249. package/src/wrappers/RegisterFormWrapper.d.ts.map +0 -1
  250. package/src/wrappers/RegisterFormWrapper.js +0 -26
  251. package/src/wrappers/UserSettingsFormWrapper.d.ts +0 -8
  252. package/src/wrappers/UserSettingsFormWrapper.d.ts.map +0 -1
  253. package/src/wrappers/UserSettingsFormWrapper.js +0 -24
  254. package/src/wrappers/VerifyEmailPageWrapper.d.ts +0 -8
  255. package/src/wrappers/VerifyEmailPageWrapper.d.ts.map +0 -1
  256. package/src/wrappers/VerifyEmailPageWrapper.js +0 -20
  257. package/src/wrappers/index.d.ts.map +0 -1
  258. package/src/wrappers/index.js +0 -20
@@ -0,0 +1,65 @@
1
+ import { MenuItem, TextField } from '@mui/material';
2
+ import { FormikProps } from 'formik';
3
+ import { ChangeEvent, FC } from 'react';
4
+
5
+ export interface ExpirationSecondsSelectorProps {
6
+ name: string;
7
+ label: string;
8
+ formik: FormikProps<any>;
9
+ optionValues: number[];
10
+ optionNames: string[];
11
+ onChange?: (value: number) => void;
12
+ }
13
+
14
+ export const ExpirationSecondsSelector: FC<ExpirationSecondsSelectorProps> = ({
15
+ name,
16
+ label,
17
+ formik,
18
+ optionValues,
19
+ optionNames,
20
+ onChange,
21
+ }) => {
22
+ return (
23
+ <TextField
24
+ select
25
+ fullWidth
26
+ label={label}
27
+ name={name}
28
+ value={formik.values[name] ?? ''}
29
+ onChange={(event: ChangeEvent<HTMLInputElement>) => {
30
+ const selectedValue = event.target.value;
31
+ formik.setFieldValue(name, selectedValue);
32
+ if (onChange) {
33
+ onChange(parseInt(selectedValue));
34
+ }
35
+ }}
36
+ error={formik.touched[name] && Boolean(formik.errors[name])}
37
+ helperText={formik.touched[name] && (formik.errors[name] as string)}
38
+ sx={{
39
+ mt: 1,
40
+ '& .MuiSelect-select': {
41
+ paddingRight: '32px',
42
+ },
43
+ '& .MuiOutlinedInput-root': {
44
+ '& fieldset': {
45
+ borderColor: 'rgba(0, 0, 0, 0.23)',
46
+ },
47
+ '&:hover fieldset': {
48
+ borderColor: 'rgba(0, 0, 0, 0.87)',
49
+ },
50
+ '&.Mui-focused fieldset': {
51
+ borderColor: 'primary.main',
52
+ },
53
+ },
54
+ }}
55
+ >
56
+ {optionNames.map((name: string, index: number) => (
57
+ <MenuItem key={name} value={optionValues[index]}>
58
+ {name}
59
+ </MenuItem>
60
+ ))}
61
+ </TextField>
62
+ );
63
+ };
64
+
65
+ export default ExpirationSecondsSelector;
@@ -0,0 +1,53 @@
1
+ import { CoreLanguageCode } from '@digitaldefiance/i18n-lib';
2
+ import { getFlagCode } from '@digitaldefiance/suite-core-lib';
3
+ import { Box, SxProps, Theme } from '@mui/material';
4
+ import { FC } from 'react';
5
+
6
+ export interface FlagProps {
7
+ language: string;
8
+ sx?: SxProps<Theme>;
9
+ }
10
+
11
+ /**
12
+ * A simple component to display a flag icon for a given language.
13
+ *
14
+ * Props:
15
+ * language: The language to display a flag for, as a StringLanguages enum value.
16
+ * sx: Optional styles to apply to the component.
17
+ *
18
+ * Returns a Box component with an SVG flag icon from flagcdn.com as a ::before pseudo-element.
19
+ * The flag is sized to 1.5rem by default, but can be overridden by passing a custom sx prop.
20
+ * The component also includes an aria-label for accessibility, set to `Flag for <language>`.
21
+ */
22
+ export const Flag: FC<FlagProps> = ({ language, sx }) => {
23
+ const flagContent = getFlagCode(language);
24
+ if (!flagContent) {
25
+ return null;
26
+ }
27
+ return (
28
+ <Box
29
+ component="span"
30
+ aria-label={`Flag for ${language}`}
31
+ sx={{
32
+ fontSize: '1.5rem',
33
+ lineHeight: 1,
34
+ verticalAlign: 'middle',
35
+ '&::before': {
36
+ content: `" "`,
37
+ display: 'inline-block',
38
+ width: '1em',
39
+ height: '1em',
40
+ backgroundImage: `url(https://flagcdn.com/${flagContent.toLowerCase()}.svg)`,
41
+ backgroundSize: 'contain',
42
+ backgroundRepeat: 'no-repeat',
43
+ backgroundPosition: 'center',
44
+ border: '1px solid rgba(0, 0, 0, 0.1)',
45
+ borderRadius: '2px',
46
+ },
47
+ ...sx,
48
+ }}
49
+ />
50
+ );
51
+ };
52
+
53
+ export default Flag;
@@ -0,0 +1,120 @@
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 { SuiteCoreComponentId, SuiteCoreStringKey } from '@digitaldefiance/suite-core-lib';
6
+ import { useI18n } from '../contexts';
7
+
8
+ export interface ForgotPasswordFormValues {
9
+ email: string;
10
+ }
11
+
12
+ export interface ForgotPasswordFormProps {
13
+ onSubmit: (values: ForgotPasswordFormValues) => Promise<void>;
14
+ emailValidation?: Yup.StringSchema;
15
+ labels?: {
16
+ title?: string;
17
+ email?: string;
18
+ sendResetLink?: string;
19
+ successMessage?: string;
20
+ };
21
+ }
22
+
23
+ export const ForgotPasswordForm: FC<ForgotPasswordFormProps> = ({
24
+ onSubmit,
25
+ emailValidation,
26
+ labels = {},
27
+ }) => {
28
+ const { tComponent } = useI18n();
29
+ const [success, setSuccess] = useState(false);
30
+ const [apiError, setApiError] = useState<string>('');
31
+
32
+ const validation = {
33
+ email: emailValidation || Yup.string()
34
+ .email(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_InvalidEmail))
35
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
36
+ };
37
+
38
+ const translatedLabels = {
39
+ title: labels.title || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.ForgotPassword_Title),
40
+ email: labels.email || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Email),
41
+ sendResetLink: labels.sendResetLink || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.ForgotPassword_SendResetLink),
42
+ successMessage: labels.successMessage || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.PasswordReset_Success),
43
+ };
44
+
45
+ const formik = useFormik<ForgotPasswordFormValues>({
46
+ initialValues: {
47
+ email: '',
48
+ },
49
+ validationSchema: Yup.object({
50
+ email: validation.email,
51
+ }),
52
+ onSubmit: async (values) => {
53
+ try {
54
+ await onSubmit(values);
55
+ setSuccess(true);
56
+ setApiError('');
57
+ } catch (error: any) {
58
+ setApiError(error.response?.data?.message || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.ForgotPassword_Error));
59
+ setSuccess(false);
60
+ }
61
+ },
62
+ });
63
+
64
+ return (
65
+ <Container maxWidth="sm">
66
+ <Box
67
+ sx={{
68
+ mt: 8,
69
+ display: 'flex',
70
+ flexDirection: 'column',
71
+ alignItems: 'center',
72
+ }}
73
+ >
74
+ <Typography variant="h4" component="h1" gutterBottom>
75
+ {translatedLabels.title}
76
+ </Typography>
77
+
78
+ <Box component="form" onSubmit={formik.handleSubmit} sx={{ mt: 1, width: '100%' }}>
79
+ <TextField
80
+ fullWidth
81
+ id="email"
82
+ name="email"
83
+ label={translatedLabels.email}
84
+ value={formik.values.email}
85
+ onChange={formik.handleChange}
86
+ onBlur={formik.handleBlur}
87
+ error={Boolean(formik.touched.email && formik.errors.email)}
88
+ helperText={formik.touched.email && formik.errors.email}
89
+ margin="normal"
90
+ />
91
+
92
+ {apiError && (
93
+ <Alert severity="error" sx={{ mt: 2, mb: 2 }}>
94
+ {apiError}
95
+ </Alert>
96
+ )}
97
+
98
+ {success && (
99
+ <Alert severity="success" sx={{ mt: 2, mb: 2 }}>
100
+ {translatedLabels.successMessage}
101
+ </Alert>
102
+ )}
103
+
104
+ <Button
105
+ type="submit"
106
+ fullWidth
107
+ variant="contained"
108
+ color="primary"
109
+ sx={{ mt: 3, mb: 2 }}
110
+ disabled={formik.isSubmitting}
111
+ >
112
+ {translatedLabels.sendResetLink}
113
+ </Button>
114
+ </Box>
115
+ </Box>
116
+ </Container>
117
+ );
118
+ };
119
+
120
+ export default ForgotPasswordForm;
@@ -0,0 +1,307 @@
1
+ import { Visibility, VisibilityOff } from '@mui/icons-material';
2
+ import {
3
+ Alert,
4
+ Box,
5
+ Button,
6
+ Container,
7
+ IconButton,
8
+ InputAdornment,
9
+ Link,
10
+ TextField,
11
+ Typography,
12
+ } from '@mui/material';
13
+ import { useFormik } from 'formik';
14
+ import { FC, useState } from 'react';
15
+ import * as Yup from 'yup';
16
+ import { SuiteCoreComponentId, SuiteCoreStringKey, Constants } from '@digitaldefiance/suite-core-lib';
17
+ import { useI18n } from '../contexts';
18
+
19
+ export interface LoginFormValues {
20
+ email?: string;
21
+ username?: string;
22
+ password?: string;
23
+ mnemonic?: string;
24
+ [key: string]: any;
25
+ }
26
+
27
+ export interface LoginFormProps {
28
+ onSubmit: (values: LoginFormValues) => Promise<void>;
29
+ loginType?: 'email' | 'username';
30
+ authType?: 'password' | 'mnemonic';
31
+ allowLoginTypeToggle?: boolean;
32
+ allowAuthTypeToggle?: boolean;
33
+ showForgotPassword?: boolean;
34
+ showSignUp?: boolean;
35
+ forgotPasswordLink?: string;
36
+ signUpLink?: string;
37
+ emailLabel?: string;
38
+ usernameLabel?: string;
39
+ passwordLabel?: string;
40
+ mnemonicLabel?: string;
41
+ signInButtonText?: string;
42
+ forgotPasswordText?: string;
43
+ signUpText?: string;
44
+ useUsernameText?: string;
45
+ useEmailText?: string;
46
+ useMnemonicText?: string;
47
+ usePasswordText?: string;
48
+ toggleVisibilityLabel?: string;
49
+ titleText?: string;
50
+ emailValidation?: Yup.StringSchema;
51
+ usernameValidation?: Yup.StringSchema;
52
+ passwordValidation?: Yup.StringSchema;
53
+ mnemonicValidation?: Yup.StringSchema;
54
+ additionalFields?: (formik: ReturnType<typeof useFormik<LoginFormValues>>) => React.ReactNode;
55
+ additionalInitialValues?: Record<string, any>;
56
+ additionalValidation?: Record<string, Yup.Schema>;
57
+ }
58
+
59
+ export const LoginForm: FC<LoginFormProps> = ({
60
+ onSubmit,
61
+ loginType: initialLoginType = 'email',
62
+ authType: initialAuthType = 'password',
63
+ allowLoginTypeToggle = true,
64
+ allowAuthTypeToggle = true,
65
+ showForgotPassword = true,
66
+ showSignUp = true,
67
+ forgotPasswordLink = '/forgot-password',
68
+ signUpLink = '/register',
69
+ emailLabel,
70
+ usernameLabel,
71
+ passwordLabel,
72
+ mnemonicLabel,
73
+ signInButtonText,
74
+ forgotPasswordText,
75
+ signUpText,
76
+ useUsernameText,
77
+ useEmailText,
78
+ useMnemonicText,
79
+ usePasswordText,
80
+ toggleVisibilityLabel,
81
+ titleText,
82
+ emailValidation,
83
+ usernameValidation,
84
+ passwordValidation,
85
+ mnemonicValidation,
86
+ additionalFields,
87
+ additionalInitialValues = {},
88
+ additionalValidation = {},
89
+ }) => {
90
+ const { tComponent } = useI18n();
91
+ const [loginType, setLoginType] = useState<'email' | 'username'>(initialLoginType);
92
+ const [authType, setAuthType] = useState<'password' | 'mnemonic'>(initialAuthType);
93
+ const [showSecret, setShowSecret] = useState(false);
94
+
95
+ // Use translations with fallbacks
96
+ const labels = {
97
+ title: titleText || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Login_Title),
98
+ email: emailLabel || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Email),
99
+ username: usernameLabel || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Username),
100
+ password: passwordLabel || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Password),
101
+ mnemonic: mnemonicLabel || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Mnemonic),
102
+ signIn: signInButtonText || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.SignInButton),
103
+ forgotPassword: forgotPasswordText || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Login_ForgotPassword),
104
+ signUp: signUpText || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Login_SignUp),
105
+ useUsername: useUsernameText || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Login_UseUsername),
106
+ useEmail: useEmailText || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Login_UseEmailAddress),
107
+ useMnemonic: useMnemonicText || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_UseMnemonic),
108
+ usePassword: usePasswordText || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_UsePassword),
109
+ toggleVisibility: toggleVisibilityLabel || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.TogglePasswordVisibility),
110
+ };
111
+
112
+ const validation = {
113
+ email: emailValidation || Yup.string()
114
+ .email(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_InvalidEmail))
115
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
116
+ username: usernameValidation || Yup.string()
117
+ .matches(Constants.UsernameRegex, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_UsernameRegexErrorTemplate))
118
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
119
+ password: passwordValidation || Yup.string()
120
+ .min(1, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required))
121
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
122
+ mnemonic: mnemonicValidation || Yup.string()
123
+ .matches(Constants.MnemonicRegex, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_InvalidMnemonic))
124
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
125
+ };
126
+
127
+ const formik = useFormik<LoginFormValues>({
128
+ initialValues: {
129
+ email: '',
130
+ username: '',
131
+ mnemonic: '',
132
+ password: '',
133
+ ...additionalInitialValues,
134
+ },
135
+ validationSchema: Yup.object({
136
+ [loginType]: loginType === 'email' ? validation.email : validation.username,
137
+ ...(authType === 'mnemonic'
138
+ ? { mnemonic: validation.mnemonic }
139
+ : { password: validation.password }),
140
+ ...additionalValidation,
141
+ }),
142
+ enableReinitialize: true,
143
+ onSubmit: async (values, { setStatus }) => {
144
+ try {
145
+ setStatus(null);
146
+ await onSubmit(values);
147
+ } catch (error: any) {
148
+ setStatus(error.message || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_UnexpectedError));
149
+ throw error;
150
+ }
151
+ },
152
+ });
153
+
154
+ return (
155
+ <Container component="main" maxWidth="xs">
156
+ <Box
157
+ sx={{
158
+ marginTop: 8,
159
+ display: 'flex',
160
+ flexDirection: 'column',
161
+ alignItems: 'center',
162
+ }}
163
+ >
164
+ <Typography component="h1" variant="h5">
165
+ {labels.title}
166
+ </Typography>
167
+ <Box component="form" onSubmit={formik.handleSubmit} sx={{ mt: 1, width: '100%' }}>
168
+ {formik.status && (
169
+ <Alert severity="error" sx={{ mb: 2 }}>
170
+ {formik.status}
171
+ </Alert>
172
+ )}
173
+ <TextField
174
+ margin="normal"
175
+ fullWidth
176
+ id={loginType}
177
+ label={loginType === 'email' ? labels.email : labels.username}
178
+ name={loginType}
179
+ autoComplete={loginType === 'email' ? 'email' : 'username'}
180
+ autoFocus
181
+ value={loginType === 'email' ? formik.values.email : formik.values.username}
182
+ onChange={formik.handleChange}
183
+ error={formik.touched[loginType] && Boolean(formik.errors[loginType])}
184
+ helperText={formik.touched[loginType] && formik.errors[loginType]}
185
+ />
186
+ {authType === 'password' ? (
187
+ <TextField
188
+ margin="normal"
189
+ required
190
+ fullWidth
191
+ name="password"
192
+ label={labels.password}
193
+ id="password"
194
+ type={showSecret ? 'text' : 'password'}
195
+ value={formik.values.password}
196
+ onChange={formik.handleChange}
197
+ error={formik.touched.password && Boolean(formik.errors.password)}
198
+ helperText={formik.touched.password && formik.errors.password}
199
+ slotProps={{
200
+ input: {
201
+ endAdornment: (
202
+ <InputAdornment position="end">
203
+ <IconButton
204
+ aria-label={labels.toggleVisibility}
205
+ onClick={() => setShowSecret(!showSecret)}
206
+ edge="end"
207
+ >
208
+ {showSecret ? <VisibilityOff /> : <Visibility />}
209
+ </IconButton>
210
+ </InputAdornment>
211
+ ),
212
+ },
213
+ }}
214
+ />
215
+ ) : (
216
+ <TextField
217
+ margin="normal"
218
+ required
219
+ fullWidth
220
+ name="mnemonic"
221
+ label={labels.mnemonic}
222
+ id="mnemonic"
223
+ multiline
224
+ rows={3}
225
+ value={formik.values.mnemonic}
226
+ onChange={formik.handleChange}
227
+ error={formik.touched.mnemonic && Boolean(formik.errors.mnemonic)}
228
+ helperText={formik.touched.mnemonic && formik.errors.mnemonic}
229
+ type={showSecret ? 'text' : 'password'}
230
+ slotProps={{
231
+ input: {
232
+ endAdornment: (
233
+ <InputAdornment position="end">
234
+ <IconButton
235
+ aria-label={labels.toggleVisibility}
236
+ onClick={() => setShowSecret(!showSecret)}
237
+ edge="end"
238
+ >
239
+ {showSecret ? <VisibilityOff /> : <Visibility />}
240
+ </IconButton>
241
+ </InputAdornment>
242
+ ),
243
+ },
244
+ }}
245
+ />
246
+ )}
247
+ {additionalFields && additionalFields(formik)}
248
+ <Button
249
+ type="submit"
250
+ fullWidth
251
+ variant="contained"
252
+ sx={{ mt: 3, mb: 2 }}
253
+ disabled={formik.isSubmitting}
254
+ >
255
+ {labels.signIn}
256
+ </Button>
257
+ {(showForgotPassword || showSignUp) && (
258
+ <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
259
+ {showForgotPassword && (
260
+ <Link href={forgotPasswordLink} variant="body2">
261
+ {labels.forgotPassword}
262
+ </Link>
263
+ )}
264
+ {showSignUp && (
265
+ <Link href={signUpLink} variant="body2">
266
+ {labels.signUp}
267
+ </Link>
268
+ )}
269
+ </Box>
270
+ )}
271
+ {(allowLoginTypeToggle || allowAuthTypeToggle) && (
272
+ <Box sx={{ display: 'flex', gap: 1, mt: 2 }}>
273
+ {allowLoginTypeToggle && (
274
+ <Button
275
+ fullWidth
276
+ variant="text"
277
+ onClick={() => {
278
+ const newType = loginType === 'email' ? 'username' : 'email';
279
+ formik.setFieldValue(loginType, '');
280
+ setLoginType(newType);
281
+ }}
282
+ >
283
+ {loginType === 'email' ? labels.useUsername : labels.useEmail}
284
+ </Button>
285
+ )}
286
+ {allowAuthTypeToggle && (
287
+ <Button
288
+ fullWidth
289
+ variant="text"
290
+ onClick={() => {
291
+ const newType = authType === 'password' ? 'mnemonic' : 'password';
292
+ formik.setFieldValue(authType, '');
293
+ setAuthType(newType);
294
+ }}
295
+ >
296
+ {authType === 'password' ? labels.useMnemonic : labels.usePassword}
297
+ </Button>
298
+ )}
299
+ </Box>
300
+ )}
301
+ </Box>
302
+ </Box>
303
+ </Container>
304
+ );
305
+ };
306
+
307
+ export default LoginForm;
@@ -0,0 +1,21 @@
1
+ import { useEffect } from 'react';
2
+
3
+ export interface LogoutPageProps {
4
+ onLogout: () => Promise<void> | void;
5
+ onNavigate?: (path: string) => void;
6
+ redirectTo?: string;
7
+ }
8
+
9
+ export const LogoutPage = ({ onLogout, onNavigate, redirectTo = '/login' }: LogoutPageProps) => {
10
+ useEffect(() => {
11
+ const performLogout = async () => {
12
+ await onLogout();
13
+ onNavigate?.(redirectTo);
14
+ };
15
+ performLogout();
16
+ }, [onLogout, onNavigate, redirectTo]);
17
+
18
+ return null;
19
+ };
20
+
21
+ export default LogoutPage;