@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Digital Defiance, Jessica Mulein
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@digitaldefiance/express-suite-react-components",
3
- "version": "2.9.1",
3
+ "version": "2.9.2",
4
+ "homepage": "https://github.com/Digital-Defiance/react-components",
4
5
  "description": "React MUI components for Digital Defiance Express Suite",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Digital-Defiance/react-components.git"
9
+ },
5
10
  "main": "src/index.js",
6
11
  "types": "src/index.d.ts",
7
12
  "scripts": {
@@ -25,8 +30,8 @@
25
30
  "react-router-dom": "6.29.0"
26
31
  },
27
32
  "dependencies": {
28
- "@digitaldefiance/i18n-lib": "3.8.0",
29
- "@digitaldefiance/suite-core-lib": "3.6.6",
33
+ "@digitaldefiance/i18n-lib": "3.8.2",
34
+ "@digitaldefiance/suite-core-lib": "3.6.7",
30
35
  "@emotion/react": "^11.14.0",
31
36
  "@emotion/styled": "^11.14.0",
32
37
  "@mui/icons-material": "^7.0.2",
@@ -68,6 +73,5 @@
68
73
  ],
69
74
  "author": "Digital Defiance",
70
75
  "license": "MIT",
71
- "packageManager": "yarn@4.10.3",
72
- "type": "commonjs"
73
- }
76
+ "packageManager": "yarn@4.10.3"
77
+ }
@@ -0,0 +1,17 @@
1
+ import { FC, ReactNode, useContext } from 'react';
2
+ import { AuthContext } from '../contexts/AuthProvider';
3
+ export interface PrivateProps {
4
+ children: ReactNode;
5
+ }
6
+
7
+ export const Private: FC<PrivateProps> = ({
8
+ children,
9
+ }) => {
10
+ const { isAuthenticated, isCheckingAuth } = useContext(AuthContext);
11
+
12
+ if (isCheckingAuth || !isAuthenticated) {
13
+ return <></>;
14
+ }
15
+
16
+ return <>{children}</>;
17
+ };
@@ -0,0 +1,28 @@
1
+ import { FC, ReactNode, useContext } from 'react';
2
+ import { Navigate, useLocation } from 'react-router-dom';
3
+ import { AuthContext } from '../contexts/AuthProvider';
4
+ import { useI18n } from '../contexts';
5
+ import { SuiteCoreComponentId, SuiteCoreStringKey } from '@digitaldefiance/suite-core-lib';
6
+
7
+ interface PrivateRouteProps {
8
+ children: ReactNode;
9
+ redirectTo?: string;
10
+ }
11
+
12
+ export const PrivateRoute: FC<PrivateRouteProps> = ({ children, redirectTo }) => {
13
+ const { tComponent } = useI18n();
14
+ const { isAuthenticated, isCheckingAuth } = useContext(AuthContext);
15
+ const location = useLocation();
16
+
17
+ if (isCheckingAuth) {
18
+ return <div>{tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_CheckingAuthentication)}...</div>;
19
+ }
20
+
21
+ if (!isAuthenticated) {
22
+ return <Navigate to={redirectTo ?? "/login"} state={{ from: location }} replace />;
23
+ }
24
+
25
+ return <>{children}</>;
26
+ };
27
+
28
+ export default PrivateRoute;
@@ -0,0 +1,16 @@
1
+ import { FC, ReactNode, useContext } from 'react';
2
+ import { AuthContext } from '../contexts/AuthProvider';
3
+
4
+ export interface UnAuthProps {
5
+ children: ReactNode;
6
+ }
7
+
8
+ export const UnAuth: FC<UnAuthProps> = ({ children }) => {
9
+ const { isAuthenticated, isCheckingAuth } = useContext(AuthContext);
10
+
11
+ if (isCheckingAuth || isAuthenticated) {
12
+ return null;
13
+ }
14
+
15
+ return <>{children}</>;
16
+ };
@@ -0,0 +1,30 @@
1
+ import { FC, ReactNode, useContext } from 'react';
2
+ import { Navigate, useLocation } from 'react-router-dom';
3
+ import { AuthContext, useI18n } from '../contexts';
4
+ import { SuiteCoreComponentId, SuiteCoreStringKey } from '@digitaldefiance/suite-core-lib';
5
+
6
+ export interface UnAuthRouteProps {
7
+ children: ReactNode;
8
+ redirectTo?: string;
9
+ }
10
+
11
+ export const UnAuthRoute: FC<UnAuthRouteProps> = ({
12
+ children,
13
+ redirectTo = '/dashboard',
14
+ }) => {
15
+ const { isAuthenticated, isCheckingAuth } = useContext(AuthContext);
16
+ const { tComponent } = useI18n();
17
+ const location = useLocation();
18
+
19
+ if (isCheckingAuth) {
20
+ return <div>{tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_CheckingAuthentication)}...</div>;
21
+ }
22
+
23
+ if (isAuthenticated) {
24
+ return <Navigate to={redirectTo ?? "/dashboard"} state={{ from: location }} replace />;
25
+ }
26
+
27
+ return <>{children}</>;
28
+ };
29
+
30
+ export default UnAuthRoute;
@@ -1,5 +1,4 @@
1
1
  export { default as PrivateRoute } from './PrivateRoute';
2
2
  export { default as UnAuthRoute } from './UnAuthRoute';
3
3
  export * from './Private';
4
- export * from './UnAuth';
5
- //# sourceMappingURL=index.d.ts.map
4
+ export * from './UnAuth'
@@ -0,0 +1,134 @@
1
+ import ContentCopyIcon from '@mui/icons-material/ContentCopy';
2
+ import {
3
+ Box,
4
+ Button,
5
+ Dialog,
6
+ DialogActions,
7
+ DialogContent,
8
+ DialogTitle,
9
+ styled,
10
+ TextField,
11
+ Typography,
12
+ } from '@mui/material';
13
+ import { FC, useState } from 'react';
14
+ import { SuiteCoreComponentId, SuiteCoreStringKey } from '@digitaldefiance/suite-core-lib';
15
+ import { useAuth, useI18n } from '../contexts';
16
+
17
+ const ApiAccessContainer = styled(Box)(({ theme }) => ({
18
+ display: 'flex',
19
+ flexDirection: 'column',
20
+ alignItems: 'center',
21
+ justifyContent: 'center',
22
+ minHeight: '100vh',
23
+ backgroundColor: theme.palette.background.default,
24
+ padding: theme.spacing(3),
25
+ }));
26
+
27
+ const ApiAccessContent = styled(Box)(({ theme }) => ({
28
+ maxWidth: '600px',
29
+ width: '100%',
30
+ backgroundColor: theme.palette.background.paper,
31
+ borderRadius: theme.shape.borderRadius,
32
+ padding: theme.spacing(4),
33
+ boxShadow: theme.shadows[3],
34
+ }));
35
+
36
+ const ApiAccessTitle = styled(Typography)(({ theme }) => ({
37
+ marginBottom: theme.spacing(3),
38
+ color: theme.palette.primary.main,
39
+ }));
40
+
41
+ export interface ApiAccessProps {
42
+ token?: string | null;
43
+ labels?: {
44
+ title?: string;
45
+ tokenNotAvailable?: string;
46
+ copyButton?: string;
47
+ notificationTitle?: string;
48
+ copied?: string;
49
+ copyFailed?: string;
50
+ ok?: string;
51
+ };
52
+ }
53
+
54
+ export const ApiAccess: FC<ApiAccessProps> = ({
55
+ token: tokenProp,
56
+ labels = {},
57
+ }) => {
58
+ const { token: authToken } = useAuth();
59
+ const token = tokenProp !== undefined ? tokenProp : authToken;
60
+ const { tComponent } = useI18n();
61
+ const [dialogOpen, setDialogOpen] = useState(false);
62
+ const [isError, setIsError] = useState(false);
63
+
64
+ const translatedLabels = {
65
+ title: labels.title || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.ApiAccess_Title),
66
+ tokenNotAvailable: labels.tokenNotAvailable || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.ApiAccess_TokenNotAvailable),
67
+ copyButton: labels.copyButton || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_CopyToClipboard),
68
+ notificationTitle: labels.notificationTitle || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Notification),
69
+ copied: labels.copied || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_CopiedToClipboard),
70
+ copyFailed: labels.copyFailed || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Error_FailedToCopy),
71
+ ok: labels.ok || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_OK),
72
+ };
73
+
74
+ const copyToClipboard = async () => {
75
+ if (token) {
76
+ try {
77
+ await navigator.clipboard.writeText(token);
78
+ setIsError(false);
79
+ setDialogOpen(true);
80
+ } catch (err) {
81
+ setIsError(true);
82
+ }
83
+ }
84
+ };
85
+
86
+ const handleClose = () => {
87
+ setDialogOpen(false);
88
+ setIsError(false);
89
+ };
90
+
91
+ return (
92
+ <ApiAccessContainer>
93
+ <ApiAccessContent>
94
+ <ApiAccessTitle variant="h4" align="center">
95
+ {translatedLabels.title}
96
+ </ApiAccessTitle>
97
+ <TextField
98
+ fullWidth
99
+ multiline
100
+ rows={4}
101
+ value={token || translatedLabels.tokenNotAvailable}
102
+ slotProps={{
103
+ input: {
104
+ readOnly: true,
105
+ },
106
+ }}
107
+ variant="outlined"
108
+ margin="normal"
109
+ />
110
+ <Button
111
+ variant="contained"
112
+ color="primary"
113
+ startIcon={<ContentCopyIcon />}
114
+ onClick={copyToClipboard}
115
+ fullWidth
116
+ style={{ marginTop: '16px' }}
117
+ >
118
+ {translatedLabels.copyButton}
119
+ </Button>
120
+ </ApiAccessContent>
121
+ <Dialog open={dialogOpen} onClose={handleClose}>
122
+ <DialogTitle>{translatedLabels.notificationTitle}</DialogTitle>
123
+ <DialogContent>{isError ? translatedLabels.copyFailed : translatedLabels.copied}</DialogContent>
124
+ <DialogActions>
125
+ <Button onClick={handleClose} color="primary">
126
+ {translatedLabels.ok}
127
+ </Button>
128
+ </DialogActions>
129
+ </Dialog>
130
+ </ApiAccessContainer>
131
+ );
132
+ };
133
+
134
+ export default ApiAccess;
@@ -0,0 +1,314 @@
1
+ import {
2
+ Box,
3
+ Button,
4
+ Checkbox,
5
+ Container,
6
+ FormControlLabel,
7
+ TextField,
8
+ Typography,
9
+ } from '@mui/material';
10
+ import { useFormik } from 'formik';
11
+ import { FC, useState } from 'react';
12
+ import * as Yup from 'yup';
13
+ import { Constants, SuiteCoreComponentId, SuiteCoreStringKey } from '@digitaldefiance/suite-core-lib';
14
+ import { useI18n } from '../contexts';
15
+
16
+ export interface BackupCodeLoginFormValues {
17
+ email: string;
18
+ username: string;
19
+ code: string;
20
+ newPassword?: string;
21
+ confirmNewPassword?: string;
22
+ recoverMnemonic: boolean;
23
+ }
24
+
25
+ export interface BackupCodeLoginFormProps {
26
+ onSubmit: (
27
+ identifier: string,
28
+ code: string,
29
+ isEmail: boolean,
30
+ recoverMnemonic: boolean,
31
+ newPassword?: string
32
+ ) => Promise<
33
+ | { token: string; codeCount: number; mnemonic?: string; message?: string }
34
+ | { error: string; status?: number }
35
+ >;
36
+ onNavigate?: (path: string, state?: any) => void;
37
+ isAuthenticated?: boolean;
38
+ validationSchema?: Yup.ObjectSchema<any>;
39
+ labels?: {
40
+ title?: string;
41
+ email?: string;
42
+ username?: string;
43
+ code?: string;
44
+ newPassword?: string;
45
+ confirmPassword?: string;
46
+ recoverMnemonic?: string;
47
+ login?: string;
48
+ useUsername?: string;
49
+ useEmail?: string;
50
+ dashboard?: string;
51
+ generateNewCodes?: string;
52
+ mnemonicLabel?: string;
53
+ codesRemaining?: string;
54
+ };
55
+ }
56
+
57
+ export const BackupCodeLoginForm: FC<BackupCodeLoginFormProps> = ({
58
+ onSubmit,
59
+ onNavigate,
60
+ isAuthenticated = false,
61
+ validationSchema,
62
+ labels = {},
63
+ }) => {
64
+ const { tComponent } = useI18n();
65
+ const [loginType, setLoginType] = useState<'email' | 'username'>('email');
66
+ const [loginError, setLoginError] = useState<string | null>(null);
67
+ const [recoveredMnemonic, setRecoveredMnemonic] = useState<string | null>(null);
68
+ const [successMessage, setSuccessMessage] = useState<string | null>(null);
69
+ const [codesRemaining, setCodesRemaining] = useState<number | null>(null);
70
+
71
+ const translatedLabels = {
72
+ title: labels.title || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.BackupCodeRecovery_Title),
73
+ email: labels.email || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Email),
74
+ username: labels.username || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Username),
75
+ code: labels.code || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_BackupCode),
76
+ newPassword: labels.newPassword || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_NewPassword),
77
+ confirmPassword: labels.confirmPassword || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ConfirmNewPassword),
78
+ recoverMnemonic: labels.recoverMnemonic || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.BackupCodeRecovery_RecoverMnemonic),
79
+ login: labels.login || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.BackupCodeRecovery_Login),
80
+ useUsername: labels.useUsername || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Login_UseUsername),
81
+ useEmail: labels.useEmail || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Login_UseEmailAddress),
82
+ dashboard: labels.dashboard || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Dashboard),
83
+ generateNewCodes: labels.generateNewCodes || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.BackupCodeRecovery_GenerateNewCodes),
84
+ mnemonicLabel: labels.mnemonicLabel || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Mnemonic),
85
+ codesRemaining: labels.codesRemaining || tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.BackupCodeRecovery_CodesRemainingTemplate),
86
+ unexpectedError: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_UnexpectedError),
87
+ };
88
+
89
+ const yupFieldValidation = {
90
+ email: Yup.string()
91
+ .email(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_InvalidEmail))
92
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
93
+ username: Yup.string()
94
+ .matches(Constants.UsernameRegex, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_UsernameRegexErrorTemplate))
95
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required)),
96
+ code: Yup.string()
97
+ .required(tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_Required))
98
+ .matches(Constants.BACKUP_CODES.DisplayRegex, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_InvalidBackupCode)),
99
+ password: Yup.string()
100
+ .matches(Constants.PasswordRegex, tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_PasswordRegexErrorTemplate)),
101
+ confirmPassword: Yup.string()
102
+ .oneOf([Yup.ref('newPassword')], tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Validation_PasswordMatch)),
103
+ };
104
+
105
+ const formik = useFormik<BackupCodeLoginFormValues>({
106
+ initialValues: {
107
+ email: '',
108
+ username: '',
109
+ code: '',
110
+ newPassword: '',
111
+ confirmNewPassword: '',
112
+ recoverMnemonic: false,
113
+ },
114
+ validationSchema: validationSchema ?? Yup.object({
115
+ [loginType]: loginType === 'email' ? yupFieldValidation.email : yupFieldValidation.username,
116
+ code: yupFieldValidation.code,
117
+ newPassword: yupFieldValidation.password,
118
+ confirmNewPassword: yupFieldValidation.confirmPassword,
119
+ }),
120
+ enableReinitialize: true,
121
+ onSubmit: async (values, { setSubmitting }) => {
122
+ try {
123
+ const loginResult = await onSubmit(
124
+ loginType === 'email' ? values.email : values.username,
125
+ values.code,
126
+ loginType === 'email',
127
+ values.recoverMnemonic,
128
+ values.newPassword && values.newPassword.length > 0 ? values.newPassword : undefined
129
+ );
130
+ if ('error' in loginResult) {
131
+ setLoginError(loginResult.error);
132
+ setCodesRemaining(null);
133
+ setRecoveredMnemonic(null);
134
+ return;
135
+ }
136
+ setLoginError(null);
137
+ if (loginResult.codeCount) {
138
+ setCodesRemaining(loginResult.codeCount);
139
+ }
140
+ if (loginResult.mnemonic) {
141
+ setRecoveredMnemonic(loginResult.mnemonic);
142
+ }
143
+ if (loginResult.message) {
144
+ setSuccessMessage(loginResult.message);
145
+ }
146
+ } catch {
147
+ setLoginError(translatedLabels.unexpectedError);
148
+ } finally {
149
+ setSubmitting(false);
150
+ }
151
+ },
152
+ });
153
+
154
+ if (isAuthenticated && recoveredMnemonic === null && codesRemaining === null) {
155
+ onNavigate?.('/dashboard');
156
+ return null;
157
+ }
158
+
159
+ return (
160
+ <Container component="main" maxWidth="xs">
161
+ <Box sx={{ marginTop: 8, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
162
+ <Typography component="h1" variant="h5">
163
+ {translatedLabels.title}
164
+ </Typography>
165
+ <Box component="form" onSubmit={formik.handleSubmit} sx={{ mt: 1, width: '100%' }}>
166
+ {!isAuthenticated && (
167
+ <>
168
+ <TextField
169
+ margin="normal"
170
+ fullWidth
171
+ id={loginType}
172
+ label={loginType === 'email' ? translatedLabels.email : translatedLabels.username}
173
+ name={loginType}
174
+ autoComplete={loginType === 'email' ? 'email' : 'username'}
175
+ autoFocus
176
+ value={loginType === 'email' ? formik.values.email : formik.values.username}
177
+ onChange={formik.handleChange}
178
+ onBlur={formik.handleBlur}
179
+ error={formik.touched[loginType] && Boolean(formik.errors[loginType])}
180
+ helperText={formik.touched[loginType] && formik.errors[loginType]}
181
+ disabled={isAuthenticated}
182
+ />
183
+ <TextField
184
+ margin="normal"
185
+ required
186
+ fullWidth
187
+ name="code"
188
+ label={translatedLabels.code}
189
+ id="code"
190
+ value={formik.values.code}
191
+ onChange={formik.handleChange}
192
+ onBlur={formik.handleBlur}
193
+ error={formik.touched.code && Boolean(formik.errors.code)}
194
+ helperText={formik.touched.code && formik.errors.code}
195
+ disabled={isAuthenticated}
196
+ />
197
+ <TextField
198
+ margin="normal"
199
+ fullWidth
200
+ name="newPassword"
201
+ label={translatedLabels.newPassword}
202
+ type="password"
203
+ id="newPassword"
204
+ value={formik.values.newPassword}
205
+ onChange={formik.handleChange}
206
+ onBlur={formik.handleBlur}
207
+ error={formik.touched.newPassword && Boolean(formik.errors.newPassword)}
208
+ helperText={formik.touched.newPassword && formik.errors.newPassword}
209
+ disabled={isAuthenticated}
210
+ />
211
+ <TextField
212
+ margin="normal"
213
+ fullWidth
214
+ name="confirmNewPassword"
215
+ label={translatedLabels.confirmPassword}
216
+ type="password"
217
+ id="confirmNewPassword"
218
+ value={formik.values.confirmNewPassword}
219
+ onChange={formik.handleChange}
220
+ onBlur={formik.handleBlur}
221
+ error={formik.touched.confirmNewPassword && Boolean(formik.errors.confirmNewPassword)}
222
+ helperText={formik.touched.confirmNewPassword && formik.errors.confirmNewPassword}
223
+ disabled={isAuthenticated}
224
+ />
225
+ <FormControlLabel
226
+ control={
227
+ <Checkbox
228
+ checked={formik.values.recoverMnemonic}
229
+ onChange={formik.handleChange}
230
+ onBlur={formik.handleBlur}
231
+ name="recoverMnemonic"
232
+ />
233
+ }
234
+ label={translatedLabels.recoverMnemonic}
235
+ disabled={isAuthenticated}
236
+ />
237
+ </>
238
+ )}
239
+ {successMessage && (
240
+ <Typography color="success.main" variant="body2" sx={{ mt: 1 }}>
241
+ {successMessage}
242
+ </Typography>
243
+ )}
244
+ {recoveredMnemonic && (
245
+ <Box sx={{ mt: 2, p: 2, bgcolor: 'grey.100', borderRadius: 1 }}>
246
+ <Typography variant="subtitle2" gutterBottom>
247
+ {translatedLabels.mnemonicLabel}:
248
+ </Typography>
249
+ <Typography variant="body2" sx={{ fontFamily: 'monospace' }}>
250
+ {recoveredMnemonic}
251
+ </Typography>
252
+ <Button
253
+ fullWidth
254
+ variant="contained"
255
+ sx={{ mt: 2 }}
256
+ onClick={() => onNavigate?.('/dashboard')}
257
+ >
258
+ {translatedLabels.dashboard}
259
+ </Button>
260
+ </Box>
261
+ )}
262
+ {codesRemaining !== null && (
263
+ <Box sx={{ mt: 2 }}>
264
+ <Typography variant="body2">
265
+ {translatedLabels.codesRemaining.replace('{count}', String(codesRemaining))}
266
+ </Typography>
267
+ <Button
268
+ fullWidth
269
+ variant="contained"
270
+ sx={{ mt: 2 }}
271
+ onClick={() => onNavigate?.('/backup-codes', { codeCount: codesRemaining })}
272
+ >
273
+ {translatedLabels.generateNewCodes}
274
+ </Button>
275
+ </Box>
276
+ )}
277
+ {loginError && (
278
+ <Typography color="error" variant="body2" sx={{ mt: 1 }}>
279
+ {loginError}
280
+ </Typography>
281
+ )}
282
+ {!isAuthenticated && (
283
+ <>
284
+ <Button
285
+ type="submit"
286
+ fullWidth
287
+ variant="contained"
288
+ sx={{ mt: 3, mb: 2 }}
289
+ disabled={formik.isSubmitting}
290
+ >
291
+ {translatedLabels.login}
292
+ </Button>
293
+ <Box sx={{ display: 'flex', justifyContent: 'center' }}>
294
+ <Button
295
+ fullWidth
296
+ variant="text"
297
+ onClick={() => {
298
+ const newType = loginType === 'email' ? 'username' : 'email';
299
+ formik.setFieldValue(loginType, '');
300
+ setLoginType(newType);
301
+ }}
302
+ >
303
+ {loginType === 'email' ? translatedLabels.useUsername : translatedLabels.useEmail}
304
+ </Button>
305
+ </Box>
306
+ </>
307
+ )}
308
+ </Box>
309
+ </Box>
310
+ </Container>
311
+ );
312
+ };
313
+
314
+ export default BackupCodeLoginForm;