@rebasepro/app 0.0.1-canary.4829d6e

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 (268) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/auth/api.d.ts +66 -0
  4. package/dist/auth/index.d.ts +13 -0
  5. package/dist/auth/types.d.ts +127 -0
  6. package/dist/auth/useRebaseAuthController.d.ts +13 -0
  7. package/dist/components/AIIcon.d.ts +17 -0
  8. package/dist/components/AuthSimulationMenu.d.ts +0 -0
  9. package/dist/components/ConfirmationDialog.d.ts +9 -0
  10. package/dist/components/Debug/UIReferenceView.d.ts +13 -0
  11. package/dist/components/Debug/UIStyleGuide.d.ts +2 -0
  12. package/dist/components/Debug/collection-views/CardViewDemo.d.ts +2 -0
  13. package/dist/components/Debug/collection-views/CollectionTableDemo.d.ts +2 -0
  14. package/dist/components/Debug/collection-views/KanbanBoardDemo.d.ts +2 -0
  15. package/dist/components/Debug/collection-views/index.d.ts +3 -0
  16. package/dist/components/Debug/collection-views/sample_data.d.ts +23 -0
  17. package/dist/components/Debug/crm-dashboard/CalendarWidget.d.ts +22 -0
  18. package/dist/components/Debug/crm-dashboard/CrmDashboardDemo.d.ts +2 -0
  19. package/dist/components/Debug/crm-dashboard/DashboardMetrics.d.ts +15 -0
  20. package/dist/components/Debug/crm-dashboard/PipelineOverview.d.ts +34 -0
  21. package/dist/components/Debug/crm-dashboard/RecentActivity.d.ts +19 -0
  22. package/dist/components/Debug/crm-dashboard/TaskFilters.d.ts +30 -0
  23. package/dist/components/Debug/crm-dashboard/TaskTable.d.ts +50 -0
  24. package/dist/components/Debug/crm-dashboard/TasksView.d.ts +41 -0
  25. package/dist/components/ErrorTooltip.d.ts +3 -0
  26. package/dist/components/ErrorView.d.ts +21 -0
  27. package/dist/components/LanguageToggle.d.ts +2 -0
  28. package/dist/components/LoginView/LoginView.d.ts +113 -0
  29. package/dist/components/LoginView/index.d.ts +2 -0
  30. package/dist/components/NotFoundPage.d.ts +2 -0
  31. package/dist/components/RebaseAuth.d.ts +10 -0
  32. package/dist/components/RebaseLogo.d.ts +7 -0
  33. package/dist/components/SchemaDriftBanner.d.ts +27 -0
  34. package/dist/components/UnsavedChangesDialog.d.ts +9 -0
  35. package/dist/components/UserDisplay.d.ts +7 -0
  36. package/dist/components/UserSelectPopover.d.ts +63 -0
  37. package/dist/components/UserSettingsView.d.ts +2 -0
  38. package/dist/components/common/index.d.ts +6 -0
  39. package/dist/components/common/table_height.d.ts +5 -0
  40. package/dist/components/common/types.d.ts +66 -0
  41. package/dist/components/common/useColumnsIds.d.ts +9 -0
  42. package/dist/components/common/useDataTableController.d.ts +45 -0
  43. package/dist/components/common/useDebouncedData.d.ts +9 -0
  44. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  45. package/dist/components/index.d.ts +18 -0
  46. package/dist/contexts/AdminModeController.d.ts +4 -0
  47. package/dist/contexts/AnalyticsContext.d.ts +3 -0
  48. package/dist/contexts/AuthControllerContext.d.ts +3 -0
  49. package/dist/contexts/CollectionScopeContext.d.ts +50 -0
  50. package/dist/contexts/ComponentOverrideContext.d.ts +42 -0
  51. package/dist/contexts/CustomizationControllerContext.d.ts +3 -0
  52. package/dist/contexts/DataDriverContext.d.ts +3 -0
  53. package/dist/contexts/DataSourcesContext.d.ts +31 -0
  54. package/dist/contexts/DatabaseAdminContext.d.ts +3 -0
  55. package/dist/contexts/DialogsProvider.d.ts +4 -0
  56. package/dist/contexts/EffectiveRoleController.d.ts +4 -0
  57. package/dist/contexts/ModeController.d.ts +4 -0
  58. package/dist/contexts/RebaseClientInstanceContext.d.ts +6 -0
  59. package/dist/contexts/RebaseDataContext.d.ts +3 -0
  60. package/dist/contexts/SnackbarProvider.d.ts +2 -0
  61. package/dist/contexts/StorageSourceContext.d.ts +3 -0
  62. package/dist/contexts/StorageSourcesContext.d.ts +25 -0
  63. package/dist/contexts/UserConfigurationPersistenceContext.d.ts +3 -0
  64. package/dist/contexts/index.d.ts +17 -0
  65. package/dist/core/PluginLifecycleManager.d.ts +17 -0
  66. package/dist/core/PluginProviderStack.d.ts +21 -0
  67. package/dist/core/Rebase.d.ts +15 -0
  68. package/dist/core/RebaseProps.d.ts +291 -0
  69. package/dist/core/RebaseRouter.d.ts +4 -0
  70. package/dist/core/RebaseRoutes.d.ts +17 -0
  71. package/dist/core/index.d.ts +4 -0
  72. package/dist/hooks/ApiConfigContext.d.ts +24 -0
  73. package/dist/hooks/data/delete.d.ts +33 -0
  74. package/dist/hooks/data/save.d.ts +40 -0
  75. package/dist/hooks/data/useCollection.d.ts +62 -0
  76. package/dist/hooks/data/useData.d.ts +13 -0
  77. package/dist/hooks/data/useDataOrder.d.ts +12 -0
  78. package/dist/hooks/data/useFetch.d.ts +43 -0
  79. package/dist/hooks/data/useRelationSelector.d.ts +52 -0
  80. package/dist/hooks/index.d.ts +36 -0
  81. package/dist/hooks/useAdminModeController.d.ts +19 -0
  82. package/dist/hooks/useAnalyticsController.d.ts +5 -0
  83. package/dist/hooks/useAuthController.d.ts +11 -0
  84. package/dist/hooks/useAuthSubscription.d.ts +2 -0
  85. package/dist/hooks/useBackendStorageSource.d.ts +30 -0
  86. package/dist/hooks/useBridgeRegistration.d.ts +18 -0
  87. package/dist/hooks/useBrowserTitleAndIcon.d.ts +6 -0
  88. package/dist/hooks/useBuildAdminModeController.d.ts +6 -0
  89. package/dist/hooks/useBuildEffectiveRoleController.d.ts +8 -0
  90. package/dist/hooks/useBuildLocalConfigurationPersistence.d.ts +2 -0
  91. package/dist/hooks/useBuildModeController.d.ts +6 -0
  92. package/dist/hooks/useClipboard.d.ts +57 -0
  93. package/dist/hooks/useCollapsedGroups.d.ts +27 -0
  94. package/dist/hooks/useComponentOverride.d.ts +32 -0
  95. package/dist/hooks/useCustomizationController.d.ts +11 -0
  96. package/dist/hooks/useDialogsController.d.ts +11 -0
  97. package/dist/hooks/useEffectiveRoleController.d.ts +7 -0
  98. package/dist/hooks/useLargeLayout.d.ts +1 -0
  99. package/dist/hooks/useModeController.d.ts +19 -0
  100. package/dist/hooks/usePermissions.d.ts +12 -0
  101. package/dist/hooks/useRebaseClient.d.ts +5 -0
  102. package/dist/hooks/useRebaseContext.d.ts +11 -0
  103. package/dist/hooks/useRebaseRegistry.d.ts +34 -0
  104. package/dist/hooks/useResolvedComponent.d.ts +47 -0
  105. package/dist/hooks/useSlot.d.ts +18 -0
  106. package/dist/hooks/useSnackbarController.d.ts +20 -0
  107. package/dist/hooks/useStorageSource.d.ts +6 -0
  108. package/dist/hooks/useStudioBridge.d.ts +79 -0
  109. package/dist/hooks/useTranslation.d.ts +17 -0
  110. package/dist/hooks/useUnsavedChangesDialog.d.ts +12 -0
  111. package/dist/hooks/useUserConfigurationPersistence.d.ts +8 -0
  112. package/dist/i18n/RebaseI18nProvider.d.ts +33 -0
  113. package/dist/index.d.ts +16 -0
  114. package/dist/index.es.js +16843 -0
  115. package/dist/index.es.js.map +1 -0
  116. package/dist/index.umd.js +17021 -0
  117. package/dist/index.umd.js.map +1 -0
  118. package/dist/internal/common.d.ts +14 -0
  119. package/dist/internal/useRestoreScroll.d.ts +15 -0
  120. package/dist/locales/de.d.ts +2 -0
  121. package/dist/locales/en.d.ts +10 -0
  122. package/dist/locales/es.d.ts +10 -0
  123. package/dist/locales/fr.d.ts +2 -0
  124. package/dist/locales/hi.d.ts +2 -0
  125. package/dist/locales/it.d.ts +2 -0
  126. package/dist/locales/pt.d.ts +7 -0
  127. package/dist/util/constants.d.ts +1 -0
  128. package/dist/util/createFormexStub.d.ts +2 -0
  129. package/dist/util/entity_cache.d.ts +22 -0
  130. package/dist/util/enums.d.ts +5 -0
  131. package/dist/util/icon_list.d.ts +5 -0
  132. package/dist/util/icons.d.ts +20 -0
  133. package/dist/util/index.d.ts +8 -0
  134. package/dist/util/previews.d.ts +4 -0
  135. package/dist/util/useStorageUploadController.d.ts +44 -0
  136. package/dist/vitePlugin.d.ts +60 -0
  137. package/dist/vitePlugin.js +113 -0
  138. package/package.json +145 -0
  139. package/src/auth/api.ts +148 -0
  140. package/src/auth/index.ts +25 -0
  141. package/src/auth/types.ts +115 -0
  142. package/src/auth/useRebaseAuthController.ts +328 -0
  143. package/src/components/AIIcon.tsx +47 -0
  144. package/src/components/AuthSimulationMenu.tsx +0 -0
  145. package/src/components/ConfirmationDialog.tsx +48 -0
  146. package/src/components/Debug/UIReferenceView.tsx +958 -0
  147. package/src/components/Debug/UIStyleGuide.tsx +156 -0
  148. package/src/components/Debug/collection-views/CardViewDemo.tsx +85 -0
  149. package/src/components/Debug/collection-views/CollectionTableDemo.tsx +107 -0
  150. package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +90 -0
  151. package/src/components/Debug/collection-views/index.ts +3 -0
  152. package/src/components/Debug/collection-views/sample_data.ts +42 -0
  153. package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +389 -0
  154. package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +1102 -0
  155. package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +133 -0
  156. package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +290 -0
  157. package/src/components/Debug/crm-dashboard/RecentActivity.tsx +215 -0
  158. package/src/components/Debug/crm-dashboard/TaskFilters.tsx +184 -0
  159. package/src/components/Debug/crm-dashboard/TaskTable.tsx +427 -0
  160. package/src/components/Debug/crm-dashboard/TasksView.tsx +234 -0
  161. package/src/components/ErrorTooltip.tsx +12 -0
  162. package/src/components/ErrorView.tsx +94 -0
  163. package/src/components/LanguageToggle.tsx +59 -0
  164. package/src/components/LoginView/LoginView.tsx +932 -0
  165. package/src/components/LoginView/index.ts +2 -0
  166. package/src/components/NotFoundPage.tsx +27 -0
  167. package/src/components/RebaseAuth.tsx +27 -0
  168. package/src/components/RebaseLogo.tsx +29 -0
  169. package/src/components/SchemaDriftBanner.tsx +102 -0
  170. package/src/components/UnsavedChangesDialog.tsx +46 -0
  171. package/src/components/UserDisplay.tsx +54 -0
  172. package/src/components/UserSelectPopover.tsx +399 -0
  173. package/src/components/UserSettingsView.tsx +318 -0
  174. package/src/components/common/index.ts +6 -0
  175. package/src/components/common/table_height.tsx +21 -0
  176. package/src/components/common/types.tsx +66 -0
  177. package/src/components/common/useColumnsIds.tsx +222 -0
  178. package/src/components/common/useDataTableController.tsx +514 -0
  179. package/src/components/common/useDebouncedData.ts +49 -0
  180. package/src/components/common/useScrollRestoration.tsx +68 -0
  181. package/src/components/index.tsx +26 -0
  182. package/src/contexts/AdminModeController.tsx +11 -0
  183. package/src/contexts/AnalyticsContext.tsx +4 -0
  184. package/src/contexts/AuthControllerContext.tsx +4 -0
  185. package/src/contexts/CollectionScopeContext.tsx +67 -0
  186. package/src/contexts/ComponentOverrideContext.tsx +81 -0
  187. package/src/contexts/CustomizationControllerContext.tsx +4 -0
  188. package/src/contexts/DataDriverContext.tsx +4 -0
  189. package/src/contexts/DataSourcesContext.tsx +36 -0
  190. package/src/contexts/DatabaseAdminContext.tsx +4 -0
  191. package/src/contexts/DialogsProvider.tsx +56 -0
  192. package/src/contexts/EffectiveRoleController.tsx +12 -0
  193. package/src/contexts/ModeController.tsx +11 -0
  194. package/src/contexts/RebaseClientInstanceContext.tsx +7 -0
  195. package/src/contexts/RebaseDataContext.tsx +4 -0
  196. package/src/contexts/SnackbarProvider.tsx +15 -0
  197. package/src/contexts/StorageSourceContext.tsx +4 -0
  198. package/src/contexts/StorageSourcesContext.tsx +30 -0
  199. package/src/contexts/UserConfigurationPersistenceContext.tsx +4 -0
  200. package/src/contexts/index.ts +17 -0
  201. package/src/core/PluginLifecycleManager.tsx +95 -0
  202. package/src/core/PluginProviderStack.tsx +40 -0
  203. package/src/core/Rebase.tsx +439 -0
  204. package/src/core/RebaseProps.tsx +320 -0
  205. package/src/core/RebaseRouter.tsx +17 -0
  206. package/src/core/RebaseRoutes.tsx +24 -0
  207. package/src/core/index.tsx +5 -0
  208. package/src/hooks/ApiConfigContext.tsx +41 -0
  209. package/src/hooks/data/delete.ts +55 -0
  210. package/src/hooks/data/save.ts +77 -0
  211. package/src/hooks/data/useCollection.tsx +176 -0
  212. package/src/hooks/data/useData.tsx +18 -0
  213. package/src/hooks/data/useDataOrder.ts +26 -0
  214. package/src/hooks/data/useFetch.tsx +140 -0
  215. package/src/hooks/data/useRelationSelector.tsx +234 -0
  216. package/src/hooks/index.tsx +45 -0
  217. package/src/hooks/useAdminModeController.tsx +23 -0
  218. package/src/hooks/useAnalyticsController.tsx +8 -0
  219. package/src/hooks/useAuthController.tsx +14 -0
  220. package/src/hooks/useAuthSubscription.ts +87 -0
  221. package/src/hooks/useBackendStorageSource.ts +288 -0
  222. package/src/hooks/useBridgeRegistration.tsx +32 -0
  223. package/src/hooks/useBrowserTitleAndIcon.tsx +23 -0
  224. package/src/hooks/useBuildAdminModeController.tsx +24 -0
  225. package/src/hooks/useBuildEffectiveRoleController.tsx +31 -0
  226. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +75 -0
  227. package/src/hooks/useBuildModeController.tsx +71 -0
  228. package/src/hooks/useClipboard.tsx +158 -0
  229. package/src/hooks/useCollapsedGroups.ts +116 -0
  230. package/src/hooks/useComponentOverride.tsx +59 -0
  231. package/src/hooks/useCustomizationController.tsx +14 -0
  232. package/src/hooks/useDialogsController.tsx +15 -0
  233. package/src/hooks/useEffectiveRoleController.tsx +10 -0
  234. package/src/hooks/useLargeLayout.tsx +65 -0
  235. package/src/hooks/useModeController.tsx +23 -0
  236. package/src/hooks/usePermissions.ts +44 -0
  237. package/src/hooks/useRebaseClient.tsx +10 -0
  238. package/src/hooks/useRebaseContext.tsx +83 -0
  239. package/src/hooks/useRebaseRegistry.tsx +91 -0
  240. package/src/hooks/useResolvedComponent.tsx +159 -0
  241. package/src/hooks/useSlot.tsx +64 -0
  242. package/src/hooks/useSnackbarController.tsx +52 -0
  243. package/src/hooks/useStorageSource.tsx +11 -0
  244. package/src/hooks/useStudioBridge.tsx +209 -0
  245. package/src/hooks/useTranslation.ts +33 -0
  246. package/src/hooks/useUnsavedChangesDialog.tsx +64 -0
  247. package/src/hooks/useUserConfigurationPersistence.tsx +11 -0
  248. package/src/i18n/RebaseI18nProvider.tsx +160 -0
  249. package/src/index.ts +25 -0
  250. package/src/internal/common.tsx +16 -0
  251. package/src/internal/useRestoreScroll.tsx +69 -0
  252. package/src/locales/de.ts +902 -0
  253. package/src/locales/en.ts +980 -0
  254. package/src/locales/es.ts +922 -0
  255. package/src/locales/fr.ts +901 -0
  256. package/src/locales/hi.ts +901 -0
  257. package/src/locales/it.ts +901 -0
  258. package/src/locales/pt.ts +909 -0
  259. package/src/util/constants.ts +7 -0
  260. package/src/util/createFormexStub.tsx +66 -0
  261. package/src/util/entity_cache.ts +239 -0
  262. package/src/util/enums.ts +32 -0
  263. package/src/util/icon_list.ts +22 -0
  264. package/src/util/icons.tsx +149 -0
  265. package/src/util/index.ts +12 -0
  266. package/src/util/previews.ts +105 -0
  267. package/src/util/useStorageUploadController.tsx +350 -0
  268. package/src/vitePlugin.ts +196 -0
@@ -0,0 +1,318 @@
1
+
2
+ import React, { useEffect, useState } from "react";
3
+ import {
4
+ Avatar,
5
+ Button,
6
+ Card,
7
+ Chip,
8
+ CircularProgress,
9
+ cls,
10
+ IconButton,
11
+ Tab,
12
+ Tabs,
13
+ TextField,
14
+ Trash2Icon,
15
+ Typography
16
+ } from "@rebasepro/ui";
17
+ import { useAuthController, useTranslation } from "../hooks";
18
+
19
+ interface SessionInfo {
20
+ id: string;
21
+ userAgent?: string;
22
+ ipAddress?: string;
23
+ createdAt: string;
24
+ isCurrentSession?: boolean;
25
+ }
26
+
27
+ interface ExtendedAuthController {
28
+ user: { displayName?: string | null; photoURL?: string | null; email?: string | null } | null;
29
+ updateProfile?: (displayName: string, photoURL: string) => Promise<void>;
30
+ changePassword?: (oldPassword: string, newPassword: string) => Promise<void>;
31
+ fetchSessions?: () => Promise<SessionInfo[]>;
32
+ revokeSession?: (id: string) => Promise<void>;
33
+ revokeAllSessions?: () => Promise<void>;
34
+ signOut: () => Promise<void>;
35
+ }
36
+
37
+ type ActiveTab = "profile" | "security" | "sessions";
38
+
39
+ export function UserSettingsView() {
40
+ const authController = useAuthController() as ExtendedAuthController;
41
+ const user = authController.user;
42
+ const { t } = useTranslation();
43
+
44
+ const hasPasswordChange = !!authController.changePassword;
45
+ const [activeTab, setActiveTab] = useState<ActiveTab>("profile");
46
+
47
+ // Profile state
48
+ const [displayName, setDisplayName] = useState(user?.displayName || "");
49
+ const [photoURL, setPhotoURL] = useState(user?.photoURL || "");
50
+ const [savingProfile, setSavingProfile] = useState(false);
51
+ const [profileError, setProfileError] = useState<string | null>(null);
52
+
53
+ // Password change state
54
+ const [currentPassword, setCurrentPassword] = useState("");
55
+ const [newPassword, setNewPassword] = useState("");
56
+ const [confirmPassword, setConfirmPassword] = useState("");
57
+ const [changingPassword, setChangingPassword] = useState(false);
58
+ const [passwordError, setPasswordError] = useState<string | null>(null);
59
+ const [passwordSuccess, setPasswordSuccess] = useState<string | null>(null);
60
+
61
+ // Sessions state
62
+ const [sessions, setSessions] = useState<SessionInfo[]>([]);
63
+ const [loadingSessions, setLoadingSessions] = useState(false);
64
+ const [sessionsError, setSessionsError] = useState<string | null>(null);
65
+ const [revokingSessionId, setRevokingSessionId] = useState<string | null>(null);
66
+ const [revokingAll, setRevokingAll] = useState(false);
67
+
68
+ useEffect(() => {
69
+ setDisplayName(user?.displayName || "");
70
+ setPhotoURL(user?.photoURL || "");
71
+ if (activeTab === "sessions") {
72
+ loadSessions();
73
+ }
74
+ }, [activeTab, user]);
75
+
76
+ const handleSaveProfile = async () => {
77
+ setSavingProfile(true);
78
+ setProfileError(null);
79
+ try {
80
+ if (authController.updateProfile) {
81
+ await authController.updateProfile(displayName, photoURL);
82
+ } else {
83
+ throw new Error("updateProfile not implemented in this auth controller.");
84
+ }
85
+ } catch (e: unknown) {
86
+ setProfileError(e instanceof Error ? e.message : String(e));
87
+ } finally {
88
+ setSavingProfile(false);
89
+ }
90
+ };
91
+
92
+ const handleChangePassword = async () => {
93
+ setPasswordError(null);
94
+ setPasswordSuccess(null);
95
+
96
+ // Validate
97
+ if (newPassword.length < 8) {
98
+ setPasswordError(t("password_too_short"));
99
+ return;
100
+ }
101
+ if (newPassword !== confirmPassword) {
102
+ setPasswordError(t("passwords_dont_match"));
103
+ return;
104
+ }
105
+
106
+ setChangingPassword(true);
107
+ try {
108
+ if (authController.changePassword) {
109
+ await authController.changePassword(currentPassword, newPassword);
110
+ setPasswordSuccess(t("password_changed"));
111
+ setCurrentPassword("");
112
+ setNewPassword("");
113
+ setConfirmPassword("");
114
+ // Backend invalidates all sessions on password change,
115
+ // so the user will be logged out shortly
116
+ setTimeout(() => {
117
+ authController.signOut();
118
+ }, 2000);
119
+ }
120
+ } catch (e: unknown) {
121
+ setPasswordError(e instanceof Error ? e.message : String(e));
122
+ } finally {
123
+ setChangingPassword(false);
124
+ }
125
+ };
126
+
127
+ const loadSessions = async () => {
128
+ setLoadingSessions(true);
129
+ setSessionsError(null);
130
+ try {
131
+ if (authController.fetchSessions) {
132
+ const fetchedSessions = await authController.fetchSessions();
133
+ const sortedSessions = (fetchedSessions || []).sort((a: SessionInfo, b: SessionInfo) =>
134
+ new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
135
+ );
136
+ setSessions(sortedSessions);
137
+ } else {
138
+ throw new Error("fetchSessions not implemented in this auth controller.");
139
+ }
140
+ } catch (e: unknown) {
141
+ setSessionsError(e instanceof Error ? e.message : String(e));
142
+ } finally {
143
+ setLoadingSessions(false);
144
+ }
145
+ };
146
+
147
+ const handleRevokeSession = async (id: string, isCurrentSession?: boolean) => {
148
+ setRevokingSessionId(id);
149
+ try {
150
+ if (authController.revokeSession) {
151
+ await authController.revokeSession(id);
152
+ setSessions(sessions.filter(s => s.id !== id));
153
+ if (isCurrentSession) {
154
+ await authController.signOut();
155
+ }
156
+ } else {
157
+ throw new Error("revokeSession not implemented in this auth controller.");
158
+ }
159
+ } catch (e: unknown) {
160
+ setSessionsError(e instanceof Error ? e.message : String(e));
161
+ } finally {
162
+ setRevokingSessionId(null);
163
+ }
164
+ };
165
+
166
+ const handleRevokeAll = async () => {
167
+ setRevokingAll(true);
168
+ try {
169
+ if (authController.revokeAllSessions) {
170
+ await authController.revokeAllSessions();
171
+ } else {
172
+ throw new Error("revokeAllSessions not implemented in this auth controller.");
173
+ }
174
+ } catch (e: unknown) {
175
+ setSessionsError(e instanceof Error ? e.message : String(e));
176
+ } finally {
177
+ setRevokingAll(false);
178
+ }
179
+ };
180
+
181
+ if (!user) return null;
182
+
183
+ return (
184
+ <div className="flex-grow max-w-4xl w-full mx-auto p-4 sm:p-6 md:p-12">
185
+ <Typography variant="h4" className="mb-8">{t("account_settings")}</Typography>
186
+
187
+ <Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as ActiveTab)} className="mb-8">
188
+ <Tab value="profile">{t("profile")}</Tab>
189
+ {hasPasswordChange && <Tab value="security">{t("security")}</Tab>}
190
+ <Tab value="sessions">{t("sessions")}</Tab>
191
+ </Tabs>
192
+
193
+ {activeTab === "profile" && (
194
+ <div className="flex flex-col gap-6 max-w-xl">
195
+ <div className="flex items-center gap-6 mb-2">
196
+ <Avatar src={photoURL || undefined} className="w-24 h-24 text-3xl">
197
+ {displayName ? displayName[0].toUpperCase() : (user.email ? user.email[0].toUpperCase() : "A")}
198
+ </Avatar>
199
+ </div>
200
+ <TextField
201
+ label={t("display_name")}
202
+ value={displayName}
203
+ onChange={(e) => setDisplayName(e.target.value)}
204
+ />
205
+ <TextField
206
+ label={t("photo_url")}
207
+ value={photoURL}
208
+ onChange={(e) => setPhotoURL(e.target.value)}
209
+ />
210
+ {profileError && <Typography color="error">{profileError}</Typography>}
211
+ <div className="mt-4">
212
+ <Button variant="filled" onClick={handleSaveProfile} disabled={savingProfile}>
213
+ {savingProfile ? t("saving") : t("save_profile")}
214
+ </Button>
215
+ </div>
216
+ </div>
217
+ )}
218
+
219
+ {activeTab === "security" && hasPasswordChange && (
220
+ <div className="flex flex-col gap-6 max-w-xl">
221
+ <Typography variant="h6" className="mb-2">{t("change_password")}</Typography>
222
+
223
+ <TextField
224
+ label={t("current_password")}
225
+ type="password"
226
+ value={currentPassword}
227
+ onChange={(e) => setCurrentPassword(e.target.value)}
228
+ autoComplete="current-password"
229
+ />
230
+ <TextField
231
+ label={t("new_password")}
232
+ type="password"
233
+ value={newPassword}
234
+ onChange={(e) => setNewPassword(e.target.value)}
235
+ autoComplete="new-password"
236
+ />
237
+ <TextField
238
+ label={t("confirm_password")}
239
+ type="password"
240
+ value={confirmPassword}
241
+ onChange={(e) => setConfirmPassword(e.target.value)}
242
+ autoComplete="new-password"
243
+ />
244
+
245
+ {passwordError && (
246
+ <Typography color="error">{passwordError}</Typography>
247
+ )}
248
+ {passwordSuccess && (
249
+ <Typography className="text-emerald-600 dark:text-emerald-400">{passwordSuccess}</Typography>
250
+ )}
251
+
252
+ <div className="mt-4">
253
+ <Button
254
+ variant="filled"
255
+ onClick={handleChangePassword}
256
+ disabled={changingPassword || !currentPassword || !newPassword || !confirmPassword}
257
+ >
258
+ {changingPassword ? t("changing_password") : t("change_password")}
259
+ </Button>
260
+ </div>
261
+ </div>
262
+ )}
263
+
264
+ {activeTab === "sessions" && (
265
+ <div className="flex flex-col gap-4 max-w-3xl">
266
+ {loadingSessions ? (
267
+ <div className="flex justify-center p-8"><CircularProgress/></div>
268
+ ) : sessionsError ? (
269
+ <Typography color="error">{sessionsError}</Typography>
270
+ ) : sessions.length === 0 ? (
271
+ <Typography>{t("no_active_sessions")}</Typography>
272
+ ) : (
273
+ <div className="flex flex-col gap-4">
274
+ <div className="flex justify-end mb-2">
275
+ <Button
276
+ variant="text"
277
+ color="error"
278
+ onClick={handleRevokeAll}
279
+ disabled={revokingAll}
280
+ >
281
+ {revokingAll ? t("revoking") : t("revoke_all_sessions")}
282
+ </Button>
283
+ </div>
284
+ {sessions.map(session => (
285
+ <Card key={session.id} className="flex justify-between items-center p-4">
286
+ <div className="flex flex-col">
287
+ <div className="flex items-center gap-2 mb-1">
288
+ <Typography variant="body1">
289
+ {session.userAgent || t("unknown_device")}
290
+ </Typography>
291
+ {session.isCurrentSession && (
292
+ <Chip colorScheme="blue" size="small">
293
+ {t("current")}
294
+ </Chip>
295
+ )}
296
+ </div>
297
+ <Typography variant="caption" color="secondary">
298
+ IP: {session.ipAddress || "Unknown"} • Created: {new Date(session.createdAt).toLocaleString()}
299
+ </Typography>
300
+ </div>
301
+ <div className="ml-4">
302
+ {revokingSessionId === session.id ? (
303
+ <CircularProgress size="small"/>
304
+ ) : (
305
+ <IconButton onClick={() => handleRevokeSession(session.id, session.isCurrentSession)} aria-label="Revoke Session">
306
+ <Trash2Icon/>
307
+ </IconButton>
308
+ )}
309
+ </div>
310
+ </Card>
311
+ ))}
312
+ </div>
313
+ )}
314
+ </div>
315
+ )}
316
+ </div>
317
+ );
318
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./types";
2
+ export * from "./useDebouncedData";
3
+ export * from "./useColumnsIds";
4
+ export * from "./useDataTableController";
5
+ export * from "./useScrollRestoration";
6
+ export * from "./table_height";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @group Components
3
+ */
4
+ export type TableSize = "xs" | "s" | "m" | "l" | "xl";
5
+
6
+ export function getRowHeight(size: TableSize): number {
7
+ switch (size) {
8
+ case "xl":
9
+ return 400;
10
+ case "l":
11
+ return 280;
12
+ case "m":
13
+ return 140;
14
+ case "s":
15
+ return 80;
16
+ case "xs":
17
+ return 54;
18
+ default:
19
+ throw Error("Missing mapping for collection size -> height");
20
+ }
21
+ }
@@ -0,0 +1,66 @@
1
+ import type { Property, Entity } from "@rebasepro/types";
2
+ import { CollectionSize, SelectedCellProps } from "@rebasepro/types";
3
+
4
+ export type DataCollectionTableController<M extends Record<string, unknown>> = {
5
+
6
+ /**
7
+ * This cell is displayed as selected
8
+ */
9
+ selectedCell?: SelectedCellProps;
10
+ /**
11
+ * Store used to sync selection state across cells efficiently.
12
+ */
13
+ selectionStore?: { getEntity: () => SelectedCellProps | undefined; subscribe: (cb: () => void) => () => void };
14
+ /**
15
+ * Select a table cell
16
+ * @param cell
17
+ */
18
+ select: (cell?: SelectedCellProps<M>) => void;
19
+ /**
20
+ * The cell that is displayed as a popup view.
21
+ * @param cell
22
+ */
23
+ setPopupCell?: (cell?: SelectedCellProps<M>) => void;
24
+ /**
25
+ * Callback used when the value of a cell has changed.
26
+ * @param params
27
+ */
28
+ onValueChange?: (params: OnCellValueChangeParams<unknown, Entity<M>>) => void;
29
+ /**
30
+ * Size of the elements in the collection
31
+ */
32
+ size: CollectionSize;
33
+ }
34
+
35
+ /**
36
+ * Props passed in a callback when the content of a cell in a table has been edited
37
+ * @group Collection components
38
+ */
39
+ export interface OnCellValueChangeParams<T = unknown, D = unknown> {
40
+ value: T,
41
+ propertyKey: string,
42
+ data?: D,
43
+ onValueUpdated: () => void
44
+ setError: (e: Error | undefined) => void
45
+ }
46
+
47
+ /**
48
+ * @group Collection components
49
+ */
50
+ export type UniqueFieldValidator = (props: {
51
+ name: string,
52
+ value: unknown,
53
+ property: Property,
54
+ entityId?: string | number
55
+ }) => Promise<boolean>;
56
+
57
+ /**
58
+ * Callback when a cell has changed in a table
59
+ * @group Collection components
60
+ */
61
+ export type OnCellValueChange<T, M extends Record<string, unknown>> = (params: OnCellValueChangeParams<T, Entity<M>>) => Promise<void> | void;
62
+
63
+ /**
64
+ * @group Collection components
65
+ */
66
+ export type OnColumnResizeParams = { width: number, key: string };
@@ -0,0 +1,222 @@
1
+ import type { CollectionConfig, Property, MapProperty, Properties } from "@rebasepro/types";
2
+ import { useMemo } from "react";
3
+ ;
4
+ import { getSubcollections } from "@rebasepro/common";
5
+
6
+ /**
7
+ * Get a property in a property tree from a dot-path like `address.street`.
8
+ * Inlined here to avoid importing property-aware utilities from the CMS layer.
9
+ */
10
+ function getPropertyInPath(properties: Properties, path: string): Property | undefined {
11
+ if (typeof properties === "object") {
12
+ if (path in properties) {
13
+ return (properties as Record<string, Property>)[path];
14
+ }
15
+ if (path.includes(".")) {
16
+ const pathSegments = path.split(".");
17
+ const childProperty = (properties as Record<string, Property>)[pathSegments[0]];
18
+ if (typeof childProperty === "object" && childProperty.type === "map" && childProperty.properties) {
19
+ return getPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
20
+ }
21
+ }
22
+ }
23
+ return undefined;
24
+ }
25
+
26
+ export type PropertyColumnConfig = {
27
+ key: string;
28
+ disabled: boolean;
29
+ };
30
+
31
+ export function getSubcollectionColumnId(collection: CollectionConfig<any>) {
32
+ return `subcollection:${collection.slug}`;
33
+ }
34
+ export function useColumnIds<M extends Record<string, any>>(collection: CollectionConfig<M>, includeSubcollections: boolean): PropertyColumnConfig[] {
35
+ return useMemo(() => {
36
+ if (collection.propertiesOrder) {
37
+ return hideAndExpandKeys(collection, collection.propertiesOrder);
38
+ }
39
+ return getDefaultColumnKeys(collection, includeSubcollections);
40
+ }, [collection, includeSubcollections]);
41
+ }
42
+
43
+ function hideAndExpandKeys<M extends Record<string, any>>(collection: CollectionConfig<M>, keys: string[]): PropertyColumnConfig[] {
44
+
45
+ // First, figure out which spread map roots have individual child keys in the order
46
+ // If so, we should NOT auto-expand them - just use the explicit child keys
47
+ const rootsWithExplicitChildren = new Set<string>();
48
+ for (const key of keys) {
49
+ if (key.includes(".")) {
50
+ const rootKey = key.split(".")[0];
51
+ const rootProperty = collection.properties[rootKey];
52
+ if (rootProperty && rootProperty.type === "map" && rootProperty.ui?.spreadChildren && rootProperty.properties) {
53
+ rootsWithExplicitChildren.add(rootKey);
54
+ }
55
+ }
56
+ }
57
+
58
+ // Track processed keys to avoid duplicates
59
+ const processedPropertyKeys = new Set<string>();
60
+
61
+ const result = keys.flatMap((key) => {
62
+ // Skip if already processed (handles duplicates in propertiesOrder)
63
+ if (processedPropertyKeys.has(key)) return [null];
64
+
65
+ // Check if it's a top-level property
66
+ const property = collection.properties[key];
67
+ if (property) {
68
+ processedPropertyKeys.add(key);
69
+ if (property.ui?.hideFromCollection)
70
+ return [null];
71
+ if (property.ui?.disabled && typeof property.ui?.disabled === "object" && property.ui?.disabled.hidden)
72
+ return [null];
73
+
74
+ if (property.type === "map" && property.ui?.spreadChildren && property.properties) {
75
+ // Check if this spread map has explicit child keys in propertiesOrder
76
+ if (rootsWithExplicitChildren.has(key)) {
77
+ // DON'T auto-expand - the children are explicitly listed elsewhere
78
+ return [null];
79
+ }
80
+ // Auto-expand all children
81
+ const childConfigs = getColumnKeysForProperty(property, key);
82
+ childConfigs.forEach(c => processedPropertyKeys.add(c.key));
83
+ return childConfigs;
84
+ }
85
+ return [{
86
+ key,
87
+ disabled: Boolean(property.ui?.disabled) || Boolean(property.ui?.readOnly)
88
+ }];
89
+ }
90
+
91
+ // Check if it's a nested key like "data.mode" (for spread map properties)
92
+ if (key.includes(".")) {
93
+ const rootKey = key.split(".")[0];
94
+ const rootProperty = collection.properties[rootKey];
95
+
96
+ if (rootProperty && rootProperty.type === "map" && rootProperty.properties) {
97
+ const nestedProperty = getPropertyInPath(collection.properties, key) as Property | undefined;
98
+ if (nestedProperty) {
99
+ processedPropertyKeys.add(key);
100
+ // Mark root as seen
101
+ processedPropertyKeys.add(rootKey);
102
+
103
+ if (nestedProperty.ui?.hideFromCollection)
104
+ return [null];
105
+ if (nestedProperty.ui?.disabled && typeof nestedProperty.ui?.disabled === "object" && nestedProperty.ui?.disabled.hidden)
106
+ return [null];
107
+
108
+ return [{
109
+ key,
110
+ disabled: Boolean(rootProperty.ui?.disabled) || Boolean(rootProperty.ui?.readOnly) ||
111
+ Boolean(nestedProperty.ui?.disabled) || Boolean(nestedProperty.ui?.readOnly)
112
+ }];
113
+ }
114
+ }
115
+ }
116
+
117
+ // Check additional fields
118
+ const additionalField = collection.additionalFields?.find(field => field.key === key);
119
+ if (additionalField) {
120
+ return [{
121
+ key,
122
+ disabled: true
123
+ }];
124
+ }
125
+
126
+ // Check subcollections
127
+ const subcollections = getSubcollections(collection);
128
+ if (subcollections) {
129
+ const subCollection = subcollections
130
+ .find(subCol => getSubcollectionColumnId(subCol) === key);
131
+ if (subCollection) {
132
+ return [{
133
+ key,
134
+ disabled: true
135
+ }];
136
+ }
137
+ }
138
+
139
+ return [null];
140
+ }).filter(Boolean) as PropertyColumnConfig[];
141
+
142
+ // Add any missing properties that weren't in propertiesOrder
143
+ // This ensures properties NEVER disappear
144
+ for (const propKey of Object.keys(collection.properties)) {
145
+ // Skip if already processed
146
+ if (processedPropertyKeys.has(propKey)) continue;
147
+
148
+ const property = collection.properties[propKey];
149
+ if (!property) continue;
150
+ if (property.ui?.hideFromCollection) continue;
151
+ if (property.ui?.disabled && typeof property.ui?.disabled === "object" && property.ui?.disabled.hidden) continue;
152
+
153
+ if (property.type === "map" && property.ui?.spreadChildren && property.properties) {
154
+ // For spread maps, add all children that weren't already added
155
+ const allChildConfigs = getColumnKeysForProperty(property as MapProperty, propKey);
156
+ for (const childConfig of allChildConfigs) {
157
+ if (!processedPropertyKeys.has(childConfig.key)) {
158
+ result.push(childConfig);
159
+ processedPropertyKeys.add(childConfig.key);
160
+ }
161
+ }
162
+ } else {
163
+ result.push({
164
+ key: propKey,
165
+ disabled: Boolean(property.ui?.disabled) || Boolean(property.ui?.readOnly)
166
+ });
167
+ processedPropertyKeys.add(propKey);
168
+ }
169
+ }
170
+
171
+ return result;
172
+ }
173
+
174
+ function getDefaultColumnKeys<M extends Record<string, any> = any>(collection: CollectionConfig<M>, includeSubCollections: boolean) {
175
+ const propertyKeys = Object.keys(collection.properties);
176
+
177
+ const additionalFields = collection.additionalFields ?? [];
178
+ const subCollections: CollectionConfig[] = getSubcollections(collection) ?? [];
179
+
180
+ const columnIds: string[] = [
181
+ ...propertyKeys,
182
+ // Filter out additional fields whose key already exists in propertyKeys to avoid duplicate column keys
183
+ ...additionalFields.filter((field) => !propertyKeys.includes(field.key)).map((field) => field.key)
184
+ ];
185
+
186
+ if (includeSubCollections) {
187
+ const subCollectionIds = subCollections
188
+ .map((collection) => getSubcollectionColumnId(collection));
189
+ columnIds.push(...subCollectionIds.filter((subColId) => !columnIds.includes(subColId)));
190
+ }
191
+
192
+ return hideAndExpandKeys(collection, columnIds);
193
+ }
194
+
195
+ export function getColumnKeysForProperty(property: Property, key: string, disabled?: boolean): PropertyColumnConfig[] {
196
+ if (property.type === "map" && property.ui?.spreadChildren && property.properties) {
197
+ return Object.entries(property.properties)
198
+ .flatMap(([childKey, childProperty]) => getColumnKeysForProperty(
199
+ childProperty as Readonly<Property>,
200
+ `${key}.${childKey}`,
201
+ disabled || Boolean(property.ui?.disabled) || Boolean(property.ui?.readOnly))
202
+ );
203
+ }
204
+ return [{
205
+ key,
206
+ disabled: disabled || Boolean(property.ui?.disabled) || Boolean(property.ui?.readOnly)
207
+ }];
208
+ }
209
+
210
+ export function getFormFieldKeys(collection: CollectionConfig): string[] {
211
+ const propertyKeys = collection.properties ? Object.keys(collection.properties) : [];
212
+ const additionalFields = collection.additionalFields ?? [];
213
+ const allKeys = [
214
+ ...propertyKeys,
215
+ ...additionalFields.map((field) => field.key)
216
+ ];
217
+
218
+ if (collection.propertiesOrder) {
219
+ return collection.propertiesOrder.filter(key => allKeys.includes(key));
220
+ }
221
+ return allKeys;
222
+ }