{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "settings-import-data",
  "title": "Settings Import Data",
  "description": "Import data from JSON or CSV files with conflict resolution.",
  "registryDependencies": [
    "badge",
    "button",
    "card",
    "field",
    "progress",
    "radio-group",
    "separator"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/settings/settings-import-data.tsx",
      "content": "\"use client\";\n\nimport { AlertCircle, Check, FileUp, Loader2, Upload, X } from \"lucide-react\";\nimport { useCallback, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Badge } from \"@/registry/new-york/ui/badge\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport { Field, FieldContent, FieldLabel } from \"@/registry/new-york/ui/field\";\nimport { Progress } from \"@/registry/new-york/ui/progress\";\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\n\nexport interface ImportPreview {\n  totalRecords: number;\n  categories: Record<string, number>;\n  conflicts: number;\n  fields: string[];\n}\n\nexport interface ImportJob {\n  id: string;\n  filename: string;\n  format: \"json\" | \"csv\";\n  status: \"preview\" | \"dry-run\" | \"importing\" | \"completed\" | \"failed\";\n  progress?: number;\n  preview?: ImportPreview;\n  conflictResolution?: \"skip\" | \"overwrite\" | \"merge\";\n  createdAt: Date;\n  completedAt?: Date;\n  error?: string;\n  recordsImported?: number;\n  recordsSkipped?: number;\n  recordsFailed?: number;\n}\n\nexport interface SettingsImportDataProps {\n  importHistory?: ImportJob[];\n  onUpload?: (file: File) => Promise<ImportPreview>;\n  onImport?: (data: {\n    file: File;\n    conflictResolution: \"skip\" | \"overwrite\" | \"merge\";\n    dryRun?: boolean;\n  }) => Promise<ImportJob>;\n  className?: string;\n}\n\nconst conflictResolutionOptions = [\n  {\n    value: \"skip\",\n    label: \"Skip\",\n    description: \"Skip conflicting records, keep existing data\",\n  },\n  {\n    value: \"overwrite\",\n    label: \"Overwrite\",\n    description: \"Replace existing data with imported data\",\n  },\n  {\n    value: \"merge\",\n    label: \"Merge\",\n    description: \"Combine existing and imported data\",\n  },\n];\n\nfunction formatFileSize(bytes: number): string {\n  if (bytes === 0) return \"0 Bytes\";\n  const k = 1024;\n  const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"];\n  const i = Math.floor(Math.log(bytes) / Math.log(k));\n  return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`;\n}\n\nexport default function SettingsImportData({\n  importHistory = [],\n  onUpload,\n  onImport,\n  className,\n}: SettingsImportDataProps) {\n  const [isDragging, setIsDragging] = useState(false);\n  const [isUploading, setIsUploading] = useState(false);\n  const [isImporting, setIsImporting] = useState(false);\n  const [uploadedFile, setUploadedFile] = useState<File | null>(null);\n  const [preview, setPreview] = useState<ImportPreview | null>(null);\n  const [conflictResolution, setConflictResolution] = useState<\n    \"skip\" | \"overwrite\" | \"merge\"\n  >(\"skip\");\n  const [errors, setErrors] = useState<Record<string, string>>({});\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const handleFileSelect = useCallback(\n    async (file: File) => {\n      setErrors({});\n\n      if (!(file.name.endsWith(\".json\") || file.name.endsWith(\".csv\"))) {\n        setErrors({\n          file: \"Please upload a JSON or CSV file\",\n        });\n        return;\n      }\n\n      if (file.size > 50 * 1024 * 1024) {\n        setErrors({\n          file: \"File size must be less than 50MB\",\n        });\n        return;\n      }\n\n      setUploadedFile(file);\n      setIsUploading(true);\n\n      try {\n        const previewData = await onUpload?.(file);\n        if (previewData) {\n          setPreview(previewData);\n        }\n      } catch (error) {\n        setErrors({\n          file:\n            error instanceof Error ? error.message : \"Failed to process file\",\n        });\n        setUploadedFile(null);\n      } finally {\n        setIsUploading(false);\n      }\n    },\n    [onUpload]\n  );\n\n  const handleDragOver = useCallback((e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setIsDragging(true);\n  }, []);\n\n  const handleDragLeave = useCallback((e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setIsDragging(false);\n  }, []);\n\n  const handleDrop = useCallback(\n    (e: React.DragEvent) => {\n      e.preventDefault();\n      e.stopPropagation();\n      setIsDragging(false);\n\n      const file = e.dataTransfer.files[0];\n      if (file) {\n        handleFileSelect(file);\n      }\n    },\n    [handleFileSelect]\n  );\n\n  const handleFileInputChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const file = e.target.files?.[0];\n      if (file) {\n        handleFileSelect(file);\n      }\n    },\n    [handleFileSelect]\n  );\n\n  const handleImport = async (dryRun = false) => {\n    if (!uploadedFile) return;\n\n    setIsImporting(true);\n    try {\n      await onImport?.({\n        file: uploadedFile,\n        conflictResolution,\n        dryRun,\n      });\n\n      if (!dryRun) {\n        setUploadedFile(null);\n        setPreview(null);\n        setConflictResolution(\"skip\");\n      }\n    } catch (error) {\n      setErrors({\n        import:\n          error instanceof Error ? error.message : \"Failed to import data\",\n      });\n    } finally {\n      setIsImporting(false);\n    }\n  };\n\n  const handleRemove = () => {\n    setUploadedFile(null);\n    setPreview(null);\n    setErrors({});\n    if (fileInputRef.current) {\n      fileInputRef.current.value = \"\";\n    }\n  };\n\n  const formatDate = (date: Date): string =>\n    new Intl.DateTimeFormat(\"en-US\", {\n      month: \"short\",\n      day: \"numeric\",\n      year: \"numeric\",\n      hour: \"numeric\",\n      minute: \"2-digit\",\n    }).format(date);\n\n  const getStatusBadge = (status: ImportJob[\"status\"]) => {\n    switch (status) {\n      case \"completed\":\n        return (\n          <Badge className=\"text-xs\" variant=\"default\">\n            Completed\n          </Badge>\n        );\n      case \"importing\":\n        return (\n          <Badge className=\"text-xs\" variant=\"secondary\">\n            Importing\n          </Badge>\n        );\n      case \"dry-run\":\n        return (\n          <Badge className=\"text-xs\" variant=\"outline\">\n            Dry Run\n          </Badge>\n        );\n      case \"failed\":\n        return (\n          <Badge className=\"text-xs\" variant=\"destructive\">\n            Failed\n          </Badge>\n        );\n      default:\n        return null;\n    }\n  };\n\n  return (\n    <Card className={cn(\"w-full shadow-xs\", className)}>\n      <CardHeader>\n        <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n          <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n            <CardTitle className=\"wrap-break-word\">Import Data</CardTitle>\n            <CardDescription className=\"wrap-break-word\">\n              Import your data from a JSON or CSV file\n            </CardDescription>\n          </div>\n        </div>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col gap-6\">\n          {/* File Upload */}\n          <div className=\"flex flex-col gap-4\">\n            {uploadedFile ? (\n              <div className=\"flex flex-col gap-4 rounded-lg border p-4\">\n                <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n                  <div className=\"flex min-w-0 flex-1 items-center gap-3\">\n                    <div className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary/10\">\n                      <FileUp className=\"size-5 text-primary\" />\n                    </div>\n                    <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n                      <p className=\"wrap-break-word font-medium text-sm\">\n                        {uploadedFile.name}\n                      </p>\n                      <p className=\"text-muted-foreground text-xs\">\n                        {formatFileSize(uploadedFile.size)}\n                      </p>\n                    </div>\n                  </div>\n                  <Button\n                    className=\"self-start sm:self-auto\"\n                    onClick={handleRemove}\n                    size=\"icon-sm\"\n                    type=\"button\"\n                    variant=\"ghost\"\n                  >\n                    <X className=\"size-4\" />\n                  </Button>\n                </div>\n\n                {preview && (\n                  <>\n                    <Separator />\n                    <div className=\"flex flex-col gap-3\">\n                      <h4 className=\"font-medium text-sm\">Preview</h4>\n                      <div className=\"flex flex-col gap-2 rounded-lg bg-muted/30 p-3\">\n                        <div className=\"flex items-center justify-between\">\n                          <span className=\"text-muted-foreground text-sm\">\n                            Total Records:\n                          </span>\n                          <span className=\"font-medium text-sm\">\n                            {preview.totalRecords}\n                          </span>\n                        </div>\n                        {preview.conflicts > 0 && (\n                          <div className=\"flex items-center justify-between\">\n                            <span className=\"text-muted-foreground text-sm\">\n                              Conflicts:\n                            </span>\n                            <Badge className=\"text-xs\" variant=\"destructive\">\n                              {preview.conflicts}\n                            </Badge>\n                          </div>\n                        )}\n                        {Object.keys(preview.categories).length > 0 && (\n                          <div className=\"flex flex-col gap-1\">\n                            <span className=\"text-muted-foreground text-sm\">\n                              Categories:\n                            </span>\n                            <div className=\"flex flex-wrap gap-1\">\n                              {Object.entries(preview.categories).map(\n                                ([category, count]) => (\n                                  <Badge\n                                    className=\"text-xs\"\n                                    key={category}\n                                    variant=\"outline\"\n                                  >\n                                    {category}: {count}\n                                  </Badge>\n                                )\n                              )}\n                            </div>\n                          </div>\n                        )}\n                      </div>\n                    </div>\n                  </>\n                )}\n              </div>\n            ) : (\n              <div\n                className={cn(\n                  \"flex cursor-pointer flex-col items-center justify-center gap-4 rounded-lg border-2 border-dashed p-8 transition-colors\",\n                  isDragging\n                    ? \"border-primary bg-primary/5\"\n                    : \"border-muted bg-muted/30 hover:border-primary/50\"\n                )}\n                onClick={() => fileInputRef.current?.click()}\n                onDragLeave={handleDragLeave}\n                onDragOver={handleDragOver}\n                onDrop={handleDrop}\n              >\n                {isUploading ? (\n                  <Loader2 className=\"size-8 animate-spin text-primary\" />\n                ) : (\n                  <Upload className=\"size-8 text-muted-foreground\" />\n                )}\n                <div className=\"flex flex-col gap-2 text-center\">\n                  <p className=\"font-medium text-sm\">\n                    {isUploading\n                      ? \"Processing file…\"\n                      : \"Drag and drop a file here, or click to browse\"}\n                  </p>\n                  <p className=\"text-muted-foreground text-xs\">\n                    Supported formats: JSON, CSV • Max size: 50MB\n                  </p>\n                </div>\n                <input\n                  accept=\".json,.csv\"\n                  className=\"hidden\"\n                  onChange={handleFileInputChange}\n                  ref={fileInputRef}\n                  type=\"file\"\n                />\n              </div>\n            )}\n\n            {errors.file && (\n              <div className=\"flex items-center gap-2 rounded-lg border border-destructive/50 bg-destructive/10 p-3\">\n                <AlertCircle className=\"size-4 text-destructive\" />\n                <p className=\"text-destructive text-sm\">{errors.file}</p>\n              </div>\n            )}\n          </div>\n\n          {/* Import Options */}\n          {uploadedFile && preview && (\n            <>\n              <Separator />\n              <div className=\"flex flex-col gap-4\">\n                <h3 className=\"font-semibold text-base\">Import Options</h3>\n                <Field>\n                  <FieldLabel>Conflict Resolution</FieldLabel>\n                  <FieldContent>\n                    <RadioGroup\n                      onValueChange={(value: \"skip\" | \"overwrite\" | \"merge\") =>\n                        setConflictResolution(value)\n                      }\n                      value={conflictResolution}\n                    >\n                      {conflictResolutionOptions.map((option) => (\n                        <div\n                          className=\"flex items-start gap-3 rounded-lg border p-3\"\n                          key={option.value}\n                        >\n                          <RadioGroupItem\n                            id={`resolution-${option.value}`}\n                            value={option.value}\n                          />\n                          <div className=\"flex flex-1 flex-col gap-1\">\n                            <label\n                              className=\"cursor-pointer font-medium text-sm\"\n                              htmlFor={`resolution-${option.value}`}\n                            >\n                              {option.label}\n                            </label>\n                            <p className=\"text-muted-foreground text-xs\">\n                              {option.description}\n                            </p>\n                          </div>\n                        </div>\n                      ))}\n                    </RadioGroup>\n                  </FieldContent>\n                </Field>\n\n                <div className=\"flex flex-col gap-2 sm:flex-row\">\n                  <Button\n                    className=\"w-full sm:w-auto\"\n                    disabled={isImporting}\n                    onClick={() => handleImport(true)}\n                    type=\"button\"\n                    variant=\"outline\"\n                  >\n                    <Check className=\"size-4\" />\n                    Dry Run\n                  </Button>\n                  <Button\n                    className=\"w-full sm:w-auto\"\n                    disabled={isImporting}\n                    onClick={() => handleImport(false)}\n                    type=\"button\"\n                  >\n                    {isImporting ? (\n                      <>\n                        <Loader2 className=\"size-4 animate-spin\" />\n                        Importing…\n                      </>\n                    ) : (\n                      <>\n                        <FileUp className=\"size-4\" />\n                        Import Data\n                      </>\n                    )}\n                  </Button>\n                </div>\n                {errors.import && (\n                  <div className=\"flex items-center gap-2 rounded-lg border border-destructive/50 bg-destructive/10 p-3\">\n                    <AlertCircle className=\"size-4 text-destructive\" />\n                    <p className=\"text-destructive text-sm\">{errors.import}</p>\n                  </div>\n                )}\n              </div>\n            </>\n          )}\n\n          <Separator />\n\n          {/* Import History */}\n          <div className=\"flex flex-col gap-4\">\n            <h3 className=\"font-semibold text-base\">Import History</h3>\n            {importHistory.length === 0 ? (\n              <p className=\"text-muted-foreground text-sm\">\n                No imports yet. Upload a file above to get started.\n              </p>\n            ) : (\n              <div className=\"flex flex-col gap-3\">\n                {importHistory.map((job) => (\n                  <div\n                    className=\"flex flex-col gap-3 rounded-lg border p-4\"\n                    key={job.id}\n                  >\n                    <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n                      <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n                        <div className=\"flex flex-wrap items-center gap-2\">\n                          <span className=\"font-medium text-sm\">\n                            {job.filename}\n                          </span>\n                          {getStatusBadge(job.status)}\n                          <Badge\n                            className=\"text-xs uppercase\"\n                            variant=\"outline\"\n                          >\n                            {job.format}\n                          </Badge>\n                        </div>\n                        {job.status === \"importing\" &&\n                          job.progress !== undefined && (\n                            <div className=\"flex flex-col gap-2\">\n                              <Progress value={job.progress} />\n                              <p className=\"text-muted-foreground text-xs\">\n                                {job.progress}% complete\n                              </p>\n                            </div>\n                          )}\n                        {job.status === \"completed\" && (\n                          <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground text-xs\">\n                            {job.recordsImported !== undefined && (\n                              <span className=\"text-green-600\">\n                                {job.recordsImported} imported\n                              </span>\n                            )}\n                            {job.recordsSkipped !== undefined &&\n                              job.recordsSkipped > 0 && (\n                                <span>{job.recordsSkipped} skipped</span>\n                              )}\n                            {job.recordsFailed !== undefined &&\n                              job.recordsFailed > 0 && (\n                                <span className=\"text-destructive\">\n                                  {job.recordsFailed} failed\n                                </span>\n                              )}\n                          </div>\n                        )}\n                        <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground text-xs\">\n                          <span>Started: {formatDate(job.createdAt)}</span>\n                          {job.completedAt && (\n                            <span>\n                              Completed: {formatDate(job.completedAt)}\n                            </span>\n                          )}\n                        </div>\n                        {job.error && (\n                          <p className=\"text-destructive text-sm\">\n                            {job.error}\n                          </p>\n                        )}\n                      </div>\n                    </div>\n                  </div>\n                ))}\n              </div>\n            )}\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "settings"
  ],
  "type": "registry:ui"
}