@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,87 @@
1
+ import { useState, useEffect, useMemo, useCallback } from "react";
2
+ import { AuthClient, AuthController, User } from "@rebasepro/types";
3
+
4
+ export function useAuthSubscription(authClient?: AuthClient): AuthController {
5
+
6
+ // Check initial state
7
+ const currentSession = authClient?.getSession();
8
+ const [user, setUser] = useState<User | null>(currentSession?.user ?? null);
9
+
10
+ const [initialLoading, setInitialLoading] = useState(!currentSession);
11
+ const [authLoading, setAuthLoading] = useState(false);
12
+ const [authError, setAuthError] = useState<Error>();
13
+ const [loginSkipped, setLoginSkipped] = useState(false);
14
+ const [extra, setExtra] = useState<any>();
15
+
16
+ useEffect(() => {
17
+ if (!authClient) return;
18
+ // If we don't have a session initially, try to get user which restores session if a persistent token exists
19
+ if (!currentSession) {
20
+ setInitialLoading(true);
21
+ authClient.getUser()
22
+ .then(user => {
23
+ if (user) {
24
+ setUser(user);
25
+ }
26
+ })
27
+ .catch(e => {
28
+ // Ignore, user just isn't logged in
29
+ })
30
+ .finally(() => {
31
+ setInitialLoading(false);
32
+ });
33
+ }
34
+ }, [authClient, currentSession]);
35
+
36
+ useEffect(() => {
37
+ if (!authClient) return;
38
+ const unsubscribe = authClient.onAuthStateChange((event, session) => {
39
+ if (event === "SIGNED_IN" || event === "USER_UPDATED" || event === "TOKEN_REFRESHED") {
40
+ setUser(session?.user ?? null);
41
+ } else if (event === "SIGNED_OUT") {
42
+ setUser(null);
43
+ }
44
+ });
45
+ return unsubscribe;
46
+ }, [authClient]);
47
+
48
+ const signOut = useCallback(async () => {
49
+ if (!authClient) return;
50
+ setAuthLoading(true);
51
+ try {
52
+ await authClient.signOut();
53
+ } finally {
54
+ setAuthLoading(false);
55
+ }
56
+ }, [authClient]);
57
+
58
+ const getAuthToken = useCallback(async () => {
59
+ if (!authClient) return "";
60
+ const session = authClient.getSession();
61
+ if (!session) return "";
62
+ if (session.expiresAt < Date.now()) {
63
+ try {
64
+ const refreshed = await authClient.refreshSession();
65
+ return refreshed.accessToken;
66
+ } catch (e) {
67
+ return "";
68
+ }
69
+ }
70
+ return session.accessToken;
71
+ }, [authClient]);
72
+
73
+ return useMemo(() => ({
74
+ user,
75
+ initialLoading: authClient ? initialLoading : false,
76
+ authLoading,
77
+ signOut,
78
+ authError,
79
+ getAuthToken,
80
+ loginSkipped,
81
+ extra,
82
+ setExtra
83
+ } as AuthController), [
84
+ user, initialLoading, authLoading, signOut, authError,
85
+ getAuthToken, loginSkipped, extra, setExtra, authClient
86
+ ]);
87
+ }
@@ -0,0 +1,288 @@
1
+ /**
2
+ * React hook for using backend storage API as a StorageSource
3
+ */
4
+
5
+ import { useMemo, useCallback } from "react";
6
+ import {
7
+ StorageSource,
8
+ UploadFileProps,
9
+ UploadFileResult,
10
+ DownloadConfig,
11
+ StorageListResult
12
+ } from "@rebasepro/types";
13
+
14
+ export interface BackendStorageSourceProps {
15
+ /**
16
+ * Backend API URL (e.g., 'http://localhost:3001')
17
+ */
18
+ apiUrl: string;
19
+ /**
20
+ * Function to get the current auth token
21
+ */
22
+ getAuthToken: () => Promise<string>;
23
+ }
24
+
25
+ /**
26
+ * Hook to create a StorageSource that uses the backend storage REST API.
27
+ * Use this for self-hosted Rebase with local or S3 storage.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * const storageSource = useBackendStorageSource({
32
+ * apiUrl: 'http://localhost:3001',
33
+ * getAuthToken: authController.getAuthToken
34
+ * });
35
+ *
36
+ * // Then pass to Rebase:
37
+ * <Rebase storageSource={storageSource} ... />
38
+ * ```
39
+ */
40
+ export function useBackendStorageSource({
41
+ apiUrl,
42
+ getAuthToken
43
+ }: BackendStorageSourceProps): StorageSource {
44
+
45
+ const storageBasePath = `${apiUrl}/api/storage`;
46
+
47
+ // Cache for download URLs to avoid redundant API calls
48
+ const urlsCache = useMemo(() => new Map<string, DownloadConfig>(), []);
49
+
50
+ /**
51
+ * Make an authenticated request to the storage API
52
+ */
53
+ const fetchWithAuth = useCallback(async (
54
+ url: string,
55
+ options: RequestInit = {}
56
+ ): Promise<Response> => {
57
+ const token = await getAuthToken();
58
+ return fetch(url, {
59
+ ...options,
60
+ headers: {
61
+ ...options.headers,
62
+ "Authorization": `Bearer ${token}`
63
+ }
64
+ });
65
+ }, [getAuthToken]);
66
+
67
+ /**
68
+ * Upload a file to storage
69
+ */
70
+ const putObject = useCallback(async ({
71
+ file,
72
+ key,
73
+ metadata,
74
+ bucket
75
+ }: UploadFileProps): Promise<UploadFileResult> => {
76
+ const formData = new FormData();
77
+ formData.append("file", file);
78
+
79
+ if (key) {
80
+ formData.append("key", key);
81
+ }
82
+ if (bucket) {
83
+ formData.append("bucket", bucket);
84
+ }
85
+
86
+ // Add metadata fields with prefix
87
+ if (metadata) {
88
+ for (const [key, value] of Object.entries(metadata)) {
89
+ if (value !== undefined && value !== null) {
90
+ formData.append(
91
+ `metadata_${key}`,
92
+ typeof value === "string" ? value : JSON.stringify(value)
93
+ );
94
+ }
95
+ }
96
+ }
97
+
98
+ const response = await fetchWithAuth(`${storageBasePath}/upload`, {
99
+ method: "POST",
100
+ body: formData
101
+ });
102
+
103
+ if (!response.ok) {
104
+ const error = await response.json().catch(() => ({ error: "Upload failed" }));
105
+ throw new Error(error.error || "Upload failed");
106
+ }
107
+
108
+ const result = await response.json();
109
+ return result.data;
110
+ }, [fetchWithAuth, storageBasePath]);
111
+
112
+ /**
113
+ * Get download URL for a file
114
+ */
115
+ const getSignedUrl = useCallback(async (
116
+ keyOrUrl: string,
117
+ bucket?: string
118
+ ): Promise<DownloadConfig> => {
119
+ // Check cache first
120
+ const cacheKey = bucket ? `${bucket}/${keyOrUrl}` : keyOrUrl;
121
+ const cached = urlsCache.get(cacheKey);
122
+ if (cached) {
123
+ return cached;
124
+ }
125
+
126
+ // Build the file path for the API
127
+ let filePath = keyOrUrl;
128
+
129
+ // Handle local:// and s3:// URLs
130
+ if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) {
131
+ const withoutProtocol = filePath.substring(filePath.indexOf("://") + 3);
132
+ filePath = withoutProtocol;
133
+ }
134
+
135
+ // If bucket is provided separately, prepend it
136
+ if (bucket && filePath && !filePath.startsWith(bucket)) {
137
+ filePath = `${bucket}/${filePath}`;
138
+ }
139
+
140
+ if (!filePath || filePath.trim() === "" || filePath === "/") {
141
+ return { url: null,
142
+ fileNotFound: true };
143
+ }
144
+
145
+ const response = await fetchWithAuth(`${storageBasePath}/metadata/${filePath}`);
146
+
147
+ if (response.status === 404) {
148
+ return {
149
+ url: null,
150
+ fileNotFound: true
151
+ };
152
+ }
153
+
154
+ if (!response.ok) {
155
+ const error = await response.json().catch(() => ({ error: "Failed to get download URL" }));
156
+ throw new Error(error.error || "Failed to get download URL");
157
+ }
158
+
159
+ const result = await response.json();
160
+
161
+ const token = await getAuthToken();
162
+ const tokenQuery = token ? `?token=${token}` : "";
163
+
164
+ // The URL should point to the storage file endpoint
165
+ const downloadConfig: DownloadConfig = {
166
+ url: `${storageBasePath}/file/${filePath}${tokenQuery}`,
167
+ metadata: result.data
168
+ };
169
+
170
+ // Cache the result
171
+ urlsCache.set(cacheKey, downloadConfig);
172
+
173
+ return downloadConfig;
174
+ }, [fetchWithAuth, storageBasePath, urlsCache]);
175
+
176
+ /**
177
+ * Get file as a File object
178
+ */
179
+ const getObject = useCallback(async (
180
+ key: string,
181
+ bucket?: string
182
+ ): Promise<File | null> => {
183
+ let filePath = key;
184
+
185
+ // Handle protocol URLs
186
+ if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) {
187
+ const withoutProtocol = filePath.substring(filePath.indexOf("://") + 3);
188
+ filePath = withoutProtocol;
189
+ }
190
+
191
+ if (bucket && filePath && !filePath.startsWith(bucket)) {
192
+ filePath = `${bucket}/${filePath}`;
193
+ }
194
+
195
+ if (!filePath || filePath.trim() === "" || filePath === "/") {
196
+ return null;
197
+ }
198
+
199
+ const response = await fetchWithAuth(`${storageBasePath}/file/${filePath}`);
200
+
201
+ if (response.status === 404) {
202
+ return null;
203
+ }
204
+
205
+ if (!response.ok) {
206
+ throw new Error("Failed to get file");
207
+ }
208
+
209
+ const blob = await response.blob();
210
+ const fileName = filePath.split("/").pop() || "file";
211
+ return new File([blob], fileName, { type: blob.type });
212
+ }, [fetchWithAuth, storageBasePath]);
213
+
214
+ /**
215
+ * Delete a file
216
+ */
217
+ const deleteObject = useCallback(async (
218
+ key: string,
219
+ bucket?: string
220
+ ): Promise<void> => {
221
+ let filePath = key;
222
+
223
+ // Handle protocol URLs
224
+ if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) {
225
+ const withoutProtocol = filePath.substring(filePath.indexOf("://") + 3);
226
+ filePath = withoutProtocol;
227
+ }
228
+
229
+ if (bucket && filePath && !filePath.startsWith(bucket)) {
230
+ filePath = `${bucket}/${filePath}`;
231
+ }
232
+
233
+ if (!filePath || filePath.trim() === "" || filePath === "/") {
234
+ return;
235
+ }
236
+
237
+ const response = await fetchWithAuth(`${storageBasePath}/file/${filePath}`, {
238
+ method: "DELETE"
239
+ });
240
+
241
+ if (!response.ok && response.status !== 404) {
242
+ const error = await response.json().catch(() => ({ error: "Failed to delete file" }));
243
+ throw new Error(error.error || "Failed to delete file");
244
+ }
245
+
246
+ // Clear from cache
247
+ urlsCache.delete(bucket ? `${bucket}/${key}` : key);
248
+ }, [fetchWithAuth, storageBasePath, urlsCache]);
249
+
250
+ /**
251
+ * List files in a path
252
+ */
253
+ const listObjects = useCallback(async (
254
+ prefix: string,
255
+ options?: {
256
+ bucket?: string;
257
+ maxResults?: number;
258
+ pageToken?: string;
259
+ }
260
+ ): Promise<StorageListResult> => {
261
+ const params = new URLSearchParams();
262
+ if (prefix) params.set("prefix", prefix);
263
+ if (options?.bucket) params.set("bucket", options.bucket);
264
+ if (options?.maxResults) params.set("maxResults", String(options.maxResults));
265
+ if (options?.pageToken) params.set("pageToken", options.pageToken);
266
+
267
+ const response = await fetchWithAuth(
268
+ `${storageBasePath}/list?${params.toString()}`
269
+ );
270
+
271
+ if (!response.ok) {
272
+ const error = await response.json().catch(() => ({ error: "Failed to list files" }));
273
+ throw new Error(error.error || "Failed to list files");
274
+ }
275
+
276
+ const result = await response.json();
277
+ return result.data;
278
+ }, [fetchWithAuth, storageBasePath]);
279
+
280
+ // Return memoized StorageSource
281
+ return useMemo<StorageSource>(() => ({
282
+ putObject,
283
+ getSignedUrl,
284
+ getObject,
285
+ deleteObject,
286
+ listObjects
287
+ }), [putObject, getSignedUrl, getObject, deleteObject, listObjects]);
288
+ }
@@ -0,0 +1,32 @@
1
+ import { useContext, useEffect } from "react";
2
+ import type { StudioBridge } from "./useStudioBridge";
3
+ import { StudioBridgeRegistryContext } from "./useStudioBridge";
4
+
5
+ /**
6
+ * Registers a value into the self-assembling Studio bridge.
7
+ *
8
+ * Each controller (collectionRegistry, sidePanel, url, navigation, breadcrumbs)
9
+ * calls this hook on mount to inject its real implementation into the bridge.
10
+ * On unmount the slice is automatically removed, reverting to the noop default.
11
+ *
12
+ * Usage:
13
+ * ```tsx
14
+ * function SomeProvider({ children }) {
15
+ * const controller = useBuildSomeController();
16
+ * useBridgeRegistration("sidePanelController", controller);
17
+ * return <SomeContext.Provider value={controller}>{children}</SomeContext.Provider>;
18
+ * }
19
+ * ```
20
+ */
21
+ export function useBridgeRegistration<K extends keyof StudioBridge>(
22
+ key: K,
23
+ value: StudioBridge[K]
24
+ ): void {
25
+ const registry = useContext(StudioBridgeRegistryContext);
26
+
27
+ useEffect(() => {
28
+ if (!registry) return;
29
+ registry.register(key, value);
30
+ return () => registry.unregister(key);
31
+ }, [registry, key, value]);
32
+ }
@@ -0,0 +1,23 @@
1
+ import { useEffect } from "react";
2
+
3
+ const rebaseLogo = "data:image/png;base64,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";
4
+
5
+ /**
6
+ * Internal hook to handle the browser title and icon
7
+ * @param name
8
+ * @param logo
9
+ */
10
+ export function useBrowserTitleAndIcon(name: string, logo?: string) {
11
+ useEffect(() => {
12
+ if (document) {
13
+ document.title = `${name} - Rebase`;
14
+ let link = document.querySelector("link[rel~='icon']") as HTMLLinkElement | null;
15
+ if (!link) {
16
+ link = document.createElement("link");
17
+ link.rel = "icon";
18
+ document.getElementsByTagName("head")[0].appendChild(link);
19
+ }
20
+ link.href = logo ?? rebaseLogo;
21
+ }
22
+ }, [name, logo]);
23
+ }
@@ -0,0 +1,24 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+ import { AdminModeController } from "./index";
3
+
4
+ /**
5
+ * Use this hook to build an admin mode controller that determines
6
+ * whether the UI shows Developer or Editor tools.
7
+ */
8
+ export function useBuildAdminModeController(): AdminModeController {
9
+
10
+ const savedMode = typeof window !== "undefined" ? localStorage.getItem("rebase-admin-mode") as "content" | "studio" | "settings" | null : null;
11
+ const [mode, setMode] = useState<"content" | "studio" | "settings">(savedMode ?? "content");
12
+
13
+ const setModeInternal = useCallback((newMode: "content" | "studio" | "settings") => {
14
+ if (typeof window !== "undefined") {
15
+ localStorage.setItem("rebase-admin-mode", newMode);
16
+ }
17
+ setMode(newMode);
18
+ }, []);
19
+
20
+ return useMemo(() => ({
21
+ mode,
22
+ setMode: setModeInternal
23
+ }), [mode, setModeInternal]);
24
+ }
@@ -0,0 +1,31 @@
1
+ import type { EffectiveRoleController } from "@rebasepro/types";
2
+ import { useCallback, useState, useMemo } from "react";
3
+ ;
4
+
5
+ /**
6
+ * Use this hook to build an effective role controller that determines
7
+ * what role is simulated in Editor mode when Dev mode is active.
8
+ *
9
+ * It uses localStorage to persist the simulated role across reloads.
10
+ */
11
+ export function useBuildEffectiveRoleController(): EffectiveRoleController {
12
+
13
+ const savedRole = typeof window !== "undefined" ? localStorage.getItem("rebase-effective-role") : null;
14
+ const [effectiveRole, setEffectiveRole] = useState<string | null>(savedRole ?? null);
15
+
16
+ const setRoleInternal = useCallback((newRole: string | null) => {
17
+ if (typeof window !== "undefined") {
18
+ if (newRole) {
19
+ localStorage.setItem("rebase-effective-role", newRole);
20
+ } else {
21
+ localStorage.removeItem("rebase-effective-role");
22
+ }
23
+ }
24
+ setEffectiveRole(newRole);
25
+ }, []);
26
+
27
+ return useMemo(() => ({
28
+ effectiveRole,
29
+ setEffectiveRole: setRoleInternal
30
+ }), [effectiveRole, setRoleInternal]);
31
+ }
@@ -0,0 +1,75 @@
1
+ import { useCallback, useEffect, useRef, useState, useMemo } from "react";
2
+ import { PartialCollectionConfig, UserConfigurationPersistence } from "@rebasepro/types";
3
+ import { stripCollectionPath } from "@rebasepro/common";
4
+ import { mergeDeep } from "@rebasepro/utils";
5
+
6
+ export function useBuildLocalConfigurationPersistence(): UserConfigurationPersistence {
7
+
8
+ const configCache = useRef<Record<string, PartialCollectionConfig>>({});
9
+
10
+ const getCollectionFromStorage = useCallback((storageKey: string) => {
11
+ const item = localStorage.getItem(storageKey);
12
+ return item ? JSON.parse(item) : {};
13
+ }, []);
14
+
15
+ const getCollectionConfig = useCallback(<M extends Record<string, any>>(path: string): PartialCollectionConfig<M> => {
16
+ const storageKey = `collection_config::${stripCollectionPath(path)}`;
17
+ if (configCache.current[storageKey]) {
18
+ return configCache.current[storageKey] as PartialCollectionConfig<M>;
19
+ }
20
+ return getCollectionFromStorage(storageKey);
21
+ }, [getCollectionFromStorage]);
22
+
23
+ const onCollectionModified = useCallback(<M extends Record<string, any>>(path: string, data: PartialCollectionConfig<M>) => {
24
+ const storageKey = `collection_config::${stripCollectionPath(path)}`;
25
+ localStorage.setItem(storageKey, JSON.stringify(data));
26
+ const cachedConfig = configCache.current[storageKey];
27
+ const newConfig = mergeDeep(cachedConfig ?? getCollectionFromStorage(path), data);
28
+ configCache.current[storageKey] = mergeDeep(configCache.current[storageKey], newConfig);
29
+ }, [getCollectionFromStorage]);
30
+
31
+ const [recentlyVisitedPaths, _setRecentlyVisitedPaths] = useState<string[]>([]);
32
+ const [favouritePaths, _setFavouritePaths] = useState<string[]>([]);
33
+ const [collapsedGroups, _setCollapsedGroups] = useState<string[]>([]);
34
+
35
+ useEffect(() => {
36
+ _setRecentlyVisitedPaths(localStorage.getItem("recently_visited_paths") ? JSON.parse(localStorage.getItem("recently_visited_paths")!) : []);
37
+ _setFavouritePaths(localStorage.getItem("favourite_paths") ? JSON.parse(localStorage.getItem("favourite_paths")!) : []);
38
+ _setCollapsedGroups(localStorage.getItem("collapsed_groups") ? JSON.parse(localStorage.getItem("collapsed_groups")!) : []);
39
+ }, []);
40
+
41
+ const setRecentlyVisitedPaths = useCallback((paths: string[]) => {
42
+ localStorage.setItem("recently_visited_paths", JSON.stringify(paths));
43
+ _setRecentlyVisitedPaths(paths);
44
+ }, []);
45
+
46
+ const setFavouritePaths = useCallback((paths: string[]) => {
47
+ localStorage.setItem("favourite_paths", JSON.stringify(paths));
48
+ _setFavouritePaths(paths);
49
+ }, []);
50
+
51
+ const setCollapsedGroups = useCallback((paths: string[]) => {
52
+ localStorage.setItem("collapsed_groups", JSON.stringify(paths));
53
+ _setCollapsedGroups(paths);
54
+ }, []);
55
+
56
+ return useMemo(() => ({
57
+ onCollectionModified,
58
+ getCollectionConfig,
59
+ recentlyVisitedPaths,
60
+ setRecentlyVisitedPaths,
61
+ favouritePaths,
62
+ setFavouritePaths,
63
+ collapsedGroups,
64
+ setCollapsedGroups
65
+ }), [
66
+ onCollectionModified,
67
+ getCollectionConfig,
68
+ recentlyVisitedPaths,
69
+ setRecentlyVisitedPaths,
70
+ favouritePaths,
71
+ setFavouritePaths,
72
+ collapsedGroups,
73
+ setCollapsedGroups
74
+ ]);
75
+ }
@@ -0,0 +1,71 @@
1
+ import { useCallback, useEffect, useState, useMemo } from "react";
2
+
3
+ import { ModeController } from "./index";
4
+
5
+ /**
6
+ * Use this hook to build a color mode controller that determines
7
+ * the theme of the CMS
8
+ */
9
+ export function useBuildModeController(): ModeController {
10
+
11
+ const prefersDarkModeQuery = useCallback((): boolean => {
12
+ if (typeof window === "undefined")
13
+ return false;
14
+ const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
15
+ return mediaQueryList.matches;
16
+ }, []);
17
+
18
+ const [mode, setMode] = useState<"light" | "dark">(() => {
19
+ const prefersDarkModeStorage = typeof window !== "undefined" && localStorage.getItem("prefers-dark-mode") != null
20
+ ? localStorage.getItem("prefers-dark-mode") === "true"
21
+ : null;
22
+ const prefersDarkMode = prefersDarkModeStorage ?? prefersDarkModeQuery();
23
+ return prefersDarkMode ? "dark" : "light";
24
+ });
25
+
26
+ useEffect(() => {
27
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
28
+ const handleChange = (e: MediaQueryListEvent) => {
29
+ if (localStorage.getItem("prefers-dark-mode") == null) {
30
+ setMode(e.matches ? "dark" : "light");
31
+ setDocumentMode(e.matches ? "dark" : "light");
32
+ }
33
+ };
34
+
35
+ // Initial setup
36
+ setDocumentMode(mode);
37
+
38
+ mediaQuery.addEventListener("change", handleChange);
39
+ return () => mediaQuery.removeEventListener("change", handleChange);
40
+ }, []);
41
+
42
+ const setDocumentMode = (mode: "light" | "dark") => {
43
+ if (mode === "dark") {
44
+ document.documentElement.classList.add("dark");
45
+ } else {
46
+ document.documentElement.classList.remove("dark");
47
+ }
48
+ };
49
+
50
+ const setModeInternal = useCallback((mode: "light" | "dark" | "system") => {
51
+ if (mode === "light") {
52
+ setDocumentMode("light");
53
+ localStorage.setItem("prefers-dark-mode", "false");
54
+ setMode("light");
55
+ } else if (mode === "dark") {
56
+ setDocumentMode("dark");
57
+ localStorage.setItem("prefers-dark-mode", "true");
58
+ setMode("dark");
59
+ } else {
60
+ const preferredMode = prefersDarkModeQuery() ? "dark" : "light";
61
+ setDocumentMode(preferredMode);
62
+ localStorage.removeItem("prefers-dark-mode");
63
+ setMode(preferredMode);
64
+ }
65
+ }, [prefersDarkModeQuery]);
66
+
67
+ return useMemo(() => ({
68
+ mode,
69
+ setMode: setModeInternal
70
+ }), [mode, setModeInternal]);
71
+ }