@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,465 +0,0 @@
1
- import * as React from "react"
2
- import { useQuery } from "@tanstack/react-query"
3
- import { useDyrected } from "../../providers/dyrected-provider"
4
- import { Button } from "../ui/button"
5
- import {
6
- Dialog,
7
- DialogContent,
8
- DialogTitle,
9
- } from "../ui/dialog"
10
- import {
11
- Tabs,
12
- TabsContent,
13
- TabsList,
14
- TabsTrigger
15
- } from "../ui/tabs"
16
- import {
17
- Image as ImageIcon,
18
- Video,
19
- Search,
20
- Upload,
21
- Library,
22
- Check,
23
- Link as LinkIcon,
24
- Globe,
25
- Info,
26
- Sparkles
27
- } from "lucide-react"
28
- import { ScrollArea } from "../ui/scroll-area"
29
- import { Input } from "../ui/input"
30
- import { getMediaUrl, cn } from "../../lib/utils"
31
-
32
- interface MediaLibraryDialogProps {
33
- collection: string
34
- isOpen: boolean
35
- onOpenChange: (open: boolean) => void
36
- selectedValues: string[]
37
- onSelect: (id: string) => void
38
- multiple?: boolean
39
- onConfirm?: (selectedIds: string[]) => void
40
- }
41
-
42
- export function MediaLibraryDialog({
43
- collection,
44
- isOpen,
45
- onOpenChange,
46
- selectedValues,
47
- onSelect,
48
- multiple,
49
- onConfirm
50
- }: MediaLibraryDialogProps) {
51
- const { client } = useDyrected()
52
- const [searchQuery, setSearchQuery] = React.useState("")
53
- const [externalUrl, setExternalUrl] = React.useState("")
54
- const [activeTab, setActiveTab] = React.useState("library")
55
- const [selectedItem, setSelectedItem] = React.useState<any>(null)
56
- const [isUploading, setIsUploading] = React.useState(false)
57
-
58
- const { data: media, refetch } = useQuery({
59
- queryKey: [collection, searchQuery],
60
- queryFn: () => client!.listMedia({
61
- where: searchQuery ? { filename: { contains: searchQuery } } : undefined
62
- }, collection).then((r: any) => r.docs),
63
- enabled: isOpen && !!client,
64
- })
65
-
66
- const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
67
- const file = e.target.files?.[0]
68
- if (!file || !client) return
69
-
70
- setIsUploading(true)
71
- try {
72
- const result = await client.collection(collection).upload(file, {})
73
- await refetch()
74
- onSelect(result.id)
75
- if (!multiple) onOpenChange(false)
76
- } catch (error) {
77
- console.error("Upload failed:", error)
78
- alert("Upload failed. Please try again.")
79
- } finally {
80
- setIsUploading(false)
81
- }
82
- }
83
-
84
- const handleExternalUrlSubmit = async () => {
85
- if (!externalUrl || !client) return
86
-
87
- setIsUploading(true)
88
- try {
89
- let mimeType = 'application/octet-stream'
90
- let filename = 'External Asset'
91
- let idPrefix = 'ext'
92
-
93
- // YouTube Detection
94
- const ytMatch = externalUrl.match(/(?:youtu\.be\/|youtube\.com\/(?:v\/|u\/\w\/|embed\/|watch\?v=))([^#\&\?]*)/)
95
- if (ytMatch && ytMatch[1]) {
96
- mimeType = 'video/youtube'
97
- filename = `YouTube: ${ytMatch[1]}`
98
- idPrefix = `yt_${ytMatch[1]}`
99
- }
100
- // Vimeo Detection
101
- else if (externalUrl.match(/vimeo\.com\/(?:video\/)?([0-9]+)/)) {
102
- const vimeoId = externalUrl.match(/vimeo\.com\/(?:video\/)?([0-9]+)/)![1]
103
- mimeType = 'video/vimeo'
104
- filename = `Vimeo: ${vimeoId}`
105
- idPrefix = `vm_${vimeoId}`
106
- }
107
- // Image Detection
108
- else if (externalUrl.match(/\.(jpeg|jpg|gif|png|webp|svg|avif)(?:\?.*)?$/i)) {
109
- mimeType = 'image/external'
110
- filename = externalUrl.split('/').pop()?.split('?')[0] || 'External Image'
111
- idPrefix = `img_${Math.random().toString(36).substring(7)}`
112
- }
113
- // Default / Generic
114
- else {
115
- filename = externalUrl.split('/').pop()?.split('?')[0] || 'External File'
116
- }
117
-
118
- const result = await client.collection(collection).create({
119
- filename,
120
- url: externalUrl,
121
- mimeType,
122
- filesize: 0,
123
- id: idPrefix
124
- })
125
-
126
- await refetch()
127
- onSelect(result.id)
128
- if (!multiple) onOpenChange(false)
129
- setExternalUrl("")
130
- } catch (error) {
131
- console.error("Failed to add external URL:", error)
132
- alert("Failed to add URL. Please make sure it is valid.")
133
- } finally {
134
- setIsUploading(false)
135
- }
136
- }
137
-
138
- const getPreviewUrl = (item: any) => {
139
- if (!item) return ""
140
- if (item.mimeType === 'video/youtube') {
141
- const match = item.url?.match(/(?:youtu\.be\/|youtube\.com\/(?:v\/|u\/\w\/|embed\/|watch\?v=))([^#\&\?]*)/)
142
- const videoId = match && match[1]
143
- return `https://img.youtube.com/vi/${videoId}/mqdefault.jpg`
144
- }
145
- if (item.mimeType === 'video/vimeo') {
146
- // Vimeo thumbnails are harder to get purely client side without API,
147
- // but we can use a placeholder or better, try to fetch if we had a proper utility.
148
- // For now, let's use a generic vimeo-style placeholder or icon
149
- return "https://vimeo.com/assets/images/logo_vimeo_blue.png"
150
- }
151
- if (item.mimeType === 'image/external') {
152
- return item.url
153
- }
154
- return getMediaUrl(item, client?.getBaseUrl() || "");
155
- }
156
-
157
- const handleConfirm = () => {
158
- if (onConfirm) {
159
- onConfirm(selectedValues)
160
- }
161
- onOpenChange(false)
162
- }
163
-
164
- return (
165
- <Dialog open={isOpen} onOpenChange={onOpenChange}>
166
- <DialogContent className="sm:dy-max-w-[900px] dy-p-0 dy-overflow-hidden dy-gap-0 dy-bg-background dy-border-none dy-shadow-2xl">
167
- <Tabs value={activeTab} onValueChange={setActiveTab} className="dy-flex dy-flex-col dy-h-[650px]">
168
- <div className="dy-px-6 dy-py-4 dy-border-b dy-flex dy-items-center dy-justify-between dy-bg-muted/20">
169
- <div className="dy-flex dy-items-center dy-gap-4">
170
- <DialogTitle className="dy-text-xl dy-font-serif dy-font-bold dy-tracking-tight">Media Library</DialogTitle>
171
- {multiple && selectedValues.length > 0 && (
172
- <div className="dy-flex dy-items-center dy-gap-2 dy-px-3 dy-py-1 dy-bg-primary/10 dy-rounded-full dy-border dy-border-primary/20 dy-animate-in dy-fade-in dy-slide-in-from-left-2">
173
- <span className="dy-text-xs dy-font-bold dy-text-primary">{selectedValues.length} Selected</span>
174
- <Button variant="ghost" size="icon" className="dy-h-4 dy-w-4 dy-text-primary hover:dy-bg-transparent" onClick={handleConfirm}>
175
- <Check className="dy-h-3 dy-w-3" />
176
- </Button>
177
- </div>
178
- )}
179
- </div>
180
- <TabsList className="dy-bg-muted/50 dy-p-1 dy-rounded-xl">
181
- <TabsTrigger value="library" className="dy-gap-2 dy-rounded-lg dy-px-4 dy-font-bold dy-text-xs dy-uppercase dy-tracking-wider dy-transition-all data-[state=active]:dy-bg-background data-[state=active]:dy-shadow-sm">
182
- <Library className="dy-h-3.5 dy-w-3.5" /> Library
183
- </TabsTrigger>
184
- <TabsTrigger value="upload" className="dy-gap-2 dy-rounded-lg dy-px-4 dy-font-bold dy-text-xs dy-uppercase dy-tracking-wider dy-transition-all data-[state=active]:dy-bg-background data-[state=active]:dy-shadow-sm">
185
- <Upload className="dy-h-3.5 dy-w-3.5" /> Upload
186
- </TabsTrigger>
187
- <TabsTrigger value="external" className="dy-gap-2 dy-rounded-lg dy-px-4 dy-font-bold dy-text-xs dy-uppercase dy-tracking-wider dy-transition-all data-[state=active]:dy-bg-background data-[state=active]:dy-shadow-sm">
188
- <Globe className="dy-h-3.5 dy-w-3.5" /> External URL
189
- </TabsTrigger>
190
- </TabsList>
191
- </div>
192
-
193
- <div className="dy-flex-1 dy-overflow-hidden">
194
- <TabsContent value="library" className="dy-h-full dy-m-0 dy-p-0 focus-visible:dy-ring-0">
195
- <div className="dy-flex dy-h-full">
196
- <div className="dy-flex-1 dy-flex dy-flex-col dy-p-6 dy-space-y-4 dy-border-r">
197
- <div className="dy-flex dy-items-center dy-gap-4">
198
- <div className="dy-relative dy-flex-1 dy-group">
199
- <Search className="dy-absolute dy-left-3.5 dy-top-1/2 dy--translate-y-1/2 dy-h-4 dy-w-4 dy-text-muted-foreground dy-group-focus-within:dy-text-primary dy-transition-colors" />
200
- <Input
201
- placeholder="Search your media library..."
202
- className="dy-pl-11 dy-h-11 dy-rounded-xl dy-border-muted dy-bg-muted/10 focus:dy-bg-background dy-transition-all"
203
- value={searchQuery}
204
- onChange={(e) => setSearchQuery(e.target.value)}
205
- />
206
- </div>
207
- {multiple && (
208
- <div className="dy-flex dy-items-center dy-gap-1 dy-bg-muted/30 dy-p-1 dy-rounded-lg">
209
- <Button
210
- variant="ghost"
211
- size="sm"
212
- className="dy-h-8 dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-wider dy-px-3 hover:dy-bg-background dy-rounded-md"
213
- onClick={() => {
214
- media?.forEach((item: any) => {
215
- if (!selectedValues.includes(item.id)) onSelect(item.id)
216
- })
217
- }}
218
- >
219
- Select All
220
- </Button>
221
- <div className="dy-w-px dy-h-4 dy-bg-border/50 dy-mx-1" />
222
- <Button
223
- variant="ghost"
224
- size="sm"
225
- className="dy-h-8 dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-wider dy-px-3 dy-text-destructive hover:dy-text-destructive hover:dy-bg-destructive/10 dy-rounded-md"
226
- onClick={() => {
227
- selectedValues.forEach(id => onSelect(id))
228
- }}
229
- >
230
- Clear
231
- </Button>
232
- </div>
233
- )}
234
- </div>
235
- <ScrollArea className="dy-flex-1 dy--mx-2 dy-px-2">
236
- <div className="dy-grid dy-grid-cols-3 sm:dy-grid-cols-4 md:dy-grid-cols-5 dy-gap-4 dy-pb-4">
237
- {media?.map((item: any) => (
238
- <button
239
- key={item.id}
240
- type="button"
241
- onClick={() => {
242
- if (multiple) {
243
- onSelect(item.id)
244
- setSelectedItem(item)
245
- } else {
246
- if (selectedItem?.id === item.id) {
247
- onSelect(item.id)
248
- onOpenChange(false)
249
- } else {
250
- setSelectedItem(item)
251
- }
252
- }
253
- }}
254
- className={cn(
255
- "dy-relative dy-group dy-rounded-2xl dy-overflow-hidden dy-border-2 dy-aspect-square dy-transition-all hover:dy-scale-[1.02] active:dy-scale-95 dy-shadow-sm dy-bg-muted/5",
256
- selectedItem?.id === item.id
257
- ? "dy-border-primary dy-ring-4 dy-ring-primary/10 dy-shadow-lg dy-shadow-primary/5"
258
- : "dy-border-border/40 hover:dy-border-border"
259
- )}
260
- >
261
- <img
262
- src={getPreviewUrl(item)}
263
- alt={item.filename}
264
- className="dy-object-cover dy-w-full dy-h-full"
265
- />
266
- {selectedValues.includes(item.id) && (
267
- <div className="dy-absolute dy-top-2.5 dy-right-2.5 dy-h-7 dy-w-7 dy-bg-primary dy-rounded-full dy-flex dy-items-center dy-justify-center dy-text-white dy-shadow-xl dy-animate-in dy-zoom-in dy-border-2 dy-border-white">
268
- <Check className="dy-h-4 dy-w-4" />
269
- </div>
270
- )}
271
- {(item.mimeType?.startsWith('video/') || item.mimeType === 'video/youtube' || item.mimeType === 'video/vimeo') && (
272
- <div className="dy-absolute dy-inset-0 dy-flex dy-items-center dy-justify-center dy-bg-black/20 dy-group-hover:dy-bg-black/40 dy-transition-colors">
273
- <div className="dy-h-10 dy-w-10 dy-bg-white/20 dy-backdrop-blur-md dy-rounded-full dy-flex dy-items-center dy-justify-center dy-border dy-border-white/30 dy-shadow-2xl">
274
- <Video className="dy-h-5 dy-w-5 dy-text-white" />
275
- </div>
276
- </div>
277
- )}
278
- <div className="dy-absolute dy-inset-x-0 dy-bottom-0 dy-p-2.5 dy-bg-gradient-to-t dy-from-black/80 dy-via-black/40 dy-to-transparent dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-opacity">
279
- <p className="dy-text-[10px] dy-text-white dy-truncate dy-font-bold dy-uppercase dy-tracking-wider">{item.filename}</p>
280
- </div>
281
- </button>
282
- ))}
283
- </div>
284
- </ScrollArea>
285
- </div>
286
-
287
- <div className="dy-w-80 dy-bg-muted/5 dy-p-6 dy-flex dy-flex-col dy-gap-6 dy-overflow-y-auto dy-border-l dy-border-muted/20">
288
- {selectedItem ? (
289
- <>
290
- <div className="dy-space-y-5">
291
- <div className="dy-aspect-square dy-rounded-3xl dy-overflow-hidden dy-border dy-bg-background dy-shadow-2xl dy-group dy-relative dy-ring-1 dy-ring-border/50">
292
- <img
293
- src={getPreviewUrl(selectedItem)}
294
- className="dy-w-full dy-h-full dy-object-contain dy-p-2"
295
- alt=""
296
- />
297
- <div className="dy-absolute dy-inset-0 dy-bg-black/40 dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-opacity dy-flex dy-items-center dy-justify-center dy-backdrop-blur-sm">
298
- <Button variant="secondary" size="sm" className="dy-rounded-full dy-shadow-lg dy-font-bold" onClick={() => window.open(getPreviewUrl(selectedItem), '_blank')}>
299
- View Full
300
- </Button>
301
- </div>
302
- </div>
303
- <div className="dy-space-y-2">
304
- <h4 className="dy-font-bold dy-text-sm dy-truncate dy-leading-tight" title={selectedItem.filename}>
305
- {selectedItem.filename}
306
- </h4>
307
- <div className="dy-flex dy-flex-wrap dy-items-center dy-gap-2">
308
- <span className="dy-text-[9px] dy-font-black dy-uppercase dy-tracking-widest dy-bg-primary/10 dy-text-primary dy-px-2 dy-py-1 dy-rounded-md dy-border dy-border-primary/10">
309
- {selectedItem.mimeType?.split('/')[1] || selectedItem.mimeType}
310
- </span>
311
- <span className="dy-text-[10px] dy-font-bold dy-text-muted-foreground/60">
312
- {selectedItem.filesize ? `${(selectedItem.filesize / 1024).toFixed(1)} KB` : 'External Asset'}
313
- </span>
314
- </div>
315
- </div>
316
- </div>
317
-
318
- <div className="dy-space-y-3 dy-pt-6 dy-border-t dy-border-muted/20">
319
- {multiple ? (
320
- <>
321
- <Button
322
- className="dy-w-full dy-h-11 dy-rounded-xl dy-shadow-sm dy-font-bold dy-tracking-tight dy-transition-all"
323
- variant={selectedValues.includes(selectedItem.id) ? "outline" : "default"}
324
- onClick={() => onSelect(selectedItem.id)}
325
- >
326
- {selectedValues.includes(selectedItem.id) ? "Deselect Item" : "Add to Selection"}
327
- </Button>
328
- {selectedValues.length > 0 && (
329
- <Button
330
- className="dy-w-full dy-h-11 dy-rounded-xl dy-shadow-xl dy-bg-primary hover:dy-bg-primary/90 dy-font-bold dy-tracking-tight dy-transition-all dy-group"
331
- onClick={handleConfirm}
332
- >
333
- <span>Confirm {selectedValues.length} {selectedValues.length === 1 ? 'Asset' : 'Assets'}</span>
334
- <Sparkles className="dy-ml-2 dy-h-4 dy-w-4 dy-opacity-50 dy-group-hover:dy-opacity-100 dy-group-hover:dy-scale-110 dy-transition-all" />
335
- </Button>
336
- )}
337
- </>
338
- ) : (
339
- <Button
340
- className="dy-w-full dy-h-11 dy-rounded-xl dy-shadow-lg dy-font-bold dy-tracking-tight dy-bg-primary hover:dy-bg-primary/90 dy-transition-all"
341
- onClick={() => {
342
- onSelect(selectedItem.id)
343
- onOpenChange(false)
344
- }}
345
- >
346
- Select Media
347
- </Button>
348
- )}
349
- </div>
350
- </>
351
- ) : (
352
- <div className="dy-flex-1 dy-flex dy-flex-col dy-items-center dy-justify-center dy-text-center dy-space-y-5 dy-text-muted-foreground/30">
353
- <div className="dy-p-6 dy-bg-muted/10 dy-rounded-full dy-border dy-border-muted/20 dy-shadow-inner">
354
- <ImageIcon className="dy-h-10 dy-w-10" />
355
- </div>
356
- <div className="dy-space-y-1">
357
- <p className="dy-text-xs dy-font-bold dy-uppercase dy-tracking-widest">No Selection</p>
358
- <p className="dy-text-[10px] dy-font-medium dy-max-w-[150px] dy-leading-relaxed">Select an item from the library to view details and metadata</p>
359
- </div>
360
- </div>
361
- )}
362
- </div>
363
- </div>
364
- </TabsContent>
365
-
366
- <TabsContent value="upload" className="dy-h-full dy-m-0 dy-p-8 focus-visible:dy-ring-0">
367
- <div className="dy-h-full dy-flex dy-flex-col dy-items-center dy-justify-center dy-border-2 dy-border-dashed dy-border-primary/20 dy-rounded-[2.5rem] dy-bg-primary/5 hover:dy-bg-primary/10 dy-transition-all dy-group dy-relative dy-overflow-hidden">
368
- <div className="dy-absolute dy-inset-0 dy-bg-[radial-gradient(circle_at_center,var(--primary)_0%,transparent_100%)] dy-opacity-[0.03]" />
369
- <input
370
- type="file"
371
- id="media-upload-dialog"
372
- className="dy-hidden"
373
- onChange={handleUpload}
374
- disabled={isUploading}
375
- />
376
- <label
377
- htmlFor="media-upload-dialog"
378
- className="dy-flex dy-flex-col dy-items-center dy-gap-8 dy-cursor-pointer dy-p-12 dy-text-center dy-relative dy-z-10"
379
- >
380
- <div className="dy-h-24 dy-w-24 dy-bg-background dy-rounded-full dy-flex dy-items-center dy-justify-center dy-text-primary dy-group-hover:dy-scale-110 dy-transition-all dy-shadow-2xl dy-shadow-primary/20 dy-border dy-border-primary/10">
381
- <Upload className="dy-h-10 dy-w-10" />
382
- </div>
383
- <div className="dy-space-y-2">
384
- <p className="dy-font-serif dy-font-bold dy-text-3xl dy-tracking-tight">Upload new assets</p>
385
- <p className="dy-text-muted-foreground/60 dy-font-medium">Drag and drop files here or click to browse your computer</p>
386
- </div>
387
- <Button variant="secondary" className="dy-rounded-full dy-px-8 dy-h-12 dy-font-bold dy-shadow-sm dy-pointer-events-none dy-group-hover:dy-bg-primary dy-group-hover:dy-text-white dy-transition-all">
388
- Choose Files
389
- </Button>
390
- </label>
391
- </div>
392
- </TabsContent>
393
-
394
- <TabsContent value="external" className="dy-h-full dy-m-0 dy-p-12 focus-visible:dy-ring-0">
395
- <div className="dy-max-w-2xl dy-mx-auto dy-space-y-10 dy-pt-4">
396
- <div className="dy-text-center dy-space-y-4">
397
- <div className="dy-h-20 dy-w-20 dy-bg-primary/10 dy-text-primary dy-rounded-3xl dy-flex dy-items-center dy-justify-center dy-mx-auto dy-mb-6 dy-shadow-sm dy-rotate-3 dy-group-hover:dy-rotate-0 dy-transition-transform">
398
- <Globe className="dy-h-10 dy-w-10" />
399
- </div>
400
- <h3 className="dy-text-3xl dy-font-serif dy-font-bold dy-tracking-tight">Add External Resource</h3>
401
- <p className="dy-text-sm dy-text-muted-foreground/70 dy-leading-relaxed dy-max-w-md dy-mx-auto">
402
- Paste a link to any image, YouTube video, Vimeo link, or file to add it to your library without uploading.
403
- </p>
404
- </div>
405
-
406
- <div className="dy-space-y-6">
407
- <div className="dy-flex dy-gap-3">
408
- <div className="dy-relative dy-flex-1">
409
- <LinkIcon className="dy-absolute dy-left-4 dy-top-1/2 dy--translate-y-1/2 dy-h-4 dy-w-4 dy-text-muted-foreground" />
410
- <Input
411
- placeholder="https://example.com/image.jpg or video link..."
412
- className="dy-h-14 dy-rounded-2xl dy-shadow-xl dy-border-muted dy-bg-muted/5 dy-pl-12 dy-text-base dy-font-medium focus:dy-bg-background dy-transition-all"
413
- value={externalUrl}
414
- onChange={(e) => setExternalUrl(e.target.value)}
415
- />
416
- </div>
417
- <Button
418
- onClick={handleExternalUrlSubmit}
419
- disabled={isUploading || !externalUrl}
420
- className="dy-h-14 dy-rounded-2xl dy-px-10 dy-font-bold dy-shadow-xl dy-shadow-primary/20 dy-bg-primary hover:dy-bg-primary/90 dy-transition-all active:dy-scale-95"
421
- >
422
- {isUploading ? "Adding..." : "Add URL"}
423
- </Button>
424
- </div>
425
-
426
- <div className="dy-grid dy-grid-cols-2 dy-gap-4">
427
- <div className="dy-p-4 dy-rounded-2xl dy-bg-red-50/50 dy-border dy-border-red-100 dy-flex dy-items-start dy-gap-3">
428
- <div className="dy-mt-0.5 dy-p-1.5 dy-bg-red-100 dy-rounded-lg dy-text-red-600">
429
- <Video className="dy-h-4 dy-w-4" />
430
- </div>
431
- <div>
432
- <p className="dy-text-xs dy-font-bold dy-text-red-900">Video Streaming</p>
433
- <p className="dy-text-[10px] dy-text-red-700/70 dy-leading-relaxed dy-font-medium dy-mt-0.5">Supports YouTube & Vimeo. We recommend these for the best performance and compatibility.</p>
434
- </div>
435
- </div>
436
- <div className="dy-p-4 dy-rounded-2xl dy-bg-blue-50/50 dy-border dy-border-blue-100 dy-flex dy-items-start dy-gap-3">
437
- <div className="dy-mt-0.5 dy-p-1.5 dy-bg-blue-100 dy-rounded-lg dy-text-blue-600">
438
- <ImageIcon className="dy-h-4 dy-w-4" />
439
- </div>
440
- <div>
441
- <p className="dy-text-xs dy-font-bold dy-text-blue-900">External Assets</p>
442
- <p className="dy-text-[10px] dy-text-blue-700/70 dy-leading-relaxed dy-font-medium dy-mt-0.5">Add direct links to images or files from other CDNs. We'll automatically detect the type.</p>
443
- </div>
444
- </div>
445
- </div>
446
-
447
- <div className="dy-flex dy-items-center dy-gap-3 dy-p-4 dy-rounded-2xl dy-bg-muted/20 dy-border dy-border-muted/30">
448
- <div className="dy-p-2 dy-bg-background dy-rounded-xl dy-shadow-sm dy-text-muted-foreground">
449
- <Info className="dy-h-4 dy-w-4" />
450
- </div>
451
- <p className="dy-text-[11px] dy-text-muted-foreground/80 dy-font-medium dy-leading-relaxed">
452
- <span className="dy-font-bold dy-text-foreground">Pro Tip:</span> External videos are better streamed from
453
- <span className="dy-text-red-600 dy-font-bold dy-ml-1">YouTube</span> or
454
- <span className="dy-text-blue-500 dy-font-bold dy-ml-1">Vimeo</span> to ensure smooth playback on all devices.
455
- </p>
456
- </div>
457
- </div>
458
- </div>
459
- </TabsContent>
460
- </div>
461
- </Tabs>
462
- </DialogContent>
463
- </Dialog>
464
- )
465
- }
@@ -1,7 +0,0 @@
1
- "use client"
2
-
3
- import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
4
-
5
- const AspectRatio = AspectRatioPrimitive.Root
6
-
7
- export { AspectRatio }
@@ -1,36 +0,0 @@
1
- import * as React from "react"
2
- import { cva, type VariantProps } from "class-variance-authority"
3
-
4
- import { cn } from "../../lib/utils"
5
-
6
- const badgeVariants = cva(
7
- "dy-inline-flex dy-items-center dy-rounded-full dy-border dy-px-2.5 dy-py-0.5 dy-text-xs dy-font-semibold dy-transition-colors focus:dy-outline-none focus:dy-ring-2 focus:dy-ring-ring focus:dy-ring-offset-2",
8
- {
9
- variants: {
10
- variant: {
11
- default:
12
- "dy-border-transparent dy-bg-primary dy-text-primary-foreground hover:dy-bg-primary/80",
13
- secondary:
14
- "dy-border-transparent dy-bg-secondary dy-text-secondary-foreground hover:dy-bg-secondary/80",
15
- destructive:
16
- "dy-border-transparent dy-bg-destructive dy-text-destructive-foreground hover:dy-bg-destructive/80",
17
- outline: "dy-text-foreground",
18
- },
19
- },
20
- defaultVariants: {
21
- variant: "default",
22
- },
23
- }
24
- )
25
-
26
- export interface BadgeProps
27
- extends React.HTMLAttributes<HTMLDivElement>,
28
- VariantProps<typeof badgeVariants> {}
29
-
30
- function Badge({ className, variant, ...props }: BadgeProps) {
31
- return (
32
- <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
- )
34
- }
35
-
36
- export { Badge, badgeVariants }
@@ -1,56 +0,0 @@
1
- import * as React from "react"
2
- import { Slot } from "@radix-ui/react-slot"
3
- import { cva, type VariantProps } from "class-variance-authority"
4
-
5
- import { cn } from "../../lib/utils"
6
-
7
- const buttonVariants = cva(
8
- "dy-inline-flex dy-items-center dy-justify-center dy-gap-2 dy-whitespace-nowrap dy-rounded-md dy-text-sm dy-font-medium dy-ring-offset-background dy-transition-colors focus-visible:dy-outline-none focus-visible:dy-ring-2 focus-visible:dy-ring-ring focus-visible:dy-ring-offset-2 disabled:dy-pointer-events-none disabled:dy-opacity-50 [&_svg]:dy-pointer-events-none [&_svg]:dy-size-4 [&_svg]:dy-shrink-0",
9
- {
10
- variants: {
11
- variant: {
12
- default: "dy-bg-primary dy-text-primary-foreground hover:dy-bg-primary/90",
13
- destructive:
14
- "dy-bg-destructive dy-text-destructive-foreground hover:dy-bg-destructive/90",
15
- outline:
16
- "dy-border dy-border-input dy-bg-background hover:dy-bg-accent hover:dy-text-accent-foreground",
17
- secondary:
18
- "dy-bg-secondary dy-text-secondary-foreground hover:dy-bg-secondary/80",
19
- ghost: "hover:dy-bg-accent hover:dy-text-accent-foreground",
20
- link: "dy-text-primary dy-underline-offset-4 hover:dy-underline",
21
- },
22
- size: {
23
- default: "dy-h-10 dy-px-4 dy-py-2",
24
- sm: "dy-h-9 dy-rounded-md dy-px-3",
25
- lg: "dy-h-11 dy-rounded-md dy-px-8",
26
- icon: "dy-h-10 dy-w-10",
27
- },
28
- },
29
- defaultVariants: {
30
- variant: "default",
31
- size: "default",
32
- },
33
- }
34
- )
35
-
36
- export interface ButtonProps
37
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
- VariantProps<typeof buttonVariants> {
39
- asChild?: boolean
40
- }
41
-
42
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
- ({ className, variant, size, asChild = false, ...props }, ref) => {
44
- const Comp = asChild ? Slot : "button"
45
- return (
46
- <Comp
47
- className={cn(buttonVariants({ variant, size, className }))}
48
- ref={ref}
49
- {...props}
50
- />
51
- )
52
- }
53
- )
54
- Button.displayName = "Button"
55
-
56
- export { Button, buttonVariants }