@dyrected/admin 2.4.0 → 2.4.1

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 (170) hide show
  1. package/dist/App.d.ts +1 -0
  2. package/dist/admin.css +2 -0
  3. package/dist/components/auth/auth-gate.d.ts +13 -0
  4. package/dist/components/error-boundary.d.ts +16 -0
  5. package/dist/components/forms/field-renderer.d.ts +22 -0
  6. package/dist/components/forms/fields/block-builder.d.ts +9 -0
  7. package/dist/components/forms/fields/date-picker.d.ts +8 -0
  8. package/dist/components/forms/fields/json-editor.d.ts +8 -0
  9. package/dist/components/forms/fields/media-picker.d.ts +12 -0
  10. package/dist/components/forms/fields/multi-select.d.ts +19 -0
  11. package/dist/components/forms/fields/radio-field.d.ts +8 -0
  12. package/dist/components/forms/fields/relationship-picker.d.ts +10 -0
  13. package/dist/components/forms/fields/rich-text-editor.d.ts +9 -0
  14. package/dist/components/forms/fields/select-field.d.ts +8 -0
  15. package/dist/components/forms/fields/switch-field.d.ts +6 -0
  16. package/dist/components/forms/fields/text-area-field.d.ts +8 -0
  17. package/dist/components/forms/fields/text-field.d.ts +8 -0
  18. package/dist/components/forms/form-engine.d.ts +14 -0
  19. package/dist/components/forms/form-field-renderer.d.ts +20 -0
  20. package/dist/components/forms/utils.d.ts +11 -0
  21. package/dist/components/layout/admin-shell.d.ts +5 -0
  22. package/dist/components/layout/branding-provider.d.ts +4 -0
  23. package/dist/components/live-preview/LivePreviewPane.d.ts +7 -0
  24. package/dist/components/media/focal-point-picker.d.ts +12 -0
  25. package/dist/components/media/media-card.d.ts +8 -0
  26. package/dist/components/media/media-grid.d.ts +8 -0
  27. package/dist/components/media/media-library-dialog.d.ts +11 -0
  28. package/dist/components/ui/aspect-ratio.d.ts +3 -0
  29. package/dist/components/ui/badge.d.ts +9 -0
  30. package/dist/components/ui/button.d.ts +11 -0
  31. package/dist/components/ui/calendar.d.ts +8 -0
  32. package/dist/components/ui/card.d.ts +8 -0
  33. package/dist/components/ui/checkbox.d.ts +4 -0
  34. package/dist/components/ui/command.d.ts +80 -0
  35. package/dist/components/ui/data-table.d.ts +14 -0
  36. package/dist/components/ui/dialog.d.ts +19 -0
  37. package/dist/components/ui/dropdown-menu.d.ts +27 -0
  38. package/dist/components/ui/form.d.ts +23 -0
  39. package/dist/components/ui/input.d.ts +3 -0
  40. package/dist/components/ui/label.d.ts +5 -0
  41. package/dist/components/ui/page-header.d.ts +10 -0
  42. package/dist/components/ui/pagination.d.ts +11 -0
  43. package/dist/components/ui/popover.d.ts +6 -0
  44. package/dist/components/ui/progress.d.ts +4 -0
  45. package/dist/components/ui/radio-group.d.ts +5 -0
  46. package/dist/components/ui/render-cell.d.ts +8 -0
  47. package/dist/components/ui/scroll-area.d.ts +5 -0
  48. package/dist/components/ui/select.d.ts +13 -0
  49. package/dist/components/ui/separator.d.ts +4 -0
  50. package/dist/components/ui/sheet.d.ts +25 -0
  51. package/dist/components/ui/sidebar.d.ts +65 -0
  52. package/dist/components/ui/skeleton.d.ts +2 -0
  53. package/dist/components/ui/sonner.d.ts +4 -0
  54. package/dist/components/ui/switch.d.ts +4 -0
  55. package/dist/components/ui/table.d.ts +10 -0
  56. package/dist/components/ui/tabs.d.ts +7 -0
  57. package/dist/components/ui/textarea.d.ts +3 -0
  58. package/dist/components/ui/toggle.d.ts +12 -0
  59. package/dist/components/ui/tooltip.d.ts +7 -0
  60. package/dist/hooks/use-mobile.d.ts +1 -0
  61. package/dist/hooks/use-preferences.d.ts +6 -0
  62. package/dist/index.d.ts +38 -0
  63. package/dist/index.mjs +69091 -0
  64. package/dist/lib/utils.d.ts +3 -0
  65. package/dist/main.d.ts +0 -0
  66. package/dist/pages/auth/first-user-page.d.ts +4 -0
  67. package/dist/pages/auth/login-page.d.ts +4 -0
  68. package/dist/pages/collections/edit-page.d.ts +1 -0
  69. package/dist/pages/collections/list-page.d.ts +5 -0
  70. package/dist/pages/dashboard/dashboard.d.ts +1 -0
  71. package/dist/pages/globals/editor-page.d.ts +1 -0
  72. package/dist/pages/media/media-page.d.ts +4 -0
  73. package/dist/pages/setup/setup-prompt.d.ts +6 -0
  74. package/dist/providers/dyrected-provider.d.ts +29 -0
  75. package/dist/providers/query-provider.d.ts +3 -0
  76. package/package.json +6 -3
  77. package/CHANGELOG.md +0 -153
  78. package/components.json +0 -17
  79. package/eslint.config.js +0 -22
  80. package/index.html +0 -13
  81. package/postcss.config.js +0 -6
  82. package/scripts/prefix-tailwind-precision.py +0 -98
  83. package/scripts/prefix-tailwind.py +0 -67
  84. package/src/App.css +0 -184
  85. package/src/App.tsx +0 -25
  86. package/src/assets/dyrected.svg +0 -155
  87. package/src/assets/hero.png +0 -0
  88. package/src/assets/react.svg +0 -1
  89. package/src/assets/vite.svg +0 -1
  90. package/src/components/auth/auth-gate.tsx +0 -64
  91. package/src/components/error-boundary.tsx +0 -45
  92. package/src/components/forms/field-renderer.tsx +0 -111
  93. package/src/components/forms/fields/block-builder.tsx +0 -213
  94. package/src/components/forms/fields/date-picker.tsx +0 -60
  95. package/src/components/forms/fields/json-editor.tsx +0 -62
  96. package/src/components/forms/fields/media-picker.tsx +0 -286
  97. package/src/components/forms/fields/multi-select.tsx +0 -145
  98. package/src/components/forms/fields/radio-field.tsx +0 -51
  99. package/src/components/forms/fields/relationship-picker.tsx +0 -143
  100. package/src/components/forms/fields/rich-text-editor.tsx +0 -224
  101. package/src/components/forms/fields/select-field.tsx +0 -35
  102. package/src/components/forms/fields/switch-field.tsx +0 -16
  103. package/src/components/forms/fields/text-area-field.tsx +0 -15
  104. package/src/components/forms/fields/text-field.tsx +0 -24
  105. package/src/components/forms/form-engine.tsx +0 -87
  106. package/src/components/forms/form-field-renderer.tsx +0 -269
  107. package/src/components/forms/utils.ts +0 -97
  108. package/src/components/layout/admin-shell.tsx +0 -479
  109. package/src/components/layout/branding-provider.tsx +0 -112
  110. package/src/components/live-preview/LivePreviewPane.tsx +0 -128
  111. package/src/components/media/focal-point-picker.tsx +0 -66
  112. package/src/components/media/media-card.tsx +0 -44
  113. package/src/components/media/media-grid.tsx +0 -32
  114. package/src/components/media/media-library-dialog.tsx +0 -465
  115. package/src/components/ui/aspect-ratio.tsx +0 -7
  116. package/src/components/ui/badge.tsx +0 -36
  117. package/src/components/ui/button.tsx +0 -56
  118. package/src/components/ui/calendar.tsx +0 -214
  119. package/src/components/ui/card.tsx +0 -79
  120. package/src/components/ui/checkbox.tsx +0 -28
  121. package/src/components/ui/command.tsx +0 -151
  122. package/src/components/ui/data-table.tsx +0 -219
  123. package/src/components/ui/dialog.tsx +0 -122
  124. package/src/components/ui/dropdown-menu.tsx +0 -200
  125. package/src/components/ui/form.tsx +0 -178
  126. package/src/components/ui/input.tsx +0 -24
  127. package/src/components/ui/label.tsx +0 -24
  128. package/src/components/ui/page-header.tsx +0 -30
  129. package/src/components/ui/pagination.tsx +0 -57
  130. package/src/components/ui/popover.tsx +0 -29
  131. package/src/components/ui/progress.tsx +0 -26
  132. package/src/components/ui/radio-group.tsx +0 -42
  133. package/src/components/ui/render-cell.tsx +0 -110
  134. package/src/components/ui/scroll-area.tsx +0 -46
  135. package/src/components/ui/select.tsx +0 -160
  136. package/src/components/ui/separator.tsx +0 -29
  137. package/src/components/ui/sheet.tsx +0 -140
  138. package/src/components/ui/sidebar.tsx +0 -771
  139. package/src/components/ui/skeleton.tsx +0 -15
  140. package/src/components/ui/sonner.tsx +0 -27
  141. package/src/components/ui/switch.tsx +0 -27
  142. package/src/components/ui/table.tsx +0 -117
  143. package/src/components/ui/tabs.tsx +0 -53
  144. package/src/components/ui/textarea.tsx +0 -22
  145. package/src/components/ui/toggle.tsx +0 -43
  146. package/src/components/ui/tooltip.tsx +0 -28
  147. package/src/hooks/use-mobile.tsx +0 -19
  148. package/src/hooks/use-preferences.ts +0 -56
  149. package/src/index.css +0 -111
  150. package/src/index.tsx +0 -198
  151. package/src/lib/utils.ts +0 -36
  152. package/src/main.tsx +0 -10
  153. package/src/pages/auth/first-user-page.tsx +0 -115
  154. package/src/pages/auth/login-page.tsx +0 -91
  155. package/src/pages/collections/edit-page.tsx +0 -280
  156. package/src/pages/collections/list-page.tsx +0 -343
  157. package/src/pages/dashboard/dashboard.tsx +0 -150
  158. package/src/pages/globals/editor-page.tsx +0 -122
  159. package/src/pages/media/media-page.tsx +0 -564
  160. package/src/pages/setup/setup-prompt.tsx +0 -181
  161. package/src/providers/dyrected-provider.tsx +0 -122
  162. package/src/providers/query-provider.tsx +0 -19
  163. package/src/types/jexl.d.ts +0 -11
  164. package/tailwind.config.ts +0 -103
  165. package/tsconfig.app.json +0 -28
  166. package/tsconfig.json +0 -12
  167. package/tsconfig.node.json +0 -25
  168. package/vite.config.ts +0 -39
  169. /package/{public → dist}/favicon.svg +0 -0
  170. /package/{public → dist}/icons.svg +0 -0
@@ -1,87 +0,0 @@
1
- import { useEffect } from "react"
2
- import { useForm, useWatch } from "react-hook-form"
3
- import { zodResolver } from "@hookform/resolvers/zod"
4
- import * as z from "zod"
5
- import { Form } from "../ui/form"
6
- import { Button } from "../ui/button"
7
- import type { Field as FieldSchema, Block as BlockSchema } from "@dyrected/sdk"
8
- import { buildSchemaShape, buildDefaultValues } from "./utils"
9
- import { FormFieldRenderer } from "./form-field-renderer"
10
-
11
- export type { FieldSchema, BlockSchema }
12
-
13
- interface FormEngineProps {
14
- collection: string
15
- fields: FieldSchema[]
16
- defaultValues?: Record<string, any>
17
- onSubmit: (data: any) => void
18
- onChange?: (isDirty: boolean) => void
19
- isLoading?: boolean
20
- submitLabel?: string
21
- readOnly?: boolean
22
- onDataChange?: (data: any) => void
23
- }
24
-
25
- export function FormEngine({
26
- collection,
27
- fields,
28
- defaultValues = {},
29
- onSubmit,
30
- onChange,
31
- isLoading,
32
- submitLabel = "Save",
33
- readOnly,
34
- onDataChange
35
- }: FormEngineProps) {
36
- const schemaShape = buildSchemaShape(fields)
37
- const formSchema = z.object(schemaShape)
38
-
39
- const form = useForm<z.infer<typeof formSchema>>({
40
- resolver: zodResolver(formSchema),
41
- defaultValues: buildDefaultValues(fields, defaultValues),
42
- })
43
-
44
- const { isDirty } = form.formState
45
-
46
- useEffect(() => {
47
- onChange?.(isDirty)
48
- }, [isDirty, onChange])
49
-
50
- const watchedValues = useWatch({
51
- control: form.control,
52
- })
53
-
54
- useEffect(() => {
55
- if (onDataChange) {
56
- const handler = setTimeout(() => {
57
- onDataChange(watchedValues)
58
- }, 100)
59
- return () => clearTimeout(handler)
60
- }
61
- }, [watchedValues, onDataChange])
62
-
63
- return (
64
- <Form {...form}>
65
- <form onSubmit={form.handleSubmit(onSubmit)} className="dy-space-y-8">
66
- <div className="dy-grid dy-gap-6">
67
- {fields.filter(f => !f.admin?.hidden).map((field) => (
68
- <FormFieldRenderer
69
- key={field.name}
70
- schema={field}
71
- basePath=""
72
- control={form.control}
73
- collection={collection}
74
- />
75
- ))}
76
- </div>
77
- <div className="dy-flex dy-justify-end dy-gap-4">
78
- {!readOnly && (
79
- <Button type="submit" disabled={isLoading}>
80
- {isLoading ? "Saving..." : submitLabel}
81
- </Button>
82
- )}
83
- </div>
84
- </form>
85
- </Form>
86
- )
87
- }
@@ -1,269 +0,0 @@
1
- import * as React from "react"
2
- import { useWatch, useFieldArray } from "react-hook-form"
3
- import { useDyrected } from "../../providers/dyrected-provider"
4
- import {
5
- FormControl,
6
- FormField,
7
- FormItem,
8
- FormLabel,
9
- FormMessage,
10
- } from "../ui/form"
11
- import { Button } from "../ui/button"
12
- import { Plus, Trash2, Layers } from "lucide-react"
13
- import { MediaLibraryDialog } from "../media/media-library-dialog"
14
- import { cn } from "../../lib/utils"
15
- import jexl from 'jexl'
16
- import type { Field as FieldSchema } from "@dyrected/sdk"
17
- import { FieldRenderer } from "./field-renderer"
18
- import { BlockBuilder } from "./fields/block-builder"
19
- import { buildDefaultValues } from "./utils"
20
-
21
- interface FormFieldRendererProps {
22
- schema: FieldSchema
23
- basePath: string
24
- control: any
25
- collection: string
26
- }
27
-
28
- /**
29
- * FormFieldRenderer (Field Orchestrator)
30
- *
31
- * This component handles the high-level logic for a single form field, including:
32
- * - Field Lifecycle: Manages registration and state via react-hook-form Controller.
33
- * - Conditional Visibility: Evaluates 'admin.condition' to show/hide fields dynamically.
34
- * - Layout & Presentation: Renders labels, descriptions, and error states.
35
- * - Validation: Displays Zod validation errors.
36
- *
37
- * It delegates the actual rendering of the input UI to the FieldRenderer.
38
- */
39
- export function FormFieldRenderer({ schema, basePath, control, collection }: FormFieldRendererProps) {
40
- const { user, schemas } = useDyrected()
41
-
42
- if (schema.admin?.hidden) return null
43
-
44
- const formValues = useWatch({ control })
45
- const siblingData = useWatch({ control, name: (basePath || undefined) as any }) || {}
46
- const conditionData = basePath ? { ...formValues, ...siblingData } : formValues
47
-
48
- let isVisible = true
49
- const condition = schema.admin?.condition
50
-
51
- if (typeof condition === 'function') {
52
- isVisible = condition(conditionData, siblingData)
53
- } else if (typeof condition === 'string') {
54
- try {
55
- const sanitizedCondition = condition.replace(/===/g, '==').replace(/!==/g, '!=')
56
- isVisible = jexl.evalSync(sanitizedCondition, conditionData)
57
- } catch (e) {
58
- console.warn("Jexl eval failed:", e)
59
- isVisible = true
60
- }
61
- }
62
-
63
- if (!isVisible) return null
64
-
65
- const readAccess = (schema.access as any)?.read
66
- let canRead = true
67
- if (readAccess === false) {
68
- canRead = false
69
- } else if (typeof readAccess === 'string') {
70
- try {
71
- canRead = jexl.evalSync(readAccess, { user, ...conditionData })
72
- } catch (e) {
73
- console.warn("Read access eval failed:", e)
74
- }
75
- }
76
-
77
- if (!canRead) return null
78
-
79
- const fullPath = basePath ? `${basePath}.${schema.name}` : schema.name
80
-
81
- if (schema.type === "object") {
82
- return (
83
- <div className="dy-left-accent dy-space-y-6">
84
- <div className="dy-flex dy-items-center dy-gap-2 dy-mb-2">
85
- <h4 className="dy-font-bold dy-text-sm dy-text-foreground/80 dy-tracking-tight">{schema.label || schema.name.charAt(0).toUpperCase() + schema.name.slice(1)}</h4>
86
- {schema.admin?.description && (
87
- <p className="dy-text-[10px] dy-text-muted-foreground/50 dy-italic">{schema.admin.description}</p>
88
- )}
89
- </div>
90
- <div className="dy-space-y-6">
91
- {schema.fields?.map(subField => (
92
- <FormFieldRenderer key={subField.name} schema={subField} basePath={fullPath} control={control} collection={collection} />
93
- ))}
94
- </div>
95
- </div>
96
- )
97
- }
98
-
99
- if (schema.type === "array") {
100
- return <ArrayFieldRenderer schema={schema} basePath={fullPath} control={control} collection={collection} />
101
- }
102
-
103
- if (schema.type === "blocks" && schema.blocks) {
104
- return <BlockBuilder schema={schema} basePath={fullPath} control={control} collection={collection} />
105
- }
106
-
107
- const isBoolean = schema.type === "boolean"
108
-
109
- return (
110
- <FormField
111
- control={control}
112
- name={fullPath}
113
- render={({ field: formField }: { field: any }) => (
114
- <FormItem className={cn(
115
- isBoolean
116
- ? "dy-flex dy-flex-row dy-items-center dy-justify-between dy-rounded-xl dy-border dy-border-border/40 dy-p-4 dy-bg-white/50 dy-shadow-sm dy-space-y-0"
117
- : "dy-space-y-3"
118
- )}>
119
- <div className={cn(isBoolean ? "dy-space-y-1" : "dy-flex dy-items-center dy-gap-2 dy-mb-1")}>
120
- <FormLabel className="dy-text-sm dy-font-semibold dy-text-foreground/80 dy-cursor-pointer">
121
- {schema.label || schema.name.charAt(0).toUpperCase() + schema.name.slice(1)}
122
- {schema.required && <span className="dy-text-destructive dy-ml-1">*</span>}
123
- </FormLabel>
124
- {schema.admin?.description && (
125
- <p className={cn(
126
- "dy-text-muted-foreground/60 dy-italic",
127
- isBoolean ? "dy-text-[11px] dy-leading-tight" : "dy-text-[11px] dy-leading-relaxed"
128
- )}>
129
- {schema.admin.description}
130
- </p>
131
- )}
132
- {!isBoolean && schema.unique && (
133
- <span className="dy-inline-flex dy-items-center dy-rounded-full dy-bg-primary/10 dy-px-1.5 dy-py-0.5 dy-text-[10px] dy-font-medium dy-text-primary dy-ring-1 dy-ring-inset dy-ring-primary/10">
134
- Unique
135
- </span>
136
- )}
137
- </div>
138
- <FormControl>
139
- <FieldRenderer schema={schema} field={formField} collection={collection} context={{ user, schemas, siblingData: conditionData }} />
140
- </FormControl>
141
- {!isBoolean && schema.admin?.description && (
142
- <p className="dy-text-[11px] dy-text-muted-foreground/70 dy-leading-relaxed dy-italic">{schema.admin.description}</p>
143
- )}
144
- <FormMessage className="dy-text-xs dy-font-medium" />
145
- </FormItem>
146
- )}
147
- />
148
- )
149
- }
150
-
151
- function ArrayFieldRenderer({ schema, basePath, control, collection }: { schema: FieldSchema, basePath: string, control: any, collection: string }) {
152
- const { fields, append, remove } = useFieldArray({ control, name: basePath })
153
- const { schemas } = useDyrected()
154
- const [isBulkOpen, setIsBulkOpen] = React.useState(false)
155
-
156
- // Find if there is an image field or a relationship to an upload collection
157
- const imageField = React.useMemo(() => {
158
- return schema.fields?.find(f => {
159
- if (f.type === 'image') return true
160
- if (f.type === 'relationship' && f.relationTo) {
161
- const relatedSchema = schemas?.collections?.find(s => s.slug === f.relationTo)
162
- return relatedSchema?.upload === true
163
- }
164
- return false
165
- })
166
- }, [schema.fields, schemas])
167
-
168
- const bulkCollection = (imageField?.type === 'relationship' ? imageField.relationTo : 'media') || 'media'
169
-
170
- const handleBulkAdd = (ids: string[]) => {
171
- ids.forEach(id => {
172
- const newItem = buildDefaultValues(schema.fields || [], {})
173
- if (imageField) {
174
- newItem[imageField.name] = id
175
- }
176
- append(newItem)
177
- })
178
- setIsBulkOpen(false)
179
- }
180
-
181
- return (
182
- <div className="dy-space-y-6 dy-transition-all dy-py-6">
183
- <div className="dy-flex dy-justify-between dy-items-end dy-pb-2">
184
- <div className="dy-space-y-1">
185
- <div className="dy-flex dy-items-center dy-gap-2">
186
- <Layers className="dy-h-4 dy-w-4 dy-text-primary" />
187
- <h4 className="dy-font-serif dy-font-bold dy-text-base dy-text-foreground dy-tracking-tight">{schema.label || schema.name.charAt(0).toUpperCase() + schema.name.slice(1)}</h4>
188
- </div>
189
- {schema.admin?.description && (
190
- <p className="dy-text-[11px] dy-text-muted-foreground/60 dy-italic dy-leading-relaxed">{schema.admin.description}</p>
191
- )}
192
- </div>
193
- <div className="dy-flex dy-items-center dy-gap-2">
194
- <Button
195
- type="button"
196
- variant="outline"
197
- size="sm"
198
- className="dy-h-9 dy-text-[11px] dy-font-bold dy-rounded-xl dy-border-primary/20 hover:dy-bg-primary/5 hover:dy-text-primary dy-transition-all dy-shadow-sm"
199
- onClick={() => append(buildDefaultValues(schema.fields || [], {}))}
200
- >
201
- <Plus className="dy-w-3.5 dy-h-3.5 dy-mr-1.5" />
202
- Add Item
203
- </Button>
204
- </div>
205
- </div>
206
-
207
- <div className="dy-space-y-8 dy-pl-0 dy-border-l dy-border-muted/30">
208
- {fields.map((item, index) => (
209
- <div key={item.id} className="dy-relative dy-group dy-animate-in dy-slide-in-from-left-2 dy-duration-300">
210
- <div className="dy-bg-muted/5 dy-left-accent dy-transition-all dy-relative">
211
- <Button
212
- type="button"
213
- variant="ghost"
214
- size="icon"
215
- className="dy-absolute dy-top-4 dy-right-4 dy-h-8 dy-w-8 dy-text-muted-foreground/20 hover:dy-text-destructive hover:dy-bg-destructive/10 dy-rounded-xl dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-all"
216
- onClick={() => remove(index)}
217
- >
218
- <Trash2 className="dy-w-4 dy-h-4" />
219
- </Button>
220
- <div className="dy-space-y-6">
221
- {schema.fields?.map(subField => (
222
- <FormFieldRenderer
223
- key={subField.name}
224
- schema={subField}
225
- basePath={`${basePath}.${index}`}
226
- control={control}
227
- collection={collection}
228
- />
229
- ))}
230
- </div>
231
- </div>
232
- </div>
233
- ))}
234
-
235
- {fields.length === 0 && (
236
- <div className="dy-flex dy-flex-col dy-items-center dy-justify-center dy-py-12 dy-border-2 dy-border-dashed dy-border-muted dy-rounded-3xl dy-bg-muted/5 dy-space-y-3">
237
- <div className="dy-p-3 dy-bg-muted dy-rounded-full">
238
- <Layers className="dy-h-6 dy-w-6 dy-text-muted-foreground/40" />
239
- </div>
240
- <p className="dy-text-xs dy-font-medium dy-text-muted-foreground/50">No items added yet</p>
241
- </div>
242
- )}
243
-
244
- <Button
245
- type="button"
246
- variant="outline"
247
- size="sm"
248
- className="dy-w-full dy-h-10 dy-text-xs dy-font-bold dy-rounded-2xl dy-border-dashed dy-border-primary/20 hover:dy-bg-primary/5 hover:dy-text-primary dy-transition-all dy-shadow-sm"
249
- onClick={() => append(buildDefaultValues(schema.fields || [], {}))}
250
- >
251
- <Plus className="dy-w-4 dy-h-4 dy-mr-2" />
252
- Add Item
253
- </Button>
254
- </div>
255
-
256
- {imageField && (
257
- <MediaLibraryDialog
258
- collection={bulkCollection}
259
- isOpen={isBulkOpen}
260
- onOpenChange={setIsBulkOpen}
261
- selectedValues={[]}
262
- multiple={true}
263
- onSelect={() => { }}
264
- onConfirm={(ids: string[]) => handleBulkAdd(ids)}
265
- />
266
- )}
267
- </div>
268
- )
269
- }
@@ -1,97 +0,0 @@
1
- import * as z from "zod"
2
- import type { Field as FieldSchema } from "@dyrected/sdk"
3
-
4
- export function normalizeOptions(options: string[] | { label: string; value: string }[] | undefined): { label: string; value: string }[] {
5
- if (!options) return []
6
- return options.map(opt => typeof opt === "string" ? { label: opt, value: opt } : opt)
7
- }
8
-
9
- export function buildSchemaShape(fields: FieldSchema[]) {
10
- const shape: Record<string, z.ZodTypeAny> = {}
11
- fields.forEach((field) => {
12
- let validator: any = z.any()
13
- const label = field.label || field.name.charAt(0).toUpperCase() + field.name.slice(1)
14
-
15
- if (field.type === "object" && field.fields) {
16
- validator = z.object(buildSchemaShape(field.fields))
17
- if (!field.required) validator = validator.optional()
18
- shape[field.name] = validator
19
- return
20
- }
21
-
22
- if (field.type === "blocks") {
23
- validator = z.array(z.any())
24
- if (!field.required) validator = validator.optional()
25
- shape[field.name] = validator
26
- return
27
- }
28
-
29
- if (field.type === "array" && field.fields) {
30
- validator = z.array(z.object(buildSchemaShape(field.fields)))
31
- if (!field.required) validator = validator.optional()
32
- shape[field.name] = validator
33
- return
34
- }
35
-
36
- const fieldType = field.type as string
37
- if (fieldType === "text" || fieldType === "textarea" || fieldType === "select" || fieldType === "image" || fieldType === "richText" || fieldType === "relationship" || fieldType === "date") {
38
- validator = z.string()
39
- if (field.required) validator = validator.min(1, `${label} is required`)
40
- } else if (field.type === "email") {
41
- validator = z.string().email(`${label} must be a valid email`)
42
- if (field.required) validator = validator.min(1, `${label} is required`)
43
- } else if (field.type === "url") {
44
- validator = z.string().url(`${label} must be a valid URL`)
45
- if (field.required) validator = validator.min(1, `${label} is required`)
46
- } else if (field.type === "number") {
47
- validator = z.coerce.number()
48
- } else if (field.type === "boolean") {
49
- validator = z.boolean()
50
- } else if (field.type === "json") {
51
- validator = z.any()
52
- } else if (field.type === "multiSelect") {
53
- validator = z.array(z.string())
54
- if (field.required) validator = validator.min(1, `${label} requires at least one selection`)
55
- }
56
-
57
- if (!field.required && field.type !== "multiSelect") {
58
- validator = validator.optional().or(z.literal(""))
59
- } else if (!field.required && field.type === "multiSelect") {
60
- validator = validator.optional()
61
- }
62
-
63
- shape[field.name] = validator
64
- })
65
- return shape
66
- }
67
-
68
- export function buildDefaultValues(fields: FieldSchema[], defaults: any) {
69
- return fields.reduce((acc, field) => {
70
- let defaultVal = defaults[field.name] ?? field.defaultValue
71
-
72
- if (field.type === "object" && field.fields) {
73
- acc[field.name] = buildDefaultValues(field.fields, defaultVal || {})
74
- return acc
75
- }
76
-
77
- if (field.type === "array") {
78
- acc[field.name] = Array.isArray(defaultVal) ? defaultVal : []
79
- return acc
80
- }
81
-
82
- if (field.type === "blocks") {
83
- acc[field.name] = Array.isArray(defaultVal) ? defaultVal : []
84
- return acc
85
- }
86
-
87
- if (defaultVal === undefined) {
88
- if (field.type === "boolean") defaultVal = false
89
- else if (field.type === "multiSelect") defaultVal = []
90
- else if (field.type === "json") defaultVal = {}
91
- else defaultVal = ""
92
- }
93
-
94
- acc[field.name] = defaultVal
95
- return acc
96
- }, {} as any)
97
- }