@firecms/core 3.0.0-beta.4.pre.1 → 3.0.0-beta.5
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 +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +1 -1
- package/dist/components/EntityPreview.d.ts +2 -2
- package/dist/components/FieldCaption.d.ts +1 -0
- package/dist/contexts/AuthControllerContext.d.ts +1 -1
- package/dist/hooks/data/delete.d.ts +2 -2
- package/dist/hooks/data/save.d.ts +1 -1
- package/dist/hooks/data/useDataSource.d.ts +1 -1
- package/dist/hooks/data/useEntityFetch.d.ts +3 -3
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useBuildNavigationController.d.ts +1 -2
- package/dist/hooks/useProjectLog.d.ts +2 -2
- package/dist/hooks/useValidateAuthenticator.d.ts +25 -0
- package/dist/index.es.js +2333 -2257
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +5 -5
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useRestoreScroll.d.ts +1 -1
- package/dist/preview/PropertyPreviewProps.d.ts +1 -4
- package/dist/types/auth.d.ts +30 -1
- package/dist/types/collections.d.ts +3 -3
- package/dist/types/datasource.d.ts +1 -1
- package/dist/types/entity_callbacks.d.ts +2 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/navigation.d.ts +4 -4
- package/dist/types/plugins.d.ts +2 -2
- package/dist/types/roles.d.ts +31 -0
- package/dist/types/storage.d.ts +11 -3
- package/dist/types/user.d.ts +5 -0
- package/dist/util/collections.d.ts +1 -1
- package/dist/util/useTraceUpdate.d.ts +1 -0
- package/package.json +10 -10
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
- package/src/components/EntityPreview.tsx +5 -2
- package/src/components/FieldCaption.tsx +1 -0
- package/src/components/HomePage/DefaultHomePage.tsx +1 -1
- package/src/components/HomePage/NavigationCard.tsx +1 -1
- package/src/components/ReferenceWidget.tsx +1 -1
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +1 -1
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
- package/src/contexts/AuthControllerContext.tsx +1 -1
- package/src/core/FireCMS.tsx +1 -1
- package/src/form/EntityForm.tsx +1 -1
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
- package/src/hooks/data/delete.ts +3 -3
- package/src/hooks/data/save.ts +1 -1
- package/src/hooks/data/useDataSource.tsx +1 -1
- package/src/hooks/data/useEntityFetch.tsx +3 -3
- package/src/hooks/index.tsx +2 -0
- package/src/hooks/useBuildNavigationController.tsx +63 -43
- package/src/hooks/useProjectLog.tsx +7 -5
- package/src/hooks/useValidateAuthenticator.tsx +135 -0
- package/src/internal/useBuildSideEntityController.tsx +3 -0
- package/src/preview/PropertyPreviewProps.tsx +1 -11
- package/src/preview/components/BooleanPreview.tsx +4 -2
- package/src/preview/components/ReferencePreview.tsx +1 -1
- package/src/types/auth.tsx +40 -1
- package/src/types/collections.ts +3 -3
- package/src/types/datasource.ts +1 -1
- package/src/types/entity_callbacks.ts +2 -2
- package/src/types/index.ts +1 -0
- package/src/types/navigation.ts +6 -6
- package/src/types/plugins.tsx +2 -2
- package/src/types/properties.ts +2 -1
- package/src/types/roles.ts +41 -0
- package/src/types/storage.ts +12 -3
- package/src/types/user.ts +7 -0
- package/src/util/collections.ts +1 -1
- package/src/util/strings.ts +2 -2
- package/src/util/useTraceUpdate.tsx +2 -1
|
@@ -29,7 +29,7 @@ import { getParentReferencesFromPath } from "../util/parent_references_from_path
|
|
|
29
29
|
const DEFAULT_BASE_PATH = "/";
|
|
30
30
|
const DEFAULT_COLLECTION_PATH = "/c";
|
|
31
31
|
|
|
32
|
-
type BuildNavigationContextProps<EC extends EntityCollection, UserType extends User> = {
|
|
32
|
+
export type BuildNavigationContextProps<EC extends EntityCollection, UserType extends User> = {
|
|
33
33
|
basePath?: string,
|
|
34
34
|
baseCollectionPath?: string,
|
|
35
35
|
authController: AuthController<UserType>;
|
|
@@ -65,10 +65,10 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
65
65
|
injectCollections
|
|
66
66
|
} = props;
|
|
67
67
|
|
|
68
|
-
const collectionsRef = useRef<EntityCollection[] |
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
68
|
+
const collectionsRef = useRef<EntityCollection[] | undefined>();
|
|
69
|
+
const viewsRef = useRef<CMSView[] | undefined>();
|
|
70
|
+
const adminViewsRef = useRef<CMSView[] | undefined>();
|
|
71
|
+
|
|
72
72
|
const [initialised, setInitialised] = useState<boolean>(false);
|
|
73
73
|
|
|
74
74
|
const [topLevelNavigation, setTopLevelNavigation] = useState<TopNavigationResult | undefined>(undefined);
|
|
@@ -98,7 +98,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
98
98
|
path: collection.id ?? collection.path,
|
|
99
99
|
collection,
|
|
100
100
|
description: collection.description?.trim(),
|
|
101
|
-
group: collection
|
|
101
|
+
group: getGroup(collection)
|
|
102
102
|
} satisfies TopNavigationEntry)
|
|
103
103
|
: undefined))
|
|
104
104
|
.filter(Boolean) as TopNavigationEntry[],
|
|
@@ -111,8 +111,8 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
111
111
|
path: view.path,
|
|
112
112
|
view,
|
|
113
113
|
description: view.description?.trim(),
|
|
114
|
-
group: view
|
|
115
|
-
}satisfies TopNavigationEntry)
|
|
114
|
+
group: getGroup(view)
|
|
115
|
+
} satisfies TopNavigationEntry)
|
|
116
116
|
: undefined)
|
|
117
117
|
.filter(Boolean) as TopNavigationEntry[],
|
|
118
118
|
...(adminViews ?? []).map(view =>
|
|
@@ -164,22 +164,23 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
164
164
|
return;
|
|
165
165
|
|
|
166
166
|
try {
|
|
167
|
+
|
|
167
168
|
const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
|
|
168
169
|
resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
|
|
169
170
|
resolveCMSViews(viewsProp, authController, dataSourceDelegate),
|
|
170
171
|
resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
|
|
171
172
|
]
|
|
172
173
|
);
|
|
174
|
+
|
|
173
175
|
if (
|
|
174
176
|
!equal(collectionsRef.current, resolvedCollections) ||
|
|
175
|
-
!equal(
|
|
176
|
-
!equal(
|
|
177
|
+
!equal(viewsRef.current, resolvedViews) ||
|
|
178
|
+
!equal(adminViewsRef.current, resolvedAdminViews) ||
|
|
177
179
|
!equal(topLevelNavigation, computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder))
|
|
178
180
|
) {
|
|
179
181
|
collectionsRef.current = resolvedCollections;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
setAdminViews(resolvedAdminViews);
|
|
182
|
+
viewsRef.current = resolvedViews;
|
|
183
|
+
adminViewsRef.current = resolvedAdminViews;
|
|
183
184
|
setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews, resolvedAdminViews, viewsOrder));
|
|
184
185
|
}
|
|
185
186
|
} catch (e) {
|
|
@@ -189,25 +190,34 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
189
190
|
|
|
190
191
|
setNavigationLoading(false);
|
|
191
192
|
setInitialised(true);
|
|
192
|
-
|
|
193
|
+
|
|
194
|
+
}, [
|
|
195
|
+
collectionsProp,
|
|
196
|
+
collectionPermissions,
|
|
197
|
+
authController.user,
|
|
198
|
+
authController.initialLoading,
|
|
199
|
+
viewsProp,
|
|
200
|
+
adminViewsProp,
|
|
201
|
+
computeTopNavigation,
|
|
202
|
+
injectCollections
|
|
203
|
+
]);
|
|
193
204
|
|
|
194
205
|
useEffect(() => {
|
|
195
206
|
refreshNavigation();
|
|
196
207
|
}, [refreshNavigation]);
|
|
197
208
|
|
|
198
|
-
const getCollection = useCallback(
|
|
209
|
+
const getCollection = useCallback((
|
|
199
210
|
idOrPath: string,
|
|
200
211
|
entityId?: string,
|
|
201
212
|
includeUserOverride = false
|
|
202
213
|
): EC | undefined => {
|
|
203
|
-
|
|
214
|
+
const collections = collectionsRef.current;
|
|
204
215
|
if (!collections)
|
|
205
216
|
return undefined;
|
|
206
217
|
|
|
207
218
|
const baseCollection = getCollectionByPathOrId(removeInitialAndTrailingSlashes(idOrPath), collections);
|
|
208
219
|
|
|
209
220
|
const userOverride = includeUserOverride ? userConfigPersistence?.getCollectionConfig(idOrPath) : undefined;
|
|
210
|
-
|
|
211
221
|
const overriddenCollection = baseCollection ? mergeDeep(baseCollection, userOverride) : undefined;
|
|
212
222
|
|
|
213
223
|
let result: Partial<EntityCollection> | undefined = overriddenCollection;
|
|
@@ -228,14 +238,12 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
228
238
|
|
|
229
239
|
return { ...overriddenCollection, ...result } as EC;
|
|
230
240
|
|
|
231
|
-
}, [
|
|
232
|
-
basePath,
|
|
233
|
-
baseCollectionPath,
|
|
234
|
-
collections
|
|
235
|
-
]);
|
|
241
|
+
}, [userConfigPersistence]);
|
|
236
242
|
|
|
237
243
|
const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
|
|
238
|
-
|
|
244
|
+
|
|
245
|
+
const collections = collectionsRef.current;
|
|
246
|
+
let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
|
|
239
247
|
if (!currentCollections)
|
|
240
248
|
throw Error("Collections have not been initialised yet");
|
|
241
249
|
|
|
@@ -251,10 +259,12 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
251
259
|
|
|
252
260
|
return undefined;
|
|
253
261
|
|
|
254
|
-
}, [
|
|
262
|
+
}, []);
|
|
255
263
|
|
|
256
264
|
const getCollectionFromIds = useCallback(<EC extends EntityCollection>(ids: string[]): EC | undefined => {
|
|
257
|
-
|
|
265
|
+
|
|
266
|
+
const collections = collectionsRef.current;
|
|
267
|
+
let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
|
|
258
268
|
if (!currentCollections)
|
|
259
269
|
throw Error("Collections have not been initialised yet");
|
|
260
270
|
|
|
@@ -270,7 +280,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
270
280
|
|
|
271
281
|
return undefined;
|
|
272
282
|
|
|
273
|
-
}, [
|
|
283
|
+
}, []);
|
|
274
284
|
|
|
275
285
|
const isUrlCollectionPath = useCallback(
|
|
276
286
|
(path: string): boolean => removeInitialAndTrailingSlashes(path + "/").startsWith(removeInitialAndTrailingSlashes(fullCollectionPath) + "/"),
|
|
@@ -292,17 +302,19 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
292
302
|
[]);
|
|
293
303
|
|
|
294
304
|
const resolveAliasesFrom = useCallback((path: string): string => {
|
|
305
|
+
const collections = collectionsRef.current;
|
|
295
306
|
if (!collections)
|
|
296
307
|
throw Error("Collections have not been initialised yet");
|
|
297
308
|
return resolveCollectionPathIds(path, collections);
|
|
298
|
-
}, [
|
|
309
|
+
}, []);
|
|
299
310
|
|
|
300
311
|
const getAllParentReferencesForPath = useCallback((path: string): EntityReference[] => {
|
|
312
|
+
const collections = collectionsRef.current ?? [];
|
|
301
313
|
return getParentReferencesFromPath({
|
|
302
314
|
path,
|
|
303
315
|
collections
|
|
304
316
|
});
|
|
305
|
-
}, [
|
|
317
|
+
}, []);
|
|
306
318
|
|
|
307
319
|
const getParentCollectionIds = useCallback((path: string): string[] => {
|
|
308
320
|
|
|
@@ -321,24 +333,24 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
321
333
|
}, [getAllParentReferencesForPath])
|
|
322
334
|
|
|
323
335
|
const convertIdsToPaths = useCallback((ids: string[]): string[] => {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return paths;
|
|
336
|
+
const collections = collectionsRef.current;
|
|
337
|
+
let currentCollections = collections;
|
|
338
|
+
const paths: string[] = [];
|
|
339
|
+
for (let i = 0; i < ids.length; i++) {
|
|
340
|
+
const id = ids[i];
|
|
341
|
+
const collection: EntityCollection | undefined = currentCollections!.find(c => c.id === id);
|
|
342
|
+
if (!collection)
|
|
343
|
+
throw Error(`Collection with id ${id} not found`);
|
|
344
|
+
paths.push(collection.path);
|
|
345
|
+
currentCollections = collection.subcollections;
|
|
335
346
|
}
|
|
336
|
-
|
|
347
|
+
return paths;
|
|
348
|
+
}, [getCollectionFromIds]);
|
|
337
349
|
|
|
338
350
|
return {
|
|
339
|
-
collections,
|
|
340
|
-
views,
|
|
341
|
-
adminViews,
|
|
351
|
+
collections: collectionsRef.current,
|
|
352
|
+
views: viewsRef.current,
|
|
353
|
+
adminViews: adminViewsRef.current,
|
|
342
354
|
loading: !initialised || navigationLoading,
|
|
343
355
|
navigationLoadingError,
|
|
344
356
|
homeUrl,
|
|
@@ -431,3 +443,11 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
|
|
|
431
443
|
}
|
|
432
444
|
return resolvedViews;
|
|
433
445
|
}
|
|
446
|
+
|
|
447
|
+
function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
|
|
448
|
+
const trimmed = collectionOrView.group?.trim();
|
|
449
|
+
if (!trimmed || trimmed === "") {
|
|
450
|
+
return "Views";
|
|
451
|
+
}
|
|
452
|
+
return trimmed ?? "Views";
|
|
453
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { AuthController } from "../types";
|
|
2
|
+
import { AuthController, FireCMSPlugin } from "../types";
|
|
3
3
|
|
|
4
4
|
export const DEFAULT_SERVER_DEV = "https://api-kdoe6pj3qq-ey.a.run.app";
|
|
5
5
|
export const DEFAULT_SERVER = "https://api-drplyi3b6q-ey.a.run.app";
|
|
@@ -9,7 +9,7 @@ export type AccessResponse = {
|
|
|
9
9
|
message?: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
async function makeRequest(authController: AuthController) {
|
|
12
|
+
async function makeRequest(authController: AuthController, pluginKeys: string | undefined) {
|
|
13
13
|
const firebaseToken = await authController.getAuthToken();
|
|
14
14
|
return fetch(DEFAULT_SERVER + "/access_log",
|
|
15
15
|
{
|
|
@@ -19,19 +19,21 @@ async function makeRequest(authController: AuthController) {
|
|
|
19
19
|
"Content-Type": "application/json",
|
|
20
20
|
Authorization: `Basic ${firebaseToken}`,
|
|
21
21
|
},
|
|
22
|
-
body: JSON.stringify({})
|
|
22
|
+
body: JSON.stringify({ plugins: pluginKeys })
|
|
23
23
|
})
|
|
24
24
|
.then(async (res) => {
|
|
25
25
|
return res.json();
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export function useProjectLog(authController: AuthController
|
|
29
|
+
export function useProjectLog(authController: AuthController,
|
|
30
|
+
plugins?: FireCMSPlugin<any, any, any>[]): AccessResponse | null {
|
|
30
31
|
const [accessResponse, setAccessResponse] = useState<AccessResponse | null>(null);
|
|
31
32
|
const accessedUserRef = useRef<string | null>(null);
|
|
33
|
+
const pluginKeys = plugins?.map(plugin => plugin.key).join(",");
|
|
32
34
|
useEffect(() => {
|
|
33
35
|
if (authController.user && authController.user.uid !== accessedUserRef.current && !authController.initialLoading) {
|
|
34
|
-
makeRequest(authController).then(setAccessResponse);
|
|
36
|
+
makeRequest(authController, pluginKeys).then(setAccessResponse);
|
|
35
37
|
accessedUserRef.current = authController.user.uid;
|
|
36
38
|
}
|
|
37
39
|
}, [authController]);
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import equal from "react-fast-compare";
|
|
3
|
+
|
|
4
|
+
import { AppCheckTokenResult, AuthController, Authenticator, DataSourceDelegate, StorageSource, User } from "../index";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This hook is used internally for validating an authenticator.
|
|
8
|
+
*
|
|
9
|
+
* @param authController
|
|
10
|
+
* @param authentication
|
|
11
|
+
* @param getAppCheckToken
|
|
12
|
+
* @param appCheckForceRefresh
|
|
13
|
+
* @param storageSource
|
|
14
|
+
* @param dataSourceDelegate
|
|
15
|
+
*/
|
|
16
|
+
export function useValidateAuthenticator<UserType extends User = User, Controller extends AuthController<UserType> = AuthController<UserType>>({
|
|
17
|
+
disabled,
|
|
18
|
+
authController,
|
|
19
|
+
authenticator,
|
|
20
|
+
getAppCheckToken,
|
|
21
|
+
appCheckForceRefresh = false,
|
|
22
|
+
storageSource,
|
|
23
|
+
dataSourceDelegate
|
|
24
|
+
}:
|
|
25
|
+
{
|
|
26
|
+
disabled?: boolean,
|
|
27
|
+
authController: Controller,
|
|
28
|
+
authenticator?: boolean | Authenticator<UserType, Controller>,
|
|
29
|
+
getAppCheckToken?: (forceRefresh: boolean) => Promise<AppCheckTokenResult> | undefined,
|
|
30
|
+
appCheckForceRefresh?: boolean,
|
|
31
|
+
dataSourceDelegate: DataSourceDelegate;
|
|
32
|
+
storageSource: StorageSource;
|
|
33
|
+
}): {
|
|
34
|
+
canAccessMainView: boolean,
|
|
35
|
+
authLoading: boolean,
|
|
36
|
+
notAllowedError: any,
|
|
37
|
+
authVerified: boolean,
|
|
38
|
+
} {
|
|
39
|
+
|
|
40
|
+
const authenticationEnabled = Boolean(authenticator);
|
|
41
|
+
|
|
42
|
+
const [authLoading, setAuthLoading] = useState<boolean>(authenticationEnabled);
|
|
43
|
+
const [notAllowedError, setNotAllowedError] = useState<any>(false);
|
|
44
|
+
const [authVerified, setAuthVerified] = useState<boolean>(!authenticationEnabled || Boolean(authController.loginSkipped));
|
|
45
|
+
|
|
46
|
+
const canAccessMainView = (authVerified) &&
|
|
47
|
+
(!authenticationEnabled || Boolean(authController.user) || Boolean(authController.loginSkipped)) &&
|
|
48
|
+
!notAllowedError;
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (authController.loginSkipped)
|
|
52
|
+
setAuthVerified(true);
|
|
53
|
+
}, [authController.loginSkipped]);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* We use this ref to check the authentication only if the user has
|
|
57
|
+
* changed.
|
|
58
|
+
*/
|
|
59
|
+
const checkedUserRef = useRef<User | undefined>();
|
|
60
|
+
|
|
61
|
+
const checkAuthentication = useCallback(async () => {
|
|
62
|
+
|
|
63
|
+
if (disabled) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (authController.initialLoading) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!authController.user && !authController.loginSkipped) {
|
|
72
|
+
checkedUserRef.current = undefined;
|
|
73
|
+
setAuthLoading(false);
|
|
74
|
+
setAuthVerified(false);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const delegateUser = authController.user;
|
|
79
|
+
console.debug("Checking authentication for user", delegateUser);
|
|
80
|
+
|
|
81
|
+
if (getAppCheckToken) {
|
|
82
|
+
try {
|
|
83
|
+
if (!await getAppCheckToken(appCheckForceRefresh)) {
|
|
84
|
+
setNotAllowedError("App Check failed.");
|
|
85
|
+
authController.signOut();
|
|
86
|
+
} else {
|
|
87
|
+
console.debug("App Check success.");
|
|
88
|
+
}
|
|
89
|
+
} catch (e: any) {
|
|
90
|
+
setNotAllowedError(e.message);
|
|
91
|
+
authController.signOut();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (authenticator instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
|
|
96
|
+
setAuthLoading(true);
|
|
97
|
+
try {
|
|
98
|
+
const allowed = await authenticator({
|
|
99
|
+
user: delegateUser,
|
|
100
|
+
authController,
|
|
101
|
+
dataSourceDelegate,
|
|
102
|
+
storageSource
|
|
103
|
+
});
|
|
104
|
+
if (!allowed) {
|
|
105
|
+
authController.signOut();
|
|
106
|
+
setNotAllowedError(true);
|
|
107
|
+
}
|
|
108
|
+
} catch (e) {
|
|
109
|
+
setNotAllowedError(e);
|
|
110
|
+
authController.signOut();
|
|
111
|
+
}
|
|
112
|
+
setAuthLoading(false);
|
|
113
|
+
setAuthVerified(true);
|
|
114
|
+
checkedUserRef.current = delegateUser;
|
|
115
|
+
} else {
|
|
116
|
+
setAuthLoading(false);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!authController.initialLoading && !delegateUser) {
|
|
120
|
+
setAuthVerified(true);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}, [disabled, authController, authenticator, getAppCheckToken, appCheckForceRefresh, dataSourceDelegate, storageSource]);
|
|
124
|
+
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
checkAuthentication();
|
|
127
|
+
}, [checkAuthentication]);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
canAccessMainView,
|
|
131
|
+
authLoading: authenticationEnabled && authLoading,
|
|
132
|
+
notAllowedError,
|
|
133
|
+
authVerified
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -34,6 +34,7 @@ export const useBuildSideEntityController = (navigation: NavigationController,
|
|
|
34
34
|
// only on initialisation, create panels from URL
|
|
35
35
|
useEffect(() => {
|
|
36
36
|
if (!navigation.loading && !initialised.current) {
|
|
37
|
+
console.debug("Initialising side entity controller");
|
|
37
38
|
if (navigation.isUrlCollectionPath(location.pathname)) {
|
|
38
39
|
const newFlag = location.hash === `#${NEW_URL_HASH}`;
|
|
39
40
|
const entityOrCollectionPath = navigation.urlPathToDataPath(location.pathname);
|
|
@@ -47,6 +48,8 @@ export const useBuildSideEntityController = (navigation: NavigationController,
|
|
|
47
48
|
sideDialogsController.open(propsToSidePanel(panel, navigation, smallLayout))
|
|
48
49
|
}, 1);
|
|
49
50
|
}
|
|
51
|
+
} else {
|
|
52
|
+
console.warn("Location path is not a collection path");
|
|
50
53
|
}
|
|
51
54
|
initialised.current = true;
|
|
52
55
|
}
|
|
@@ -8,7 +8,7 @@ export type PreviewSize = "medium" | "small" | "tiny";
|
|
|
8
8
|
/**
|
|
9
9
|
* @group Preview components
|
|
10
10
|
*/
|
|
11
|
-
export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any
|
|
11
|
+
export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any> {
|
|
12
12
|
/**
|
|
13
13
|
* Name of the property
|
|
14
14
|
*/
|
|
@@ -24,11 +24,6 @@ export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any
|
|
|
24
24
|
*/
|
|
25
25
|
property: Property<T> | ResolvedProperty<T>;
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
* Click handler
|
|
29
|
-
*/
|
|
30
|
-
// onClick?: () => void;
|
|
31
|
-
|
|
32
27
|
/**
|
|
33
28
|
* Desired size of the preview, depending on the context.
|
|
34
29
|
*/
|
|
@@ -51,9 +46,4 @@ export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any
|
|
|
51
46
|
*/
|
|
52
47
|
customProps?: CustomProps;
|
|
53
48
|
|
|
54
|
-
/**
|
|
55
|
-
* Entity this property refers to
|
|
56
|
-
*/
|
|
57
|
-
// entity?: Entity<M>;
|
|
58
|
-
|
|
59
49
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Checkbox } from "@firecms/ui";
|
|
2
|
+
import { Checkbox, cn } from "@firecms/ui";
|
|
3
3
|
import { PreviewSize } from "../PropertyPreviewProps";
|
|
4
4
|
import { Property } from "../../types";
|
|
5
5
|
|
|
@@ -17,8 +17,10 @@ export function BooleanPreview({
|
|
|
17
17
|
}): React.ReactElement {
|
|
18
18
|
return <div className={"flex flex-row gap-2 items-center"}>
|
|
19
19
|
<Checkbox checked={value}
|
|
20
|
+
padding={false}
|
|
20
21
|
size={size}
|
|
21
22
|
color={"secondary"}/>
|
|
22
|
-
{property.name && <span
|
|
23
|
+
{property.name && <span
|
|
24
|
+
className={cn("text-text-secondary dark:text-text-secondary-dark", size === "tiny" ? "text-sm" : "")}>{property.name}</span>}
|
|
23
25
|
</div>;
|
|
24
26
|
}
|
|
@@ -64,7 +64,7 @@ function ReferencePreviewInternal<M extends Record<string, any>>({
|
|
|
64
64
|
|
|
65
65
|
const navigationController = useNavigationController();
|
|
66
66
|
|
|
67
|
-
const collection = navigationController.getCollection
|
|
67
|
+
const collection = navigationController.getCollection(reference.path);
|
|
68
68
|
if (!collection) {
|
|
69
69
|
if (customizationController.components?.missingReference) {
|
|
70
70
|
return <customizationController.components.missingReference path={reference.path}/>;
|
package/src/types/auth.tsx
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { User } from "./user";
|
|
2
|
+
import { Role } from "./roles";
|
|
3
|
+
import { DataSourceDelegate } from "./datasource";
|
|
4
|
+
import { StorageSource } from "./storage";
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Controller for retrieving the logged user or performing auth related operations.
|
|
@@ -6,7 +9,7 @@ import { User } from "./user";
|
|
|
6
9
|
* to do it as the result of a hook.
|
|
7
10
|
* @group Hooks and utilities
|
|
8
11
|
*/
|
|
9
|
-
export type AuthController<UserType extends User =
|
|
12
|
+
export type AuthController<UserType extends User = any, ExtraData extends any = any> = {
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* The user currently logged in
|
|
@@ -14,6 +17,11 @@ export type AuthController<UserType extends User = User, ExtraData extends any =
|
|
|
14
17
|
*/
|
|
15
18
|
user: UserType | null;
|
|
16
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Roles related to the logged user
|
|
22
|
+
*/
|
|
23
|
+
roles?: Role[];
|
|
24
|
+
|
|
17
25
|
/**
|
|
18
26
|
* Initial loading flag. It is used not to display the login screen
|
|
19
27
|
* when the app first loads, and it has not been checked whether the user
|
|
@@ -56,4 +64,35 @@ export type AuthController<UserType extends User = User, ExtraData extends any =
|
|
|
56
64
|
|
|
57
65
|
setExtra: (extra: ExtraData) => void;
|
|
58
66
|
|
|
67
|
+
// setUser?: (user: UserType | null) => void;
|
|
68
|
+
|
|
69
|
+
// setRoles?: (roles: Role[]) => void;
|
|
59
70
|
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Implement this function to allow access to specific users.
|
|
74
|
+
* @group Hooks and utilities
|
|
75
|
+
*/
|
|
76
|
+
export type Authenticator<UserType extends User = User, Controller extends AuthController<UserType> = AuthController<UserType>> = (props: {
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Logged-in user or null
|
|
80
|
+
*/
|
|
81
|
+
user: UserType | null;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* AuthController
|
|
85
|
+
*/
|
|
86
|
+
authController: Controller;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Connector to your database, e.g. your Firestore database
|
|
90
|
+
*/
|
|
91
|
+
dataSourceDelegate: DataSourceDelegate;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Used storage implementation
|
|
95
|
+
*/
|
|
96
|
+
storageSource: StorageSource;
|
|
97
|
+
|
|
98
|
+
}) => boolean | Promise<boolean>;
|
package/src/types/collections.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { EntityOverrides } from "./entity_overrides";
|
|
|
17
17
|
*
|
|
18
18
|
* @group Models
|
|
19
19
|
*/
|
|
20
|
-
export interface EntityCollection<M extends Record<string, any> = any, UserType extends User =
|
|
20
|
+
export interface EntityCollection<M extends Record<string, any> = any, UserType extends User = any> {
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* You can set an alias that will be used internally instead of the `path`.
|
|
@@ -147,7 +147,7 @@ export interface EntityCollection<M extends Record<string, any> = any, UserType
|
|
|
147
147
|
* is being created, updated or deleted.
|
|
148
148
|
* Useful for adding your own logic or blocking the execution of the operation.
|
|
149
149
|
*/
|
|
150
|
-
callbacks?: EntityCallbacks<M>;
|
|
150
|
+
callbacks?: EntityCallbacks<M, UserType>;
|
|
151
151
|
|
|
152
152
|
/**
|
|
153
153
|
* Builder for rendering additional components such as buttons in the
|
|
@@ -451,7 +451,7 @@ export interface AdditionalFieldDelegate<M extends Record<string, any> = any,
|
|
|
451
451
|
* view.
|
|
452
452
|
* @param entity
|
|
453
453
|
*/
|
|
454
|
-
value?: (props: { entity: Entity<M>, context: FireCMSContext }) => string | number | Promise<string | number> | undefined;
|
|
454
|
+
value?: (props: { entity: Entity<M>, context: FireCMSContext<any> }) => string | number | Promise<string | number> | undefined;
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
/**
|
package/src/types/datasource.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { ResolvedEntityCollection } from "./resolved_entities";
|
|
|
8
8
|
export interface FetchEntityProps<M extends Record<string, any> = any> {
|
|
9
9
|
path: string;
|
|
10
10
|
entityId: string;
|
|
11
|
-
collection?: EntityCollection<M>
|
|
11
|
+
collection?: EntityCollection<M, any>
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -78,7 +78,7 @@ export interface EntityOnFetchProps<M extends Record<string, any> = any, UserTyp
|
|
|
78
78
|
/**
|
|
79
79
|
* Collection of the entity
|
|
80
80
|
*/
|
|
81
|
-
collection: EntityCollection<M>;
|
|
81
|
+
collection: EntityCollection<M, UserType>;
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
84
|
* Full path of the CMS where this collection is being fetched.
|
|
@@ -193,7 +193,7 @@ export interface EntityOnDeleteProps<M extends Record<string, any> = any, UserTy
|
|
|
193
193
|
/**
|
|
194
194
|
* Context of the app status
|
|
195
195
|
*/
|
|
196
|
-
context: FireCMSContext
|
|
196
|
+
context: FireCMSContext<UserType>;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
/**
|
package/src/types/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from "./local_config_persistence";
|
|
|
22
22
|
export * from "./plugins";
|
|
23
23
|
export * from "./analytics";
|
|
24
24
|
export * from "./firecms";
|
|
25
|
+
export * from "./roles";
|
|
25
26
|
export * from "./appcheck";
|
|
26
27
|
export * from "./export_import";
|
|
27
28
|
export * from "./modify_collections";
|
package/src/types/navigation.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { EntityReference } from "./entities";
|
|
|
7
7
|
* attributes.
|
|
8
8
|
* @group Models
|
|
9
9
|
*/
|
|
10
|
-
export type NavigationController = {
|
|
10
|
+
export type NavigationController<EC extends EntityCollection = EntityCollection<any>> = {
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* List of the mapped collections in the CMS.
|
|
@@ -56,18 +56,18 @@ export type NavigationController = {
|
|
|
56
56
|
* Get the collection configuration for a given path.
|
|
57
57
|
* The collection is resolved from the given path or alias.
|
|
58
58
|
*/
|
|
59
|
-
getCollection:
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
getCollection: (pathOrAlias: string,
|
|
60
|
+
entityId?: string,
|
|
61
|
+
includeUserOverride?: boolean) => EC | undefined;
|
|
62
62
|
/**
|
|
63
63
|
* Get the collection configuration from its parent path segments.
|
|
64
64
|
*/
|
|
65
|
-
getCollectionFromIds:
|
|
65
|
+
getCollectionFromIds: (ids: string[]) => EC | undefined;
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* Get the collection configuration from its parent path segments.
|
|
69
69
|
*/
|
|
70
|
-
getCollectionFromPaths:
|
|
70
|
+
getCollectionFromPaths: (pathSegments: string[]) => EC | undefined;
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Default path under the navigation routes of the CMS will be created
|
package/src/types/plugins.tsx
CHANGED
|
@@ -17,9 +17,9 @@ import { CMSView } from "./navigation";
|
|
|
17
17
|
export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollection = EntityCollection, COL_ACTIONS_PROPS = any> = {
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Key of the plugin. This is used to identify the plugin in the CMS.
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
key: string;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* If this flag is set to true, no content will be shown in the CMS
|
package/src/types/properties.ts
CHANGED
|
@@ -770,7 +770,8 @@ export type StorageConfig = {
|
|
|
770
770
|
* Postprocess the saved value (storage path or URL)
|
|
771
771
|
* after it has been resolved.
|
|
772
772
|
*/
|
|
773
|
-
postProcess?: (pathOrUrl: string) => Promise<string
|
|
773
|
+
postProcess?: (pathOrUrl: string) => Promise<string>;
|
|
774
|
+
|
|
774
775
|
}
|
|
775
776
|
|
|
776
777
|
/**
|