@firecms/core 3.0.0-beta.4.pre.2 → 3.0.0-beta.6

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 (109) hide show
  1. package/README.md +1 -1
  2. package/dist/components/ClearFilterSortButton.d.ts +5 -0
  3. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +11 -11
  4. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +1 -1
  5. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -4
  6. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +11 -1
  7. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
  8. package/dist/components/EntityPreview.d.ts +2 -2
  9. package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
  10. package/dist/components/common/types.d.ts +1 -1
  11. package/dist/contexts/AuthControllerContext.d.ts +1 -1
  12. package/dist/form/components/ErrorFocus.d.ts +1 -1
  13. package/dist/hooks/data/delete.d.ts +2 -2
  14. package/dist/hooks/data/save.d.ts +1 -1
  15. package/dist/hooks/data/useDataSource.d.ts +1 -1
  16. package/dist/hooks/data/useEntityFetch.d.ts +3 -3
  17. package/dist/hooks/index.d.ts +1 -0
  18. package/dist/hooks/useBuildNavigationController.d.ts +1 -2
  19. package/dist/hooks/useProjectLog.d.ts +2 -2
  20. package/dist/hooks/useValidateAuthenticator.d.ts +21 -0
  21. package/dist/index.es.js +3948 -3737
  22. package/dist/index.es.js.map +1 -1
  23. package/dist/index.umd.js +5 -5
  24. package/dist/index.umd.js.map +1 -1
  25. package/dist/internal/useBuildDataSource.d.ts +1 -16
  26. package/dist/internal/useRestoreScroll.d.ts +1 -1
  27. package/dist/preview/PropertyPreviewProps.d.ts +1 -4
  28. package/dist/types/auth.d.ts +30 -1
  29. package/dist/types/collections.d.ts +8 -4
  30. package/dist/types/datasource.d.ts +3 -6
  31. package/dist/types/entities.d.ts +5 -1
  32. package/dist/types/entity_callbacks.d.ts +2 -2
  33. package/dist/types/index.d.ts +1 -1
  34. package/dist/types/navigation.d.ts +4 -4
  35. package/dist/types/plugins.d.ts +5 -3
  36. package/dist/types/properties.d.ts +2 -2
  37. package/dist/types/roles.d.ts +31 -0
  38. package/dist/types/storage.d.ts +11 -3
  39. package/dist/types/user.d.ts +5 -0
  40. package/dist/util/collections.d.ts +1 -1
  41. package/dist/util/entities.d.ts +1 -1
  42. package/dist/util/navigation_utils.d.ts +2 -2
  43. package/dist/util/resolutions.d.ts +5 -5
  44. package/dist/util/useTraceUpdate.d.ts +1 -0
  45. package/package.json +23 -20
  46. package/src/components/ClearFilterSortButton.tsx +41 -0
  47. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +10 -11
  48. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
  49. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +16 -19
  50. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
  51. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +27 -32
  52. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +11 -6
  53. package/src/components/EntityCollectionView/EntityCollectionView.tsx +28 -5
  54. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  55. package/src/components/EntityPreview.tsx +5 -2
  56. package/src/components/EntityView.tsx +1 -1
  57. package/src/components/HomePage/DefaultHomePage.tsx +2 -2
  58. package/src/components/HomePage/NavigationCard.tsx +1 -1
  59. package/src/components/ReferenceWidget.tsx +1 -1
  60. package/src/components/SelectableTable/SelectableTable.tsx +1 -1
  61. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
  62. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +23 -8
  63. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +24 -5
  64. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +35 -15
  65. package/src/components/VirtualTable/VirtualTable.tsx +28 -20
  66. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
  67. package/src/components/common/types.tsx +1 -1
  68. package/src/contexts/AuthControllerContext.tsx +1 -1
  69. package/src/core/FireCMS.tsx +2 -3
  70. package/src/core/field_configs.tsx +1 -2
  71. package/src/form/EntityForm.tsx +1 -1
  72. package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
  73. package/src/hooks/data/delete.ts +3 -3
  74. package/src/hooks/data/save.ts +2 -1
  75. package/src/hooks/data/useDataSource.tsx +1 -1
  76. package/src/hooks/data/useEntityFetch.tsx +3 -3
  77. package/src/hooks/index.tsx +2 -0
  78. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +9 -10
  79. package/src/hooks/useBuildModeController.tsx +11 -5
  80. package/src/hooks/useBuildNavigationController.tsx +136 -59
  81. package/src/hooks/useProjectLog.tsx +8 -6
  82. package/src/hooks/useValidateAuthenticator.tsx +115 -0
  83. package/src/internal/useBuildDataSource.ts +42 -47
  84. package/src/internal/useBuildSideEntityController.tsx +18 -12
  85. package/src/preview/PropertyPreview.tsx +2 -12
  86. package/src/preview/PropertyPreviewProps.tsx +1 -11
  87. package/src/preview/components/BooleanPreview.tsx +4 -2
  88. package/src/preview/components/ReferencePreview.tsx +1 -1
  89. package/src/types/auth.tsx +40 -1
  90. package/src/types/collections.ts +8 -4
  91. package/src/types/datasource.ts +8 -5
  92. package/src/types/entities.ts +9 -1
  93. package/src/types/entity_callbacks.ts +2 -2
  94. package/src/types/index.ts +1 -1
  95. package/src/types/navigation.ts +6 -6
  96. package/src/types/plugins.tsx +6 -5
  97. package/src/types/properties.ts +5 -4
  98. package/src/types/roles.ts +41 -0
  99. package/src/types/storage.ts +12 -3
  100. package/src/types/user.ts +7 -0
  101. package/src/util/collections.ts +1 -1
  102. package/src/util/entities.ts +1 -1
  103. package/src/util/navigation_utils.ts +6 -6
  104. package/src/util/strings.ts +2 -2
  105. package/src/util/useTraceUpdate.tsx +2 -1
  106. package/dist/internal/useLocaleConfig.d.ts +0 -1
  107. package/dist/types/appcheck.d.ts +0 -26
  108. package/src/internal/useLocaleConfig.tsx +0 -18
  109. package/src/types/appcheck.ts +0 -29
@@ -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 =>
@@ -130,6 +130,30 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
130
130
  .filter(Boolean) as TopNavigationEntry[]
131
131
  ];
132
132
 
133
+ // Sort by group, entries with group "Admin" will go last, and second to last will be the group "Views"
134
+ navigationEntries = navigationEntries.sort((a, b) => {
135
+ if (a.group !== "Views" && a.group !== "Admin" && (b.group === "Views" || b.group === "Admin")) {
136
+ return -1;
137
+ }
138
+ if (b.group !== "Views" && b.group !== "Admin" && (a.group === "Views" || a.group === "Admin")) {
139
+ return 1;
140
+ }
141
+ if (a.group === "Admin" && b.group !== "Admin") {
142
+ return 1;
143
+ }
144
+ if (a.group !== "Admin" && b.group === "Admin") {
145
+ return -1;
146
+ }
147
+ if (a.group === "Views" && b.group !== "Views") {
148
+ return -1;
149
+ }
150
+ if (a.group !== "Views" && b.group === "Views") {
151
+ return 1;
152
+ }
153
+ return 0;
154
+
155
+ });
156
+
133
157
  if (viewsOrder) {
134
158
  navigationEntries = navigationEntries.sort((a, b) => {
135
159
  const aIndex = viewsOrder.indexOf(a.path);
@@ -163,24 +187,34 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
163
187
  if (authController.initialLoading)
164
188
  return;
165
189
 
190
+ console.debug("Refreshing navigation");
191
+
166
192
  try {
193
+
167
194
  const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
168
195
  resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
169
196
  resolveCMSViews(viewsProp, authController, dataSourceDelegate),
170
197
  resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
171
198
  ]
172
199
  );
173
- if (
174
- !equal(collectionsRef.current, resolvedCollections) ||
175
- !equal(views, resolvedViews) ||
176
- !equal(adminViews, resolvedAdminViews) ||
177
- !equal(topLevelNavigation, computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder))
178
- ) {
200
+
201
+ let shouldUpdateTopLevelNav = false;
202
+ if (!areCollectionListsEqual(collectionsRef.current ?? [], resolvedCollections)) {
179
203
  collectionsRef.current = resolvedCollections;
180
- setCollections(resolvedCollections);
181
- setViews(resolvedViews);
182
- setAdminViews(resolvedAdminViews);
183
- setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews, resolvedAdminViews, viewsOrder));
204
+ shouldUpdateTopLevelNav = true;
205
+ }
206
+ if (!equal(viewsRef.current, resolvedViews)) {
207
+ viewsRef.current = resolvedViews;
208
+ shouldUpdateTopLevelNav = true;
209
+ }
210
+ if (!equal(adminViewsRef.current, resolvedAdminViews)) {
211
+ adminViewsRef.current = resolvedAdminViews;
212
+ shouldUpdateTopLevelNav = true;
213
+ }
214
+
215
+ const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder);
216
+ if (shouldUpdateTopLevelNav && !equal(topLevelNavigation, computedTopLevelNav)) {
217
+ setTopLevelNavigation(computedTopLevelNav);
184
218
  }
185
219
  } catch (e) {
186
220
  console.error(e);
@@ -189,25 +223,34 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
189
223
 
190
224
  setNavigationLoading(false);
191
225
  setInitialised(true);
192
- }, [collectionsProp, collectionPermissions, authController.user, authController.initialLoading, viewsProp, adminViewsProp, computeTopNavigation, injectCollections]);
226
+
227
+ }, [
228
+ collectionsProp,
229
+ collectionPermissions,
230
+ authController.user,
231
+ authController.initialLoading,
232
+ viewsProp,
233
+ adminViewsProp,
234
+ computeTopNavigation,
235
+ injectCollections
236
+ ]);
193
237
 
194
238
  useEffect(() => {
195
239
  refreshNavigation();
196
240
  }, [refreshNavigation]);
197
241
 
198
- const getCollection = useCallback(<EC extends EntityCollection>(
242
+ const getCollection = useCallback((
199
243
  idOrPath: string,
200
244
  entityId?: string,
201
245
  includeUserOverride = false
202
246
  ): EC | undefined => {
203
-
247
+ const collections = collectionsRef.current;
204
248
  if (!collections)
205
249
  return undefined;
206
250
 
207
251
  const baseCollection = getCollectionByPathOrId(removeInitialAndTrailingSlashes(idOrPath), collections);
208
252
 
209
253
  const userOverride = includeUserOverride ? userConfigPersistence?.getCollectionConfig(idOrPath) : undefined;
210
-
211
254
  const overriddenCollection = baseCollection ? mergeDeep(baseCollection, userOverride) : undefined;
212
255
 
213
256
  let result: Partial<EntityCollection> | undefined = overriddenCollection;
@@ -228,16 +271,14 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
228
271
 
229
272
  return { ...overriddenCollection, ...result } as EC;
230
273
 
231
- }, [
232
- basePath,
233
- baseCollectionPath,
234
- collections
235
- ]);
274
+ }, [userConfigPersistence]);
236
275
 
237
276
  const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
238
- let currentCollections = collections;
239
- if (!currentCollections)
240
- throw Error("Collections have not been initialised yet");
277
+
278
+ const collections = collectionsRef.current;
279
+ if (collections === undefined)
280
+ throw Error("getCollectionFromPaths: Collections have not been initialised yet");
281
+ let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
241
282
 
242
283
  for (let i = 0; i < pathSegments.length; i++) {
243
284
  const pathSegment = pathSegments[i];
@@ -251,12 +292,14 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
251
292
 
252
293
  return undefined;
253
294
 
254
- }, [collections]);
295
+ }, []);
255
296
 
256
297
  const getCollectionFromIds = useCallback(<EC extends EntityCollection>(ids: string[]): EC | undefined => {
257
- let currentCollections = collections;
258
- if (!currentCollections)
259
- throw Error("Collections have not been initialised yet");
298
+
299
+ const collections = collectionsRef.current;
300
+ if (collections === undefined)
301
+ throw Error("getCollectionFromIds: Collections have not been initialised yet");
302
+ let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
260
303
 
261
304
  for (let i = 0; i < ids.length; i++) {
262
305
  const id = ids[i];
@@ -270,7 +313,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
270
313
 
271
314
  return undefined;
272
315
 
273
- }, [collections]);
316
+ }, []);
274
317
 
275
318
  const isUrlCollectionPath = useCallback(
276
319
  (path: string): boolean => removeInitialAndTrailingSlashes(path + "/").startsWith(removeInitialAndTrailingSlashes(fullCollectionPath) + "/"),
@@ -292,17 +335,17 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
292
335
  []);
293
336
 
294
337
  const resolveAliasesFrom = useCallback((path: string): string => {
295
- if (!collections)
296
- throw Error("Collections have not been initialised yet");
338
+ const collections = collectionsRef.current ?? [];
297
339
  return resolveCollectionPathIds(path, collections);
298
- }, [collections]);
340
+ }, []);
299
341
 
300
342
  const getAllParentReferencesForPath = useCallback((path: string): EntityReference[] => {
343
+ const collections = collectionsRef.current ?? [];
301
344
  return getParentReferencesFromPath({
302
345
  path,
303
346
  collections
304
347
  });
305
- }, [collections]);
348
+ }, []);
306
349
 
307
350
  const getParentCollectionIds = useCallback((path: string): string[] => {
308
351
 
@@ -321,24 +364,24 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
321
364
  }, [getAllParentReferencesForPath])
322
365
 
323
366
  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;
367
+ const collections = collectionsRef.current;
368
+ let currentCollections = collections;
369
+ const paths: string[] = [];
370
+ for (let i = 0; i < ids.length; i++) {
371
+ const id = ids[i];
372
+ const collection: EntityCollection | undefined = currentCollections!.find(c => c.id === id);
373
+ if (!collection)
374
+ throw Error(`Collection with id ${id} not found`);
375
+ paths.push(collection.path);
376
+ currentCollections = collection.subcollections;
335
377
  }
336
- , [getCollectionFromIds]);
378
+ return paths;
379
+ }, [getCollectionFromIds]);
337
380
 
338
381
  return {
339
- collections,
340
- views,
341
- adminViews,
382
+ collections: collectionsRef.current,
383
+ views: viewsRef.current,
384
+ adminViews: adminViewsRef.current,
342
385
  loading: !initialised || navigationLoading,
343
386
  navigationLoadingError,
344
387
  homeUrl,
@@ -395,7 +438,7 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
395
438
  collectionPermissions: PermissionsBuilder | undefined,
396
439
  authController: AuthController,
397
440
  dataSource: DataSourceDelegate,
398
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
441
+ injectCollections?: (collections: EntityCollection[]) => EntityCollection[]): Promise<EntityCollection[]> {
399
442
  let resolvedCollections: EntityCollection[] = [];
400
443
  if (typeof collections === "function") {
401
444
  resolvedCollections = await collections({
@@ -407,14 +450,14 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
407
450
  resolvedCollections = collections;
408
451
  }
409
452
 
410
- resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
411
-
412
- resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
413
-
414
453
  if (injectCollections) {
415
454
  resolvedCollections = injectCollections(resolvedCollections ?? []);
416
455
  }
417
456
 
457
+ resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
458
+
459
+ resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
460
+
418
461
  return resolvedCollections;
419
462
  }
420
463
 
@@ -431,3 +474,37 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
431
474
  }
432
475
  return resolvedViews;
433
476
  }
477
+
478
+ function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
479
+ const trimmed = collectionOrView.group?.trim();
480
+ if (!trimmed || trimmed === "") {
481
+ return "Views";
482
+ }
483
+ return trimmed ?? "Views";
484
+ }
485
+
486
+ function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
487
+ if (a.length !== b.length) {
488
+ return false;
489
+ }
490
+ const aCopy = [...a];
491
+ const bCopy = [...b];
492
+ const aSorted = aCopy.sort((x, y) => x.id.localeCompare(y.id));
493
+ const bSorted = bCopy.sort((x, y) => x.id.localeCompare(y.id));
494
+ return aSorted.every((value, index) => areCollectionsEqual(value, bSorted[index]));
495
+ }
496
+
497
+ function areCollectionsEqual(a: EntityCollection, b: EntityCollection) {
498
+ const {
499
+ subcollections: subcollectionsA,
500
+ ...restA
501
+ } = a;
502
+ const {
503
+ subcollections: subcollectionsB,
504
+ ...restB
505
+ } = b;
506
+ if (!areCollectionListsEqual(subcollectionsA ?? [], subcollectionsB ?? [])) {
507
+ return false;
508
+ }
509
+ return equal(restA, restB);
510
+ }
@@ -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,21 +19,23 @@ 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);
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
- }, [authController]);
39
+ }, [authController, pluginKeys]);
38
40
  return accessResponse;
39
41
  }
@@ -0,0 +1,115 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import equal from "react-fast-compare";
3
+
4
+ import { 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 storageSource
12
+ * @param dataSourceDelegate
13
+ */
14
+ export function useValidateAuthenticator<UserType extends User = User, Controller extends AuthController<UserType> = AuthController<UserType>>({
15
+ disabled,
16
+ authController,
17
+ authenticator,
18
+ storageSource,
19
+ dataSourceDelegate
20
+ }:
21
+ {
22
+ disabled?: boolean,
23
+ authController: Controller,
24
+ authenticator?: boolean | Authenticator<UserType, Controller>,
25
+ dataSourceDelegate: DataSourceDelegate;
26
+ storageSource: StorageSource;
27
+ }): {
28
+ canAccessMainView: boolean,
29
+ authLoading: boolean,
30
+ notAllowedError: any,
31
+ authVerified: boolean,
32
+ } {
33
+
34
+ const authenticationEnabled = Boolean(authenticator);
35
+
36
+ const [authLoading, setAuthLoading] = useState<boolean>(authenticationEnabled);
37
+ const [notAllowedError, setNotAllowedError] = useState<any>(false);
38
+ const [authVerified, setAuthVerified] = useState<boolean>(!authenticationEnabled || Boolean(authController.loginSkipped));
39
+
40
+ const canAccessMainView = (authVerified) &&
41
+ (!authenticationEnabled || Boolean(authController.user) || Boolean(authController.loginSkipped)) &&
42
+ !notAllowedError;
43
+
44
+ useEffect(() => {
45
+ if (authController.loginSkipped)
46
+ setAuthVerified(true);
47
+ }, [authController.loginSkipped]);
48
+
49
+ /**
50
+ * We use this ref to check the authentication only if the user has
51
+ * changed.
52
+ */
53
+ const checkedUserRef = useRef<User | undefined>();
54
+
55
+ const checkAuthentication = useCallback(async () => {
56
+
57
+ if (disabled) {
58
+ return;
59
+ }
60
+
61
+ if (authController.initialLoading) {
62
+ return;
63
+ }
64
+
65
+ if (!authController.user && !authController.loginSkipped) {
66
+ checkedUserRef.current = undefined;
67
+ setAuthLoading(false);
68
+ setAuthVerified(false);
69
+ return;
70
+ }
71
+
72
+ const delegateUser = authController.user;
73
+ console.debug("Checking authentication for user", delegateUser);
74
+
75
+ if (authenticator instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
76
+ setAuthLoading(true);
77
+ try {
78
+ const allowed = await authenticator({
79
+ user: delegateUser,
80
+ authController,
81
+ dataSourceDelegate,
82
+ storageSource
83
+ });
84
+ if (!allowed) {
85
+ authController.signOut();
86
+ setNotAllowedError(true);
87
+ }
88
+ } catch (e) {
89
+ setNotAllowedError(e);
90
+ authController.signOut();
91
+ }
92
+ setAuthLoading(false);
93
+ setAuthVerified(true);
94
+ checkedUserRef.current = delegateUser;
95
+ } else {
96
+ setAuthLoading(false);
97
+ }
98
+
99
+ if (!authController.initialLoading && !delegateUser) {
100
+ setAuthVerified(true);
101
+ }
102
+
103
+ }, [disabled, authController, authenticator, dataSourceDelegate, storageSource]);
104
+
105
+ useEffect(() => {
106
+ checkAuthentication();
107
+ }, [checkAuthentication]);
108
+
109
+ return {
110
+ canAccessMainView,
111
+ authLoading: authenticationEnabled && authLoading,
112
+ notAllowedError,
113
+ authVerified
114
+ }
115
+ }
@@ -5,12 +5,10 @@ import {
5
5
  DeleteEntityProps,
6
6
  Entity,
7
7
  EntityCollection,
8
- EntityReference,
9
8
  EntityValues,
10
9
  FetchCollectionProps,
11
10
  FetchEntityProps,
12
11
  FilterValues,
13
- GeoPoint,
14
12
  ListenCollectionProps,
15
13
  ListenEntityProps,
16
14
  NavigationController,
@@ -207,13 +205,10 @@ export function useBuildDataSource({
207
205
 
208
206
  const properties: ResolvedProperties<M> | undefined = resolvedCollection?.properties;
209
207
 
210
- const firestoreValues = cmsToDelegateModel(
208
+ const firestoreValues = delegate.cmsToDelegateModel(
211
209
  values,
212
- delegate.buildReference,
213
- delegate.buildGeoPoint,
214
- delegate.buildDate,
215
- delegate.buildDeleteFieldValue
216
210
  );
211
+
217
212
  const updatedFirestoreValues: EntityValues<M> = properties
218
213
  ? updateDateAutoValues(
219
214
  {
@@ -316,48 +311,48 @@ export function useBuildDataSource({
316
311
  sortBy
317
312
  }
318
313
  )
319
- }, [delegate.isFilterCombinationValid])
314
+ }, [delegate.isFilterCombinationValid]),
320
315
 
321
316
  };
322
317
 
323
318
  }
324
319
 
325
- /**
326
- * Recursive function that converts Firestore data types into CMS or plain
327
- * JS types.
328
- * FireCMS uses Javascript dates internally instead of Firestore timestamps.
329
- * This makes it easier to interact with the rest of the libraries and
330
- * bindings.
331
- * Also, Firestore references are replaced with {@link EntityReference}
332
- * @param data
333
- * @param buildReference
334
- * @param buildGeoPoint
335
- * @param buildDate
336
- * @param buildDelete
337
- * @group Firestore
338
- */
339
- export function cmsToDelegateModel(data: any,
340
- buildReference: (reference: EntityReference) => any,
341
- buildGeoPoint: (geoPoint: GeoPoint) => any,
342
- buildDate: (date: Date) => any,
343
- buildDelete: () => any
344
- ): any {
345
- if (data === undefined) {
346
- return buildDelete();
347
- } else if (data === null) {
348
- return null;
349
- } else if (Array.isArray(data)) {
350
- return data.map(v => cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete));
351
- } else if (data.isEntityReference && data.isEntityReference()) {
352
- return buildReference(data);
353
- } else if (data instanceof GeoPoint) {
354
- return buildGeoPoint(data);
355
- } else if (data instanceof Date) {
356
- return buildDate(data);
357
- } else if (data && typeof data === "object") {
358
- return Object.entries(data)
359
- .map(([key, v]) => ({ [key]: cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete) }))
360
- .reduce((a, b) => ({ ...a, ...b }), {});
361
- }
362
- return data;
363
- }
320
+ // /**
321
+ // * Recursive function that converts Firestore data types into CMS or plain
322
+ // * JS types.
323
+ // * FireCMS uses Javascript dates internally instead of Firestore timestamps.
324
+ // * This makes it easier to interact with the rest of the libraries and
325
+ // * bindings.
326
+ // * Also, Firestore references are replaced with {@link EntityReference}
327
+ // * @param data
328
+ // * @param buildReference
329
+ // * @param buildGeoPoint
330
+ // * @param buildDate
331
+ // * @param buildDelete
332
+ // * @group Firestore
333
+ // */
334
+ // export function cmsToDelegateModel(data: any,
335
+ // buildReference: (reference: EntityReference) => any,
336
+ // buildGeoPoint: (geoPoint: GeoPoint) => any,
337
+ // buildDate: (date: Date) => any,
338
+ // buildDelete: () => any
339
+ // ): any {
340
+ // if (data === undefined) {
341
+ // return buildDelete();
342
+ // } else if (data === null) {
343
+ // return null;
344
+ // } else if (Array.isArray(data)) {
345
+ // return data.map(v => cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete));
346
+ // } else if (data.isEntityReference && data.isEntityReference()) {
347
+ // return buildReference(data);
348
+ // } else if (data instanceof GeoPoint) {
349
+ // return buildGeoPoint(data);
350
+ // } else if (data instanceof Date) {
351
+ // return buildDate(data);
352
+ // } else if (data && typeof data === "object") {
353
+ // return Object.entries(data)
354
+ // .map(([key, v]) => ({ [key]: cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete) }))
355
+ // .reduce((a, b) => ({ ...a, ...b }), {});
356
+ // }
357
+ // return data;
358
+ // }