@rebasepro/admin 0.12.0 → 0.12.1-canary.g06f263c

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 (56) hide show
  1. package/README.md +1 -1
  2. package/dist/{CollectionEditorDialog-Dpc75wIZ.js → CollectionEditorDialog--VwQl4pk.js} +75 -62
  3. package/dist/{CollectionEditorDialog-Dpc75wIZ.js.map → CollectionEditorDialog--VwQl4pk.js.map} +1 -1
  4. package/dist/{PropertyEditView-DH3XZC2x.js → PropertyEditView-QaEYldcT.js} +12 -9
  5. package/dist/{PropertyEditView-DH3XZC2x.js.map → PropertyEditView-QaEYldcT.js.map} +1 -1
  6. package/dist/{RouterCollectionsStudioView-BZLq4MGp.js → RouterCollectionsStudioView-DvRSUk9o.js} +5 -5
  7. package/dist/{RouterCollectionsStudioView-BZLq4MGp.js.map → RouterCollectionsStudioView-DvRSUk9o.js.map} +1 -1
  8. package/dist/collection_editor/useLocalCollectionsConfigController.d.ts +8 -0
  9. package/dist/collection_editor_ui.js +5 -5
  10. package/dist/collection_editor_ui.js.map +1 -1
  11. package/dist/editor.js +18 -10
  12. package/dist/editor.js.map +1 -1
  13. package/dist/{export-BzIpUdSK.js → export-BZZfQkT5.js} +2 -2
  14. package/dist/{export-BzIpUdSK.js.map → export-BZZfQkT5.js.map} +1 -1
  15. package/dist/{history-g9688IaK.js → history-Ao1HBAK3.js} +2 -2
  16. package/dist/{history-g9688IaK.js.map → history-Ao1HBAK3.js.map} +1 -1
  17. package/dist/{import-BjUG-cIY.js → import-AFJRUo99.js} +2 -2
  18. package/dist/{import-BjUG-cIY.js.map → import-AFJRUo99.js.map} +1 -1
  19. package/dist/index.js +105 -34
  20. package/dist/index.js.map +1 -1
  21. package/dist/markdown-CklalUUk.js.map +1 -1
  22. package/dist/{util-B5fJm1FA.js → util-ByPlsapJ.js} +103 -61
  23. package/dist/util-ByPlsapJ.js.map +1 -0
  24. package/package.json +32 -34
  25. package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
  26. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +65 -22
  27. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +38 -30
  28. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +1 -1
  29. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +1 -1
  30. package/src/collection_editor/useLocalCollectionsConfigController.tsx +127 -15
  31. package/src/components/AdminModeSyncer.tsx +1 -1
  32. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +1 -1
  33. package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +1 -1
  34. package/src/components/CollectionViewBinding/SplitListView.tsx +1 -1
  35. package/src/components/DefaultAppBar.tsx +1 -1
  36. package/src/components/DefaultDrawer.tsx +1 -1
  37. package/src/components/DetailViewBinding.tsx +1 -1
  38. package/src/components/DrawerNavigationItem.tsx +1 -1
  39. package/src/components/EditViewBinding.tsx +1 -1
  40. package/src/components/HomePage/FavouritesView.tsx +1 -1
  41. package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
  42. package/src/components/HomePage/SmallNavigationCard.tsx +1 -1
  43. package/src/components/RebaseLayout.tsx +1 -1
  44. package/src/components/RebaseNavigation.tsx +45 -5
  45. package/src/components/RebaseRouteDefs.tsx +1 -1
  46. package/src/components/SidePanelBinding.tsx +1 -1
  47. package/src/components/UserSelector.tsx +6 -5
  48. package/src/components/common/default_entity_actions.tsx +4 -3
  49. package/src/components/history/LastEditedByIndicator.tsx +4 -3
  50. package/src/hooks/navigation/useBuildUrlController.tsx +1 -1
  51. package/src/hooks/useBuildSideDialogsController.tsx +2 -2
  52. package/src/hooks/useBuildSidePanel.tsx +1 -1
  53. package/src/hooks/useHistory.ts +6 -5
  54. package/src/hooks/useResolvedUsers.ts +8 -5
  55. package/src/routes/RebaseRoute.tsx +2 -2
  56. package/dist/util-B5fJm1FA.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
 
2
2
  import React from "react";
3
3
 
4
- import { Link, useNavigate } from "react-router-dom";
4
+ import { Link, useNavigate } from "react-router";
5
5
  import { RebaseLogo, LanguageToggle } from "@rebasepro/app";
6
6
  import { ErrorBoundary, iconSize } from "@rebasepro/ui";
7
7
  import {
@@ -6,7 +6,7 @@ import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
6
6
  import { useNavigationStateController } from "../hooks/navigation/contexts/NavigationStateContext";
7
7
 
8
8
 
9
- import { Link, useNavigate } from "react-router-dom";
9
+ import { Link, useNavigate } from "react-router";
10
10
  import { AnalyticsEvent } from "@rebasepro/admin-types";
11
11
  import {
12
12
  Avatar,
@@ -41,7 +41,7 @@ import {
41
41
  } from "@rebasepro/app";
42
42
  import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
43
43
  import { useCollectionRegistryController } from "../hooks/navigation/contexts/CollectionRegistryContext";
44
- import { useNavigate } from "react-router-dom";
44
+ import { useNavigate } from "react-router";
45
45
  import { getValueInPath } from "@rebasepro/utils";
46
46
  import { getEntityTitlePropertyKeyForEntity, isUserSelectProperty, resolveTitleToString } from "../util/previews";
47
47
  import { getUserLabel, useResolvedUser } from "../hooks/useResolvedUsers";
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { NavLink } from "react-router-dom";
3
+ import { NavLink } from "react-router";
4
4
  import { cls, Tooltip } from "@rebasepro/ui";
5
5
 
6
6
  export function DrawerNavigationItem({
@@ -48,7 +48,7 @@ const EntityHistoryView = lazy(() => import("../components/history").then(m => (
48
48
  import { createFormexStub, getEntityFromCache } from "@rebasepro/app";
49
49
  import { usePermissions } from "@rebasepro/app";
50
50
  import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
51
- import { useNavigate } from "react-router-dom";
51
+ import { useNavigate } from "react-router";
52
52
 
53
53
  import { MAIN_TAB_VALUE, JSON_TAB_VALUE, HISTORY_TAB_VALUE } from "../util/view_constants";
54
54
  export { MAIN_TAB_VALUE, JSON_TAB_VALUE, HISTORY_TAB_VALUE };
@@ -1,5 +1,5 @@
1
1
  import type { NavigationEntry } from "@rebasepro/admin-types";
2
- import { useNavigate } from "react-router-dom";
2
+ import { useNavigate } from "react-router";
3
3
  ;
4
4
  import { Chip, Collapse, StarIcon } from "@rebasepro/ui";
5
5
  import { useUserConfigurationPersistence } from "@rebasepro/app";
@@ -1,6 +1,6 @@
1
1
 
2
2
  import type { NavigationEntry, AdminCollection } from "@rebasepro/admin-types";
3
- import { useNavigate } from "react-router-dom";
3
+ import { useNavigate } from "react-router";
4
4
  ;
5
5
  import { IconButton, StarIcon, Tooltip } from "@rebasepro/ui";
6
6
  import { NavigationCard } from "./NavigationCard";
@@ -1,6 +1,6 @@
1
1
  import { cardClickableMixin, cardMixin, cls, Typography } from "@rebasepro/ui";
2
2
 
3
- import { Link } from "react-router-dom";
3
+ import { Link } from "react-router";
4
4
 
5
5
  export type SmallNavigationCardProps = {
6
6
  name: string,
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Outlet } from "react-router-dom";
2
+ import { Outlet } from "react-router";
3
3
  import {
4
4
  useAdminModeController,
5
5
  useComponentOverride,
@@ -38,6 +38,7 @@ import { SidePanelProvider } from "./SidePanelProvider";
38
38
  // Collection editor internals — used when collectionEditor is enabled
39
39
  import { useLocalCollectionsConfigController } from "../collection_editor/useLocalCollectionsConfigController";
40
40
  import { ConfigControllerProvider } from "../collection_editor/ConfigControllerProvider";
41
+ import { useCollectionsConfigController } from "../collection_editor/useCollectionsConfigController";
41
42
 
42
43
  // Lazy-load the schema view — only fetched when studio schema tool is active
43
44
  const CollectionsStudioView = lazy(() =>
@@ -168,12 +169,19 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
168
169
  }
169
170
  }, [dataSources]);
170
171
 
172
+ // `readOnly` is left undefined unless the developer set it: the controller
173
+ // asks the backend whether its schema editor will accept a write, which is
174
+ // the only process that knows. Deciding it here from `process.env.NODE_ENV`
175
+ // — the *frontend bundle's* build mode — is what made the editor offer
176
+ // itself against production backends, `baas` projects and servers without
177
+ // `ts-morph`, and turned every save into a bare 404.
171
178
  const internalConfigController = useLocalCollectionsConfigController(
172
179
  rebaseClient,
173
180
  resolvedCollections,
174
181
  collectionEditorEnabled ? {
175
- readOnly: collectionEditorOptions?.readOnly ?? process.env.NODE_ENV === "production",
176
- getAuthToken: collectionEditorOptions?.getAuthToken ?? authController?.getAuthToken
182
+ readOnly: collectionEditorOptions?.readOnly,
183
+ getAuthToken: collectionEditorOptions?.getAuthToken ?? authController?.getAuthToken,
184
+ authKey: authController?.user?.uid ?? null
177
185
  } : { readOnly: true }
178
186
  );
179
187
 
@@ -189,13 +197,22 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
189
197
  group: "Database",
190
198
  icon: "LayoutList",
191
199
  nestedRoutes: true,
200
+ // Reads the controller from context at render time rather than
201
+ // capturing it here. A view is a React *element*, and the
202
+ // navigation state controller resolves the view list once and holds
203
+ // on to the elements it resolved — so a controller passed as a prop
204
+ // is frozen at whatever it was on the first render, forever. That
205
+ // hid every later change: the editor stayed writable after the
206
+ // backend said it was read-only, and the collection list never
207
+ // refreshed. Everything else in the editor already reads this from
208
+ // `ConfigControllerProvider`, which wraps this subtree.
192
209
  view: (
193
210
  <Suspense fallback={<CircularProgressCenter/>}>
194
- <CollectionsStudioView configController={internalConfigController}/>
211
+ <CollectionsStudioViewFromContext/>
195
212
  </Suspense>
196
213
  )
197
214
  };
198
- }, [collectionEditorEnabled, registry.studioConfig, internalConfigController]);
215
+ }, [collectionEditorEnabled, registry.studioConfig]);
199
216
 
200
217
  const devViews = useMemo(() => {
201
218
  const base = registry.studioConfig?.devViews ?? [];
@@ -275,6 +292,7 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
275
292
  collectionRegistryController={collectionRegistryController}
276
293
  urlController={urlController}
277
294
  navigationStateController={navigationStateController}
295
+ canWriteToCodebase={!internalConfigController.readOnly}
278
296
  />
279
297
  {children}
280
298
  </SidePanelProvider>
@@ -301,6 +319,17 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
301
319
  return navigationContent;
302
320
  }
303
321
 
322
+ /**
323
+ * The injected "Edit collections" view, bound to the live config controller.
324
+ *
325
+ * Exists so the element stored in the navigation state does not close over a
326
+ * controller instance — see the comment at its construction site.
327
+ */
328
+ function CollectionsStudioViewFromContext() {
329
+ const configController = useCollectionsConfigController();
330
+ return <CollectionsStudioView configController={configController}/>;
331
+ }
332
+
304
333
  /**
305
334
  * Internal component that auto-registers admin controllers into the
306
335
  * self-assembling Studio bridge. Must be rendered inside both the
@@ -309,14 +338,25 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
309
338
  function BridgeAutoRegistrar({
310
339
  collectionRegistryController,
311
340
  urlController,
312
- navigationStateController
341
+ navigationStateController,
342
+ canWriteToCodebase
313
343
  }: {
314
344
  collectionRegistryController: CollectionRegistryController;
315
345
  urlController: UrlController;
316
346
  navigationStateController: NavigationStateController;
347
+ canWriteToCodebase: boolean;
317
348
  }) {
318
349
  useBridgeRegistration("collectionRegistry", collectionRegistryController);
319
350
  useBridgeRegistration("urlController", urlController);
320
351
  useBridgeRegistration("navigationState", navigationStateController);
352
+
353
+ // Studio tools that would write into the project's collection source — the
354
+ // RLS editor's policy save for a mapped table, its "Import to codebase" —
355
+ // ask the bridge whether that write can land. The default is `true`, which
356
+ // is what an admin panel sitting next to its own `collectionsDir` assumes;
357
+ // it is wrong for exactly the same backends that make the collection editor
358
+ // read-only, so the two now answer from one source.
359
+ const capabilities = useMemo(() => ({ codebase: canWriteToCodebase }), [canWriteToCodebase]);
360
+ useBridgeRegistration("capabilities", capabilities);
321
361
  return null;
322
362
  }
@@ -1,5 +1,5 @@
1
1
  import React, { lazy, Suspense, useMemo } from "react";
2
- import { Route } from "react-router-dom";
2
+ import { Route } from "react-router";
3
3
  import {
4
4
  useRebaseRegistry,
5
5
  RebaseRoutes,
@@ -8,7 +8,7 @@ import { IconButton, Maximize2Icon, XIcon } from "@rebasepro/ui";
8
8
  import { EditViewBinding } from "./EditViewBinding";
9
9
  import { DetailViewBinding } from "./DetailViewBinding";
10
10
  import { useSideDialogContext } from "./SideDialogs";
11
- import { useNavigate } from "react-router-dom";
11
+ import { useNavigate } from "react-router";
12
12
  import { saveEntityToMemoryCache, useComponentOverride } from "@rebasepro/app";
13
13
  import { useCollectionRegistryController } from "../hooks/navigation/contexts/CollectionRegistryContext";
14
14
  import { useSidePanel } from "../hooks/useSidePanel";
@@ -21,7 +21,7 @@ import * as React from "react";
21
21
  import { useCallback, useEffect, useRef, useState } from "react";
22
22
  import { Command as CommandPrimitive } from "cmdk";
23
23
  import { User } from "@rebasepro/types";
24
- import { useRebaseClient, useAuthController, UserDisplay } from "@rebasepro/app";
24
+ import { apiBaseOf, useRebaseClient, useAuthController, UserDisplay } from "@rebasepro/app";
25
25
  import { EmptyValue } from "../preview";
26
26
 
27
27
  interface UserSelectorItem {
@@ -37,7 +37,7 @@ const DEFAULT_PAGE_SIZE = 10;
37
37
  * Local hook to fetch users from the collection REST API with search and pagination.
38
38
  */
39
39
  function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE }: { pageSize?: number } = {}) {
40
- const client = useRebaseClient<{ baseUrl?: string }>();
40
+ const client = useRebaseClient<{ baseUrl?: string, apiPath?: string }>();
41
41
  const { getAuthToken } = useAuthController();
42
42
 
43
43
  const [items, setItems] = useState<UserSelectorItem[]>([]);
@@ -48,7 +48,8 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE }: { pageSize?: number }
48
48
  const userCacheRef = useRef<Map<string, User>>(new Map());
49
49
 
50
50
  const fetchUsers = useCallback(async (searchStr: string, offset: number, append: boolean) => {
51
- if (!client?.baseUrl) return;
51
+ const apiBase = apiBaseOf(client);
52
+ if (!apiBase) return;
52
53
  setIsLoading(true);
53
54
  try {
54
55
  const token = await getAuthToken?.();
@@ -57,7 +58,7 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE }: { pageSize?: number }
57
58
  offset: String(offset)
58
59
  });
59
60
  if (searchStr) params.set("search", searchStr);
60
- const response = await fetch(`${client.baseUrl}/api/users?${params}`, {
61
+ const response = await fetch(`${apiBase}/users?${params}`, {
61
62
  headers: token ? { Authorization: `Bearer ${token}` } : {}
62
63
  });
63
64
  if (!response.ok) throw new Error("Failed to fetch users");
@@ -86,7 +87,7 @@ user };
86
87
  } finally {
87
88
  setIsLoading(false);
88
89
  }
89
- }, [client?.baseUrl, getAuthToken, pageSize]);
90
+ }, [client?.baseUrl, client?.apiPath, getAuthToken, pageSize]);
90
91
 
91
92
  useEffect(() => {
92
93
  offsetRef.current = 0;
@@ -21,6 +21,7 @@ import {
21
21
  import type { User } from "@rebasepro/types";
22
22
  import type { EntityAction, UserCreationResult } from "@rebasepro/admin-types";
23
23
  import {
24
+ apiBaseOf,
24
25
  useAuthController,
25
26
  useRebaseClient,
26
27
  useSnackbarController,
@@ -246,7 +247,7 @@ export function ResetPasswordActionDialog({
246
247
  open: boolean;
247
248
  onClose: () => void;
248
249
  }) {
249
- const client = useRebaseClient<{ baseUrl?: string }>();
250
+ const client = useRebaseClient<{ baseUrl?: string, apiPath?: string }>();
250
251
  const { getAuthToken } = useAuthController();
251
252
  const snackbarController = useSnackbarController();
252
253
  const { t } = useTranslation();
@@ -270,9 +271,9 @@ export function ResetPasswordActionDialog({
270
271
  setLoading(true);
271
272
  setError(null);
272
273
  try {
273
- const baseUrl = client?.baseUrl || "";
274
+ const apiBase = apiBaseOf(client) ?? "";
274
275
  const token = await getAuthToken?.();
275
- const response = await fetch(`${baseUrl}/api/admin/users/${user.uid}/reset-password`, {
276
+ const response = await fetch(`${apiBase}/admin/users/${user.uid}/reset-password`, {
276
277
  method: "POST",
277
278
  headers: {
278
279
  "Content-Type": "application/json",
@@ -1,7 +1,7 @@
1
1
 
2
2
  import React, { useEffect, useState, useCallback } from "react";
3
3
  ;
4
- import { useApiConfig } from "@rebasepro/app";
4
+ import { useApiBase, useApiConfig } from "@rebasepro/app";
5
5
  import { useAuthController } from "@rebasepro/app";
6
6
  import { HistoryEntryData } from "../../hooks";
7
7
  import type { AdminCollection } from "@rebasepro/admin-types";
@@ -35,6 +35,7 @@ export function LastEditedByIndicator({
35
35
  collection: AdminCollection;
36
36
  }) {
37
37
  const apiConfig = useApiConfig();
38
+ const apiBase = useApiBase();
38
39
  const authController = useAuthController();
39
40
  const [latestEntry, setLatestEntry] = useState<HistoryEntryData | undefined>();
40
41
 
@@ -46,7 +47,7 @@ export function LastEditedByIndicator({
46
47
  const headers: Record<string, string> = {};
47
48
  if (token) headers["Authorization"] = `Bearer ${token}`;
48
49
 
49
- const url = `${apiConfig.apiUrl}/api/data/${collection.slug}/${entityId}/history?limit=1&offset=0`;
50
+ const url = `${apiBase}/data/${collection.slug}/${entityId}/history?limit=1&offset=0`;
50
51
  const response = await fetch(url, { headers });
51
52
 
52
53
  if (response.ok) {
@@ -59,7 +60,7 @@ export function LastEditedByIndicator({
59
60
  // Silently fail — this is a non-critical UI indicator
60
61
  console.debug("Failed to fetch latest history entry:", error);
61
62
  }
62
- }, [apiConfig, entityId, collection.slug]);
63
+ }, [apiConfig, apiBase, entityId, collection.slug]);
63
64
 
64
65
  useEffect(() => {
65
66
  fetchLatest();
@@ -2,7 +2,7 @@ import type { UrlController, NavigateOptions } from "@rebasepro/admin-types";
2
2
  import { useCallback, useMemo } from "react";
3
3
  import { removeInitialAndTrailingSlashes, resolveCollectionPathIds } from "@rebasepro/app";
4
4
  import { CollectionRegistryController } from "@rebasepro/types";
5
- import { useNavigate } from "react-router-dom";
5
+ import { useNavigate } from "react-router";
6
6
 
7
7
  export function useBuildUrlController(props: {
8
8
  basePath: string,
@@ -1,9 +1,9 @@
1
1
  import type { SideDialogPanelProps, SideDialogsController } from "../hooks/useSideDialogsController";
2
2
  import { useCallback, useEffect, useRef, useState, useMemo } from "react";
3
- import { useLocation, useNavigate } from "react-router-dom";
3
+ import { useLocation, useNavigate } from "react-router";
4
4
  ;
5
5
  import { deepEqual as equal } from "fast-equals"
6
- import { Location } from "react-router-dom";
6
+ import { Location } from "react-router";
7
7
 
8
8
  interface SideDialogLocationState {
9
9
  base_location?: Location;
@@ -4,7 +4,7 @@ import type { CustomizationController, SidePanelBindingProps, SideDialogPanelPro
4
4
  import { useCallback, useEffect, useRef, useMemo } from "react";
5
5
  import { CollectionRegistryController } from "@rebasepro/types";
6
6
  import { AuthController } from "@rebasepro/admin-types";
7
- import { useLocation } from "react-router-dom";
7
+ import { useLocation } from "react-router";
8
8
  import { NavigationViewInternal } from "@rebasepro/app";
9
9
  import { getNavigationEntriesFromPath, removeInitialAndTrailingSlashes, removeTrailingSlash, resolveDefaultSelectedView } from "@rebasepro/app";
10
10
  import { resolvedSelectedEntityView } from "../util/resolutions";
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useRef, useState, useMemo } from "react";
2
- import { useApiConfig } from "@rebasepro/app";
2
+ import { useApiBase, useApiConfig } from "@rebasepro/app";
3
3
  import type { EntityHistoryEntry } from "@rebasepro/types";
4
4
 
5
5
  /**
@@ -31,6 +31,7 @@ export function useHistory(params: {
31
31
  }): UseEntityHistoryResult {
32
32
  const { slug, entityId, enabled = true, pageSize = 10 } = params;
33
33
  const apiConfig = useApiConfig();
34
+ const apiBase = useApiBase();
34
35
 
35
36
  const [entries, setEntries] = useState<HistoryEntryData[]>([]);
36
37
  const [total, setTotal] = useState(0);
@@ -59,7 +60,7 @@ export function useHistory(params: {
59
60
  };
60
61
  if (token) headers["Authorization"] = `Bearer ${token}`;
61
62
 
62
- const url = `${apiConfig.apiUrl}/api/data/${slug}/${entityId}/history?limit=${pageSize}&offset=${currentOffset}`;
63
+ const url = `${apiBase}/data/${slug}/${entityId}/history?limit=${pageSize}&offset=${currentOffset}`;
63
64
  const response = await fetch(url, { headers,
64
65
  signal });
65
66
 
@@ -90,7 +91,7 @@ signal });
90
91
  } finally {
91
92
  setIsLoading(false);
92
93
  }
93
- }, [apiConfig, slug, entityId, enabled, pageSize]);
94
+ }, [apiConfig, apiBase, slug, entityId, enabled, pageSize]);
94
95
 
95
96
  // Single unified effect: reset state when entity changes, fetch when offset changes.
96
97
  // Uses AbortController to cancel stale requests on entity change or unmount.
@@ -136,7 +137,7 @@ signal });
136
137
  };
137
138
  if (token) headers["Authorization"] = `Bearer ${token}`;
138
139
 
139
- const url = `${apiConfig.apiUrl}/api/data/${slug}/${entityId}/history/${historyId}/revert`;
140
+ const url = `${apiBase}/data/${slug}/${entityId}/history/${historyId}/revert`;
140
141
  const response = await fetch(url, { method: "POST",
141
142
  headers });
142
143
 
@@ -154,7 +155,7 @@ headers });
154
155
 
155
156
  // Return the reverted entity data so callers can update the form
156
157
  return result.data as Record<string, unknown>;
157
- }, [apiConfig, slug, entityId]);
158
+ }, [apiConfig, apiBase, slug, entityId]);
158
159
 
159
160
  return useMemo(() => ({
160
161
  entries,
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useState } from "react";
2
2
  import type { User } from "@rebasepro/types";
3
- import { useAuthController, useRebaseClient } from "@rebasepro/app";
3
+ import { apiBaseOf, useAuthController, useRebaseClient } from "@rebasepro/app";
4
4
 
5
5
  /**
6
6
  * Resolves the auth users behind `userSelect` values.
@@ -31,12 +31,12 @@ queue: new Set() };
31
31
  return cache;
32
32
  }
33
33
 
34
- async function fetchUsers(baseUrl: string, ids: string[], token: string | undefined): Promise<Map<string, User | null>> {
34
+ async function fetchUsers(apiBase: string, ids: string[], token: string | undefined): Promise<Map<string, User | null>> {
35
35
  const resolved = new Map<string, User | null>();
36
36
  for (const id of ids) resolved.set(id, null);
37
37
 
38
38
  const params = new URLSearchParams({ ids: ids.join(",") });
39
- const response = await fetch(`${baseUrl}/api/users?${params}`, {
39
+ const response = await fetch(`${apiBase}/users?${params}`, {
40
40
  headers: token ? { Authorization: `Bearer ${token}` } : {}
41
41
  });
42
42
  if (!response.ok) {
@@ -103,9 +103,12 @@ function requestUser(
103
103
  * when it cannot be resolved.
104
104
  */
105
105
  export function useResolvedUser(uid: string | undefined): User | null {
106
- const client = useRebaseClient<{ baseUrl?: string }>();
106
+ const client = useRebaseClient<{ baseUrl?: string, apiPath?: string }>();
107
107
  const { getAuthToken } = useAuthController();
108
- const baseUrl = client?.baseUrl;
108
+ /* The API prefix, not the bare origin: it keys the cache and is what a
109
+ request is built from, and a backend with a non-default `basePath`
110
+ serves neither at `/api`. */
111
+ const baseUrl = apiBaseOf(client);
109
112
 
110
113
  const [user, setUser] = useState<User | null>(() =>
111
114
  (baseUrl && uid) ? getCache(baseUrl).users.get(uid) ?? null : null);
@@ -1,10 +1,10 @@
1
1
 
2
2
  import type { ViewMode, AdminCollection } from "@rebasepro/admin-types";
3
- import { useLocation } from "react-router-dom";
3
+ import { useLocation } from "react-router";
4
4
  import { EditViewBinding } from "../components/EditViewBinding";
5
5
  import { DetailViewBinding } from "../components/DetailViewBinding";
6
6
  import { useCallback, useEffect, useRef, useState } from "react";
7
- import { useNavigate } from "react-router-dom";
7
+ import { useNavigate } from "react-router";
8
8
  import { CollectionViewBinding } from "../components/CollectionViewBinding/CollectionViewBinding";
9
9
  import { NotFoundPage, useUserConfigurationPersistence, useComponentOverride, useNavigationBlocker } from "@rebasepro/app";
10
10
  import { resolveOpenEntityMode, resolveViewMode } from "../util/view_mode";