Files
InaTrading-Portal/src/components/upload-field.tsx

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>
);
}