@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.
- package/README.md +4 -1
- package/dist/components/index.d.ts +0 -3
- package/dist/contexts/AuthControllerContext.d.ts +2 -1
- package/dist/contexts/CustomizationControllerContext.d.ts +2 -1
- package/dist/contexts/DialogsProvider.d.ts +2 -1
- package/dist/contexts/RebaseDataContext.d.ts +10 -1
- package/dist/contexts/StorageSourceContext.d.ts +11 -1
- package/dist/core/RebaseProps.d.ts +2 -12
- package/dist/debug/index.d.ts +19 -0
- package/dist/debug.js +3631 -0
- package/dist/hooks/data/delete.d.ts +16 -7
- package/dist/hooks/data/save.d.ts +18 -4
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useAdminModeController.d.ts +5 -4
- package/dist/hooks/useNavigationGroupLabel.d.ts +17 -0
- package/dist/hooks/useStudioBridge.d.ts +33 -0
- package/dist/index.es.js +2599 -6980
- package/dist/index.es.js.map +1 -1
- package/dist/vitePlugin.d.ts +22 -0
- package/dist/vitePlugin.js +40 -1
- package/package.json +42 -31
- package/src/auth/api.ts +0 -134
- package/src/auth/index.ts +0 -26
- package/src/auth/types.ts +0 -115
- package/src/auth/useRebaseAuthController.ts +0 -345
- package/src/collections/collection_view_config.ts +0 -76
- package/src/collections/entity-display-cache.ts +0 -195
- package/src/collections/entity-display.ts +0 -77
- package/src/collections/entity_image_preview.ts +0 -61
- package/src/collections/filter-operator-resolution.ts +0 -119
- package/src/collections/form-layout.ts +0 -394
- package/src/collections/index.ts +0 -13
- package/src/collections/navigation_from_path.ts +0 -123
- package/src/collections/navigation_utils.ts +0 -171
- package/src/collections/parent_references_from_path.ts +0 -59
- package/src/collections/property-path.ts +0 -30
- package/src/collections/property_presentation.ts +0 -264
- package/src/collections/summary-property.ts +0 -114
- package/src/collections/title-property.ts +0 -312
- package/src/components/AIIcon.tsx +0 -47
- package/src/components/AuthSimulationMenu.tsx +0 -0
- package/src/components/ConfirmationDialog.tsx +0 -48
- package/src/components/Debug/UIReferenceView.tsx +0 -1053
- package/src/components/Debug/UIStyleGuide.tsx +0 -159
- package/src/components/Debug/collection-views/CardViewDemo.tsx +0 -87
- package/src/components/Debug/collection-views/CollectionTableDemo.tsx +0 -107
- package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +0 -89
- package/src/components/Debug/collection-views/index.ts +0 -3
- package/src/components/Debug/collection-views/sample_data.ts +0 -44
- package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +0 -387
- package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +0 -1100
- package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +0 -131
- package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +0 -288
- package/src/components/Debug/crm-dashboard/RecentActivity.tsx +0 -213
- package/src/components/Debug/crm-dashboard/TaskFilters.tsx +0 -182
- package/src/components/Debug/crm-dashboard/TaskTable.tsx +0 -425
- package/src/components/Debug/crm-dashboard/TasksView.tsx +0 -234
- package/src/components/ErrorTooltip.tsx +0 -12
- package/src/components/ErrorView.tsx +0 -94
- package/src/components/LanguageToggle.tsx +0 -59
- package/src/components/LoginView/LoginView.tsx +0 -1235
- package/src/components/LoginView/index.ts +0 -2
- package/src/components/LoginView/oauth-redirect-flow.ts +0 -184
- package/src/components/NotFoundPage.tsx +0 -27
- package/src/components/RebaseAuth.tsx +0 -27
- package/src/components/RebaseLogo.tsx +0 -88
- package/src/components/SchemaDriftBanner.tsx +0 -102
- package/src/components/UnsavedChangesDialog.tsx +0 -46
- package/src/components/UserDisplay.tsx +0 -54
- package/src/components/UserSelectPopover.tsx +0 -399
- package/src/components/UserSettingsView.tsx +0 -318
- package/src/components/common/index.ts +0 -6
- package/src/components/common/table_height.tsx +0 -21
- package/src/components/common/types.tsx +0 -66
- package/src/components/common/useColumnsIds.tsx +0 -261
- package/src/components/common/useDataTableController.tsx +0 -571
- package/src/components/common/useDebouncedData.ts +0 -49
- package/src/components/common/useScrollRestoration.tsx +0 -68
- package/src/components/index.tsx +0 -26
- package/src/contexts/AdminModeController.tsx +0 -11
- package/src/contexts/AnalyticsContext.tsx +0 -4
- package/src/contexts/AuthControllerContext.tsx +0 -4
- package/src/contexts/CollectionResolverContext.tsx +0 -24
- package/src/contexts/CollectionScopeContext.tsx +0 -68
- package/src/contexts/ComponentOverrideContext.tsx +0 -81
- package/src/contexts/CustomizationControllerContext.tsx +0 -4
- package/src/contexts/DataDriverContext.tsx +0 -4
- package/src/contexts/DataSourcesContext.tsx +0 -36
- package/src/contexts/DatabaseAdminContext.tsx +0 -4
- package/src/contexts/DialogsProvider.tsx +0 -56
- package/src/contexts/EffectiveRoleController.tsx +0 -12
- package/src/contexts/ModeController.tsx +0 -11
- package/src/contexts/RebaseClientInstanceContext.tsx +0 -7
- package/src/contexts/RebaseDataContext.tsx +0 -4
- package/src/contexts/SnackbarProvider.tsx +0 -15
- package/src/contexts/StorageSourceContext.tsx +0 -4
- package/src/contexts/StorageSourcesContext.tsx +0 -30
- package/src/contexts/UserConfigurationPersistenceContext.tsx +0 -4
- package/src/contexts/index.ts +0 -18
- package/src/core/PluginLifecycleManager.tsx +0 -96
- package/src/core/PluginProviderStack.tsx +0 -40
- package/src/core/Rebase.tsx +0 -488
- package/src/core/RebaseProps.tsx +0 -333
- package/src/core/RebaseRouter.tsx +0 -18
- package/src/core/RebaseRoutes.tsx +0 -24
- package/src/core/index.tsx +0 -5
- package/src/hooks/ApiConfigContext.tsx +0 -83
- package/src/hooks/data/collectionQuery.ts +0 -55
- package/src/hooks/data/delete.ts +0 -56
- package/src/hooks/data/save.ts +0 -78
- package/src/hooks/data/useCollection.tsx +0 -183
- package/src/hooks/data/useData.tsx +0 -18
- package/src/hooks/data/useDataOrder.ts +0 -26
- package/src/hooks/data/useFetch.tsx +0 -156
- package/src/hooks/data/useRelationSelector.tsx +0 -280
- package/src/hooks/index.tsx +0 -45
- package/src/hooks/useAdminModeController.tsx +0 -23
- package/src/hooks/useAnalyticsController.tsx +0 -8
- package/src/hooks/useAuthController.tsx +0 -15
- package/src/hooks/useAuthSubscription.ts +0 -130
- package/src/hooks/useBackendStorageSource.ts +0 -295
- package/src/hooks/useBridgeRegistration.tsx +0 -32
- package/src/hooks/useBrowserTitleAndIcon.tsx +0 -23
- package/src/hooks/useBuildAdminModeController.tsx +0 -29
- package/src/hooks/useBuildEffectiveRoleController.tsx +0 -30
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +0 -83
- package/src/hooks/useBuildModeController.tsx +0 -73
- package/src/hooks/useClipboard.tsx +0 -158
- package/src/hooks/useCollapsedGroups.ts +0 -116
- package/src/hooks/useComponentOverride.tsx +0 -59
- package/src/hooks/useCustomizationController.tsx +0 -14
- package/src/hooks/useDialogsController.tsx +0 -15
- package/src/hooks/useEffectiveRoleController.tsx +0 -10
- package/src/hooks/useLargeLayout.tsx +0 -65
- package/src/hooks/useModeController.tsx +0 -23
- package/src/hooks/useNavigationBlocker.tsx +0 -108
- package/src/hooks/usePermissions.ts +0 -45
- package/src/hooks/useRebaseClient.tsx +0 -10
- package/src/hooks/useRebaseContext.tsx +0 -84
- package/src/hooks/useRebaseRegistry.tsx +0 -91
- package/src/hooks/useResolvedComponent.tsx +0 -195
- package/src/hooks/useSlot.tsx +0 -64
- package/src/hooks/useSnackbarController.tsx +0 -80
- package/src/hooks/useStorageSource.tsx +0 -11
- package/src/hooks/useStudioBridge.tsx +0 -258
- package/src/hooks/useTranslation.ts +0 -38
- package/src/hooks/useUnsavedChangesDialog.tsx +0 -67
- package/src/hooks/useUserConfigurationPersistence.tsx +0 -11
- package/src/i18n/RebaseI18nProvider.tsx +0 -159
- package/src/index.ts +0 -32
- package/src/internal/common.tsx +0 -28
- package/src/internal/useRestoreScroll.tsx +0 -69
- package/src/locales/de.ts +0 -941
- package/src/locales/en.ts +0 -1021
- package/src/locales/es.ts +0 -962
- package/src/locales/fr.ts +0 -941
- package/src/locales/hi.ts +0 -941
- package/src/locales/it.ts +0 -941
- package/src/locales/pt.ts +0 -949
- package/src/util/constants.ts +0 -7
- package/src/util/createFormexStub.tsx +0 -66
- package/src/util/entity_cache.ts +0 -242
- package/src/util/enums.ts +0 -34
- package/src/util/icon_list.ts +0 -22
- package/src/util/icons.tsx +0 -129
- package/src/util/index.ts +0 -13
- package/src/util/local_storage.ts +0 -66
- package/src/util/previews.ts +0 -127
- package/src/util/useStorageUploadController.tsx +0 -354
- package/src/vitePlugin.ts +0 -215
- /package/dist/{components/Debug → debug}/UIReferenceView.d.ts +0 -0
- /package/dist/{components/Debug → debug}/UIStyleGuide.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/CardViewDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/CollectionTableDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/KanbanBoardDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/index.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/sample_data.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/CalendarWidget.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/CrmDashboardDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/DashboardMetrics.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/PipelineOverview.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/RecentActivity.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TaskFilters.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TaskTable.d.ts +0 -0
- /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
|
-
}
|