@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.
Files changed (70) hide show
  1. package/README.md +1 -1
  2. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +1 -1
  3. package/dist/components/EntityPreview.d.ts +2 -2
  4. package/dist/components/FieldCaption.d.ts +1 -0
  5. package/dist/contexts/AuthControllerContext.d.ts +1 -1
  6. package/dist/hooks/data/delete.d.ts +2 -2
  7. package/dist/hooks/data/save.d.ts +1 -1
  8. package/dist/hooks/data/useDataSource.d.ts +1 -1
  9. package/dist/hooks/data/useEntityFetch.d.ts +3 -3
  10. package/dist/hooks/index.d.ts +1 -0
  11. package/dist/hooks/useBuildNavigationController.d.ts +1 -2
  12. package/dist/hooks/useProjectLog.d.ts +2 -2
  13. package/dist/hooks/useValidateAuthenticator.d.ts +25 -0
  14. package/dist/index.es.js +2333 -2257
  15. package/dist/index.es.js.map +1 -1
  16. package/dist/index.umd.js +5 -5
  17. package/dist/index.umd.js.map +1 -1
  18. package/dist/internal/useRestoreScroll.d.ts +1 -1
  19. package/dist/preview/PropertyPreviewProps.d.ts +1 -4
  20. package/dist/types/auth.d.ts +30 -1
  21. package/dist/types/collections.d.ts +3 -3
  22. package/dist/types/datasource.d.ts +1 -1
  23. package/dist/types/entity_callbacks.d.ts +2 -2
  24. package/dist/types/index.d.ts +1 -0
  25. package/dist/types/navigation.d.ts +4 -4
  26. package/dist/types/plugins.d.ts +2 -2
  27. package/dist/types/roles.d.ts +31 -0
  28. package/dist/types/storage.d.ts +11 -3
  29. package/dist/types/user.d.ts +5 -0
  30. package/dist/util/collections.d.ts +1 -1
  31. package/dist/util/useTraceUpdate.d.ts +1 -0
  32. package/package.json +10 -10
  33. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
  34. package/src/components/EntityPreview.tsx +5 -2
  35. package/src/components/FieldCaption.tsx +1 -0
  36. package/src/components/HomePage/DefaultHomePage.tsx +1 -1
  37. package/src/components/HomePage/NavigationCard.tsx +1 -1
  38. package/src/components/ReferenceWidget.tsx +1 -1
  39. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +1 -1
  40. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
  41. package/src/contexts/AuthControllerContext.tsx +1 -1
  42. package/src/core/FireCMS.tsx +1 -1
  43. package/src/form/EntityForm.tsx +1 -1
  44. package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
  45. package/src/hooks/data/delete.ts +3 -3
  46. package/src/hooks/data/save.ts +1 -1
  47. package/src/hooks/data/useDataSource.tsx +1 -1
  48. package/src/hooks/data/useEntityFetch.tsx +3 -3
  49. package/src/hooks/index.tsx +2 -0
  50. package/src/hooks/useBuildNavigationController.tsx +63 -43
  51. package/src/hooks/useProjectLog.tsx +7 -5
  52. package/src/hooks/useValidateAuthenticator.tsx +135 -0
  53. package/src/internal/useBuildSideEntityController.tsx +3 -0
  54. package/src/preview/PropertyPreviewProps.tsx +1 -11
  55. package/src/preview/components/BooleanPreview.tsx +4 -2
  56. package/src/preview/components/ReferencePreview.tsx +1 -1
  57. package/src/types/auth.tsx +40 -1
  58. package/src/types/collections.ts +3 -3
  59. package/src/types/datasource.ts +1 -1
  60. package/src/types/entity_callbacks.ts +2 -2
  61. package/src/types/index.ts +1 -0
  62. package/src/types/navigation.ts +6 -6
  63. package/src/types/plugins.tsx +2 -2
  64. package/src/types/properties.ts +2 -1
  65. package/src/types/roles.ts +41 -0
  66. package/src/types/storage.ts +12 -3
  67. package/src/types/user.ts +7 -0
  68. package/src/util/collections.ts +1 -1
  69. package/src/util/strings.ts +2 -2
  70. 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[] | null>();
69
- const [collections, setCollections] = useState<EntityCollection[] | undefined>();
70
- const [views, setViews] = useState<CMSView[] | undefined>();
71
- const [adminViews, setAdminViews] = useState<CMSView[] | undefined>();
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.group?.trim() ?? "Views"
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.group?.trim() ?? "Views"
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(views, resolvedViews) ||
176
- !equal(adminViews, resolvedAdminViews) ||
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
- setCollections(resolvedCollections);
181
- setViews(resolvedViews);
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
- }, [collectionsProp, collectionPermissions, authController.user, authController.initialLoading, viewsProp, adminViewsProp, computeTopNavigation, injectCollections]);
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(<EC extends EntityCollection>(
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
- let currentCollections = collections;
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
- }, [collections]);
262
+ }, []);
255
263
 
256
264
  const getCollectionFromIds = useCallback(<EC extends EntityCollection>(ids: string[]): EC | undefined => {
257
- let currentCollections = collections;
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
- }, [collections]);
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
- }, [collections]);
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
- }, [collections]);
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
- let currentCollections = collections;
325
- const paths: string[] = [];
326
- for (let i = 0; i < ids.length; i++) {
327
- const id = ids[i];
328
- const collection: EntityCollection | undefined = currentCollections!.find(c => c.id === id);
329
- if (!collection)
330
- throw Error(`Collection with id ${id} not found`);
331
- paths.push(collection.path);
332
- currentCollections = collection.subcollections;
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
- , [getCollectionFromIds]);
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): AccessResponse | null {
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, M extends Record<string, any> = Record<string, 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 className={size === "tiny" ? "text-sm" : ""}>{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<EntityCollection<M>>(reference.path);
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}/>;
@@ -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 = User, ExtraData extends any = any> = {
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>;
@@ -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 = 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
  /**
@@ -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
  /**
@@ -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";
@@ -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: <EC extends EntityCollection = EntityCollection<any>>(pathOrAlias: string,
60
- entityId?: string,
61
- includeUserOverride?: boolean) => EC | undefined;
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: <EC extends EntityCollection = EntityCollection<any>>(ids: string[]) => EC | undefined;
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: <EC extends EntityCollection = EntityCollection<any>>(pathSegments: string[]) => EC | undefined;
70
+ getCollectionFromPaths: (pathSegments: string[]) => EC | undefined;
71
71
 
72
72
  /**
73
73
  * Default path under the navigation routes of the CMS will be created
@@ -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
- * Name of the plugin
20
+ * Key of the plugin. This is used to identify the plugin in the CMS.
21
21
  */
22
- name: string;
22
+ key: string;
23
23
 
24
24
  /**
25
25
  * If this flag is set to true, no content will be shown in the CMS
@@ -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
  /**