import { useEffect, useState } from "react"; import { Download, Lock, PlayCircle } from "lucide-react"; // Standalone, login-free video page for a shared /watch/ link. Rendered OUTSIDE the app // tree (no auth, no providers) — like the /privacy and /terms pages. Uses plain fetch and a tiny // self-contained i18n dict (the public viewer has no app language preference). type Meta = { needs_password: boolean; title?: string | null; uploader?: string | null; duration_s?: number | null; allow_download?: boolean; file_url?: string; }; const STR = { en: { loading: "Loading…", gone: "This link is no longer available.", goneHint: "It may have expired or been revoked by the owner.", passwordTitle: "This video is password-protected", passwordPlaceholder: "Enter password", watch: "Watch", wrong: "Wrong password.", download: "Download", brand: "Shared via Siftlode", }, hu: { loading: "Betöltés…", gone: "Ez a link már nem elérhető.", goneHint: "Lehet, hogy lejárt, vagy a tulajdonos visszavonta.", passwordTitle: "Ez a videó jelszóval védett", passwordPlaceholder: "Add meg a jelszót", watch: "Megnézem", wrong: "Hibás jelszó.", download: "Letöltés", brand: "Megosztva a Siftlode-dal", }, de: { loading: "Wird geladen…", gone: "Dieser Link ist nicht mehr verfügbar.", goneHint: "Er ist möglicherweise abgelaufen oder wurde widerrufen.", passwordTitle: "Dieses Video ist passwortgeschützt", passwordPlaceholder: "Passwort eingeben", watch: "Ansehen", wrong: "Falsches Passwort.", download: "Herunterladen", brand: "Geteilt über Siftlode", }, } as const; const lang = ((navigator.language || "en").slice(0, 2) as keyof typeof STR) in STR ? ((navigator.language || "en").slice(0, 2) as keyof typeof STR) : "en"; const T = STR[lang]; export default function WatchPage() { const token = window.location.pathname.split("/watch/")[1]?.split(/[/?#]/)[0] ?? ""; const [status, setStatus] = useState<"loading" | "ready" | "password" | "gone">("loading"); const [meta, setMeta] = useState(null); const [password, setPassword] = useState(""); const [error, setError] = useState(null); const [unlocking, setUnlocking] = useState(false); useEffect(() => { if (!token) { setStatus("gone"); return; } fetch(`/api/public/watch/${encodeURIComponent(token)}`) .then((r) => (r.ok ? r.json() : Promise.reject(r.status))) .then((m: Meta) => { if (m.needs_password) setStatus("password"); else { setMeta(m); setStatus("ready"); } }) .catch(() => setStatus("gone")); }, [token]); const unlock = async () => { setUnlocking(true); setError(null); try { const r = await fetch(`/api/public/watch/${encodeURIComponent(token)}/unlock`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password }), }); if (r.status === 403) { setError(T.wrong); return; } if (!r.ok) { setStatus("gone"); return; } setMeta(await r.json()); setStatus("ready"); } finally { setUnlocking(false); } }; return (
Siftlode
{status === "loading" &&
{T.loading}
} {status === "gone" && (
{T.gone}
{T.goneHint}
)} {status === "password" && (
{T.passwordTitle}
setPassword(e.target.value)} onKeyDown={(e) => e.key === "Enter" && password && unlock()} placeholder={T.passwordPlaceholder} className="w-full rounded-lg bg-slate-800 border border-slate-700 px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-teal-500/40" /> {error &&
{error}
}
)} {status === "ready" && meta && (
{/* Shrink-wrap the player to the video so a vertical/short clip renders as a narrow player instead of a wide box with black bars; a landscape clip fills the width. */}

{meta.title || "Video"}

{meta.uploader &&
{meta.uploader}
}
{meta.allow_download && ( {T.download} )}
)}
{T.brand}
); }