@questpie/admin 1.0.0 → 1.0.2

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.
@@ -1,5 +1,4 @@
1
1
  import { g as cn, h as Button } from "./content-locales-provider-BXvuIgfg.mjs";
2
- import "./runtime-6VZM878K.mjs";
3
2
  import { a as ResponsiveDialogDescription, c as ResponsiveDialogTitle, i as ResponsiveDialogContent, n as useUpload, o as ResponsiveDialogFooter, r as ResponsiveDialog, s as ResponsiveDialogHeader, t as Dropzone } from "./dropzone-Do3awXKd.mjs";
4
3
  import { CheckCircle, WarningCircle, X } from "@phosphor-icons/react";
5
4
  import * as React$1 from "react";
@@ -271,4 +270,4 @@ function BulkUploadDialog({ collection = "assets", onClose, onSuccess }) {
271
270
 
272
271
  //#endregion
273
272
  export { BulkUploadDialog };
274
- //# sourceMappingURL=bulk-upload-dialog-h7zXD78Y.mjs.map
273
+ //# sourceMappingURL=bulk-upload-dialog-D7w7W1Hl.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bulk-upload-dialog-h7zXD78Y.mjs","names":["React","newFiles: FileUploadState[]"],"sources":["../src/client/components/media/bulk-upload-dialog.tsx"],"sourcesContent":["/**\n * BulkUploadDialog Component\n *\n * Dialog for bulk uploading multiple files to the assets collection.\n * Triggered from the assets collection header action \"Upload Files\".\n *\n * Features:\n * - Multi-file dropzone\n * - Individual file progress tracking\n * - Validation (type, size)\n * - Error handling with retry\n * - Success notification\n *\n * @example\n * ```tsx\n * <BulkUploadDialog\n * collection=\"assets\"\n * onClose={() => setOpen(false)}\n * onSuccess={() => queryClient.invalidateQueries()}\n * />\n * ```\n */\n\nimport * as React from \"react\";\nimport { Trash, CheckCircle, WarningCircle, X } from \"@phosphor-icons/react\";\nimport { toast } from \"sonner\";\nimport { cn } from \"../../lib/utils\";\nimport { useUpload, type Asset } from \"../../hooks/use-upload\";\nimport { Button } from \"../ui/button\";\nimport {\n\tResponsiveDialog,\n\tResponsiveDialogContent,\n\tResponsiveDialogHeader,\n\tResponsiveDialogTitle,\n\tResponsiveDialogDescription,\n\tResponsiveDialogFooter,\n} from \"../ui/responsive-dialog\";\nimport { Dropzone } from \"../primitives/dropzone\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BulkUploadDialogProps {\n\t/**\n\t * Target collection\n\t * @default \"assets\"\n\t */\n\tcollection?: string;\n\n\t/**\n\t * Called when dialog should close\n\t */\n\tonClose: () => void;\n\n\t/**\n\t * Called after successful upload\n\t */\n\tonSuccess?: () => void;\n}\n\n/**\n * File upload state\n */\ninterface FileUploadState {\n\tfile: File;\n\tstatus: \"pending\" | \"uploading\" | \"success\" | \"error\";\n\tprogress: number;\n\tasset?: Asset;\n\terror?: string;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Sanitize filename for safe storage\n */\nfunction sanitizeFilename(filename: string): string {\n\tconst lastDot = filename.lastIndexOf(\".\");\n\tconst ext = lastDot > 0 ? filename.slice(lastDot) : \"\";\n\tconst name = lastDot > 0 ? filename.slice(0, lastDot) : filename;\n\n\tconst sanitized = name\n\t\t.normalize(\"NFD\")\n\t\t.replace(/[\\u0300-\\u036f]/g, \"\") // Remove diacritics\n\t\t.replace(/\\s+/g, \"-\") // Replace spaces with hyphens\n\t\t.replace(/[^a-zA-Z0-9._-]/g, \"\") // Remove invalid chars\n\t\t.replace(/-+/g, \"-\") // Collapse multiple hyphens\n\t\t.replace(/^-|-$/g, \"\") // Remove leading/trailing hyphens\n\t\t.toLowerCase();\n\n\treturn (sanitized || \"file\") + ext.toLowerCase();\n}\n\n/**\n * Format file size\n */\nfunction formatFileSize(bytes: number): string {\n\tif (bytes < 1024) return `${bytes} B`;\n\tif (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n// ============================================================================\n// File Item Component\n// ============================================================================\n\ninterface FileItemProps {\n\tfile: FileUploadState;\n\tonRemove?: () => void;\n}\n\nfunction FileItem({ file, onRemove }: FileItemProps) {\n\tconst statusIcon = {\n\t\tpending: null,\n\t\tuploading: null,\n\t\tsuccess: <CheckCircle weight=\"fill\" className=\"size-5 text-green-600\" />,\n\t\terror: <WarningCircle weight=\"fill\" className=\"size-5 text-destructive\" />,\n\t};\n\n\tconst statusColor = {\n\t\tpending: \"text-muted-foreground\",\n\t\tuploading: \"text-primary\",\n\t\tsuccess: \"text-green-600\",\n\t\terror: \"text-destructive\",\n\t};\n\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\n\t\t\t\t\"flex items-start gap-3 rounded-lg border p-3\",\n\t\t\t\tfile.status === \"error\" && \"border-destructive/50 bg-destructive/5\",\n\t\t\t\tfile.status === \"success\" && \"border-green-600/50 bg-green-600/5\",\n\t\t\t)}\n\t\t>\n\t\t\t{/* File info */}\n\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t<p className=\"truncate text-sm font-medium\" title={file.file.name}>\n\t\t\t\t\t\t{file.file.name}\n\t\t\t\t\t</p>\n\t\t\t\t\t{statusIcon[file.status]}\n\t\t\t\t</div>\n\n\t\t\t\t<p className=\"text-muted-foreground text-xs\">\n\t\t\t\t\t{formatFileSize(file.file.size)}\n\t\t\t\t</p>\n\n\t\t\t\t{/* Progress bar */}\n\t\t\t\t{file.status === \"uploading\" && (\n\t\t\t\t\t<div className=\"mt-2\">\n\t\t\t\t\t\t<div className=\"bg-muted h-1.5 overflow-hidden rounded-full\">\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName=\"bg-primary h-full rounded-full transition-all duration-300\"\n\t\t\t\t\t\t\t\tstyle={{ width: `${file.progress}%` }}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<p className={cn(\"mt-1 text-xs\", statusColor[file.status])}>\n\t\t\t\t\t\t\tUploading... {file.progress}%\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\n\t\t\t\t{/* Error message */}\n\t\t\t\t{file.status === \"error\" && file.error && (\n\t\t\t\t\t<p className=\"mt-1 text-xs text-destructive\">{file.error}</p>\n\t\t\t\t)}\n\n\t\t\t\t{/* Success message */}\n\t\t\t\t{file.status === \"success\" && (\n\t\t\t\t\t<p className={cn(\"mt-1 text-xs\", statusColor[file.status])}>\n\t\t\t\t\t\tUploaded successfully\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* Remove button (only for pending files) */}\n\t\t\t{file.status === \"pending\" && onRemove && (\n\t\t\t\t<Button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\tsize=\"icon-xs\"\n\t\t\t\t\tonClick={onRemove}\n\t\t\t\t\tclassName=\"text-muted-foreground hover:text-destructive shrink-0\"\n\t\t\t\t>\n\t\t\t\t\t<X weight=\"bold\" />\n\t\t\t\t</Button>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\nexport function BulkUploadDialog({\n\tcollection = \"assets\",\n\tonClose,\n\tonSuccess,\n}: BulkUploadDialogProps) {\n\tconst { uploadMany } = useUpload();\n\n\t// State\n\tconst [files, setFiles] = React.useState<FileUploadState[]>([]);\n\tconst [isUploading, setIsUploading] = React.useState(false);\n\n\t// Computed states\n\tconst hasFiles = files.length > 0;\n\tconst pendingFiles = files.filter((f) => f.status === \"pending\");\n\tconst uploadedFiles = files.filter((f) => f.status === \"success\");\n\tconst failedFiles = files.filter((f) => f.status === \"error\");\n\tconst canUpload = pendingFiles.length > 0 && !isUploading;\n\tconst allComplete =\n\t\thasFiles && pendingFiles.length === 0 && !isUploading;\n\n\t// Handle file drop\n\tconst handleDrop = (droppedFiles: File[]) => {\n\t\t// Sanitize filenames\n\t\tconst sanitizedFiles = droppedFiles.map((file) => {\n\t\t\tconst sanitizedName = sanitizeFilename(file.name);\n\t\t\treturn new File([file], sanitizedName, { type: file.type });\n\t\t});\n\n\t\tconst newFiles: FileUploadState[] = sanitizedFiles.map((file) => ({\n\t\t\tfile,\n\t\t\tstatus: \"pending\",\n\t\t\tprogress: 0,\n\t\t}));\n\n\t\tsetFiles((prev) => [...prev, ...newFiles]);\n\t};\n\n\t// Handle remove file\n\tconst handleRemove = (index: number) => {\n\t\tsetFiles((prev) => prev.filter((_, i) => i !== index));\n\t};\n\n\t// Handle upload all\n\tconst handleUploadAll = async () => {\n\t\tif (!canUpload) return;\n\n\t\tsetIsUploading(true);\n\n\t\ttry {\n\t\t\t// Get pending files\n\t\t\tconst filesToUpload = files\n\t\t\t\t.filter((f) => f.status === \"pending\")\n\t\t\t\t.map((f) => f.file);\n\n\t\t\t// Upload sequentially with progress tracking\n\t\t\tfor (let i = 0; i < filesToUpload.length; i++) {\n\t\t\t\tconst file = filesToUpload[i];\n\t\t\t\tconst fileIndex = files.findIndex(\n\t\t\t\t\t(f) => f.file === file && f.status === \"pending\",\n\t\t\t\t);\n\n\t\t\t\t// Mark as uploading\n\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\tprev.map((f, idx) =>\n\t\t\t\t\t\tidx === fileIndex ? { ...f, status: \"uploading\" as const } : f,\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\ttry {\n\t\t\t\t\t// Upload file\n\t\t\t\t\tconst asset = await uploadMany([file], {\n\t\t\t\t\t\tcollection,\n\t\t\t\t\t\tonProgress: (progress) => {\n\t\t\t\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\t\t\t\tprev.map((f, idx) =>\n\t\t\t\t\t\t\t\t\tidx === fileIndex ? { ...f, progress } : f,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\t// Mark as success\n\t\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\t\tprev.map((f, idx) =>\n\t\t\t\t\t\t\tidx === fileIndex\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t...f,\n\t\t\t\t\t\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\t\t\t\t\t\tprogress: 100,\n\t\t\t\t\t\t\t\t\t\tasset: asset[0],\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: f,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// Mark as error\n\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\terr instanceof Error ? err.message : \"Upload failed\";\n\n\t\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\t\tprev.map((f, idx) =>\n\t\t\t\t\t\t\tidx === fileIndex\n\t\t\t\t\t\t\t\t? { ...f, status: \"error\" as const, error: errorMessage }\n\t\t\t\t\t\t\t\t: f,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Show success toast\n\t\t\tconst successCount = files.filter((f) => f.status === \"success\").length;\n\t\t\tconst failureCount = files.filter((f) => f.status === \"error\").length;\n\n\t\t\tif (successCount > 0) {\n\t\t\t\ttoast.success(\n\t\t\t\t\t`${successCount} file${successCount !== 1 ? \"s\" : \"\"} uploaded successfully`,\n\t\t\t\t);\n\t\t\t\tonSuccess?.();\n\t\t\t}\n\n\t\t\tif (failureCount > 0) {\n\t\t\t\ttoast.error(\n\t\t\t\t\t`${failureCount} file${failureCount !== 1 ? \"s\" : \"\"} failed to upload`,\n\t\t\t\t);\n\t\t\t}\n\t\t} finally {\n\t\t\tsetIsUploading(false);\n\t\t}\n\t};\n\n\t// Handle close\n\tconst handleClose = () => {\n\t\tif (isUploading) {\n\t\t\ttoast.warning(\"Please wait for uploads to complete\");\n\t\t\treturn;\n\t\t}\n\t\tonClose();\n\t};\n\n\t// Handle done (close after all complete)\n\tconst handleDone = () => {\n\t\tsetFiles([]);\n\t\tonClose();\n\t};\n\n\treturn (\n\t\t<ResponsiveDialog open onOpenChange={handleClose}>\n\t\t\t<ResponsiveDialogContent className=\"flex max-h-[90vh] flex-col sm:max-w-2xl\">\n\t\t\t\t<ResponsiveDialogHeader>\n\t\t\t\t\t<ResponsiveDialogTitle>Upload Files</ResponsiveDialogTitle>\n\t\t\t\t\t<ResponsiveDialogDescription>\n\t\t\t\t\t\tAdd multiple files to your media library\n\t\t\t\t\t</ResponsiveDialogDescription>\n\t\t\t\t</ResponsiveDialogHeader>\n\n\t\t\t\t{/* Content */}\n\t\t\t\t<div className=\"flex-1 space-y-4 overflow-y-auto\">\n\t\t\t\t\t{/* Dropzone */}\n\t\t\t\t\t{!allComplete && (\n\t\t\t\t\t\t<Dropzone\n\t\t\t\t\t\t\tonDrop={handleDrop}\n\t\t\t\t\t\t\tmultiple={true}\n\t\t\t\t\t\t\tdisabled={isUploading}\n\t\t\t\t\t\t\tlabel=\"Drop files here or click to browse\"\n\t\t\t\t\t\t\thint=\"Upload multiple files at once\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Files list */}\n\t\t\t\t\t{hasFiles && (\n\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">\n\t\t\t\t\t\t\t\t\tFiles ({files.length})\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t{uploadedFiles.length > 0 && (\n\t\t\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-xs\">\n\t\t\t\t\t\t\t\t\t\t{uploadedFiles.length} uploaded\n\t\t\t\t\t\t\t\t\t\t{failedFiles.length > 0 &&\n\t\t\t\t\t\t\t\t\t\t\t`, ${failedFiles.length} failed`}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t{files.map((file, index) => (\n\t\t\t\t\t\t\t\t\t<FileItem\n\t\t\t\t\t\t\t\t\t\tkey={`${file.file.name}-${index}`}\n\t\t\t\t\t\t\t\t\t\tfile={file}\n\t\t\t\t\t\t\t\t\t\tonRemove={\n\t\t\t\t\t\t\t\t\t\t\tfile.status === \"pending\"\n\t\t\t\t\t\t\t\t\t\t\t\t? () => handleRemove(index)\n\t\t\t\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{/* Footer */}\n\t\t\t\t<ResponsiveDialogFooter className=\"border-t pt-4\">\n\t\t\t\t\t{allComplete ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t<Button variant=\"outline\" onClick={handleDone}>\n\t\t\t\t\t\t\t\tDone\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\t\t\tonClick={handleClose}\n\t\t\t\t\t\t\t\tdisabled={isUploading}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tonClick={handleUploadAll}\n\t\t\t\t\t\t\t\tdisabled={!canUpload || isUploading}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{isUploading ? \"Uploading...\" : \"Upload\"}{\" \"}\n\t\t\t\t\t\t\t\t{pendingFiles.length > 0 && `(${pendingFiles.length})`}\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</>\n\t\t\t\t\t)}\n\t\t\t\t</ResponsiveDialogFooter>\n\t\t\t</ResponsiveDialogContent>\n\t\t</ResponsiveDialog>\n\t);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bulk-upload-dialog-D7w7W1Hl.mjs","names":["React","newFiles: FileUploadState[]"],"sources":["../src/client/components/media/bulk-upload-dialog.tsx"],"sourcesContent":["/**\n * BulkUploadDialog Component\n *\n * Dialog for bulk uploading multiple files to the assets collection.\n * Triggered from the assets collection header action \"Upload Files\".\n *\n * Features:\n * - Multi-file dropzone\n * - Individual file progress tracking\n * - Validation (type, size)\n * - Error handling with retry\n * - Success notification\n *\n * @example\n * ```tsx\n * <BulkUploadDialog\n * collection=\"assets\"\n * onClose={() => setOpen(false)}\n * onSuccess={() => queryClient.invalidateQueries()}\n * />\n * ```\n */\n\nimport * as React from \"react\";\nimport { Trash, CheckCircle, WarningCircle, X } from \"@phosphor-icons/react\";\nimport { toast } from \"sonner\";\nimport { cn } from \"../../lib/utils\";\nimport { useUpload, type Asset } from \"../../hooks/use-upload\";\nimport { Button } from \"../ui/button\";\nimport {\n\tResponsiveDialog,\n\tResponsiveDialogContent,\n\tResponsiveDialogHeader,\n\tResponsiveDialogTitle,\n\tResponsiveDialogDescription,\n\tResponsiveDialogFooter,\n} from \"../ui/responsive-dialog\";\nimport { Dropzone } from \"../primitives/dropzone\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BulkUploadDialogProps {\n\t/**\n\t * Target collection\n\t * @default \"assets\"\n\t */\n\tcollection?: string;\n\n\t/**\n\t * Called when dialog should close\n\t */\n\tonClose: () => void;\n\n\t/**\n\t * Called after successful upload\n\t */\n\tonSuccess?: () => void;\n}\n\n/**\n * File upload state\n */\ninterface FileUploadState {\n\tfile: File;\n\tstatus: \"pending\" | \"uploading\" | \"success\" | \"error\";\n\tprogress: number;\n\tasset?: Asset;\n\terror?: string;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Sanitize filename for safe storage\n */\nfunction sanitizeFilename(filename: string): string {\n\tconst lastDot = filename.lastIndexOf(\".\");\n\tconst ext = lastDot > 0 ? filename.slice(lastDot) : \"\";\n\tconst name = lastDot > 0 ? filename.slice(0, lastDot) : filename;\n\n\tconst sanitized = name\n\t\t.normalize(\"NFD\")\n\t\t.replace(/[\\u0300-\\u036f]/g, \"\") // Remove diacritics\n\t\t.replace(/\\s+/g, \"-\") // Replace spaces with hyphens\n\t\t.replace(/[^a-zA-Z0-9._-]/g, \"\") // Remove invalid chars\n\t\t.replace(/-+/g, \"-\") // Collapse multiple hyphens\n\t\t.replace(/^-|-$/g, \"\") // Remove leading/trailing hyphens\n\t\t.toLowerCase();\n\n\treturn (sanitized || \"file\") + ext.toLowerCase();\n}\n\n/**\n * Format file size\n */\nfunction formatFileSize(bytes: number): string {\n\tif (bytes < 1024) return `${bytes} B`;\n\tif (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n// ============================================================================\n// File Item Component\n// ============================================================================\n\ninterface FileItemProps {\n\tfile: FileUploadState;\n\tonRemove?: () => void;\n}\n\nfunction FileItem({ file, onRemove }: FileItemProps) {\n\tconst statusIcon = {\n\t\tpending: null,\n\t\tuploading: null,\n\t\tsuccess: <CheckCircle weight=\"fill\" className=\"size-5 text-green-600\" />,\n\t\terror: <WarningCircle weight=\"fill\" className=\"size-5 text-destructive\" />,\n\t};\n\n\tconst statusColor = {\n\t\tpending: \"text-muted-foreground\",\n\t\tuploading: \"text-primary\",\n\t\tsuccess: \"text-green-600\",\n\t\terror: \"text-destructive\",\n\t};\n\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\n\t\t\t\t\"flex items-start gap-3 rounded-lg border p-3\",\n\t\t\t\tfile.status === \"error\" && \"border-destructive/50 bg-destructive/5\",\n\t\t\t\tfile.status === \"success\" && \"border-green-600/50 bg-green-600/5\",\n\t\t\t)}\n\t\t>\n\t\t\t{/* File info */}\n\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t<p className=\"truncate text-sm font-medium\" title={file.file.name}>\n\t\t\t\t\t\t{file.file.name}\n\t\t\t\t\t</p>\n\t\t\t\t\t{statusIcon[file.status]}\n\t\t\t\t</div>\n\n\t\t\t\t<p className=\"text-muted-foreground text-xs\">\n\t\t\t\t\t{formatFileSize(file.file.size)}\n\t\t\t\t</p>\n\n\t\t\t\t{/* Progress bar */}\n\t\t\t\t{file.status === \"uploading\" && (\n\t\t\t\t\t<div className=\"mt-2\">\n\t\t\t\t\t\t<div className=\"bg-muted h-1.5 overflow-hidden rounded-full\">\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName=\"bg-primary h-full rounded-full transition-all duration-300\"\n\t\t\t\t\t\t\t\tstyle={{ width: `${file.progress}%` }}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<p className={cn(\"mt-1 text-xs\", statusColor[file.status])}>\n\t\t\t\t\t\t\tUploading... {file.progress}%\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\n\t\t\t\t{/* Error message */}\n\t\t\t\t{file.status === \"error\" && file.error && (\n\t\t\t\t\t<p className=\"mt-1 text-xs text-destructive\">{file.error}</p>\n\t\t\t\t)}\n\n\t\t\t\t{/* Success message */}\n\t\t\t\t{file.status === \"success\" && (\n\t\t\t\t\t<p className={cn(\"mt-1 text-xs\", statusColor[file.status])}>\n\t\t\t\t\t\tUploaded successfully\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* Remove button (only for pending files) */}\n\t\t\t{file.status === \"pending\" && onRemove && (\n\t\t\t\t<Button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\tsize=\"icon-xs\"\n\t\t\t\t\tonClick={onRemove}\n\t\t\t\t\tclassName=\"text-muted-foreground hover:text-destructive shrink-0\"\n\t\t\t\t>\n\t\t\t\t\t<X weight=\"bold\" />\n\t\t\t\t</Button>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\nexport function BulkUploadDialog({\n\tcollection = \"assets\",\n\tonClose,\n\tonSuccess,\n}: BulkUploadDialogProps) {\n\tconst { uploadMany } = useUpload();\n\n\t// State\n\tconst [files, setFiles] = React.useState<FileUploadState[]>([]);\n\tconst [isUploading, setIsUploading] = React.useState(false);\n\n\t// Computed states\n\tconst hasFiles = files.length > 0;\n\tconst pendingFiles = files.filter((f) => f.status === \"pending\");\n\tconst uploadedFiles = files.filter((f) => f.status === \"success\");\n\tconst failedFiles = files.filter((f) => f.status === \"error\");\n\tconst canUpload = pendingFiles.length > 0 && !isUploading;\n\tconst allComplete =\n\t\thasFiles && pendingFiles.length === 0 && !isUploading;\n\n\t// Handle file drop\n\tconst handleDrop = (droppedFiles: File[]) => {\n\t\t// Sanitize filenames\n\t\tconst sanitizedFiles = droppedFiles.map((file) => {\n\t\t\tconst sanitizedName = sanitizeFilename(file.name);\n\t\t\treturn new File([file], sanitizedName, { type: file.type });\n\t\t});\n\n\t\tconst newFiles: FileUploadState[] = sanitizedFiles.map((file) => ({\n\t\t\tfile,\n\t\t\tstatus: \"pending\",\n\t\t\tprogress: 0,\n\t\t}));\n\n\t\tsetFiles((prev) => [...prev, ...newFiles]);\n\t};\n\n\t// Handle remove file\n\tconst handleRemove = (index: number) => {\n\t\tsetFiles((prev) => prev.filter((_, i) => i !== index));\n\t};\n\n\t// Handle upload all\n\tconst handleUploadAll = async () => {\n\t\tif (!canUpload) return;\n\n\t\tsetIsUploading(true);\n\n\t\ttry {\n\t\t\t// Get pending files\n\t\t\tconst filesToUpload = files\n\t\t\t\t.filter((f) => f.status === \"pending\")\n\t\t\t\t.map((f) => f.file);\n\n\t\t\t// Upload sequentially with progress tracking\n\t\t\tfor (let i = 0; i < filesToUpload.length; i++) {\n\t\t\t\tconst file = filesToUpload[i];\n\t\t\t\tconst fileIndex = files.findIndex(\n\t\t\t\t\t(f) => f.file === file && f.status === \"pending\",\n\t\t\t\t);\n\n\t\t\t\t// Mark as uploading\n\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\tprev.map((f, idx) =>\n\t\t\t\t\t\tidx === fileIndex ? { ...f, status: \"uploading\" as const } : f,\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\ttry {\n\t\t\t\t\t// Upload file\n\t\t\t\t\tconst asset = await uploadMany([file], {\n\t\t\t\t\t\tcollection,\n\t\t\t\t\t\tonProgress: (progress) => {\n\t\t\t\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\t\t\t\tprev.map((f, idx) =>\n\t\t\t\t\t\t\t\t\tidx === fileIndex ? { ...f, progress } : f,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\t// Mark as success\n\t\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\t\tprev.map((f, idx) =>\n\t\t\t\t\t\t\tidx === fileIndex\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t...f,\n\t\t\t\t\t\t\t\t\t\tstatus: \"success\" as const,\n\t\t\t\t\t\t\t\t\t\tprogress: 100,\n\t\t\t\t\t\t\t\t\t\tasset: asset[0],\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: f,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// Mark as error\n\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\terr instanceof Error ? err.message : \"Upload failed\";\n\n\t\t\t\t\tsetFiles((prev) =>\n\t\t\t\t\t\tprev.map((f, idx) =>\n\t\t\t\t\t\t\tidx === fileIndex\n\t\t\t\t\t\t\t\t? { ...f, status: \"error\" as const, error: errorMessage }\n\t\t\t\t\t\t\t\t: f,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Show success toast\n\t\t\tconst successCount = files.filter((f) => f.status === \"success\").length;\n\t\t\tconst failureCount = files.filter((f) => f.status === \"error\").length;\n\n\t\t\tif (successCount > 0) {\n\t\t\t\ttoast.success(\n\t\t\t\t\t`${successCount} file${successCount !== 1 ? \"s\" : \"\"} uploaded successfully`,\n\t\t\t\t);\n\t\t\t\tonSuccess?.();\n\t\t\t}\n\n\t\t\tif (failureCount > 0) {\n\t\t\t\ttoast.error(\n\t\t\t\t\t`${failureCount} file${failureCount !== 1 ? \"s\" : \"\"} failed to upload`,\n\t\t\t\t);\n\t\t\t}\n\t\t} finally {\n\t\t\tsetIsUploading(false);\n\t\t}\n\t};\n\n\t// Handle close\n\tconst handleClose = () => {\n\t\tif (isUploading) {\n\t\t\ttoast.warning(\"Please wait for uploads to complete\");\n\t\t\treturn;\n\t\t}\n\t\tonClose();\n\t};\n\n\t// Handle done (close after all complete)\n\tconst handleDone = () => {\n\t\tsetFiles([]);\n\t\tonClose();\n\t};\n\n\treturn (\n\t\t<ResponsiveDialog open onOpenChange={handleClose}>\n\t\t\t<ResponsiveDialogContent className=\"flex max-h-[90vh] flex-col sm:max-w-2xl\">\n\t\t\t\t<ResponsiveDialogHeader>\n\t\t\t\t\t<ResponsiveDialogTitle>Upload Files</ResponsiveDialogTitle>\n\t\t\t\t\t<ResponsiveDialogDescription>\n\t\t\t\t\t\tAdd multiple files to your media library\n\t\t\t\t\t</ResponsiveDialogDescription>\n\t\t\t\t</ResponsiveDialogHeader>\n\n\t\t\t\t{/* Content */}\n\t\t\t\t<div className=\"flex-1 space-y-4 overflow-y-auto\">\n\t\t\t\t\t{/* Dropzone */}\n\t\t\t\t\t{!allComplete && (\n\t\t\t\t\t\t<Dropzone\n\t\t\t\t\t\t\tonDrop={handleDrop}\n\t\t\t\t\t\t\tmultiple={true}\n\t\t\t\t\t\t\tdisabled={isUploading}\n\t\t\t\t\t\t\tlabel=\"Drop files here or click to browse\"\n\t\t\t\t\t\t\thint=\"Upload multiple files at once\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Files list */}\n\t\t\t\t\t{hasFiles && (\n\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">\n\t\t\t\t\t\t\t\t\tFiles ({files.length})\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t{uploadedFiles.length > 0 && (\n\t\t\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-xs\">\n\t\t\t\t\t\t\t\t\t\t{uploadedFiles.length} uploaded\n\t\t\t\t\t\t\t\t\t\t{failedFiles.length > 0 &&\n\t\t\t\t\t\t\t\t\t\t\t`, ${failedFiles.length} failed`}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t{files.map((file, index) => (\n\t\t\t\t\t\t\t\t\t<FileItem\n\t\t\t\t\t\t\t\t\t\tkey={`${file.file.name}-${index}`}\n\t\t\t\t\t\t\t\t\t\tfile={file}\n\t\t\t\t\t\t\t\t\t\tonRemove={\n\t\t\t\t\t\t\t\t\t\t\tfile.status === \"pending\"\n\t\t\t\t\t\t\t\t\t\t\t\t? () => handleRemove(index)\n\t\t\t\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{/* Footer */}\n\t\t\t\t<ResponsiveDialogFooter className=\"border-t pt-4\">\n\t\t\t\t\t{allComplete ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t<Button variant=\"outline\" onClick={handleDone}>\n\t\t\t\t\t\t\t\tDone\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\t\t\tonClick={handleClose}\n\t\t\t\t\t\t\t\tdisabled={isUploading}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tonClick={handleUploadAll}\n\t\t\t\t\t\t\t\tdisabled={!canUpload || isUploading}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{isUploading ? \"Uploading...\" : \"Upload\"}{\" \"}\n\t\t\t\t\t\t\t\t{pendingFiles.length > 0 && `(${pendingFiles.length})`}\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</>\n\t\t\t\t\t)}\n\t\t\t\t</ResponsiveDialogFooter>\n\t\t\t</ResponsiveDialogContent>\n\t\t</ResponsiveDialog>\n\t);\n}\n"],"mappings":"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"}