@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,85 @@
1
+ import {
2
+ GlobalActiveContext,
3
+ IActiveContext,
4
+ LanguageRegistry,
5
+ I18nEngine,
6
+ CoreLanguageCode,
7
+ } from '@digitaldefiance/i18n-lib';
8
+ import { createContext, FC, ReactNode, useCallback, useContext, useState } from 'react';
9
+
10
+ export interface I18nProviderProps {
11
+ children: ReactNode;
12
+ i18nEngine: I18nEngine;
13
+ onLanguageChange?: (language: string) => Promise<void>;
14
+ }
15
+
16
+ export interface I18nContextType {
17
+ t: (key: string, vars?: Record<string, string | number>, language?: string) => string;
18
+ tComponent: <TStringKey extends string>(componentId: string, stringKey: TStringKey, vars?: Record<string, string | number>, language?: string) => string;
19
+ changeLanguage: (language: string) => void;
20
+ currentLanguage: string;
21
+ }
22
+
23
+ const I18nContext = createContext<I18nContextType | undefined>(undefined);
24
+
25
+ export const I18nProvider: FC<I18nProviderProps> = ({
26
+ children,
27
+ i18nEngine,
28
+ onLanguageChange,
29
+ }) => {
30
+ const context = GlobalActiveContext.getInstance<
31
+ string,
32
+ IActiveContext<string>
33
+ >();
34
+ const [currentLanguage, setCurrentLanguage] = useState<string>(
35
+ context.userLanguage
36
+ );
37
+
38
+ const changeLanguage = useCallback(
39
+ async (language: string) => {
40
+ const languageDetails = LanguageRegistry.getLanguageByCode(language);
41
+ if (language && languageDetails) {
42
+ context.userLanguage = language;
43
+ i18nEngine.setLanguage(language);
44
+ localStorage.setItem('language', languageDetails.name);
45
+ localStorage.setItem('languageCode', language);
46
+ setCurrentLanguage(language);
47
+ if (onLanguageChange) {
48
+ await onLanguageChange(language);
49
+ }
50
+ }
51
+ },
52
+ [onLanguageChange, i18nEngine, context]
53
+ );
54
+
55
+ const t = useCallback(
56
+ (key: string, vars?: Record<string, string | number>, language?: string) => {
57
+ return i18nEngine.t(key, vars, language ?? currentLanguage);
58
+ },
59
+ [i18nEngine, currentLanguage]
60
+ );
61
+
62
+ const tComponent = useCallback(
63
+ <TStringKey extends string>(componentId: string, stringKey: TStringKey, vars?: Record<string, string | number>, language?: string): string => {
64
+ return i18nEngine.translate(componentId, stringKey, vars, language ?? currentLanguage);
65
+ },
66
+ [currentLanguage, i18nEngine]
67
+ );
68
+
69
+ const value = {
70
+ t,
71
+ tComponent,
72
+ changeLanguage,
73
+ currentLanguage,
74
+ };
75
+
76
+ return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
77
+ };
78
+
79
+ export const useI18n = () => {
80
+ const context = useContext(I18nContext);
81
+ if (context === undefined) {
82
+ throw new Error('useI18n must be used within an I18nProvider');
83
+ }
84
+ return context;
85
+ };
@@ -0,0 +1,310 @@
1
+ // src/app/menuContext.tsx
2
+ import { IRoleDTO, SuiteCoreComponentId } from '@digitaldefiance/suite-core-lib';
3
+ import { SuiteCoreStringKey } from '@digitaldefiance/suite-core-lib';
4
+ import {
5
+ AccountCircle,
6
+ Autorenew as AutorenewIcon,
7
+ Brightness4,
8
+ Brightness7,
9
+ Dashboard as DashboardIcon,
10
+ Key as KeyIcon,
11
+ LockOpen as LockOpenIcon,
12
+ LockReset as LockResetIcon,
13
+ Login as LoginIcon,
14
+ ExitToApp as LogoutIcon,
15
+ PersonAdd as PersonAddIcon,
16
+ Settings,
17
+ } from '@mui/icons-material';
18
+ import {
19
+ FC,
20
+ ReactNode,
21
+ createContext,
22
+ useCallback,
23
+ useContext,
24
+ useEffect,
25
+ useMemo,
26
+ useRef,
27
+ useState,
28
+ } from 'react';
29
+ import { useAuth } from './AuthProvider';
30
+ import { useTheme } from './ThemeProvider';
31
+ import { MenuType, MenuTypes } from '../types/MenuType';
32
+ import { useI18n } from './I18nProvider';
33
+ import { IMenuOption } from '../interfaces/IMenuOption';
34
+ import { IMenuConfig } from '../interfaces/IMenuConfig';
35
+ import { useUserSettings } from '../hooks';
36
+ import { useSuiteConfig } from './SuiteConfigProvider';
37
+ import { createAuthenticatedApiClient } from '../services';
38
+
39
+ interface MenuProviderProps {
40
+ children: ReactNode;
41
+ menuConfigs?: IMenuConfig[];
42
+ enableBackupCodes?: boolean;
43
+ }
44
+
45
+ interface MenuContextType {
46
+ menuOptions: IMenuOption[];
47
+ getMenuOptions: (
48
+ menuType: MenuType,
49
+ includeDividers: boolean,
50
+ ) => IMenuOption[];
51
+ registerMenuOption: (option: IMenuOption) => () => void;
52
+ registerMenuOptions: (options: IMenuOption[]) => () => void;
53
+ getTopMenus: () => Array<IMenuConfig>;
54
+ }
55
+
56
+ const MenuContext = createContext<MenuContextType | undefined>(undefined);
57
+
58
+ export const MenuProvider: FC<MenuProviderProps> = ({ children, menuConfigs = [], enableBackupCodes = true }) => {
59
+ const { userData: user, isAuthenticated, mnemonic, clearMnemonic, wallet, clearWallet } = useAuth();
60
+ const { mode: colorMode } = useTheme();
61
+ const registeredMenuOptions = useRef(new Set<() => void>());
62
+ const [registeredOptions, setRegisteredOptions] = useState<
63
+ Map<string, IMenuOption>
64
+ >(new Map<string, IMenuOption>());
65
+ const { tComponent } = useI18n();
66
+ const { baseUrl } = useSuiteConfig();
67
+ const authenticatedApi = useMemo(() => createAuthenticatedApiClient(baseUrl), [baseUrl]);
68
+ const { toggleColorMode } = useUserSettings({ authenticatedApi, isAuthenticated });
69
+
70
+ const registerMenuOption = useCallback((option: IMenuOption) => {
71
+ const unregister = () => {
72
+ setRegisteredOptions((prev) => {
73
+ const newMap = new Map(prev);
74
+ newMap.delete(option.id);
75
+ return newMap;
76
+ });
77
+ registeredMenuOptions.current.delete(unregister);
78
+ };
79
+
80
+ setRegisteredOptions((prev) => {
81
+ const newMap = new Map(prev);
82
+ newMap.set(option.id, option);
83
+ return newMap;
84
+ });
85
+ registeredMenuOptions.current.add(unregister);
86
+
87
+ return unregister;
88
+ }, []);
89
+
90
+ const registerMenuOptions = useCallback(
91
+ (options: IMenuOption[]) => {
92
+ const unregisterFunctions = options.map(registerMenuOption);
93
+ return () => unregisterFunctions.forEach((f) => f());
94
+ },
95
+ [registerMenuOption],
96
+ );
97
+
98
+ const menuOptions = useMemo(() => {
99
+ const isUserRestricted = () => {
100
+ return user?.roles?.some((role: IRoleDTO) => role.child) ?? false;
101
+ };
102
+ let index = 0;
103
+ const baseOptions: IMenuOption[] = [
104
+ {
105
+ id: 'dashboard',
106
+ label: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_Dashboard),
107
+ icon: <DashboardIcon />,
108
+ link: '/dashboard',
109
+ requiresAuth: true,
110
+ includeOnMenus: [MenuTypes.SideMenu],
111
+ index: index++,
112
+ },
113
+ {
114
+ id: 'user-divider',
115
+ label: '',
116
+ divider: true,
117
+ includeOnMenus: [MenuTypes.SideMenu],
118
+ index: index++,
119
+ requiresAuth: false,
120
+ },
121
+ {
122
+ id: 'logout',
123
+ label: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.LogoutButton),
124
+ icon: <LogoutIcon />,
125
+ link: '/logout',
126
+ requiresAuth: true,
127
+ includeOnMenus: [MenuTypes.UserMenu, MenuTypes.SideMenu],
128
+ index: index++,
129
+ },
130
+ {
131
+ id: 'login',
132
+ label: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Login_LoginButton),
133
+ icon: <LoginIcon />,
134
+ link: '/login',
135
+ requiresAuth: false,
136
+ includeOnMenus: [MenuTypes.UserMenu, MenuTypes.SideMenu],
137
+ index: index++,
138
+ },
139
+ {
140
+ id: 'register',
141
+ label: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.RegisterButton),
142
+ icon: <PersonAddIcon />,
143
+ link: '/register',
144
+ requiresAuth: false,
145
+ includeOnMenus: [MenuTypes.UserMenu, MenuTypes.SideMenu],
146
+ index: index++,
147
+ },
148
+ {
149
+ id: 'forgot-password',
150
+ label: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.ForgotPassword_Title),
151
+ icon: <LockOpenIcon />,
152
+ link: '/forgot-password',
153
+ requiresAuth: false,
154
+ includeOnMenus: [MenuTypes.UserMenu, MenuTypes.SideMenu],
155
+ index: index++,
156
+ },
157
+ {
158
+ id: 'change-password',
159
+ label: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ChangePassword),
160
+ icon: <LockResetIcon />,
161
+ link: '/change-password',
162
+ requiresAuth: true,
163
+ includeOnMenus: [MenuTypes.UserMenu, MenuTypes.SideMenu],
164
+ index: index++,
165
+ },
166
+ ...(enableBackupCodes ? [{
167
+ id: 'backup-code',
168
+ label: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.BackupCodeRecovery_Title),
169
+ icon: <KeyIcon />,
170
+ link: '/backup-code',
171
+ requiresAuth: false,
172
+ includeOnMenus: [MenuTypes.UserMenu, MenuTypes.SideMenu],
173
+ index: index++,
174
+ },
175
+ {
176
+ id: 'backup-codes',
177
+ label: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.BackupCodeRecovery_GenerateNewCodes),
178
+ icon: <AutorenewIcon />,
179
+ link: '/backup-codes',
180
+ requiresAuth: true,
181
+ includeOnMenus: [MenuTypes.UserMenu, MenuTypes.SideMenu],
182
+ index: index++,
183
+ }] : []),
184
+ {
185
+ id: 'divider',
186
+ label: '',
187
+ divider: true,
188
+ includeOnMenus: [MenuTypes.SideMenu],
189
+ index: index++,
190
+ requiresAuth: false,
191
+ },
192
+ {
193
+ id: 'clear-mnemonic',
194
+ label: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ClearMnemonic),
195
+ action: clearMnemonic,
196
+ icon: <KeyIcon />,
197
+ requiresAuth: true,
198
+ includeOnMenus: [MenuTypes.UserMenu, MenuTypes.SideMenu],
199
+ index: index++,
200
+ filter: () => !!mnemonic,
201
+ },
202
+ {
203
+ id: 'clear-wallet',
204
+ label: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ClearWallet),
205
+ action: clearWallet,
206
+ icon: <KeyIcon />,
207
+ requiresAuth: true,
208
+ includeOnMenus: [MenuTypes.UserMenu, MenuTypes.SideMenu],
209
+ index: index++,
210
+ filter: () => !!wallet,
211
+ },
212
+ ...(isUserRestricted() ? [] : []),
213
+ {
214
+ id: 'color-divider',
215
+ label: '',
216
+ divider: true,
217
+ includeOnMenus: [MenuTypes.SideMenu],
218
+ index: index++,
219
+ requiresAuth: undefined,
220
+ },
221
+ {
222
+ id: 'theme-toggle',
223
+ label:
224
+ colorMode === 'dark'
225
+ ? tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ThemeToggle_Light)
226
+ : tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Common_ThemeToggle_Dark),
227
+ icon: colorMode === 'dark' ? <Brightness7 /> : <Brightness4 />,
228
+ includeOnMenus: [MenuTypes.SideMenu],
229
+ index: index++,
230
+ requiresAuth: undefined,
231
+ action: toggleColorMode,
232
+ },
233
+ {
234
+ id: 'user-settings',
235
+ label: tComponent<SuiteCoreStringKey>(SuiteCoreComponentId, SuiteCoreStringKey.Settings_Title),
236
+ icon: <Settings />,
237
+ link: '/user-settings',
238
+ requiresAuth: true,
239
+ includeOnMenus: [MenuTypes.UserMenu, MenuTypes.SideMenu],
240
+ index: index++,
241
+ }
242
+ ];
243
+
244
+ const allOptions = [...baseOptions, ...registeredOptions.values()];
245
+ return allOptions.sort((a, b) => a.index - b.index);
246
+ }, [tComponent, registeredOptions, user?.roles, colorMode, toggleColorMode, clearMnemonic, clearWallet, mnemonic, wallet]);
247
+
248
+ const getMenuOptions = useCallback(
249
+ (menuType: MenuType, includeDividers: boolean) => {
250
+ const MenuFilter = (o: IMenuOption) => {
251
+ // Apply the custom filter first
252
+ let customFilterPasses = true;
253
+ if (o.filter !== undefined) {
254
+ customFilterPasses = o.filter(o);
255
+ }
256
+ if (!customFilterPasses) return false;
257
+
258
+ if (o.divider === true && !includeDividers) return false;
259
+
260
+ return (
261
+ o.includeOnMenus.includes(menuType) &&
262
+ (o.requiresAuth === undefined || o.requiresAuth === isAuthenticated)
263
+ );
264
+ };
265
+
266
+ return menuOptions.filter(MenuFilter);
267
+ },
268
+ [isAuthenticated, menuOptions],
269
+ );
270
+
271
+ useEffect(() => {
272
+ if (menuConfigs.length > 0) {
273
+ return registerMenuOptions(menuConfigs.flatMap(config => config.options));
274
+ }
275
+ return undefined;
276
+ }, [menuConfigs, registerMenuOptions]);
277
+
278
+ const getTopMenus = useCallback(() => {
279
+ const menus: Array<IMenuConfig & { isUserMenu?: boolean }> = [
280
+ ...menuConfigs.map(config => ({ ...config, isUserMenu: false })),
281
+ { menuType: MenuTypes.UserMenu, menuIcon: <AccountCircle />, priority: 0, options: [], isUserMenu: true }
282
+ ];
283
+ return menus.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
284
+ }, [menuConfigs]);
285
+
286
+ const contextValue = useMemo(() => {
287
+ return {
288
+ menuOptions: menuOptions,
289
+ getMenuOptions: getMenuOptions,
290
+ registerMenuOption: registerMenuOption,
291
+ registerMenuOptions: registerMenuOptions,
292
+ getTopMenus: getTopMenus,
293
+ };
294
+ }, [menuOptions, getMenuOptions, registerMenuOption, registerMenuOptions, getTopMenus]);
295
+
296
+ const memoizedChildren = useMemo(() => children, [children]);
297
+ return (
298
+ <MenuContext.Provider value={contextValue}>
299
+ {memoizedChildren}
300
+ </MenuContext.Provider>
301
+ );
302
+ };
303
+
304
+ export const useMenu = (): MenuContextType => {
305
+ const context = useContext(MenuContext);
306
+ if (context === undefined) {
307
+ throw new Error('useMenu must be used within a MenuProvider');
308
+ }
309
+ return context;
310
+ };
@@ -0,0 +1,93 @@
1
+ import { createContext, ReactNode, useContext } from 'react';
2
+
3
+ export interface SuiteConfigRoutes {
4
+ dashboard?: string;
5
+ login?: string;
6
+ register?: string;
7
+ verifyEmail?: string;
8
+ forgotPassword?: string;
9
+ resetPassword?: string;
10
+ settings?: string;
11
+ }
12
+
13
+ export interface SuiteConfigContextData {
14
+ /**
15
+ * Base URL for API calls
16
+ */
17
+ baseUrl: string;
18
+
19
+ /**
20
+ * Application routes for navigation
21
+ */
22
+ routes: SuiteConfigRoutes;
23
+
24
+ /**
25
+ * Available languages for the application
26
+ */
27
+ languages: Array<{ code: string; label: string }>;
28
+
29
+ /**
30
+ * Available timezones
31
+ */
32
+ timezones?: string[];
33
+ }
34
+
35
+ const defaultRoutes: SuiteConfigRoutes = {
36
+ dashboard: '/dashboard',
37
+ login: '/login',
38
+ register: '/register',
39
+ verifyEmail: '/verify-email',
40
+ forgotPassword: '/forgot-password',
41
+ resetPassword: '/reset-password',
42
+ settings: '/settings',
43
+ };
44
+
45
+ const defaultLanguages = [
46
+ { code: 'en-US', label: 'English (US)' },
47
+ { code: 'en-GB', label: 'English (UK)' },
48
+ { code: 'es-ES', label: 'Español' },
49
+ { code: 'fr-FR', label: 'Français' },
50
+ { code: 'de-DE', label: 'Deutsch' },
51
+ { code: 'ja', label: '日本語' },
52
+ { code: 'zh-CN', label: '中文 (简体)' },
53
+ { code: 'uk', label: 'Українська' },
54
+ ];
55
+
56
+ const SuiteConfigContext = createContext<SuiteConfigContextData | undefined>(undefined);
57
+
58
+ export interface SuiteConfigProviderProps {
59
+ children: ReactNode;
60
+ baseUrl: string;
61
+ routes?: Partial<SuiteConfigRoutes>;
62
+ languages?: Array<{ code: string; label: string }>;
63
+ timezones?: string[];
64
+ }
65
+
66
+ export const SuiteConfigProvider = ({
67
+ children,
68
+ baseUrl,
69
+ routes = {},
70
+ languages = defaultLanguages,
71
+ timezones,
72
+ }: SuiteConfigProviderProps) => {
73
+ const value: SuiteConfigContextData = {
74
+ baseUrl,
75
+ routes: { ...defaultRoutes, ...routes },
76
+ languages,
77
+ timezones,
78
+ };
79
+
80
+ return (
81
+ <SuiteConfigContext.Provider value={value}>
82
+ {children}
83
+ </SuiteConfigContext.Provider>
84
+ );
85
+ };
86
+
87
+ export const useSuiteConfig = (): SuiteConfigContextData => {
88
+ const context = useContext(SuiteConfigContext);
89
+ if (!context) {
90
+ throw new Error('useSuiteConfig must be used within a SuiteConfigProvider');
91
+ }
92
+ return context;
93
+ };
@@ -0,0 +1,67 @@
1
+ import { Brightness4, Brightness7 } from '@mui/icons-material';
2
+ import {
3
+ IconButton,
4
+ ThemeProvider as MuiThemeProvider,
5
+ PaletteMode,
6
+ createTheme,
7
+ Theme,
8
+ } from '@mui/material';
9
+ import { FC, ReactNode, createContext, useContext, useMemo, useState } from 'react';
10
+
11
+ export interface ThemeContextType {
12
+ toggleColorMode: () => void;
13
+ setColorMode: (mode: PaletteMode) => void;
14
+ mode: PaletteMode;
15
+ }
16
+
17
+ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
18
+
19
+ export const useTheme = () => {
20
+ const context = useContext(ThemeContext);
21
+ if (!context) {
22
+ throw new Error('useTheme must be used within an AppThemeProvider');
23
+ }
24
+ return context;
25
+ };
26
+
27
+ export interface AppThemeProviderProps {
28
+ children: ReactNode;
29
+ customTheme?: (mode: PaletteMode) => Theme;
30
+ }
31
+
32
+ export const AppThemeProvider: FC<AppThemeProviderProps> = ({ children, customTheme }) => {
33
+ const [mode, setMode] = useState<PaletteMode>('light');
34
+
35
+ const colorMode = useMemo(
36
+ () => ({
37
+ toggleColorMode: () => {
38
+ setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
39
+ },
40
+ setColorMode: (newMode: PaletteMode) => {
41
+ setMode(newMode);
42
+ },
43
+ mode,
44
+ }),
45
+ [mode]
46
+ );
47
+
48
+ const theme = useMemo(
49
+ () => (customTheme ? customTheme(mode) : createTheme({ palette: { mode } })),
50
+ [mode, customTheme]
51
+ );
52
+
53
+ return (
54
+ <ThemeContext.Provider value={colorMode}>
55
+ <MuiThemeProvider theme={theme}>{children}</MuiThemeProvider>
56
+ </ThemeContext.Provider>
57
+ );
58
+ };
59
+
60
+ export const ThemeToggleButton: FC = () => {
61
+ const { mode, toggleColorMode } = useTheme();
62
+ return (
63
+ <IconButton onClick={toggleColorMode} color="inherit">
64
+ {mode === 'dark' ? <Brightness7 /> : <Brightness4 />}
65
+ </IconButton>
66
+ );
67
+ };
@@ -3,4 +3,3 @@ export * from './MenuContext';
3
3
  export * from './I18nProvider';
4
4
  export * from './ThemeProvider';
5
5
  export * from './SuiteConfigProvider';
6
- //# sourceMappingURL=index.d.ts.map
@@ -3,4 +3,3 @@ export * from './useLocalStorage';
3
3
  export * from './useBackupCodes';
4
4
  export * from './useUserSettings';
5
5
  export * from './useEmailVerification';
6
- //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,85 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { createAuthenticatedApiClient } from '../services';
3
+ import { useSuiteConfig } from '../contexts';
4
+ import { SuiteCoreStringKey, TranslatableSuiteError } from '@digitaldefiance/suite-core-lib';
5
+
6
+ export interface UseBackupCodesOptions {
7
+ initialCodeCount?: number | null;
8
+ }
9
+
10
+ export interface UseBackupCodesResult {
11
+ backupCodesRemaining: number | null;
12
+ isLoading: boolean;
13
+ error: Error | null;
14
+ generateBackupCodes: (password?: string, mnemonic?: string) => Promise<{
15
+ message: string;
16
+ backupCodes: string[];
17
+ }>;
18
+ refreshCodeCount: () => Promise<void>;
19
+ }
20
+
21
+ export const useBackupCodes = (
22
+ options: UseBackupCodesOptions = {}
23
+ ): UseBackupCodesResult => {
24
+ const { baseUrl } = useSuiteConfig();
25
+ const [backupCodesRemaining, setBackupCodesRemaining] = useState<number | null>(
26
+ options.initialCodeCount ?? null
27
+ );
28
+ const [isLoading, setIsLoading] = useState(false);
29
+ const [error, setError] = useState<Error | null>(null);
30
+ const requestedOnMountRef = useRef(false);
31
+ const api = useMemo(() => createAuthenticatedApiClient(baseUrl), [baseUrl]);
32
+
33
+ const refreshCodeCount = useCallback(async () => {
34
+ setIsLoading(true);
35
+ setError(null);
36
+ try {
37
+ const result = await api.get('/user/backup-codes');
38
+ if (result?.data?.codeCount !== undefined) {
39
+ setBackupCodesRemaining(result.data.codeCount);
40
+ }
41
+ } catch (err) {
42
+ setError(err instanceof Error ? err : new TranslatableSuiteError(SuiteCoreStringKey.BackupCodes_FailedToFetch));
43
+ setBackupCodesRemaining(0);
44
+ } finally {
45
+ setIsLoading(false);
46
+ }
47
+ }, [api]);
48
+
49
+ useEffect(() => {
50
+ if (requestedOnMountRef.current) return;
51
+ if (backupCodesRemaining !== null) return;
52
+
53
+ requestedOnMountRef.current = true;
54
+ refreshCodeCount();
55
+ }, [backupCodesRemaining, refreshCodeCount]);
56
+
57
+ const generateBackupCodes = async (password?: string, mnemonic?: string) => {
58
+ setIsLoading(true);
59
+ setError(null);
60
+ try {
61
+ const result = await api.post('/user/backup-codes', {
62
+ ...(password ? { password } : {}),
63
+ ...(mnemonic ? { mnemonic } : {}),
64
+ });
65
+ return {
66
+ message: result.data.message,
67
+ backupCodes: result.data.backupCodes,
68
+ };
69
+ } catch (err) {
70
+ const error = err instanceof Error ? err : new TranslatableSuiteError(SuiteCoreStringKey.BackupCodes_FailedToGenerate);
71
+ setError(error);
72
+ throw error;
73
+ } finally {
74
+ setIsLoading(false);
75
+ }
76
+ };
77
+
78
+ return {
79
+ backupCodesRemaining,
80
+ isLoading,
81
+ error,
82
+ generateBackupCodes,
83
+ refreshCodeCount,
84
+ };
85
+ };
@@ -0,0 +1,39 @@
1
+ import { useMemo, useState } from 'react';
2
+ import { createAuthenticatedApiClient } from '../services';
3
+ import { useSuiteConfig } from '../contexts';
4
+ import { getSuiteCoreTranslation, SuiteCoreStringKey } from '@digitaldefiance/suite-core-lib';
5
+
6
+ export interface UseEmailVerificationResult {
7
+ isVerifying: boolean;
8
+ error: Error | null;
9
+ verifyEmail: (token: string) => Promise<{ success: boolean; message?: string }>;
10
+ }
11
+
12
+ export const useEmailVerification = (): UseEmailVerificationResult => {
13
+ const { baseUrl } = useSuiteConfig();
14
+ const api = useMemo(() => createAuthenticatedApiClient(baseUrl), [baseUrl]);
15
+ const [isVerifying, setIsVerifying] = useState(false);
16
+ const [error, setError] = useState<Error | null>(null);
17
+
18
+ const verifyEmail = async (verificationToken: string) => {
19
+ setIsVerifying(true);
20
+ setError(null);
21
+ try {
22
+ const result = await api.post('/verify-email', { token: verificationToken });
23
+ return { success: true, message: result.data.message };
24
+ } catch (err: any) {
25
+ const errorMessage = err.response?.data?.message || getSuiteCoreTranslation(SuiteCoreStringKey.Error_VerificationFailed);
26
+ const error = new Error(errorMessage);
27
+ setError(error);
28
+ return { success: false, message: errorMessage };
29
+ } finally {
30
+ setIsVerifying(false);
31
+ }
32
+ };
33
+
34
+ return {
35
+ isVerifying,
36
+ error,
37
+ verifyEmail,
38
+ };
39
+ };