@stoker-platform/web-app 0.5.28 → 0.5.30

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,17 @@
1
1
  # @stoker-platform/web-app
2
2
 
3
+ ## 0.5.30
4
+
5
+ ### Patch Changes
6
+
7
+ - fix: wait for conditional fields to render
8
+
9
+ ## 0.5.29
10
+
11
+ ### Patch Changes
12
+
13
+ - feat: skip file permissions selection when no optional permissions present
14
+
3
15
  ## 0.5.28
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoker-platform/web-app",
3
- "version": "0.5.28",
3
+ "version": "0.5.30",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "scripts": {
package/src/Form.tsx CHANGED
@@ -301,7 +301,6 @@ const RecordFormField = (props: FieldProps) => {
301
301
  setCondition(!!condition)
302
302
 
303
303
  const [
304
- readOnly,
305
304
  label,
306
305
  description,
307
306
  descriptionCondition,
@@ -316,7 +315,6 @@ const RecordFormField = (props: FieldProps) => {
316
315
  isRadio,
317
316
  icon,
318
317
  ] = await Promise.all([
319
- tryPromise(admin?.readOnly, [operation, record]),
320
318
  tryFunction(admin?.label),
321
319
  tryFunction(admin?.description?.message, [record]),
322
320
  tryPromise(admin?.description?.condition, [record]),
@@ -362,6 +360,10 @@ const RecordFormField = (props: FieldProps) => {
362
360
  return
363
361
  }
364
362
  setCondition(true)
363
+ setTimeout(() => {
364
+ const readOnly = tryFunction(admin?.readOnly, [operation, form.getValues()])
365
+ setReadOnly(!!readOnly)
366
+ }, 0)
365
367
  }, [form.watch()])
366
368
 
367
369
  const hasUpdateAccess = useMemo(() => {
@@ -2322,6 +2324,36 @@ function RecordForm({
2322
2324
  [collectionPath, record, path],
2323
2325
  )
2324
2326
 
2327
+ const getUserRoleAssignment = useCallback(() => {
2328
+ const userRole = permissions?.Role
2329
+ if (!userRole) return null
2330
+ // eslint-disable-next-line security/detect-object-injection
2331
+ const assignment = collection.access?.files?.assignment?.[userRole]
2332
+ return assignment || null
2333
+ }, [collection, permissions])
2334
+
2335
+ const shouldSkipPermissionsDialog = useCallback(() => {
2336
+ const assignment = getUserRoleAssignment()
2337
+ if (!assignment) return false
2338
+ const optional = assignment.optional || {}
2339
+ const hasOptional = Boolean(
2340
+ (optional.read && optional.read.length) ||
2341
+ (optional.update && optional.update.length) ||
2342
+ (optional.delete && optional.delete.length),
2343
+ )
2344
+ return !hasOptional
2345
+ }, [getUserRoleAssignment])
2346
+
2347
+ const getDefaultPermissions = useCallback((): FilePermissions => {
2348
+ const assignment = getUserRoleAssignment()
2349
+ const required = assignment?.required || {}
2350
+ return {
2351
+ read: (required.read || []).join(","),
2352
+ update: (required.update || []).join(","),
2353
+ delete: (required.delete || []).join(","),
2354
+ }
2355
+ }, [getUserRoleAssignment])
2356
+
2325
2357
  const uploadFilesToRecord = useCallback(
2326
2358
  async (targetId: string, files: File[] | FileList, permissions: FilePermissions, customFilename?: string) => {
2327
2359
  if (!files || !currentUser) return
@@ -2407,56 +2439,6 @@ function RecordForm({
2407
2439
  [computeBasePath, currentUser, path, record],
2408
2440
  )
2409
2441
 
2410
- const enqueueImageForCreate = useCallback((fieldName: string, file: File) => {
2411
- setPermissionsContext("image-create")
2412
- setPendingImageFieldName(fieldName)
2413
- setPendingUploadFile(file)
2414
- setPendingUploadField(fieldName)
2415
- setEditingFilename(file.name)
2416
- setIsMultipleFileUpload(false)
2417
- setShowPermissionsDialog(true)
2418
- }, [])
2419
-
2420
- const uploadImageForUpdate = useCallback(async (fieldName: string, file: File) => {
2421
- return await new Promise<void>((resolve) => {
2422
- setPermissionsContext("image-update")
2423
- setPendingImageForUpdate({ fieldName, file })
2424
- setEditingFilename(file.name)
2425
- setIsMultipleFileUpload(false)
2426
- setShowPermissionsDialog(true)
2427
- setImageUpdateResolver(() => resolve)
2428
- })
2429
- }, [])
2430
-
2431
- const handleFormFileUpload = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
2432
- const files = event.target.files
2433
- if (!files) return
2434
- if (files.length === 1) {
2435
- const file = files[0]
2436
- setPendingUploadFile(file)
2437
- setPendingUploadField(null)
2438
- setEditingFilename(file.name)
2439
- setIsMultipleFileUpload(false)
2440
- setShowFilenameDialog(true)
2441
- } else {
2442
- setPendingUploadFiles(Array.from(files))
2443
- setIsMultipleFileUpload(true)
2444
- setShowPermissionsDialog(true)
2445
- }
2446
- event.target.value = ""
2447
- }, [])
2448
-
2449
- const handleConfirmFilename = useCallback(() => {
2450
- if (!pendingUploadFile) return
2451
- const trimmed = editingFilename.trim()
2452
- const validationError = validateStorageName(trimmed)
2453
- if (validationError) {
2454
- toast({ title: "Invalid file name", description: validationError, variant: "destructive" })
2455
- return
2456
- }
2457
- setShowPermissionsDialog(true)
2458
- }, [pendingUploadFile, editingFilename])
2459
-
2460
2442
  const handlePermissionsConfirm = useCallback(
2461
2443
  async (selectedPermissions: FilePermissions) => {
2462
2444
  if (permissionsContext === "files") {
@@ -2620,6 +2602,91 @@ function RecordForm({
2620
2602
  ],
2621
2603
  )
2622
2604
 
2605
+ const enqueueImageForCreate = useCallback(
2606
+ (fieldName: string, file: File) => {
2607
+ if (shouldSkipPermissionsDialog()) {
2608
+ setQueuedImageUploads((prev) => ({
2609
+ ...prev,
2610
+ [fieldName]: { file, permissions: getDefaultPermissions() },
2611
+ }))
2612
+ } else {
2613
+ setPermissionsContext("image-create")
2614
+ setPendingImageFieldName(fieldName)
2615
+ setPendingUploadFile(file)
2616
+ setEditingFilename(file.name)
2617
+ setIsMultipleFileUpload(false)
2618
+ setShowPermissionsDialog(true)
2619
+ }
2620
+ },
2621
+ [shouldSkipPermissionsDialog, getDefaultPermissions],
2622
+ )
2623
+
2624
+ const uploadImageForUpdate = useCallback(
2625
+ async (fieldName: string, file: File) => {
2626
+ return await new Promise<void>((resolve) => {
2627
+ setPermissionsContext("image-update")
2628
+ setPendingImageForUpdate({ fieldName, file })
2629
+ setEditingFilename(file.name)
2630
+ setIsMultipleFileUpload(false)
2631
+ setImageUpdateResolver(() => resolve)
2632
+ if (shouldSkipPermissionsDialog()) {
2633
+ setTimeout(() => handlePermissionsConfirm(getDefaultPermissions()), 0)
2634
+ } else {
2635
+ setShowPermissionsDialog(true)
2636
+ }
2637
+ })
2638
+ },
2639
+ [shouldSkipPermissionsDialog, getDefaultPermissions, handlePermissionsConfirm],
2640
+ )
2641
+
2642
+ const handleFormFileUpload = useCallback(
2643
+ async (event: React.ChangeEvent<HTMLInputElement>) => {
2644
+ const files = event.target.files
2645
+ if (!files) return
2646
+ if (files.length === 1) {
2647
+ const file = files[0]
2648
+ setPendingUploadFile(file)
2649
+ setPendingUploadField(null)
2650
+ setEditingFilename(file.name)
2651
+ setIsMultipleFileUpload(false)
2652
+ setShowFilenameDialog(true)
2653
+ } else {
2654
+ const fileList = Array.from(files)
2655
+ if (shouldSkipPermissionsDialog()) {
2656
+ setQueuedUploads((prev) => [...prev, { files: fileList, permissions: getDefaultPermissions() }])
2657
+ } else {
2658
+ setPendingUploadFiles(fileList)
2659
+ setIsMultipleFileUpload(true)
2660
+ setShowPermissionsDialog(true)
2661
+ }
2662
+ }
2663
+ event.target.value = ""
2664
+ },
2665
+ [shouldSkipPermissionsDialog, getDefaultPermissions],
2666
+ )
2667
+
2668
+ const handleConfirmFilename = useCallback(() => {
2669
+ if (!pendingUploadFile) return
2670
+ const trimmed = editingFilename.trim()
2671
+ const validationError = validateStorageName(trimmed)
2672
+ if (validationError) {
2673
+ toast({ title: "Invalid file name", description: validationError, variant: "destructive" })
2674
+ return
2675
+ }
2676
+ if (shouldSkipPermissionsDialog()) {
2677
+ setQueuedUploads((prev) => [
2678
+ ...prev,
2679
+ { files: [pendingUploadFile], permissions: getDefaultPermissions(), customFilename: trimmed },
2680
+ ])
2681
+ setShowFilenameDialog(false)
2682
+ setPendingUploadFile(null)
2683
+ setPendingUploadField(null)
2684
+ setEditingFilename("")
2685
+ } else {
2686
+ setShowPermissionsDialog(true)
2687
+ }
2688
+ }, [pendingUploadFile, editingFilename, shouldSkipPermissionsDialog, getDefaultPermissions])
2689
+
2623
2690
  const handlePermissionsCancel = useCallback(() => {
2624
2691
  if (permissionsContext === "image-create" && pendingImageFieldName) {
2625
2692
  setTimeout(() => {
@@ -3388,19 +3455,21 @@ function RecordForm({
3388
3455
  }
3389
3456
  }
3390
3457
 
3391
- if (isInitialized && (operation === "create" || operation === "update") && customization.admin?.onChange) {
3392
- tryPromise(customization.admin.onChange, [
3393
- operation,
3394
- cloneDeep(formValues) as StokerRecord,
3395
- prevState as StokerRecord,
3396
- ]).then((updatedRecord: StokerRecord) => {
3397
- if (updatedRecord && !isEqual(updatedRecord, formValues)) {
3398
- Object.entries(updatedRecord).forEach(([key, value]) => {
3399
- form.setValue(key, value)
3400
- })
3401
- }
3402
- })
3403
- }
3458
+ setTimeout(() => {
3459
+ if (isInitialized && (operation === "create" || operation === "update") && customization.admin?.onChange) {
3460
+ tryPromise(customization.admin.onChange, [
3461
+ operation,
3462
+ cloneDeep(form.getValues()) as StokerRecord,
3463
+ prevState as StokerRecord,
3464
+ ]).then((updatedRecord: StokerRecord) => {
3465
+ if (updatedRecord && !isEqual(updatedRecord, formValues)) {
3466
+ Object.entries(updatedRecord).forEach(([key, value]) => {
3467
+ form.setValue(key, value)
3468
+ })
3469
+ }
3470
+ })
3471
+ }
3472
+ }, 0)
3404
3473
  }, [form.watch()])
3405
3474
 
3406
3475
  const recordLoaded = useRef(false)
@@ -79,7 +79,7 @@ export const getFormattedFieldValue = (
79
79
  if (badge) {
80
80
  if (badge === true) {
81
81
  return (
82
- <Badge variant="outline" className="text-center">
82
+ <Badge variant="outline" className="text-xs text-center">
83
83
  {value}
84
84
  </Badge>
85
85
  )