"use client"; import { useActionState, useEffect, useId, useRef, useState } from "react"; import { Attachment, DropZone, type DropRefusal } from "@unlocalhosted/metalui"; import type { FormState } from "@/app/actions/types"; import { formatBytes } from "@/lib/format"; import { CommitDialog } from "./commit-dialog"; import { CommitIcon, UploadIcon } from "./icons"; import { Alert, Button, ButtonLink, RefChip } from "./ui"; const MAX_BYTES = 10 * 1024 * 1024; const REFUSED: Record = { size: "larger than 10 MB", type: "not a file that can be added", count: "one file too many", }; export function UploadForm({ action, repoName, branch, baseSha, dir, cancelHref, }: { action: (prev: FormState, formData: FormData) => Promise; repoName: string; branch: string; baseSha: string; dir: string; cancelHref: string; }) { const formId = useId(); const [state, formAction, pending] = useActionState(action, undefined); // The DropZone's own input has no name; this hidden one carries the chosen files with the form. const inputRef = useRef(null); const [files, setFiles] = useState([]); const [refused, setRefused] = useState([]); const [dialogOpen, setDialogOpen] = useState(false); // Keep the hidden in step with the list, so the files are submitted with the form. React // also resets the form after a failed action; this puts the chosen files back so a retry works. useEffect(() => { const input = inputRef.current; if (!input) return; const transfer = new DataTransfer(); files.forEach((f) => transfer.items.add(f)); input.files = transfer.files; }, [files, state]); const add = (taken: File[], no: DropRefusal[]) => { setRefused(no); setFiles((current) => { const names = new Set(current.map((f) => f.name)); return [...current, ...taken.filter((f) => !names.has(f.name) && names.add(f.name))]; }); }; const total = files.reduce((n, f) => n + f.size, 0); return (
{repoName} / {dir && {dir}/} on {branch}
} title="Drop files to add them to your repository" description="Any file, up to 10 MB each" chooseLabel="or browse your computer" overTitle="Let go to add them" /> {refused.length > 0 && ( {refused.length === 1 ? "One file wasn’t added: " : `${refused.length} files weren’t added: `} {refused.map((r, i) => ( {i > 0 && "; "} {r.file.name} is {REFUSED[r.reason]} ))} . )} {files.length > 0 && (
{files.map((f) => ( setFiles((current) => current.filter((x) => x.name !== f.name))} /> ))}

{files.length} file{files.length === 1 ? "" : "s"} · {formatBytes(total)}

)} {state?.error && !dialogOpen && {state.error}}
Cancel
); }