@stoker-platform/web-app 0.5.222 → 0.5.224

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @stoker-platform/web-app
2
2
 
3
+ ## 0.5.224
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: add field access groups feature
8
+ - Updated dependencies
9
+ - @stoker-platform/node-client@0.5.87
10
+ - @stoker-platform/web-client@0.5.93
11
+ - @stoker-platform/utils@0.5.78
12
+
13
+ ## 0.5.223
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies
18
+ - @stoker-platform/node-client@0.5.86
19
+ - @stoker-platform/web-client@0.5.92
20
+ - @stoker-platform/utils@0.5.77
21
+
3
22
  ## 0.5.222
4
23
 
5
24
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoker-platform/web-app",
3
- "version": "0.5.222",
3
+ "version": "0.5.224",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "scripts": {
@@ -51,9 +51,9 @@
51
51
  "@radix-ui/react-tooltip": "^1.2.8",
52
52
  "@react-google-maps/api": "^2.20.8",
53
53
  "@sentry/react": "^10.56.0",
54
- "@stoker-platform/node-client": "0.5.85",
55
- "@stoker-platform/utils": "0.5.76",
56
- "@stoker-platform/web-client": "0.5.91",
54
+ "@stoker-platform/node-client": "0.5.87",
55
+ "@stoker-platform/utils": "0.5.78",
56
+ "@stoker-platform/web-client": "0.5.93",
57
57
  "@tanstack/react-table": "^8.21.3",
58
58
  "@types/react": "18.3.13",
59
59
  "@types/react-dom": "18.3.1",
package/src/Calendar.tsx CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  preloadCollection,
22
22
  subscribeMany,
23
23
  onStokerPermissionsChange,
24
+ getCurrentUser,
24
25
  } from "@stoker-platform/web-client"
25
26
  import { useGoToRecord } from "./utils/goToRecord"
26
27
  import { useCallback, useEffect, useMemo, useRef, useState } from "react"
@@ -259,6 +260,7 @@ export function Calendar({
259
260
  const customization = getCollectionConfigModule(labels.collection)
260
261
  const permissions = getCurrentUserPermissions()
261
262
  if (!permissions?.Role) throw new Error("PERMISSION_DENIED")
263
+ const claims = getCurrentUser()?.token.claims ?? {}
262
264
  const location = useLocation()
263
265
  const goToRecord = useGoToRecord()
264
266
  const { toast } = useToast()
@@ -524,21 +526,21 @@ export function Calendar({
524
526
  const allDayFieldSchema = getField(allFields, allDayField)
525
527
 
526
528
  const hasStartUpdateAccess = !!(
527
- canUpdateField(collection, startFieldSchema, permissions) &&
529
+ canUpdateField(collection, startFieldSchema, permissions, claims) &&
528
530
  !systemFields.map((field) => field.name).includes(startField)
529
531
  )
530
532
  const hasAllDayUpdateAccess =
531
- allDayFieldSchema && !!canUpdateField(collection, allDayFieldSchema, permissions)
533
+ allDayFieldSchema && !!canUpdateField(collection, allDayFieldSchema, permissions, claims)
532
534
  setHasStartUpdateAccess(hasStartUpdateAccess && (!allDayField || hasAllDayUpdateAccess))
533
535
  if (endField) {
534
536
  const hasEndUpdateAccess = !!(
535
- canUpdateField(collection, endFieldSchema, permissions) &&
537
+ canUpdateField(collection, endFieldSchema, permissions, claims) &&
536
538
  !systemFields.map((field) => field.name).includes(endField)
537
539
  )
538
540
  setHasEndUpdateAccess(hasEndUpdateAccess && (!allDayField || hasAllDayUpdateAccess))
539
541
  }
540
542
  if (resourceField) {
541
- const hasResourceUpdateAccess = !!canUpdateField(collection, resourceFieldSchema, permissions)
543
+ const hasResourceUpdateAccess = !!canUpdateField(collection, resourceFieldSchema, permissions, claims)
542
544
  setHasResourceUpdateAccess(hasResourceUpdateAccess)
543
545
  }
544
546
 
@@ -928,17 +930,17 @@ export function Calendar({
928
930
  const systemFields = getSystemFieldsSchema()
929
931
 
930
932
  const hasAllDayUpdateAccess =
931
- allDayFieldSchema && !!canUpdateField(additionalSchema, allDayFieldSchema, permissions)
933
+ allDayFieldSchema && !!canUpdateField(additionalSchema, allDayFieldSchema, permissions, claims)
932
934
  const hasStartUpdateAccessAdditional =
933
935
  !!(
934
- canUpdateField(additionalSchema, startFieldSchema, permissions) &&
936
+ canUpdateField(additionalSchema, startFieldSchema, permissions, claims) &&
935
937
  !systemFields.map((field) => field.name).includes(startField)
936
938
  ) &&
937
939
  (!allDayField || hasAllDayUpdateAccess)
938
940
  let hasEndUpdateAccessAdditional = false
939
941
  if (endField) {
940
942
  hasEndUpdateAccessAdditional = !!(
941
- canUpdateField(additionalSchema, endFieldSchema, permissions) &&
943
+ canUpdateField(additionalSchema, endFieldSchema, permissions, claims) &&
942
944
  !systemFields.map((field) => field.name).includes(endField) &&
943
945
  (!allDayField || hasAllDayUpdateAccess)
944
946
  )
package/src/Cards.tsx CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  import {
20
20
  Cursor,
21
21
  getCollectionConfigModule,
22
+ getCurrentUser,
22
23
  getCurrentUserPermissions,
23
24
  onStokerPermissionsChange,
24
25
  subscribeOne,
@@ -94,11 +95,12 @@ function CardItem({ index, style, data }: CardItemProps) {
94
95
  const systemFields = getSystemFieldsSchema()
95
96
  const customization = getCollectionConfigModule(labels.collection)
96
97
  const permissions = getCurrentUserPermissions()
98
+ const claims = getCurrentUser()?.token.claims ?? {}
97
99
  const location = useLocation()
98
100
  if (!permissions) {
99
101
  throw new Error("PERMISSION_DENIED")
100
102
  }
101
- const hasUpdateAccess = !!canUpdateField(collection, statusField, permissions)
103
+ const hasUpdateAccess = !!canUpdateField(collection, statusField, permissions, claims)
102
104
  const goToRecord = useGoToRecord()
103
105
  const [connectionStatus] = useConnection()
104
106
  const { isGlobalLoading } = useGlobalLoading()
@@ -37,6 +37,7 @@ import {
37
37
  SubscribeManyOptions,
38
38
  updateRecord,
39
39
  getPreloadListeners,
40
+ getCurrentUser,
40
41
  } from "@stoker-platform/web-client"
41
42
  import { createElement, useCallback, useEffect, useMemo, useRef, useState } from "react"
42
43
  import { useLocation, useNavigate } from "react-router"
@@ -163,6 +164,7 @@ function Collection({
163
164
  const timezone = getTimezone()
164
165
  const [permissions, setPermissions] = useState<StokerPermissions | null>(() => getCurrentUserPermissions())
165
166
  if (!permissions?.Role) throw new Error("PERMISSION_DENIED")
167
+ const claims = getCurrentUser()?.token.claims ?? {}
166
168
  const { toast } = useToast()
167
169
 
168
170
  const isServerReadOnly = serverReadOnly(collection)
@@ -720,7 +722,7 @@ function Collection({
720
722
  }
721
723
  }
722
724
  if (!isPreloadCacheEnabled || relationList?.loadAll) {
723
- if (!isPreloadCacheEnabled) loadedKeys.current.add(key)
725
+ loadedKeys.current.add(key)
724
726
  if (loadedKeys.current.size === keysLength.current) {
725
727
  setIsRouteLoading("-", location.pathname)
726
728
  }
@@ -1596,13 +1598,13 @@ function Collection({
1596
1598
  if (!relationCollection?.fullTextSearch) return false
1597
1599
  const collectionPermissions = permissions.collections?.[relationCollection.labels.collection]
1598
1600
  const fullCollectionAccess = collectionPermissions && collectionAccess("Read", collectionPermissions)
1599
- const dependencyAccess = hasDependencyAccess(relationCollection, schema, permissions)
1601
+ const dependencyAccess = hasDependencyAccess(relationCollection, schema, permissions, claims)
1600
1602
  if (!fullCollectionAccess && dependencyAccess.length === 0) return false
1601
1603
  }
1602
1604
 
1603
1605
  return true
1604
1606
  })
1605
- }, [filters, excludedFilters, fields, permissions, schema])
1607
+ }, [filters, excludedFilters, fields, permissions, schema, claims])
1606
1608
 
1607
1609
  const hasRangeFilter = useMemo(() => {
1608
1610
  return filters.some((filter) => filter.type === "range" && !isPreloadCacheEnabled)
@@ -1820,11 +1822,8 @@ function Collection({
1820
1822
  return schema.collections[collectionName] || collection
1821
1823
  }, [selectedCreateCollection, labels.collection, schema, collection])
1822
1824
 
1823
- const createRecordTitle = useMemo(() => {
1824
- return (
1825
- additionalTitles?.[selectedCreateCollection || labels.collection]?.record ||
1826
- createCollectionSchema.labels.record
1827
- )
1825
+ const additionalRecordTitle = useMemo(() => {
1826
+ return additionalTitles?.[selectedCreateCollection || labels.collection]?.record
1828
1827
  }, [createCollectionSchema, additionalTitles, selectedCreateCollection, labels.collection])
1829
1828
 
1830
1829
  const createPrePopulatedRecord = useCallback(() => {
@@ -2778,7 +2777,7 @@ function Collection({
2778
2777
  className="font-medium leading-none"
2779
2778
  >
2780
2779
  Add{" "}
2781
- {createRecordTitle ||
2780
+ {additionalRecordTitle ||
2782
2781
  recordTitle}
2783
2782
  </h4>
2784
2783
  <Button
package/src/Filters.tsx CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  } from "@stoker-platform/utils"
21
21
  import {
22
22
  getCollectionConfigModule,
23
+ getCurrentUser,
23
24
  getCurrentUserPermissions,
24
25
  getOne,
25
26
  getSchema,
@@ -77,6 +78,7 @@ export function Filters({
77
78
  const schema = getSchema()
78
79
  const permissions = getCurrentUserPermissions()
79
80
  if (!permissions?.Role) throw new Error("PERMISSION_DENIED")
81
+ const claims = getCurrentUser()?.token.claims ?? {}
80
82
  const customization = getCollectionConfigModule(labels.collection)
81
83
 
82
84
  const { filters, setFilters } = useFilters()
@@ -125,19 +127,22 @@ export function Filters({
125
127
  const { offset: keyboardOffset, viewportHeight } = useKeyboardOffset(isMobile && someFilterOpen)
126
128
  const sheetHeight = Math.max(240, Math.min(viewportHeight - 16, viewportHeight * 0.9))
127
129
 
128
- const hasRelationFilterAccess = useCallback((filter: Filter) => {
129
- if (filter.type === "status" || filter.type === "range") return false
130
- const field = getField(fields, filter.field)
131
- if (!field || !isRelationField(field)) return false
132
- const relationCollection = schema.collections[field.collection]
133
- if (!relationCollection) return false
134
- const collectionPermissions = permissions?.collections?.[relationCollection.labels.collection]
135
- const fullCollectionAccess = collectionPermissions && collectionAccess("Read", collectionPermissions)
136
- const dependencyAccess = hasDependencyAccess(relationCollection, schema, permissions)
137
- if (!fullCollectionAccess && dependencyAccess.length === 0) return false
138
- if (relationList && relationList.field === filter.field) return false
139
- return true
140
- }, [])
130
+ const hasRelationFilterAccess = useCallback(
131
+ (filter: Filter) => {
132
+ if (filter.type === "status" || filter.type === "range") return false
133
+ const field = getField(fields, filter.field)
134
+ if (!field || !isRelationField(field)) return false
135
+ const relationCollection = schema.collections[field.collection]
136
+ if (!relationCollection) return false
137
+ const collectionPermissions = permissions?.collections?.[relationCollection.labels.collection]
138
+ const fullCollectionAccess = collectionPermissions && collectionAccess("Read", collectionPermissions)
139
+ const dependencyAccess = hasDependencyAccess(relationCollection, schema, permissions, claims)
140
+ if (!fullCollectionAccess && dependencyAccess.length === 0) return false
141
+ if (relationList && relationList.field === filter.field) return false
142
+ return true
143
+ },
144
+ [permissions, claims, schema],
145
+ )
141
146
 
142
147
  useEffect(() => {
143
148
  const initialize = async () => {
package/src/Form.tsx CHANGED
@@ -33,6 +33,7 @@ import {
33
33
  tryFunction,
34
34
  getInputSchema,
35
35
  getField,
36
+ privateFieldAccess,
36
37
  restrictCreateAccess,
37
38
  restrictUpdateAccess,
38
39
  collectionAccess,
@@ -42,6 +43,7 @@ import {
42
43
  addRecord,
43
44
  deleteRecord,
44
45
  getCollectionConfigModule,
46
+ getCurrentUser,
45
47
  getCurrentUserPermissions,
46
48
  getOne,
47
49
  updateRecord,
@@ -296,6 +298,7 @@ const RecordFormField = (props: FieldProps) => {
296
298
  const customization = getFieldCustomization(field, customizationFile)
297
299
  const admin = customization.admin
298
300
  const permissions = getCurrentUserPermissions()
301
+ const userClaims = getCurrentUser()?.token.claims ?? {}
299
302
  if (!permissions?.Role) throw new Error("PERMISSION_DENIED")
300
303
 
301
304
  const [label, setLabel] = useState("")
@@ -402,7 +405,7 @@ const RecordFormField = (props: FieldProps) => {
402
405
  return null
403
406
  }
404
407
 
405
- if (operation === "create" && !restrictCreateAccess(field, permissions)) {
408
+ if (operation === "create" && !restrictCreateAccess(field, permissions, collection.labels.collection, userClaims)) {
406
409
  return null
407
410
  }
408
411
 
@@ -413,7 +416,11 @@ const RecordFormField = (props: FieldProps) => {
413
416
  if (operation === "update-many") {
414
417
  if ("unique" in field && field.unique) return null
415
418
  if (customization?.admin && "readOnly" in customization.admin) return null
416
- if ("restrictUpdate" in field && !restrictUpdateAccess(field, permissions)) return null
419
+ if (
420
+ "restrictUpdate" in field &&
421
+ !restrictUpdateAccess(field, permissions, collection.labels.collection, userClaims)
422
+ )
423
+ return null
417
424
 
418
425
  if (collection.auth && field.name === "Role") return null
419
426
  if (connectionStatus === "offline" && collection.auth) return null
@@ -425,7 +432,7 @@ const RecordFormField = (props: FieldProps) => {
425
432
  const isReadOnly =
426
433
  (readOnly && !isRelationField(field)) ||
427
434
  (operation === "update" &&
428
- (!restrictUpdateAccess(field, permissions) ||
435
+ (!restrictUpdateAccess(field, permissions, collection.labels.collection, userClaims) ||
429
436
  (collection.auth && field.name === "Role" && record?.User_ID) ||
430
437
  !hasUpdateAccess ||
431
438
  isUpdateDisabled))
@@ -3948,22 +3955,25 @@ function RecordForm({
3948
3955
  delete values[field.name]
3949
3956
  }
3950
3957
 
3951
- if (field.access && permissions?.Role && !field.access.includes(permissions.Role)) {
3958
+ const userClaims = getCurrentUser()?.token.claims ?? {}
3959
+ if (
3960
+ field.access &&
3961
+ permissions?.Role &&
3962
+ !privateFieldAccess(field, permissions, collection, userClaims)
3963
+ ) {
3952
3964
  delete values[field.name]
3953
3965
  }
3954
3966
  if (
3955
3967
  operation === "create" &&
3956
3968
  field.restrictCreate &&
3957
- (field.restrictCreate === true ||
3958
- (permissions?.Role && !field.restrictCreate.includes(permissions.Role)))
3969
+ !restrictCreateAccess(field, permissions, collection.labels.collection, userClaims)
3959
3970
  ) {
3960
3971
  delete values[field.name]
3961
3972
  }
3962
3973
  if (
3963
3974
  operation === "update" &&
3964
3975
  field.restrictUpdate &&
3965
- (field.restrictUpdate === true ||
3966
- (permissions?.Role && !field.restrictUpdate.includes(permissions.Role)))
3976
+ !restrictUpdateAccess(field, permissions, collection.labels.collection, userClaims)
3967
3977
  ) {
3968
3978
  delete values[field.name]
3969
3979
  }
@@ -4568,11 +4578,12 @@ function RecordForm({
4568
4578
  const recordToDuplicate: Partial<StokerRecord> = {}
4569
4579
  for (const field of fields) {
4570
4580
  const fieldCustomization = getFieldCustomization(field, customization)
4581
+ const userClaims = getCurrentUser()?.token.claims ?? {}
4571
4582
  if (
4572
4583
  field.type !== "Computed" &&
4573
4584
  !(field.type === "Number" && field.autoIncrement) &&
4574
4585
  !(collection.auth && field.name === "User_ID") &&
4575
- restrictCreateAccess(field, permissions) &&
4586
+ restrictCreateAccess(field, permissions, collection.labels.collection, userClaims) &&
4576
4587
  fieldCustomization.custom?.initialValue === undefined
4577
4588
  ) {
4578
4589
  recordToDuplicate[field.name] = record[field.name]
package/src/Map.tsx CHANGED
@@ -11,6 +11,7 @@ import { canUpdateField, getCachedConfigValue, getField, getSystemFieldsSchema }
11
11
  import {
12
12
  getAppCheck,
13
13
  getCollectionConfigModule,
14
+ getCurrentUser,
14
15
  getCurrentUserPermissions,
15
16
  onStokerPermissionsChange,
16
17
  updateRecord,
@@ -135,6 +136,7 @@ export function Map({
135
136
  if (!permissions) {
136
137
  throw new Error("PERMISSION_DENIED")
137
138
  }
139
+ const claims = getCurrentUser()?.token.claims ?? {}
138
140
  const location = useLocation()
139
141
  const [connectionStatus] = useConnection()
140
142
  const goToRecord = useGoToRecord()
@@ -221,11 +223,16 @@ export function Map({
221
223
  const coordinatesField = mapConfig?.coordinatesField
222
224
  if (addressField && fields.map((field) => field.name).includes(addressField)) {
223
225
  const addressFieldSchema = getField(allFields, addressField)
224
- const hasAddressUpdateAccess = !!canUpdateField(collection, addressFieldSchema, permissions)
226
+ const hasAddressUpdateAccess = !!canUpdateField(collection, addressFieldSchema, permissions, claims)
225
227
  setHasLocationUpdateAccess(hasAddressUpdateAccess)
226
228
  } else if (coordinatesField && fields.map((field) => field.name).includes(coordinatesField)) {
227
229
  const coordinatesFieldSchema = getField(allFields, coordinatesField)
228
- const hasCoordinatesUpdateAccess = !!canUpdateField(collection, coordinatesFieldSchema, permissions)
230
+ const hasCoordinatesUpdateAccess = !!canUpdateField(
231
+ collection,
232
+ coordinatesFieldSchema,
233
+ permissions,
234
+ claims,
235
+ )
229
236
  setHasLocationUpdateAccess(hasCoordinatesUpdateAccess)
230
237
  }
231
238