'use client'; import { useState, useRef, type DragEvent, useCallback, useMemo } from 'react'; import { uploadFile } from '@/lib/api/files'; import { addToHistory, getSettings } from '@/lib/storage'; import { addPin } from '@/lib/api/pins'; import PortalLayout from '@/app/layout-portal'; import { checkUploadAllowed } from '@/lib/limits'; import { useNotify } from '@/lib/notifications'; import { formatBytes, gatewayLink } from '@/lib/helpers'; import { Zap, ArrowUp } from 'lucide-react'; import QuickUpload, { type FileEntry } from './components/QuickUpload'; import { PaymentProvider } from '@/lib/payment'; import CryptoUpload from './components/CryptoUpload'; import type { TabMode } from './helpers'; import ErrorBoundary from '@/components/ErrorBoundary'; /* ════════════════════════════ Page ════════════════════════════ */ export default function UploadPage() { const { notify } = useNotify(); const settings = useMemo(() => getSettings(), []); const inputRef = useRef(null); const cryptoInputRef = useRef(null); /* ── Tab state ── */ const [tab, setTab] = useState('quick'); /* ── Quick Upload state ── */ const [files, setFiles] = useState([]); const [dragOver, setDragOver] = useState(false); const [uploading, setUploading] = useState(false); const [uploadIndex, setUploadIndex] = useState(0); const [uploadDone, setUploadDone] = useState(false); const [copied, setCopied] = useState(null); /* ── Crypto Upload state ── */ const [cryptoFile, setCryptoFile] = useState(null); const [cryptoResult, setCryptoResult] = useState<{ cid: string; size: number } | null>(null); /* ── File management ── */ const addFiles = useCallback((incoming: FileList | File[]) => { const s = getSettings(); const arr = Array.from(incoming); const remaining = s.maxFiles - files.length; const toAdd = arr.slice(0, remaining); const allowedExts = s.allowedFileTypes ? s.allowedFileTypes.split(',').map(e => e.trim().toLowerCase()).filter(Boolean) : null; const rejected: string[] = []; const entries: FileEntry[] = toAdd .filter(f => s.maxFileSize === 0 || f.size <= s.maxFileSize) .filter(f => !files.some(ex => ex.file.name === f.name && ex.file.size === f.size)) .filter(f => { if (!allowedExts) return true; const ext = f.name.split('.').pop()?.toLowerCase(); if (ext && allowedExts.includes(ext)) return true; rejected.push(f.name); return false; }) .map(f => ({ id: Math.random().toString(36).slice(2, 9), file: f, preview: !allowedExts || allowedExts.some(ext => f.name.toLowerCase().endsWith('.' + ext)) ? URL.createObjectURL(f) : undefined, status: 'pending' as const, })); if (rejected.length > 0) { notify({ type: 'warning', title: 'Bestandstype niet toegestaan', message: `De volgende bestanden zijn overgeslagen: ${rejected.join(', ')}`, duration: 5000, }); } if (entries.length === 0) return; setFiles(prev => [...prev, ...entries]); setUploadDone(false); }, [files, notify]); function removeFile(id: string) { setFiles(prev => { const entry = prev.find(f => f.id === id); if (entry?.preview) URL.revokeObjectURL(entry.preview); return prev.filter(f => f.id !== id); }); } function clearAll() { files.forEach(f => f.preview && URL.revokeObjectURL(f.preview)); setFiles([]); setUploadDone(false); } /* ── Drag / Drop ── */ function onDragOver(e: DragEvent) { e.preventDefault(); setDragOver(true); } function onDragLeave() { setDragOver(false); } function onDrop(e: DragEvent) { e.preventDefault(); setDragOver(false); if (e.dataTransfer.files.length > 0) addFiles(e.dataTransfer.files); } function onBrowse() { inputRef.current?.click(); } function onInputChange(e: React.ChangeEvent) { if (e.target.files && e.target.files.length > 0) { addFiles(e.target.files); } e.target.value = ''; } /* ── Sequential Upload ── */ async function startUpload() { const pending = files.filter(f => f.status === 'pending'); if (pending.length === 0) return; // Check limits before uploading const limitCheck = await checkUploadAllowed().catch(() => null); if (limitCheck && !limitCheck.allowed) { notify({ type: 'error', title: 'Upload limiet bereikt', message: limitCheck.reason || 'Je upload limiet is bereikt', duration: 8000, }); return; } setUploading(true); setUploadIndex(0); setUploadDone(false); for (let i = 0; i < pending.length; i++) { const entry = pending[i]; setFiles(prev => prev.map(f => f.id === entry.id ? { ...f, status: 'uploading' as const } : f)); setUploadIndex(i + 1); try { const result = await uploadFile(entry.file); const date = new Date().toISOString(); addToHistory({ cid: result.cid, name: entry.file.name, size: result.size, date, method: 'quick' }); // Auto-pin na succesvolle upload addPin(result.cid, entry.file.name).catch((err) => { console.warn('[UploadPage] Auto-pin mislukt:', err); }); setFiles(prev => prev.map(f => f.id === entry.id ? { ...f, status: 'done' as const, result, date } : f )); } catch (e: unknown) { setFiles(prev => prev.map(f => f.id === entry.id ? { ...f, status: 'error' as const, errorMsg: e instanceof Error ? e.message : 'Upload failed' } : f )); } } setUploading(false); setUploadDone(true); } /* ── Crypto flow ── */ function onCryptoSelect() { cryptoInputRef.current?.click(); } function onCryptoFileChange(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (file) { setCryptoFile(file); setCryptoResult(null); } e.target.value = ''; } function onCryptoClear() { setCryptoFile(null); setCryptoResult(null); } async function doCryptoUpload(): Promise<{ cid: string; size: number }> { if (!cryptoFile) throw new Error('No file selected'); // Check limits before uploading const limitCheck = await checkUploadAllowed().catch(() => null); if (limitCheck && !limitCheck.allowed) { throw new Error(limitCheck.reason || 'Upload limiet bereikt'); } const r = await uploadFile(cryptoFile); setCryptoResult(r); addToHistory({ cid: r.cid, name: cryptoFile.name, size: r.size, date: new Date().toISOString(), method: 'eth' }); // Auto-pin na succesvolle crypto upload addPin(r.cid, cryptoFile.name).catch((err) => { console.warn('[UploadPage] Auto-pin crypto mislukt:', err); }); return r; } function onCryptoPaid(_info: { cid: string; txHash?: string; method: string }) { // handled in doCryptoUpload } /* ── Clipboard ── */ function copyCid(cid: string) { navigator.clipboard.writeText(cid).catch((err) => { console.error('[UploadPage] Clipboard CID failed:', err); }); setCopied(cid); setTimeout(() => setCopied(null), 2000); } function copyShareLink(cid: string) { const link = gatewayLink(cid); navigator.clipboard.writeText(link).catch((err) => { console.error('[UploadPage] Clipboard share link failed:', err); }); setCopied(`gw-${cid}`); setTimeout(() => setCopied(null), 2000); } /* ════════════ Render ════════════ */ return (

Upload

Upload files to IPFS — quick or with crypto payment

{/* ── Tab switcher ── */}
{/* Quick Upload tab */} {tab === 'quick' && ( )} {/* Crypto Payment tab */} {tab === 'crypto' && ( )}
); }