@rebasepro/app 0.17.3 → 0.18.0

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 (185) hide show
  1. package/README.md +4 -1
  2. package/dist/components/index.d.ts +0 -3
  3. package/dist/contexts/AuthControllerContext.d.ts +2 -1
  4. package/dist/contexts/CustomizationControllerContext.d.ts +2 -1
  5. package/dist/contexts/DialogsProvider.d.ts +2 -1
  6. package/dist/contexts/RebaseDataContext.d.ts +10 -1
  7. package/dist/contexts/StorageSourceContext.d.ts +11 -1
  8. package/dist/core/RebaseProps.d.ts +2 -12
  9. package/dist/debug/index.d.ts +19 -0
  10. package/dist/debug.js +3631 -0
  11. package/dist/hooks/data/delete.d.ts +16 -7
  12. package/dist/hooks/data/save.d.ts +18 -4
  13. package/dist/hooks/index.d.ts +1 -0
  14. package/dist/hooks/useAdminModeController.d.ts +5 -4
  15. package/dist/hooks/useNavigationGroupLabel.d.ts +17 -0
  16. package/dist/hooks/useStudioBridge.d.ts +33 -0
  17. package/dist/index.es.js +2599 -6980
  18. package/dist/index.es.js.map +1 -1
  19. package/dist/vitePlugin.d.ts +22 -0
  20. package/dist/vitePlugin.js +40 -1
  21. package/package.json +42 -31
  22. package/src/auth/api.ts +0 -134
  23. package/src/auth/index.ts +0 -26
  24. package/src/auth/types.ts +0 -115
  25. package/src/auth/useRebaseAuthController.ts +0 -345
  26. package/src/collections/collection_view_config.ts +0 -76
  27. package/src/collections/entity-display-cache.ts +0 -195
  28. package/src/collections/entity-display.ts +0 -77
  29. package/src/collections/entity_image_preview.ts +0 -61
  30. package/src/collections/filter-operator-resolution.ts +0 -119
  31. package/src/collections/form-layout.ts +0 -394
  32. package/src/collections/index.ts +0 -13
  33. package/src/collections/navigation_from_path.ts +0 -123
  34. package/src/collections/navigation_utils.ts +0 -171
  35. package/src/collections/parent_references_from_path.ts +0 -59
  36. package/src/collections/property-path.ts +0 -30
  37. package/src/collections/property_presentation.ts +0 -264
  38. package/src/collections/summary-property.ts +0 -114
  39. package/src/collections/title-property.ts +0 -312
  40. package/src/components/AIIcon.tsx +0 -47
  41. package/src/components/AuthSimulationMenu.tsx +0 -0
  42. package/src/components/ConfirmationDialog.tsx +0 -48
  43. package/src/components/Debug/UIReferenceView.tsx +0 -1053
  44. package/src/components/Debug/UIStyleGuide.tsx +0 -159
  45. package/src/components/Debug/collection-views/CardViewDemo.tsx +0 -87
  46. package/src/components/Debug/collection-views/CollectionTableDemo.tsx +0 -107
  47. package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +0 -89
  48. package/src/components/Debug/collection-views/index.ts +0 -3
  49. package/src/components/Debug/collection-views/sample_data.ts +0 -44
  50. package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +0 -387
  51. package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +0 -1100
  52. package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +0 -131
  53. package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +0 -288
  54. package/src/components/Debug/crm-dashboard/RecentActivity.tsx +0 -213
  55. package/src/components/Debug/crm-dashboard/TaskFilters.tsx +0 -182
  56. package/src/components/Debug/crm-dashboard/TaskTable.tsx +0 -425
  57. package/src/components/Debug/crm-dashboard/TasksView.tsx +0 -234
  58. package/src/components/ErrorTooltip.tsx +0 -12
  59. package/src/components/ErrorView.tsx +0 -94
  60. package/src/components/LanguageToggle.tsx +0 -59
  61. package/src/components/LoginView/LoginView.tsx +0 -1235
  62. package/src/components/LoginView/index.ts +0 -2
  63. package/src/components/LoginView/oauth-redirect-flow.ts +0 -184
  64. package/src/components/NotFoundPage.tsx +0 -27
  65. package/src/components/RebaseAuth.tsx +0 -27
  66. package/src/components/RebaseLogo.tsx +0 -88
  67. package/src/components/SchemaDriftBanner.tsx +0 -102
  68. package/src/components/UnsavedChangesDialog.tsx +0 -46
  69. package/src/components/UserDisplay.tsx +0 -54
  70. package/src/components/UserSelectPopover.tsx +0 -399
  71. package/src/components/UserSettingsView.tsx +0 -318
  72. package/src/components/common/index.ts +0 -6
  73. package/src/components/common/table_height.tsx +0 -21
  74. package/src/components/common/types.tsx +0 -66
  75. package/src/components/common/useColumnsIds.tsx +0 -261
  76. package/src/components/common/useDataTableController.tsx +0 -571
  77. package/src/components/common/useDebouncedData.ts +0 -49
  78. package/src/components/common/useScrollRestoration.tsx +0 -68
  79. package/src/components/index.tsx +0 -26
  80. package/src/contexts/AdminModeController.tsx +0 -11
  81. package/src/contexts/AnalyticsContext.tsx +0 -4
  82. package/src/contexts/AuthControllerContext.tsx +0 -4
  83. package/src/contexts/CollectionResolverContext.tsx +0 -24
  84. package/src/contexts/CollectionScopeContext.tsx +0 -68
  85. package/src/contexts/ComponentOverrideContext.tsx +0 -81
  86. package/src/contexts/CustomizationControllerContext.tsx +0 -4
  87. package/src/contexts/DataDriverContext.tsx +0 -4
  88. package/src/contexts/DataSourcesContext.tsx +0 -36
  89. package/src/contexts/DatabaseAdminContext.tsx +0 -4
  90. package/src/contexts/DialogsProvider.tsx +0 -56
  91. package/src/contexts/EffectiveRoleController.tsx +0 -12
  92. package/src/contexts/ModeController.tsx +0 -11
  93. package/src/contexts/RebaseClientInstanceContext.tsx +0 -7
  94. package/src/contexts/RebaseDataContext.tsx +0 -4
  95. package/src/contexts/SnackbarProvider.tsx +0 -15
  96. package/src/contexts/StorageSourceContext.tsx +0 -4
  97. package/src/contexts/StorageSourcesContext.tsx +0 -30
  98. package/src/contexts/UserConfigurationPersistenceContext.tsx +0 -4
  99. package/src/contexts/index.ts +0 -18
  100. package/src/core/PluginLifecycleManager.tsx +0 -96
  101. package/src/core/PluginProviderStack.tsx +0 -40
  102. package/src/core/Rebase.tsx +0 -488
  103. package/src/core/RebaseProps.tsx +0 -333
  104. package/src/core/RebaseRouter.tsx +0 -18
  105. package/src/core/RebaseRoutes.tsx +0 -24
  106. package/src/core/index.tsx +0 -5
  107. package/src/hooks/ApiConfigContext.tsx +0 -83
  108. package/src/hooks/data/collectionQuery.ts +0 -55
  109. package/src/hooks/data/delete.ts +0 -56
  110. package/src/hooks/data/save.ts +0 -78
  111. package/src/hooks/data/useCollection.tsx +0 -183
  112. package/src/hooks/data/useData.tsx +0 -18
  113. package/src/hooks/data/useDataOrder.ts +0 -26
  114. package/src/hooks/data/useFetch.tsx +0 -156
  115. package/src/hooks/data/useRelationSelector.tsx +0 -280
  116. package/src/hooks/index.tsx +0 -45
  117. package/src/hooks/useAdminModeController.tsx +0 -23
  118. package/src/hooks/useAnalyticsController.tsx +0 -8
  119. package/src/hooks/useAuthController.tsx +0 -15
  120. package/src/hooks/useAuthSubscription.ts +0 -130
  121. package/src/hooks/useBackendStorageSource.ts +0 -295
  122. package/src/hooks/useBridgeRegistration.tsx +0 -32
  123. package/src/hooks/useBrowserTitleAndIcon.tsx +0 -23
  124. package/src/hooks/useBuildAdminModeController.tsx +0 -29
  125. package/src/hooks/useBuildEffectiveRoleController.tsx +0 -30
  126. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +0 -83
  127. package/src/hooks/useBuildModeController.tsx +0 -73
  128. package/src/hooks/useClipboard.tsx +0 -158
  129. package/src/hooks/useCollapsedGroups.ts +0 -116
  130. package/src/hooks/useComponentOverride.tsx +0 -59
  131. package/src/hooks/useCustomizationController.tsx +0 -14
  132. package/src/hooks/useDialogsController.tsx +0 -15
  133. package/src/hooks/useEffectiveRoleController.tsx +0 -10
  134. package/src/hooks/useLargeLayout.tsx +0 -65
  135. package/src/hooks/useModeController.tsx +0 -23
  136. package/src/hooks/useNavigationBlocker.tsx +0 -108
  137. package/src/hooks/usePermissions.ts +0 -45
  138. package/src/hooks/useRebaseClient.tsx +0 -10
  139. package/src/hooks/useRebaseContext.tsx +0 -84
  140. package/src/hooks/useRebaseRegistry.tsx +0 -91
  141. package/src/hooks/useResolvedComponent.tsx +0 -195
  142. package/src/hooks/useSlot.tsx +0 -64
  143. package/src/hooks/useSnackbarController.tsx +0 -80
  144. package/src/hooks/useStorageSource.tsx +0 -11
  145. package/src/hooks/useStudioBridge.tsx +0 -258
  146. package/src/hooks/useTranslation.ts +0 -38
  147. package/src/hooks/useUnsavedChangesDialog.tsx +0 -67
  148. package/src/hooks/useUserConfigurationPersistence.tsx +0 -11
  149. package/src/i18n/RebaseI18nProvider.tsx +0 -159
  150. package/src/index.ts +0 -32
  151. package/src/internal/common.tsx +0 -28
  152. package/src/internal/useRestoreScroll.tsx +0 -69
  153. package/src/locales/de.ts +0 -941
  154. package/src/locales/en.ts +0 -1021
  155. package/src/locales/es.ts +0 -962
  156. package/src/locales/fr.ts +0 -941
  157. package/src/locales/hi.ts +0 -941
  158. package/src/locales/it.ts +0 -941
  159. package/src/locales/pt.ts +0 -949
  160. package/src/util/constants.ts +0 -7
  161. package/src/util/createFormexStub.tsx +0 -66
  162. package/src/util/entity_cache.ts +0 -242
  163. package/src/util/enums.ts +0 -34
  164. package/src/util/icon_list.ts +0 -22
  165. package/src/util/icons.tsx +0 -129
  166. package/src/util/index.ts +0 -13
  167. package/src/util/local_storage.ts +0 -66
  168. package/src/util/previews.ts +0 -127
  169. package/src/util/useStorageUploadController.tsx +0 -354
  170. package/src/vitePlugin.ts +0 -215
  171. /package/dist/{components/Debug → debug}/UIReferenceView.d.ts +0 -0
  172. /package/dist/{components/Debug → debug}/UIStyleGuide.d.ts +0 -0
  173. /package/dist/{components/Debug → debug}/collection-views/CardViewDemo.d.ts +0 -0
  174. /package/dist/{components/Debug → debug}/collection-views/CollectionTableDemo.d.ts +0 -0
  175. /package/dist/{components/Debug → debug}/collection-views/KanbanBoardDemo.d.ts +0 -0
  176. /package/dist/{components/Debug → debug}/collection-views/index.d.ts +0 -0
  177. /package/dist/{components/Debug → debug}/collection-views/sample_data.d.ts +0 -0
  178. /package/dist/{components/Debug → debug}/crm-dashboard/CalendarWidget.d.ts +0 -0
  179. /package/dist/{components/Debug → debug}/crm-dashboard/CrmDashboardDemo.d.ts +0 -0
  180. /package/dist/{components/Debug → debug}/crm-dashboard/DashboardMetrics.d.ts +0 -0
  181. /package/dist/{components/Debug → debug}/crm-dashboard/PipelineOverview.d.ts +0 -0
  182. /package/dist/{components/Debug → debug}/crm-dashboard/RecentActivity.d.ts +0 -0
  183. /package/dist/{components/Debug → debug}/crm-dashboard/TaskFilters.d.ts +0 -0
  184. /package/dist/{components/Debug → debug}/crm-dashboard/TaskTable.d.ts +0 -0
  185. /package/dist/{components/Debug → debug}/crm-dashboard/TasksView.d.ts +0 -0
@@ -1,345 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
2
- import type { User, AuthChangeEvent, RebaseSession } from "@rebasepro/types";
3
- import type { AuthConfigResponse } from "./api";
4
- import { RebaseAuthController, RebaseAuthControllerProps } from "./types";
5
- import { clearFetchCache } from "../hooks/data/useFetch";
6
-
7
- /**
8
- * Auth controller hook for JWT-based authentication.
9
- *
10
- * This is a thin React binding over the headless SDK `client.auth`.
11
- * It synchronizes React state with the SDK's internal state and delegates
12
- * all operations to the SDK to ensure a single source of truth for
13
- * authentication sessions across the entire application.
14
- *
15
- * @param props Configuration options
16
- * @returns RebaseAuthController instance
17
- */
18
- export function useRebaseAuthController(
19
- props: RebaseAuthControllerProps = {}
20
- ): RebaseAuthController {
21
- const { client, onSignOut, defineRolesFor } = props;
22
- const auth = client?.auth;
23
-
24
- const [user, setUser] = useState<User | null>(null);
25
- const [authLoading, setAuthLoading] = useState(false);
26
- const [initialLoading, setInitialLoading] = useState(true);
27
- const [authError, setAuthError] = useState<Error | null>(null);
28
- const [authProviderError, setAuthProviderError] = useState<Error | null>(null);
29
- const [loginSkipped, setLoginSkipped] = useState(false);
30
- const [extra, setExtra] = useState<unknown>(null);
31
- const [authConfig, setAuthConfig] = useState<AuthConfigResponse | null>(null);
32
-
33
- const isMountedRef = useRef(true);
34
- // Keep a stable ref to onSignOut so the listener never goes stale.
35
- const onSignOutRef = useRef(onSignOut);
36
- onSignOutRef.current = onSignOut;
37
- // Keep a stable ref to defineRolesFor too, so an inline (non-memoized)
38
- // function from the consumer does not re-run the sync effect on every
39
- // render — which would re-subscribe to auth events and re-fetch the
40
- // (uncached) auth config each time.
41
- const defineRolesForRef = useRef(defineRolesFor);
42
- defineRolesForRef.current = defineRolesFor;
43
-
44
- const clearError = useCallback(() => {
45
- setAuthProviderError(null);
46
- }, []);
47
-
48
- // Sync state with client.auth
49
- useEffect(() => {
50
- if (!auth) {
51
- setInitialLoading(false);
52
- return;
53
- }
54
-
55
- isMountedRef.current = true;
56
-
57
- const updateState = async (session: RebaseSession | null) => {
58
- if (!isMountedRef.current) return;
59
-
60
- let userToSet = session?.user ?? null;
61
- const defineRolesFor = defineRolesForRef.current;
62
- if (userToSet && defineRolesFor) {
63
- try {
64
- const roles = await defineRolesFor(userToSet);
65
- if (roles && isMountedRef.current) {
66
- userToSet = { ...userToSet, roles };
67
- }
68
- } catch (e) {
69
- console.error("Error defining roles:", e);
70
- }
71
- }
72
-
73
- if (isMountedRef.current) {
74
- setUser(userToSet);
75
- }
76
- };
77
-
78
- const syncState = async (event: AuthChangeEvent, session: RebaseSession | null) => {
79
- await updateState(session);
80
- if (event === "SIGNED_OUT") {
81
- // Entities fetched by the session that just ended. The cache is
82
- // module-level and keyed by path only, so without this the next
83
- // user in the same tab is shown rows they may not be allowed to
84
- // read — previews render straight out of it.
85
- clearFetchCache();
86
- if (isMountedRef.current) {
87
- setLoginSkipped(false);
88
- }
89
- onSignOutRef.current?.();
90
- }
91
- };
92
-
93
- // Initial sync with whatever is currently in the SDK
94
- updateState(auth.getSession());
95
-
96
- // Wait for SDK initialization (restoring session from storage or silent refresh)
97
- auth.isInitialized().then(() => {
98
- if (isMountedRef.current) {
99
- updateState(auth.getSession());
100
- setInitialLoading(false);
101
- }
102
- }).catch(() => {
103
- if (isMountedRef.current) setInitialLoading(false);
104
- });
105
-
106
- // Fetch backend auth configuration
107
- auth.getAuthConfig().then((config: AuthConfigResponse) => {
108
- if (isMountedRef.current) setAuthConfig(config);
109
- }).catch((e: unknown) => {
110
- // Swallowed entirely before. This is what tells the login view which
111
- // providers exist, so losing it renders a login form that is wrong
112
- // rather than one that is broken — the hardest kind to report. Not
113
- // promoted to `authError`, which blanks the whole app: a signed-in
114
- // user is unaffected by this failing.
115
- console.warn("[Rebase] Could not load the backend auth configuration; " +
116
- "the login view will fall back to defaults.", e);
117
- });
118
-
119
- const unsubscribe = auth.onAuthStateChange(syncState);
120
-
121
- return () => {
122
- isMountedRef.current = false;
123
- unsubscribe();
124
- };
125
- }, [auth]);
126
-
127
- const getAuthToken = useCallback(async (): Promise<string> => {
128
- if (!auth) throw new Error("Rebase client with auth is required");
129
- const session = auth.getSession();
130
-
131
- // If we have a valid token, return it
132
- if (session?.accessToken && session.expiresAt > Date.now() + 10000) {
133
- return session.accessToken;
134
- }
135
-
136
- // Otherwise, refresh
137
- const newSession = await auth.refreshSession();
138
- return newSession.accessToken;
139
- }, [auth]);
140
-
141
- const signOut = useCallback(async () => {
142
- if (!auth) return;
143
- setAuthLoading(true);
144
- try {
145
- await auth.signOut();
146
- // signOut emits SIGNED_OUT → syncState calls onSignOutRef.current
147
- } catch (error: unknown) {
148
- const err = error instanceof Error ? error : new Error(String(error));
149
- setAuthProviderError(err);
150
- } finally {
151
- setAuthLoading(false);
152
- }
153
- }, [auth]);
154
-
155
- const emailPasswordLogin = useCallback(async (email: string, password: string) => {
156
- if (!auth) throw new Error("Rebase client with auth is required");
157
- setAuthLoading(true);
158
- setAuthProviderError(null);
159
- try {
160
- await auth.signInWithEmail(email, password);
161
- } catch (error: unknown) {
162
- const err = error instanceof Error ? error : new Error(String(error));
163
- setAuthProviderError(err);
164
- throw error;
165
- } finally {
166
- setAuthLoading(false);
167
- }
168
- }, [auth]);
169
-
170
- const register = useCallback(async (email: string, password: string, displayName?: string) => {
171
- if (!auth) throw new Error("Rebase client with auth is required");
172
- setAuthLoading(true);
173
- setAuthProviderError(null);
174
- try {
175
- await auth.signUp(email, password, displayName);
176
- } catch (error: unknown) {
177
- const err = error instanceof Error ? error : new Error(String(error));
178
- setAuthProviderError(err);
179
- throw error;
180
- } finally {
181
- setAuthLoading(false);
182
- }
183
- }, [auth]);
184
-
185
- const googleLogin = useCallback(async (
186
- payload: { idToken: string } | { accessToken: string } | { code: string; redirectUri: string }
187
- ) => {
188
- if (!auth) throw new Error("Rebase client with auth is required");
189
- setAuthLoading(true);
190
- setAuthProviderError(null);
191
- try {
192
- await auth.signInWithGoogle(payload);
193
- } catch (error: unknown) {
194
- const err = error instanceof Error ? error : new Error(String(error));
195
- setAuthProviderError(err);
196
- throw error;
197
- } finally {
198
- setAuthLoading(false);
199
- }
200
- }, [auth]);
201
-
202
- const oauthLogin = useCallback(async (providerId: string, payload: Record<string, unknown>) => {
203
- if (!auth) throw new Error("Rebase client with auth is required");
204
- setAuthLoading(true);
205
- setAuthProviderError(null);
206
- try {
207
- await auth.signInWithOAuth(providerId, payload);
208
- } catch (error: unknown) {
209
- const err = error instanceof Error ? error : new Error(String(error));
210
- setAuthProviderError(err);
211
- throw error;
212
- } finally {
213
- setAuthLoading(false);
214
- }
215
- }, [auth]);
216
-
217
- const skipLogin = useCallback(() => {
218
- setLoginSkipped(true);
219
- setUser(null);
220
- }, []);
221
-
222
- const forgotPassword = useCallback(async (email: string) => {
223
- if (!auth) throw new Error("Rebase client with auth is required");
224
- setAuthLoading(true);
225
- setAuthProviderError(null);
226
- try {
227
- await auth.resetPasswordForEmail(email);
228
- } catch (error: unknown) {
229
- const err = error instanceof Error ? error : new Error(String(error));
230
- setAuthProviderError(err);
231
- throw error;
232
- } finally {
233
- setAuthLoading(false);
234
- }
235
- }, [auth]);
236
-
237
- const resetPassword = useCallback(async (token: string, password: string) => {
238
- if (!auth) throw new Error("Rebase client with auth is required");
239
- setAuthLoading(true);
240
- setAuthProviderError(null);
241
- try {
242
- await auth.resetPassword(token, password);
243
- } catch (error: unknown) {
244
- const err = error instanceof Error ? error : new Error(String(error));
245
- setAuthProviderError(err);
246
- throw error;
247
- } finally {
248
- setAuthLoading(false);
249
- }
250
- }, [auth]);
251
-
252
- const changePassword = useCallback(async (oldPassword: string, newPassword: string) => {
253
- if (!auth) throw new Error("Rebase client with auth is required");
254
- setAuthLoading(true);
255
- setAuthProviderError(null);
256
- try {
257
- await auth.changePassword(oldPassword, newPassword);
258
- // Sign out after password change as sessions are usually invalidated
259
- await auth.signOut();
260
- } catch (error: unknown) {
261
- const err = error instanceof Error ? error : new Error(String(error));
262
- setAuthProviderError(err);
263
- throw error;
264
- } finally {
265
- setAuthLoading(false);
266
- }
267
- }, [auth]);
268
-
269
- const updateProfile = useCallback(async (displayName?: string, photoURL?: string) => {
270
- if (!auth) throw new Error("Rebase client with auth is required");
271
- setAuthLoading(true);
272
- setAuthProviderError(null);
273
- try {
274
- const updatedUser = await auth.updateUser({ displayName, photoURL });
275
- return updatedUser;
276
- } catch (error: unknown) {
277
- const err = error instanceof Error ? error : new Error(String(error));
278
- setAuthProviderError(err);
279
- throw error;
280
- } finally {
281
- setAuthLoading(false);
282
- }
283
- }, [auth]);
284
-
285
- const fetchSessions = useCallback(async () => {
286
- if (!auth) throw new Error("Rebase client with auth is required");
287
- return await auth.getSessions();
288
- }, [auth]);
289
-
290
- const revokeSession = useCallback(async (sessionId: string) => {
291
- if (!auth) throw new Error("Rebase client with auth is required");
292
- await auth.revokeSession(sessionId);
293
- }, [auth]);
294
-
295
- const revokeAllSessions = useCallback(async () => {
296
- if (!auth) throw new Error("Rebase client with auth is required");
297
- await auth.revokeAllSessions();
298
- }, [auth]);
299
-
300
- return {
301
- user,
302
- authLoading,
303
- initialLoading,
304
- authError,
305
- authProviderError,
306
- loginSkipped,
307
- needsSetup: authConfig?.needsSetup ?? false,
308
- registrationEnabled: authConfig?.registrationEnabled ?? false,
309
- getAuthToken,
310
- getApiUrl: () => client?.baseUrl,
311
- signOut,
312
- emailPasswordLogin,
313
- register,
314
- googleLogin,
315
- oauthLogin,
316
- skipLogin,
317
- forgotPassword,
318
- resetPassword,
319
- changePassword,
320
- updateProfile,
321
- fetchSessions,
322
- revokeSession,
323
- revokeAllSessions,
324
- clearError,
325
- setAuthProviderError,
326
- extra,
327
- setExtra,
328
- // `authConfig` is null until `GET /auth/config` lands, so each fallback
329
- // is what to assume *while it is still in flight* — not a stand-in for a
330
- // backend that might omit the field. The backend always sends all of them.
331
- capabilities: {
332
- emailPasswordLogin: true,
333
- googleLogin: !!(props.googleClientId),
334
- registrationEnabled: authConfig?.registrationEnabled ?? false,
335
- passwordReset: authConfig?.passwordReset ?? false,
336
- // True while loading: the admin UI's "Reset Password" action is worth
337
- // showing optimistically and hiding once a backend says it cannot.
338
- adminPasswordReset: authConfig?.adminPasswordReset ?? true,
339
- sessionManagement: true,
340
- profileUpdate: true,
341
- emailVerification: authConfig?.emailVerification ?? false,
342
- enabledProviders: authConfig?.enabledProviders ?? []
343
- }
344
- };
345
- }
@@ -1,76 +0,0 @@
1
- /**
2
- * Collection helpers that only the admin panel needs.
3
- *
4
- * These lived in `@rebasepro/common`, which is on the backend's dependency path,
5
- * and they read presentation fields — `defaultSelectedView`, `localChangesBackup`
6
- * — that now live under a collection's `admin` block. A server has no use for
7
- * either, and `resolveDefaultSelectedView` was the *only* thing making
8
- * `@rebasepro/common` name an admin view-model type
9
- * (`DefaultSelectedViewBuilder`), which was the last UI symbol any core package
10
- * imported.
11
- */
12
- import type { ChildViewSource, CollectionConfig } from "@rebasepro/types";
13
- import { type AdminCollection, type DefaultSelectedViewBuilder, type DefaultSelectedViewParams, resolveAdminCollection } from "@rebasepro/cms-types";
14
- import { getEntityChildViews } from "@rebasepro/common";
15
-
16
- export function resolveDefaultSelectedView(
17
- defaultSelectedView: string | DefaultSelectedViewBuilder | undefined,
18
- params: DefaultSelectedViewParams
19
- ) {
20
- if (!defaultSelectedView) {
21
- return undefined;
22
- } else if (typeof defaultSelectedView === "string") {
23
- return defaultSelectedView;
24
- } else {
25
- return defaultSelectedView(params);
26
- }
27
- }
28
-
29
- export function getLocalChangesBackup(collection: AdminCollection) {
30
- if (!collection.localChangesBackup) {
31
- return "manual_apply";
32
- }
33
-
34
- return collection.localChangesBackup;
35
- }
36
-
37
- /**
38
- * Returns the primary keys for a entity collection by inspecting the properties
39
- * and finding any properties with `isId`.
40
- * Fallbacks to `["id"]` if no properties are marked as `isId: true`.
41
- * @param collection
42
- */
43
- /**
44
- * A collection's child collections, as the panel's view model.
45
- *
46
- * `getSubcollections` in `@rebasepro/common` returns the contract type, so the
47
- * children of a resolved collection arrived un-flattened and reading
48
- * `child.icon` or `child.hideFromNavigation` failed. Resolving is not recursive
49
- * on purpose — children are fetched lazily through a thunk
50
- * (`childCollections`/`subcollections`), so walking the whole tree eagerly to
51
- * flatten it would defeat that.
52
- */
53
- export function getAdminSubcollections(
54
- collection: AdminCollection | CollectionConfig
55
- ): AdminCollection[] {
56
- return getAdminEntityChildViews(collection).map(view => view.collection);
57
- }
58
-
59
- /**
60
- * A collection's child views as the panel's view model, keeping the `source`
61
- * that says what kind of list each one is.
62
- *
63
- * The tabs need it: on a junction-backed relation the parent owns the *link*,
64
- * so the destructive action is a removal from this record, not a delete. Every
65
- * caller that renders a tab should prefer this over
66
- * {@link getAdminSubcollections}, which flattens that distinction away.
67
- */
68
- export function getAdminEntityChildViews(
69
- collection: AdminCollection | CollectionConfig
70
- ): { key: string; collection: AdminCollection; source: ChildViewSource }[] {
71
- return (getEntityChildViews(collection as CollectionConfig) ?? []).map(view => ({
72
- key: view.key,
73
- collection: resolveAdminCollection(view.collection),
74
- source: view.source
75
- }));
76
- }
@@ -1,195 +0,0 @@
1
- /**
2
- * The store behind a computed display value.
3
- *
4
- * A record's title is asked for far more often than it changes, and by many
5
- * components at once: a list of fifty rows, each row's relation chips, the
6
- * breadcrumb above them. Resolving per component is what makes an async display
7
- * value a bad idea — fifty rows becomes fifty reads, then fifty more on the next
8
- * render.
9
- *
10
- * So resolution is keyed by record *and* role, in-flight calls are shared, and
11
- * results are kept until something says otherwise. Deliberately not a React
12
- * thing: the same store answers an imperative caller (an export, a breadcrumb
13
- * built outside the tree), and it is testable without a renderer.
14
- */
15
- import type { EntityDisplayRole } from "@rebasepro/cms-types";
16
-
17
- type CacheEntry = {
18
- /** Set once resolved. `null` means "resolved to nothing", not "unknown". */
19
- value: unknown;
20
- } | {
21
- /** Shared by every caller that arrives while the first one is in flight. */
22
- promise: Promise<unknown>;
23
- };
24
-
25
- export type EntityDisplayKey = string;
26
-
27
- /** The identity of one role of one record, as a cache key. */
28
- export function entityDisplayKey(
29
- path: string,
30
- entityId: string | number | undefined,
31
- role: EntityDisplayRole
32
- ): EntityDisplayKey {
33
- return `${role} ${path} ${entityId ?? ""}`;
34
- }
35
-
36
- export class EntityDisplayCache {
37
-
38
- private readonly entries = new Map<EntityDisplayKey, CacheEntry>();
39
- private readonly listeners = new Set<() => void>();
40
-
41
- /**
42
- * The resolved value, or `undefined` when this pair has not been resolved
43
- * yet. `null` is a resolved absence, and the two must stay distinct: a
44
- * caller that reads "not yet" as "nothing" flickers its fallback in on every
45
- * mount.
46
- */
47
- peek(key: EntityDisplayKey): unknown | undefined {
48
- const entry = this.entries.get(key);
49
- if (!entry || "promise" in entry) return undefined;
50
- return entry.value;
51
- }
52
-
53
- /** True while a resolution for this pair is in flight. */
54
- isLoading(key: EntityDisplayKey): boolean {
55
- const entry = this.entries.get(key);
56
- return Boolean(entry && "promise" in entry);
57
- }
58
-
59
- /**
60
- * Resolve once per record and role. Concurrent callers share the first
61
- * call's promise; later callers get the cached value with no promise at all.
62
- *
63
- * A resolver that throws is recorded as "nothing" rather than retried: the
64
- * alternative is every render re-running a call that just failed. And it is
65
- * reported here, which is a correction.
66
- *
67
- * It used to say "the caller that saw the rejection is the one that logs
68
- * it", and `useEntityDisplay` duly attached a `.catch()` that warned. But
69
- * both failure paths below swallow and return a *resolved* promise, so that
70
- * catch could never run — the two halves each did the reasonable thing and
71
- * between them the log was unreachable. A resolver that blew up produced a
72
- * blank chip and total silence, which is the failure mode
73
- * `EntityDisplayResolver`'s own contract ("treated as `undefined` and logged
74
- * once") exists to rule out.
75
- *
76
- * Reporting belongs here for the reason the caller could not do it: this is
77
- * the one place that runs exactly once per key, so "once" is a property of
78
- * the code rather than a hope about how many components mount.
79
- */
80
- resolve(key: EntityDisplayKey, resolver: () => unknown): Promise<unknown> {
81
- const entry = this.entries.get(key);
82
- if (entry) {
83
- return "promise" in entry ? entry.promise : Promise.resolve(entry.value);
84
- }
85
-
86
- let produced: unknown;
87
- try {
88
- produced = resolver();
89
- } catch (error: unknown) {
90
- this.fail(key, error);
91
- return Promise.resolve(null);
92
- }
93
-
94
- // A synchronous resolver never enters the loading state. Putting it
95
- // through one would render every caller's fallback for a frame, for
96
- // nothing.
97
- if (!isPromise(produced)) {
98
- const value = normalize(produced);
99
- this.set(key, value);
100
- return Promise.resolve(value);
101
- }
102
-
103
- const promise = produced
104
- .then(resolved => {
105
- const value = normalize(resolved);
106
- this.set(key, value);
107
- return value;
108
- })
109
- .catch((error: unknown) => {
110
- this.fail(key, error);
111
- return null;
112
- });
113
-
114
- this.entries.set(key, { promise });
115
- return promise;
116
- }
117
-
118
- /**
119
- * Drop what is known about a record, so the next ask resolves again. Called
120
- * after a write: the row that just saved may be called something else now.
121
- */
122
- invalidate(path: string, entityId?: string | number): void {
123
- const suffix = ` ${path} ${entityId ?? ""}`;
124
- let changed = false;
125
- for (const key of [...this.entries.keys()]) {
126
- // `undefined` id means the whole collection — after an import, or a
127
- // locale switch that changes what every title in it reads.
128
- const matches = entityId === undefined
129
- ? key.includes(` ${path} `) || key.endsWith(` ${path} `)
130
- : key.endsWith(suffix);
131
- if (matches) {
132
- this.entries.delete(key);
133
- changed = true;
134
- }
135
- }
136
- if (changed) this.emit();
137
- }
138
-
139
- /** Drop everything. The user signed out, or the app swapped datasource. */
140
- clear(): void {
141
- if (this.entries.size === 0) return;
142
- this.entries.clear();
143
- this.emit();
144
- }
145
-
146
- subscribe(listener: () => void): () => void {
147
- this.listeners.add(listener);
148
- return () => {
149
- this.listeners.delete(listener);
150
- };
151
- }
152
-
153
- private set(key: EntityDisplayKey, value: unknown): void {
154
- this.entries.set(key, { value });
155
- this.emit();
156
- }
157
-
158
- /**
159
- * Record a failed resolution as "nothing", and say so once.
160
- *
161
- * The key is the message: it is `<role> <path> <id>`, which is exactly what a
162
- * reader needs to find the resolver that blew up. A warning with no key would
163
- * tell them a display resolver failed somewhere in a list of fifty rows.
164
- *
165
- * `console.warn` rather than a thrown error, because this runs while a row is
166
- * rendering: the contract is that a title which cannot be fetched must not
167
- * take down the row that shows it.
168
- */
169
- private fail(key: EntityDisplayKey, error: unknown): void {
170
- console.warn(`[rebase] Could not resolve display value for ${key}:`, error);
171
- this.set(key, null);
172
- }
173
-
174
- private emit(): void {
175
- for (const listener of [...this.listeners]) listener();
176
- }
177
- }
178
-
179
- /**
180
- * Empty strings and empty arrays are absences, not values — a title of `" "`
181
- * would otherwise beat the derived one and render as a blank heading.
182
- */
183
- function normalize(value: unknown): unknown {
184
- if (value === undefined || value === null) return null;
185
- if (typeof value === "string") {
186
- const trimmed = value.trim();
187
- return trimmed.length > 0 ? trimmed : null;
188
- }
189
- if (Array.isArray(value)) return value.length > 0 ? value : null;
190
- return value;
191
- }
192
-
193
- function isPromise(value: unknown): value is Promise<unknown> {
194
- return typeof (value as Promise<unknown> | undefined)?.then === "function";
195
- }
@@ -1,77 +0,0 @@
1
- /**
2
- * Reading a collection's `display` block.
3
- *
4
- * Two questions, kept apart because they are answered at different times: which
5
- * *property* fills a role (readable from values already in hand) and which
6
- * *resolver* fills it (may have to go to the network). A caller that cannot
7
- * await — a sort comparator, an export column, a server render — uses the key
8
- * and is documented to ignore resolvers.
9
- */
10
- import type {
11
- AdminCollection,
12
- EntityDisplayResolver,
13
- EntityDisplayRole
14
- } from "@rebasepro/cms-types";
15
-
16
- /**
17
- * What the collection declares for a role, before deciding which form it is.
18
- *
19
- * One field states it: `display[role]`. `admin.titleProperty` used to be a
20
- * second way to say `display.title` and is gone — a role with two spellings is
21
- * a role with two readers, and this one had grown seven.
22
- */
23
- function getDeclaredSource<M extends Record<string, unknown>>(
24
- collection: AdminCollection<M>,
25
- role: EntityDisplayRole
26
- ): string | EntityDisplayResolver<M, unknown> | undefined {
27
- const display = collection.display as Record<string, unknown> | undefined;
28
- const declared = display?.[role];
29
- return declared === undefined
30
- ? undefined
31
- : declared as string | EntityDisplayResolver<M, unknown>;
32
- }
33
-
34
- /**
35
- * The property path a role is declared to read, when it is declared as a path.
36
- *
37
- * Returns `undefined` for a role filled by a resolver — a resolver has no key —
38
- * and for a role the collection says nothing about, which is then derived.
39
- *
40
- * @group Collections
41
- */
42
- export function getDisplayPropertyKey<M extends Record<string, unknown>>(
43
- collection: AdminCollection<M>,
44
- role: EntityDisplayRole
45
- ): string | undefined {
46
- const declared = getDeclaredSource(collection, role);
47
- return typeof declared === "string" ? declared : undefined;
48
- }
49
-
50
- /**
51
- * The resolver a role is declared to use, when it is declared as one.
52
- *
53
- * @group Collections
54
- */
55
- export function getDisplayResolver<M extends Record<string, unknown>>(
56
- collection: AdminCollection<M>,
57
- role: EntityDisplayRole
58
- ): EntityDisplayResolver<M, unknown> | undefined {
59
- const declared = getDeclaredSource(collection, role);
60
- return typeof declared === "function" ? declared : undefined;
61
- }
62
-
63
- /**
64
- * True when the collection states this role at all, in either form.
65
- *
66
- * The derivation is a guess about what a collection probably means; a statement
67
- * outranks it, and the heuristics that look for "the first enum" or "the leading
68
- * relation" have to stand down when one exists.
69
- *
70
- * @group Collections
71
- */
72
- export function hasDeclaredDisplay<M extends Record<string, unknown>>(
73
- collection: AdminCollection<M>,
74
- role: EntityDisplayRole
75
- ): boolean {
76
- return getDeclaredSource(collection, role) !== undefined;
77
- }