"use client"; import { useRef, useState, useTransition } from "react"; import { toast } from "sonner"; import { CalendarClock, Upload } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { DateTimePicker } from "@/components/date-time-picker"; import { Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet, } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Progress } from "@/components/ui/progress"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; export function UploadForm() { const formRef = useRef(null); const uploadRequestRef = useRef(null); const [pending, startTransition] = useTransition(); const [scheduledAt, setScheduledAt] = useState(); const [video, setVideo] = useState(null); const [stagedVideoId, setStagedVideoId] = useState(null); const [uploadProgress, setUploadProgress] = useState(0); const [uploadStatus, setUploadStatus] = useState< "idle" | "uploading" | "completed" | "failed" >("idle"); const [youtube, setYoutube] = useState({ privacyStatus: "public", categoryId: "22", tags: "", madeForKids: false, containsSyntheticMedia: false, }); const [tiktok, setTiktok] = useState({ privacyLevel: "PUBLIC_TO_EVERYONE", disableComment: false, disableDuet: false, disableStitch: false, coverTimestampMs: 0, isAigc: false, }); function uploadVideo(file: File) { uploadRequestRef.current?.abort(); setVideo(file); setStagedVideoId(null); setUploadProgress(0); setUploadStatus("uploading"); const request = new XMLHttpRequest(); uploadRequestRef.current = request; request.open("POST", "/api/admin/video-file"); request.setRequestHeader("Content-Type", file.type); request.setRequestHeader("X-File-Name", encodeURIComponent(file.name)); request.upload.onprogress = (event) => { if (event.lengthComputable) { setUploadProgress(Math.round((event.loaded / event.total) * 100)); } }; request.onload = () => { try { const data = JSON.parse(request.responseText) as { stagedVideoId?: string; error?: string; }; if ( request.status < 200 || request.status >= 300 || !data.stagedVideoId ) { throw new Error(data.error || "Video upload failed."); } setStagedVideoId(data.stagedVideoId); setUploadProgress(100); setUploadStatus("completed"); toast.success("Video uploaded. You can finish the metadata now."); } catch (error) { setUploadStatus("failed"); toast.error( error instanceof Error ? error.message : "Video upload failed.", ); } }; request.onerror = () => { setUploadStatus("failed"); toast.error( "Video upload failed because the connection was interrupted.", ); }; request.send(file); } function submit(event: React.FormEvent) { event.preventDefault(); const form = new FormData(event.currentTarget); form.delete("video"); form.set("stagedVideoId", stagedVideoId ?? ""); form.set("videoName", video?.name ?? ""); form.set("videoMimeType", video?.type ?? ""); form.set("scheduledAt", scheduledAt?.toISOString() ?? ""); form.set( "youtubeSettings", JSON.stringify({ ...youtube, tags: youtube.tags .split(",") .map((tag) => tag.trim()) .filter(Boolean), }), ); form.set( "tiktokSettings", JSON.stringify({ ...tiktok, coverTimestampMs: Number(tiktok.coverTimestampMs) || 0, }), ); startTransition(async () => { try { const response = await fetch("/api/admin/video-upload", { method: "POST", body: form, }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || "Upload failed."); } toast.success(scheduledAt ? "Upload scheduled." : "Upload started."); formRef.current?.reset(); setVideo(null); setStagedVideoId(null); setUploadProgress(0); setUploadStatus("idle"); setScheduledAt(undefined); } catch (error) { toast.error(error instanceof Error ? error.message : "Upload failed."); } }); } return (
New upload One source file, two platform jobs. Video file { const file = event.target.files?.[0]; if (file) uploadVideo(file); }} /> {video ? `${video.name} · ${(video.size / 1024 / 1024).toFixed(1)} MB` : "MP4, MOV, or WebM. No application size limit."} {uploadStatus !== "idle" && (
Background upload {uploadStatus === "failed" ? "Failed" : `${uploadProgress}%`}
{uploadStatus === "completed" ? "Upload complete. The file is ready to publish." : uploadStatus === "failed" ? "Choose the file again to retry." : "You can continue filling in metadata while this runs."}
)} YouTube thumbnail TikTok uses the video frame selected below. Shared title / caption YouTube description