@rebasepro/core 0.0.1-canary.f81da60 → 0.1.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.
@@ -7,10 +7,37 @@ export interface RebaseCollectionsPluginOptions {
7
7
  }
8
8
  /**
9
9
  * A Vite plugin that dynamically loads and automatically wires Rebase collections.
10
- * It provides a virtual module "virtual:rebase-collections" that statically exports the resolved collections array.
10
+ *
11
+ * It provides two capabilities:
12
+ * 1. A **virtual module** `"virtual:rebase-collections"` that statically exports
13
+ * the resolved collections array.
14
+ * 2. A **transform hook** that converts string-based component references
15
+ * (e.g. `Field: "../../components/MyField"`) into `LazyComponentRef` objects
16
+ * (`{ __rebaseLazy: true, load: () => import(...) }`), enabling code-splitting
17
+ * and preventing the backend from loading React-dependent modules.
11
18
  */
12
19
  export declare function rebaseCollectionsPlugin(options: RebaseCollectionsPluginOptions): {
13
20
  name: string;
21
+ configResolved(config: {
22
+ root: string;
23
+ }): void;
14
24
  resolveId(id: string): string | null;
15
25
  load(id: string): string | null;
26
+ /**
27
+ * Transform collection files to convert string component references
28
+ * into lazy-loading `LazyComponentRef` objects.
29
+ *
30
+ * Example transform:
31
+ * ```
32
+ * // Input
33
+ * Field: "../../frontend/src/components/MyField"
34
+ *
35
+ * // Output
36
+ * Field: { __rebaseLazy: true, load: () => import("../../frontend/src/components/MyField") }
37
+ * ```
38
+ */
39
+ transform(code: string, id: string): {
40
+ code: string;
41
+ map: null;
42
+ } | null;
16
43
  };
@@ -1,8 +1,22 @@
1
+ import path from "path";
2
+ const LAZY_COMPONENT_KEYS = ["Field", "Preview", "Builder"];
3
+ function buildTransformRegex() {
4
+ const keys = LAZY_COMPONENT_KEYS.join("|");
5
+ return new RegExp(
6
+ `((?:${keys})\\s*:\\s*)(['"])(\\.\\.?\\/[^'"]+)\\2`,
7
+ "g"
8
+ );
9
+ }
1
10
  function rebaseCollectionsPlugin(options) {
2
11
  const virtualModuleId = "virtual:rebase-collections";
3
12
  const resolvedVirtualModuleId = "\0" + virtualModuleId;
13
+ let resolvedCollectionsDir;
14
+ const transformRegex = buildTransformRegex();
4
15
  return {
5
16
  name: "rebase-collections-plugin",
17
+ configResolved(config) {
18
+ resolvedCollectionsDir = path.isAbsolute(options.collectionsDir) ? options.collectionsDir : path.resolve(config.root, options.collectionsDir);
19
+ },
6
20
  resolveId(id) {
7
21
  if (id === virtualModuleId) {
8
22
  return resolvedVirtualModuleId;
@@ -21,6 +35,34 @@ function rebaseCollectionsPlugin(options) {
21
35
  `;
22
36
  }
23
37
  return null;
38
+ },
39
+ /**
40
+ * Transform collection files to convert string component references
41
+ * into lazy-loading `LazyComponentRef` objects.
42
+ *
43
+ * Example transform:
44
+ * ```
45
+ * // Input
46
+ * Field: "../../frontend/src/components/MyField"
47
+ *
48
+ * // Output
49
+ * Field: { __rebaseLazy: true, load: () => import("../../frontend/src/components/MyField") }
50
+ * ```
51
+ */
52
+ transform(code, id) {
53
+ if (!resolvedCollectionsDir) return null;
54
+ if (!id.startsWith(resolvedCollectionsDir)) return null;
55
+ if (!/\.tsx?$/.test(id)) return null;
56
+ transformRegex.lastIndex = 0;
57
+ if (!transformRegex.test(code)) return null;
58
+ transformRegex.lastIndex = 0;
59
+ const transformed = code.replace(
60
+ transformRegex,
61
+ (_match, prefix, quote, importPath) => {
62
+ return `${prefix}{ __rebaseLazy: true, load: () => import(${quote}${importPath}${quote}) }`;
63
+ }
64
+ );
65
+ return { code: transformed, map: null };
24
66
  }
25
67
  };
26
68
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/core",
3
3
  "type": "module",
4
- "version": "0.0.1-canary.f81da60",
4
+ "version": "0.1.0",
5
5
  "description": "Rebase core — framework-agnostic runtime for data-driven admin panels",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -52,11 +52,11 @@
52
52
  "i18next": "^23.16.4",
53
53
  "notistack": "^3.0.2",
54
54
  "react-i18next": "^14.1.3",
55
- "@rebasepro/common": "0.0.1-canary.f81da60",
56
- "@rebasepro/ui": "0.0.1-canary.f81da60",
57
- "@rebasepro/types": "0.0.1-canary.f81da60",
58
- "@rebasepro/utils": "0.0.1-canary.f81da60",
59
- "@rebasepro/formex": "0.0.1-canary.f81da60"
55
+ "@rebasepro/utils": "0.1.0",
56
+ "@rebasepro/common": "0.1.0",
57
+ "@rebasepro/formex": "0.1.0",
58
+ "@rebasepro/types": "0.1.0",
59
+ "@rebasepro/ui": "0.1.0"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "react": ">=19.0.0",
@@ -0,0 +1,66 @@
1
+ import React, { useState } from "react";
2
+ import { useInternalUserManagementController } from "../hooks/useInternalUserManagementController";
3
+ import { useAuthController } from "../hooks/useAuthController";
4
+ import { useTranslation } from "../hooks/useTranslation";
5
+ import { useSnackbarController } from "../hooks/useSnackbarController";
6
+ import { Button, CircularProgress, Typography } from "@rebasepro/ui";
7
+
8
+ export interface BootstrapAdminBannerProps {
9
+ className?: string;
10
+ }
11
+
12
+ export function BootstrapAdminBanner({
13
+ className
14
+ }: BootstrapAdminBannerProps) {
15
+ const userManagement = useInternalUserManagementController();
16
+ const { user: loggedInUser } = useAuthController();
17
+ const { t } = useTranslation();
18
+ const snackbarController = useSnackbarController();
19
+ const [bootstrapping, setBootstrapping] = useState(false);
20
+
21
+ // If we're not running locally, don't render anything
22
+ if (typeof window !== "undefined" && window.location.hostname !== "localhost" && window.location.hostname !== "127.0.0.1") {
23
+ return null;
24
+ }
25
+
26
+ if (!userManagement || !loggedInUser) {
27
+ return null;
28
+ }
29
+
30
+ const { users, loading: delegateLoading, bootstrapAdmin, usersError } = userManagement;
31
+ const hasAdmin = users.some(u => u.roles?.includes("admin"));
32
+
33
+ if (delegateLoading || hasAdmin || usersError || !bootstrapAdmin) {
34
+ return null;
35
+ }
36
+
37
+ const handleBootstrap = async () => {
38
+ if (!bootstrapAdmin) return;
39
+ setBootstrapping(true);
40
+ try {
41
+ await bootstrapAdmin();
42
+ snackbarController.open({ type: "success", message: t("bootstrap_admin_success") || "Admin successfully created" });
43
+ window.location.reload();
44
+ } catch (error: unknown) {
45
+ snackbarController.open({ type: "error", message: error instanceof Error ? error.message : t("failed_to_bootstrap_admin") || "Failed to bootstrap admin" });
46
+ } finally {
47
+ setBootstrapping(false);
48
+ }
49
+ };
50
+
51
+ return (
52
+ <div className={`bg-yellow-100 dark:bg-yellow-900 border border-yellow-400 dark:border-yellow-700 rounded p-4 flex items-center justify-between ${className || ""}`}>
53
+ <div>
54
+ <Typography variant="label" className="text-yellow-800 dark:text-yellow-200">
55
+ {t("no_users_or_roles_defined") || "No admins found. Click to add your user as admin."}
56
+ </Typography>
57
+ </div>
58
+ <Button
59
+ onClick={handleBootstrap}
60
+ disabled={bootstrapping}
61
+ >
62
+ {bootstrapping ? <CircularProgress size="small"/> : (t("add_logged_user_as_admin") || "Add logged user as admin")}
63
+ </Button>
64
+ </div>
65
+ );
66
+ }
@@ -1,6 +1,23 @@
1
-
2
1
  import React, { ReactNode, useEffect, useRef, useState } from "react";
3
2
 
3
+ /** Google Identity Services SDK — injected by the GIS <script> tag. */
4
+ declare global {
5
+ interface Window {
6
+ google?: {
7
+ accounts: {
8
+ oauth2: {
9
+ initCodeClient(config: {
10
+ client_id: string;
11
+ scope: string;
12
+ ux_mode: "popup" | "redirect";
13
+ callback: (response: { code?: string; error?: string }) => void;
14
+ }): { requestCode(): void };
15
+ };
16
+ };
17
+ };
18
+ }
19
+ }
20
+
4
21
  import { Button, cls, IconButton, LoadingButton, Menu, MenuItem, TextField, Typography, iconSize } from "@rebasepro/ui";
5
22
  import { ArrowLeftIcon, MailIcon, MoonIcon, SunIcon, SunMoonIcon } from "lucide-react";
6
23
  import { AuthControllerExtended, User } from "@rebasepro/types";
@@ -119,7 +136,9 @@ export function LoginView({
119
136
 
120
137
  // Resolve capabilities — explicit props override authController.capabilities
121
138
  const caps = authController.capabilities ?? {};
122
- const isBootstrapMode = needsSetup ?? (authController as any).needsSetup ?? false;
139
+ const isBootstrapMode = needsSetup
140
+ ?? ("needsSetup" in authController && !!(authController as { needsSetup?: boolean }).needsSetup)
141
+ ?? false;
123
142
  const canRegister = registrationEnabled ?? caps.registration ?? false;
124
143
  const hasGoogleLogin = googleEnabled ?? caps.googleLogin ?? false;
125
144
  const hasPasswordReset = caps.passwordReset ?? !!authController.forgotPassword;
@@ -243,15 +262,18 @@ export function LoginView({
243
262
  />
244
263
  )}
245
264
  {showRegistration && (
246
- <Button
247
- className="w-full"
248
- variant="filled"
249
- color="primary"
250
- size="large"
251
- onClick={() => switchMode("register")}
252
- >
253
- Create an account
254
- </Button>
265
+ <div className="mt-2 text-center">
266
+ <Typography variant="body2" color="secondary">
267
+ Don&apos;t have an account?{" "}
268
+ <button
269
+ type="button"
270
+ className="font-semibold hover:underline cursor-pointer text-primary-600 dark:text-primary-400"
271
+ onClick={() => switchMode("register")}
272
+ >
273
+ Create one
274
+ </button>
275
+ </Typography>
276
+ </div>
255
277
  )}
256
278
  </div>
257
279
  )}
@@ -342,24 +364,30 @@ function GoogleLoginButton({
342
364
  googleClientId: string,
343
365
  authController: AuthControllerExtended
344
366
  }) {
345
- const tokenClientRef = useRef<any>(null);
367
+ const codeClientRef = useRef<{ requestCode(): void } | null>(null);
346
368
 
347
369
  useEffect(() => {
348
370
  if (!authController.googleLogin) return;
349
371
 
350
- const google = (window as any).google;
351
- if (!google || tokenClientRef.current) return;
372
+ const google = window.google;
373
+ if (!google || codeClientRef.current) return;
352
374
 
353
- tokenClientRef.current = google.accounts.oauth2.initTokenClient({
375
+ codeClientRef.current = google.accounts.oauth2.initCodeClient({
354
376
  client_id: googleClientId,
355
377
  scope: "openid email profile",
356
- callback: async (response: { access_token?: string; error?: string }) => {
357
- if (response.error || !response.access_token) {
378
+ ux_mode: "popup",
379
+ callback: async (response: { code?: string; error?: string }) => {
380
+ if (response.error || !response.code) {
358
381
  console.error("Google login error:", response.error);
359
382
  return;
360
383
  }
361
384
  try {
362
- await authController.googleLogin!(response.access_token, "accessToken");
385
+ // Send the authorization code to the backend.
386
+ // redirectUri "postmessage" is required when using popup ux_mode.
387
+ await authController.googleLogin!({
388
+ code: response.code,
389
+ redirectUri: "postmessage"
390
+ });
363
391
  } catch (err: unknown) {
364
392
  console.error("Google login error:", err);
365
393
  }
@@ -368,11 +396,11 @@ function GoogleLoginButton({
368
396
  }, [googleClientId, authController]);
369
397
 
370
398
  const handleClick = () => {
371
- if (!tokenClientRef.current) {
399
+ if (!codeClientRef.current) {
372
400
  console.error("Google Sign-In not loaded");
373
401
  return;
374
402
  }
375
- tokenClientRef.current.requestAccessToken();
403
+ codeClientRef.current.requestCode();
376
404
  };
377
405
 
378
406
  return (
@@ -81,7 +81,16 @@ export function useDataTableController<M extends Record<string, any> = any, USER
81
81
  const paginationEnabled = collection.pagination === undefined || Boolean(collection.pagination);
82
82
  const pageSize = typeof collection.pagination === "number" ? collection.pagination : DEFAULT_PAGE_SIZE;
83
83
 
84
- const [searchString, setSearchString] = React.useState<string | undefined>();
84
+ const location = useLocation();
85
+
86
+ const [searchString, setSearchString] = React.useState<string | undefined>(() => {
87
+ if (updateUrl) {
88
+ const params = new URLSearchParams(location.search);
89
+ const urlSearch = params.get("search");
90
+ return urlSearch ? decodeURIComponent(urlSearch) : undefined;
91
+ }
92
+ return undefined;
93
+ });
85
94
 
86
95
  const checkFilterCombination = useCallback((filterValues: FilterValues<any>,
87
96
  sortBy?: [string, "asc" | "desc"]) => {
@@ -97,8 +106,6 @@ export function useDataTableController<M extends Record<string, any> = any, USER
97
106
  return sort;
98
107
  }, [sort, fixedFilter]);
99
108
 
100
- const location = useLocation();
101
-
102
109
  const {
103
110
  filterValues: filterUrl,
104
111
  sortBy: sortUrl
@@ -128,6 +135,11 @@ export function useDataTableController<M extends Record<string, any> = any, USER
128
135
  } else {
129
136
  setSortBy(urlSortBy as [Extract<keyof M, string> | (string & {}), "asc" | "desc"] | undefined);
130
137
  }
138
+
139
+ // Sync search string from URL
140
+ const urlParams = new URLSearchParams(location.search);
141
+ const urlSearch = urlParams.get("search");
142
+ setSearchString(urlSearch ? decodeURIComponent(urlSearch) : undefined);
131
143
  }, [location.search, updateUrl, fixedFilter, checkFilterCombination]);
132
144
 
133
145
  useUpdateUrl(filterValues, sortBy, searchString, updateUrl);
@@ -22,4 +22,4 @@ export * from "./LoginView";
22
22
 
23
23
  export * from "./RebaseAuth";
24
24
 
25
-
25
+ export * from "./BootstrapAdminBanner";
@@ -116,20 +116,21 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
116
116
  }
117
117
 
118
118
  // 2. Auto-derive from the client's WebSocket connection (Rebase backend)
119
- const ws = (client as any)?.ws;
120
- if (ws && typeof ws.executeSql === "function") {
119
+ const ws = client?.ws;
120
+ if (ws && typeof (ws as Record<string, unknown>).executeSql === "function") {
121
+ const wsAdmin = ws as import("@rebasepro/types").DatabaseAdmin;
121
122
  return {
122
- executeSql: ws.executeSql.bind(ws),
123
- fetchAvailableDatabases: ws.fetchAvailableDatabases?.bind(ws),
124
- fetchAvailableRoles: ws.fetchAvailableRoles?.bind(ws),
125
- fetchCurrentDatabase: ws.fetchCurrentDatabase?.bind(ws),
126
- fetchUnmappedTables: ws.fetchUnmappedTables?.bind(ws),
127
- fetchTableMetadata: ws.fetchTableMetadata?.bind(ws),
123
+ executeSql: wsAdmin.executeSql!.bind(wsAdmin),
124
+ fetchAvailableDatabases: wsAdmin.fetchAvailableDatabases?.bind(wsAdmin),
125
+ fetchAvailableRoles: wsAdmin.fetchAvailableRoles?.bind(wsAdmin),
126
+ fetchCurrentDatabase: wsAdmin.fetchCurrentDatabase?.bind(wsAdmin),
127
+ fetchUnmappedTables: wsAdmin.fetchUnmappedTables?.bind(wsAdmin),
128
+ fetchTableMetadata: wsAdmin.fetchTableMetadata?.bind(wsAdmin),
128
129
  // Branch admin capabilities
129
- ...(typeof ws.createBranch === "function" ? {
130
- createBranch: ws.createBranch.bind(ws),
131
- deleteBranch: ws.deleteBranch.bind(ws),
132
- listBranches: ws.listBranches.bind(ws)
130
+ ...(typeof wsAdmin.createBranch === "function" ? {
131
+ createBranch: wsAdmin.createBranch.bind(wsAdmin),
132
+ deleteBranch: wsAdmin.deleteBranch!.bind(wsAdmin),
133
+ listBranches: wsAdmin.listBranches!.bind(wsAdmin)
133
134
  } : {})
134
135
  };
135
136
  }
@@ -224,7 +225,7 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
224
225
  </RebaseI18nProvider>
225
226
  );
226
227
 
227
- const resolvedApiUrl = apiUrl ?? (client as any)?.baseUrl;
228
+ const resolvedApiUrl = apiUrl ?? client?.baseUrl;
228
229
 
229
230
  if (resolvedApiUrl) {
230
231
  return (
@@ -255,7 +256,12 @@ function RebaseInternal({
255
256
  }) : children;
256
257
 
257
258
  const plugins = customizationController.plugins;
258
- if (!loading && plugins && plugins.length > 0) {
259
+ const authController = context.authController;
260
+ const authReady = !loading
261
+ && !authController.authLoading
262
+ && (Boolean(authController.user) || authController.loginSkipped);
263
+
264
+ if (authReady && plugins && plugins.length > 0) {
259
265
  return (
260
266
  <PluginProviderStack
261
267
  plugins={plugins}
@@ -45,3 +45,4 @@ export * from "./useRebaseClient";
45
45
 
46
46
  export * from "./useAnalyticsController";
47
47
  export * from "./useUserConfigurationPersistence";
48
+ export * from "./useResolvedComponent";
@@ -7,10 +7,18 @@ const STORAGE_KEY_PREFIX = "rebase-collapsed-groups";
7
7
  * with localStorage persistence. Automatically cleans up stale group entries
8
8
  * when groups are removed from the navigation.
9
9
  *
10
+ * Groups that have never been toggled by the user fall back to
11
+ * `defaults[groupName]` (driven by `collapsedByDefault` in config).
12
+ *
10
13
  * @param groupNames - Array of group names to track
11
14
  * @param namespace - Namespace for localStorage key (e.g., "home", "drawer") to allow independent state
15
+ * @param defaults - Optional map of group name → collapsed boolean from config
12
16
  */
13
- export function useCollapsedGroups(groupNames: string[], namespace = "default") {
17
+ export function useCollapsedGroups(
18
+ groupNames: string[],
19
+ namespace = "default",
20
+ defaults?: Record<string, boolean>
21
+ ) {
14
22
  const storageKey = `${STORAGE_KEY_PREFIX}-${namespace}`;
15
23
 
16
24
  // Load collapsed groups from localStorage on mount
@@ -57,13 +65,23 @@ export function useCollapsedGroups(groupNames: string[], namespace = "default")
57
65
  }, [groupNames]);
58
66
 
59
67
  const isGroupCollapsed = useCallback((name: string) => {
60
- return !!collapsedGroups[name];
61
- }, [collapsedGroups]);
68
+ // If the user has explicitly toggled this group, use that value
69
+ if (name in collapsedGroups) {
70
+ return collapsedGroups[name];
71
+ }
72
+ // Otherwise fall back to the config default
73
+ return defaults?.[name] ?? false;
74
+ }, [collapsedGroups, defaults]);
62
75
 
63
76
  const toggleGroupCollapsed = useCallback((name: string) => {
64
- setCollapsedGroups(prev => ({ ...prev,
65
- [name]: !prev[name] }));
66
- }, []);
77
+ setCollapsedGroups(prev => {
78
+ // Resolve current effective state (explicit or default)
79
+ const currentlyCollapsed = name in prev
80
+ ? prev[name]
81
+ : (defaults?.[name] ?? false);
82
+ return { ...prev, [name]: !currentlyCollapsed };
83
+ });
84
+ }, [defaults]);
67
85
 
68
86
  return useMemo(() => ({
69
87
  isGroupCollapsed,
@@ -71,3 +89,27 @@ export function useCollapsedGroups(groupNames: string[], namespace = "default")
71
89
  }), [isGroupCollapsed, toggleGroupCollapsed]);
72
90
  }
73
91
 
92
+ /**
93
+ * Build a defaults map from navigationGroupMappings for a given namespace.
94
+ * Returns a Record<groupName, collapsed> that can be passed to useCollapsedGroups.
95
+ */
96
+ export function buildCollapsedDefaults(
97
+ mappings: Array<{ name: string; collapsedByDefault?: boolean | { drawer?: boolean; home?: boolean } }> | undefined,
98
+ namespace: "drawer" | "home"
99
+ ): Record<string, boolean> {
100
+ if (!mappings) return {};
101
+ const result: Record<string, boolean> = {};
102
+ for (const mapping of mappings) {
103
+ const val = mapping.collapsedByDefault;
104
+ if (val === undefined) continue;
105
+ if (typeof val === "boolean") {
106
+ result[mapping.name] = val;
107
+ } else {
108
+ const ns = val[namespace];
109
+ if (ns !== undefined) {
110
+ result[mapping.name] = ns;
111
+ }
112
+ }
113
+ }
114
+ return result;
115
+ }
@@ -1,5 +1,6 @@
1
1
  import { AuthController, RebaseContext, User } from "@rebasepro/types";
2
2
  import { useAuthController } from "./useAuthController";
3
+ import { useRebaseClient } from "./useRebaseClient";
3
4
  import { useData } from "./data/useData";
4
5
  import { useStorageSource } from "./useStorageSource";
5
6
  import { useSnackbarController } from "./useSnackbarController";
@@ -11,9 +12,7 @@ import { useEffectiveRoleController } from "./useEffectiveRoleController";
11
12
  import React, { useEffect, useContext } from "react";
12
13
  import { useInternalUserManagementController } from "./useInternalUserManagementController";
13
14
 
14
- // Temporary context for databaseAdmin - assuming we might add one, or we need to get it from Rebase.tsx.
15
- // Wait, `databaseAdmin` hasn't been added to a context yet. Let's add it to a context in Rebase.tsx later.
16
- // Let's create a placeholder for databaseAdmin context access.
15
+ // DatabaseAdmin is provided by <Rebase> via DatabaseAdminContext.
17
16
  import { DatabaseAdminContext } from "../contexts/DatabaseAdminContext";
18
17
 
19
18
  /**
@@ -27,6 +26,8 @@ import { DatabaseAdminContext } from "../contexts/DatabaseAdminContext";
27
26
  */
28
27
  export const useRebaseContext = <USER extends User = User, AuthControllerType extends AuthController<USER> = AuthController<USER>>(): RebaseContext<USER, AuthControllerType> => {
29
28
 
29
+ const client = useRebaseClient<any>();
30
+
30
31
  const authController = useAuthController<USER, AuthControllerType>();
31
32
  const data = useData();
32
33
  const storageSource = useStorageSource();
@@ -52,7 +53,8 @@ export const useRebaseContext = <USER extends User = User, AuthControllerType ex
52
53
  analyticsController,
53
54
  userManagement,
54
55
  effectiveRoleController,
55
- databaseAdmin
56
+ databaseAdmin,
57
+ client: client! // Client should be provided
56
58
  });
57
59
 
58
60
  React.useEffect(() => {
@@ -67,9 +69,12 @@ export const useRebaseContext = <USER extends User = User, AuthControllerType ex
67
69
  analyticsController,
68
70
  userManagement,
69
71
  effectiveRoleController,
70
- databaseAdmin
72
+ databaseAdmin,
73
+ client: client!
71
74
  };
72
- }, [authController, dialogsController, effectiveRoleController, data, databaseAdmin]);
75
+ }, [authController, data, storageSource, snackbarController, userConfigPersistence,
76
+ dialogsController, customizationController, analyticsController, userManagement,
77
+ effectiveRoleController, databaseAdmin, client]);
73
78
 
74
79
  return rebaseContextRef.current;
75
80
  }