import { useEffect, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; import { uploadFile } from '../lib/upload.js'; interface EventInfo { coupleNames: string; allowVideo: boolean; maxFileMb: number; maxVideoSeconds: number; } type Status = 'idle' | 'preparing' | 'uploading' | 'success' | 'error'; const MB = 1024 * 1024; function formatBytes(bytes: number): string { if (bytes < MB) return `${(bytes / 1024).toFixed(0)} KB`; return `${(bytes / MB).toFixed(1)} MB`; } export default function Upload() { const [event, setEvent] = useState(null); const [files, setFiles] = useState([]); const [authorName, setAuthorName] = useState(''); const [message, setMessage] = useState(''); const [status, setStatus] = useState('idle'); const [progress, setProgress] = useState({ index: 0, loaded: 0, total: 0 }); const [error, setError] = useState(null); const [successCount, setSuccessCount] = useState(0); const abortRef = useRef(null); const inputRef = useRef(null); useEffect(() => { fetch('/api/event') .then((r) => (r.ok ? (r.json() as Promise) : null)) .then(setEvent) .catch(() => setEvent(null)); }, []); function reset() { setFiles([]); setStatus('idle'); setProgress({ index: 0, loaded: 0, total: 0 }); setError(null); setSuccessCount(0); if (inputRef.current) inputRef.current.value = ''; } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!files.length || status === 'uploading') return; const ac = new AbortController(); abortRef.current = ac; setStatus('uploading'); setError(null); setSuccessCount(0); try { for (let i = 0; i < files.length; i++) { const file = files[i]!; setProgress({ index: i, loaded: 0, total: file.size }); await uploadFile( file, { authorName, message: i === 0 ? message : undefined }, { signal: ac.signal, onProgress: (loaded, total) => setProgress({ index: i, loaded, total }), }, ); setSuccessCount((n) => n + 1); } setStatus('success'); } catch (err) { if ((err as DOMException)?.name === 'AbortError') { setStatus('idle'); return; } console.error(err); setError(humanError(err)); setStatus('error'); } finally { abortRef.current = null; } } function cancelUpload() { abortRef.current?.abort(); } const totalBytes = files.reduce((s, f) => s + f.size, 0); const overLimit = event && files.some( (f) => f.size > event.maxFileMb * MB || (f.type.startsWith('video/') && !event.allowVideo), ); if (status === 'success') { return (

Obrigado!

{successCount === 1 ? 'Sua foto/vídeo foi enviado com sucesso.' : `${successCount} arquivos enviados com sucesso.`} {message ? ' Sua mensagem aos noivos foi guardada.' : ''}

Ver galeria
); } return (
← Voltar

Enviar fotos

Compartilhe momentos do casamento de {event?.coupleNames ?? 'Stefanie & Leandro'}. {event?.allowVideo ? ` Vídeos curtos (até ${Math.floor(event.maxVideoSeconds / 60)} min) também são bem-vindos.` : ' Apenas fotos por enquanto.'}

setFiles(Array.from(e.target.files ?? []))} className="block w-full text-sm text-stone-700 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-medium file:bg-stone-200 file:text-stone-700 hover:file:bg-stone-300" /> {files.length > 0 && (

{files.length} arquivo{files.length > 1 ? 's' : ''} ({formatBytes(totalBytes)})

)} {overLimit && (

Algum arquivo excede o limite ({event?.maxFileMb} MB) ou é um vídeo (não permitido).

)}
setAuthorName(e.target.value)} disabled={status === 'uploading'} maxLength={80} placeholder="Como você quer aparecer na galeria" className="w-full rounded-lg border border-stone-300 bg-white px-3 py-2 text-stone-800 focus:border-stone-500 focus:outline-none" />