"use client";
import { useActionState, useEffect, useState, type ReactNode } from "react";
import type { FormState } from "@/app/actions/types";
import { UndoIcon, UploadIcon } from "./icons";
import { PlateBody } from "./settings-plate";
import { Alert, Button, buttonClass } from "./ui";
/**
* Uploads a picture (a repository's image, an organization's avatar) with a preview of the chosen
* file before it's saved, and offers to remove the one there is.
*/
export function ImageUploadForm({
action,
resetAction,
hasImage,
noun,
resetLabel,
description,
renderImage,
}: {
action: (prev: FormState, formData: FormData) => Promise<FormState>;
resetAction: () => Promise<void>;
hasImage: boolean;
/** "Image" or "Avatar", for the buttons. */
noun: string;
resetLabel: string;
description: ReactNode;
/** The picture as it's shown, or with `src` (the chosen file) instead. */
renderImage: (src?: string) => ReactNode;
}) {
const [preview, setPreview] = useState<string | null>(null);
const [state, formAction, pending] = useActionState(async (prev: FormState, formData: FormData) => {
const result = await action(prev, formData);
if (result?.success) setPreview(null);
return result;
}, undefined);
useEffect(() => () => {
if (preview) URL.revokeObjectURL(preview);
}, [preview]);
return (
<PlateBody>
<div className="flex flex-wrap items-start gap-5">
{renderImage(preview ?? undefined)}
<div className="flex min-w-60 flex-1 flex-col gap-3">
<p className="type-body text-ink2">{description} PNG, JPEG, GIF or WebP, up to 1 MB.</p>
<div className="flex flex-wrap items-center gap-2">
<form action={formAction} className="flex items-center gap-2">
{/* The file input stays a real input; its label is drawn as MetalUI's standard cap. */}
<label className={buttonClass("default", "has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-green-deep has-focus-visible:outline-solid")}>
<UploadIcon /> {hasImage ? `Replace ${noun}` : `Upload ${noun}`}
<input
type="file"
name="image"
accept="image/png,image/jpeg,image/gif,image/webp"
required
className="sr-only"
onChange={(e) => {
const file = e.target.files?.[0];
setPreview(file ? URL.createObjectURL(file) : null);
}}
/>
</label>
{preview && (
<Button variant="primary" disabled={pending}>
{pending ? "Saving…" : `Save ${noun}`}
</Button>
)}
</form>
{hasImage && !preview && (
<form action={resetAction}>
<Button variant="invisible" icon={<UndoIcon />}>
{resetLabel}
</Button>
</form>
)}
</div>
{state?.error && <Alert>{state.error}</Alert>}
{state?.success && !preview && <Alert kind="success">{state.success}</Alert>}
</div>
</div>
</PlateBody>
);
}