139 lines
3.5 KiB
TypeScript
139 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { useRef, useState } from "react";
|
|
import { getBackendErrorMessage } from "@/lib/error-message";
|
|
|
|
interface UploadFieldProps {
|
|
label: string;
|
|
value: string;
|
|
accept?: string;
|
|
helperText?: string;
|
|
onUploaded: (fileId: string) => void;
|
|
}
|
|
|
|
function formatDisplayValue(value: string) {
|
|
if (!value) {
|
|
return "";
|
|
}
|
|
|
|
if (value.startsWith("http://") || value.startsWith("https://")) {
|
|
try {
|
|
const url = new URL(value);
|
|
const lastSegment = url.pathname.split("/").filter(Boolean).pop();
|
|
return lastSegment || url.hostname;
|
|
} catch {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function getToken() {
|
|
if (typeof window === "undefined") {
|
|
return "";
|
|
}
|
|
|
|
return sessionStorage.getItem("token") || localStorage.getItem("token") || "";
|
|
}
|
|
|
|
export function UploadField({
|
|
label,
|
|
value,
|
|
accept,
|
|
helperText,
|
|
onUploaded,
|
|
}: UploadFieldProps) {
|
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
const [uploading, setUploading] = useState(false);
|
|
const [error, setError] = useState("");
|
|
const [fileName, setFileName] = useState("");
|
|
const displayValue = formatDisplayValue(value);
|
|
|
|
async function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
|
const file = event.target.files?.[0];
|
|
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
setUploading(true);
|
|
setError("");
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append("file", file);
|
|
|
|
const res = await fetch("/api/upload", {
|
|
method: "POST",
|
|
headers: { "x-auth-token": getToken() },
|
|
body: formData,
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
throw new Error(getBackendErrorMessage(data, "Upload gagal"));
|
|
}
|
|
|
|
const fileId = data?.data?.id || data?.data?.fileId || data?.fileId || "";
|
|
|
|
if (!fileId) {
|
|
throw new Error("File id tidak ditemukan pada response upload");
|
|
}
|
|
|
|
setFileName(file.name);
|
|
onUploaded(fileId);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Upload gagal");
|
|
} finally {
|
|
setUploading(false);
|
|
if (inputRef.current) {
|
|
inputRef.current.value = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-2xl border border-outline-variant/10 bg-surface-container-low p-4">
|
|
<div className="flex flex-col gap-4">
|
|
<div className="min-w-0">
|
|
<p className="text-[11px] font-black uppercase tracking-[0.18em] text-outline">
|
|
{label}
|
|
</p>
|
|
<p className="mt-2 text-sm font-semibold text-on-surface">
|
|
{value ? "Uploaded" : "No file uploaded"}
|
|
</p>
|
|
<p className="mt-1 truncate text-xs text-on-surface-variant" title={fileName || value}>
|
|
{fileName ||
|
|
(value
|
|
? `File: ${displayValue}`
|
|
: helperText || "Select a file")}
|
|
</p>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => inputRef.current?.click()}
|
|
disabled={uploading}
|
|
className="w-full rounded-xl bg-primary px-4 py-3 text-center text-xs font-black uppercase tracking-[0.14em] text-white disabled:opacity-60"
|
|
>
|
|
{uploading ? "Uploading..." : value ? "Replace" : "Upload"}
|
|
</button>
|
|
</div>
|
|
|
|
{error ? (
|
|
<p className="mt-3 text-xs font-semibold text-error">{error}</p>
|
|
) : null}
|
|
|
|
<input
|
|
ref={inputRef}
|
|
type="file"
|
|
accept={accept}
|
|
onChange={handleChange}
|
|
className="hidden"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|