import { useState, useEffect, useRef } from 'react' import { FileUp, X, AlertTriangle } from 'lucide-react' import { flowTransferApi } from '@/api/flowTransfer' import { parseRFFlowFile, RFFlowParseError } from '@/lib/rfflowParser' import type { RFFlowFile } from '@/types' import { toast } from '@/lib/toast' import { cn } from '@/lib/utils' import { useNavigate } from 'react-router-dom' import { getTreeEditorPath } from '@/lib/routing' import { Button } from '@/components/ui/Button' interface ImportFlowModalProps { onClose: () => void } const TYPE_LABELS: Record = { troubleshooting: 'Troubleshooting', procedural: 'Project', maintenance: 'Maintenance', } export function ImportFlowModal({ onClose }: ImportFlowModalProps) { const navigate = useNavigate() const fileInputRef = useRef(null) const [step, setStep] = useState<'pick' | 'preview'>('pick') const [parsed, setParsed] = useState(null) const [nameOverride, setNameOverride] = useState('') const [parseError, setParseError] = useState(null) const [isImporting, setIsImporting] = useState(false) const [isDragging, setIsDragging] = useState(false) useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } document.addEventListener('keydown', handleKeyDown) return () => document.removeEventListener('keydown', handleKeyDown) }, [onClose]) const handleFile = async (file: File) => { setParseError(null) if (!file.name.endsWith('.rfflow')) { setParseError('File must have .rfflow extension') return } try { const text = await file.text() const data = parseRFFlowFile(text) setParsed(data) setNameOverride(data.flow.name) setStep('preview') } catch (err) { if (err instanceof RFFlowParseError) { setParseError(err.message) } else { setParseError('Failed to read file') } } } const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (file) handleFile(file) } const handleDrop = (e: React.DragEvent) => { e.preventDefault() setIsDragging(false) const file = e.dataTransfer.files[0] if (file) handleFile(file) } const handleImport = async () => { if (!parsed) return setIsImporting(true) try { const overrideName = nameOverride.trim() !== parsed.flow.name ? nameOverride.trim() : undefined const result = await flowTransferApi.importFlow(parsed, overrideName) toast.success(`Imported "${result.name}" as draft`) onClose() navigate(getTreeEditorPath(result.tree_id, result.tree_type)) } catch (err) { console.error('Import failed:', err) toast.error('Failed to import flow') } finally { setIsImporting(false) } } return (
e.stopPropagation()} > {/* Header */}

Import Flow

{/* Body */}
{step === 'pick' && (
fileInputRef.current?.click()} onDragOver={(e) => { e.preventDefault(); setIsDragging(true) }} onDragLeave={() => setIsDragging(false)} onDrop={handleDrop} >

Drop .rfflow file here or browse

JSON format

{parseError && (

{parseError}

)}
)} {step === 'preview' && parsed && (
{/* Editable name */}
setNameOverride(e.target.value)} maxLength={255} className={cn( 'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground', 'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20' )} />
{/* Flow info */}
Type: {TYPE_LABELS[parsed.flow.tree_type] || parsed.flow.tree_type}
{parsed.flow.description && (
Description:

{parsed.flow.description}

)} {parsed.flow.category && (
Category: {parsed.flow.category.name}
)} {parsed.flow.tags.length > 0 && (
Tags: {parsed.flow.tags.map((tag) => ( {tag} ))}
)} {parsed.flow.author_name && (
Original author: {parsed.flow.author_name}
)}
Version: v{parsed.flow.version}

Flow will be imported as a draft.

)}
{/* Footer */}
{step === 'preview' && ( )} {step === 'preview' && ( )}
) }