2026-09-24 20:59:26 -03:00
|
|
|
import { probeVideoDurationSeconds } from "@/lib/probe-video-duration";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* SH-116 client-confirmed media contract on the vendor portal: photos up to
|
|
|
|
|
* 10 MB (JPEG/PNG/HEIC), videos up to 100 MB and 90 seconds (MP4/MOV).
|
|
|
|
|
* PDF documents keep the previous 10 MB document cap.
|
|
|
|
|
*/
|
2026-07-24 18:10:45 -03:00
|
|
|
const MAX_DOCUMENT_BYTES = 10 * 1024 * 1024;
|
2026-09-24 20:59:26 -03:00
|
|
|
const MAX_PHOTO_BYTES = 10_000_000;
|
|
|
|
|
const MAX_VIDEO_BYTES = 100_000_000;
|
|
|
|
|
const MAX_VIDEO_DURATION_SECONDS = 90;
|
|
|
|
|
|
|
|
|
|
const UNSUPPORTED_TYPE_MESSAGE = "Only PDF, JPG, PNG, HEIC, MP4, and MOV files are allowed.";
|
|
|
|
|
|
|
|
|
|
type VendorUploadKind = "document" | "photo" | "video";
|
|
|
|
|
|
2026-09-24 21:01:36 -03:00
|
|
|
// Mirrors the server allowlist: an allowlisted browser type wins, otherwise the
|
|
|
|
|
// extension decides (mobile browsers often send an empty or octet-stream type).
|
|
|
|
|
const KIND_BY_MIME: Record<string, VendorUploadKind> = {
|
|
|
|
|
"application/pdf": "document",
|
|
|
|
|
"image/jpeg": "photo",
|
|
|
|
|
"image/jpg": "photo",
|
|
|
|
|
"image/png": "photo",
|
|
|
|
|
"image/heic": "photo",
|
|
|
|
|
"video/mp4": "video",
|
|
|
|
|
"video/quicktime": "video",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const KIND_BY_EXTENSION: Record<string, VendorUploadKind> = {
|
|
|
|
|
pdf: "document",
|
|
|
|
|
jpg: "photo",
|
|
|
|
|
jpeg: "photo",
|
|
|
|
|
png: "photo",
|
|
|
|
|
heic: "photo",
|
|
|
|
|
mp4: "video",
|
|
|
|
|
mov: "video",
|
|
|
|
|
};
|
|
|
|
|
|
2026-09-24 20:59:26 -03:00
|
|
|
function resolveKind(file: File): VendorUploadKind | null {
|
2026-09-24 21:01:36 -03:00
|
|
|
const name = file.name.toLowerCase();
|
|
|
|
|
const extension = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1) : "";
|
|
|
|
|
return KIND_BY_MIME[file.type.toLowerCase()] ?? KIND_BY_EXTENSION[extension] ?? null;
|
2026-09-24 20:59:26 -03:00
|
|
|
}
|
2026-07-24 18:10:45 -03:00
|
|
|
|
2026-09-24 20:59:26 -03:00
|
|
|
export async function validateVendorDocument(file: File): Promise<string | undefined> {
|
|
|
|
|
const kind = resolveKind(file);
|
|
|
|
|
if (!kind) {
|
|
|
|
|
return UNSUPPORTED_TYPE_MESSAGE;
|
2026-07-24 18:10:45 -03:00
|
|
|
}
|
|
|
|
|
|
2026-09-24 20:59:26 -03:00
|
|
|
if (kind === "photo" && file.size > MAX_PHOTO_BYTES) {
|
|
|
|
|
return "Photos must be 10 MB or smaller.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (kind === "video") {
|
|
|
|
|
if (file.size > MAX_VIDEO_BYTES) {
|
|
|
|
|
return "Videos must be 100 MB or smaller.";
|
|
|
|
|
}
|
2026-09-24 21:39:07 -03:00
|
|
|
// Pre-check; the server enforces it too. Unreadable metadata never blocks.
|
2026-09-24 20:59:26 -03:00
|
|
|
const duration = await probeVideoDurationSeconds(file);
|
|
|
|
|
if (duration != null && duration > MAX_VIDEO_DURATION_SECONDS) {
|
|
|
|
|
return `Videos must be ${MAX_VIDEO_DURATION_SECONDS} seconds or shorter.`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (kind === "document" && file.size > MAX_DOCUMENT_BYTES) {
|
2026-07-24 18:10:45 -03:00
|
|
|
return "Files must be 10 MB or smaller.";
|
|
|
|
|
}
|
2026-09-24 20:59:26 -03:00
|
|
|
|
|
|
|
|
return undefined;
|
2026-07-24 18:10:45 -03:00
|
|
|
}
|