import { useState, useEffect, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import axios from "axios"; import { getTemplateStudioAuthStatus, verifyTemplateStudioPassword, } from "../../api/client"; const SESSION_KEY = "template_studio_auth"; function errorMessageFromAxios(err: unknown): string { if (!axios.isAxiosError(err)) { return err instanceof Error ? err.message : "Something went wrong. Try again."; } const status = err.response?.status; const data = err.response?.data as { detail?: unknown } | undefined; const detail = data?.detail; if (typeof detail === "string") return detail; if (Array.isArray(detail) && detail.length > 0 && typeof detail[0] === "object") { const first = detail[0] as { msg?: string }; if (first.msg) return first.msg; } if (status === 429) { const retry = err.response?.headers?.["retry-after"]; return retry ? `Too many attempts. Retry in ${retry}s.` : "Too many attempts. Please wait before trying again."; } return err.message || "Incorrect password or server error."; } interface PasswordProtectedRouteProps { children: React.ReactNode; redirectTo?: string; } export default function PasswordProtectedRoute({ children, redirectTo = "/", }: PasswordProtectedRouteProps) { const navigate = useNavigate(); const [authenticated, setAuthenticated] = useState(() => { return sessionStorage.getItem(SESSION_KEY) === "true"; }); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const [shake, setShake] = useState(false); const [checkingGate, setCheckingGate] = useState(true); const [submitting, setSubmitting] = useState(false); useEffect(() => { if (authenticated) { setCheckingGate(false); return; } let cancelled = false; (async () => { try { const { gated } = await getTemplateStudioAuthStatus(); if (cancelled) return; if (!gated) { sessionStorage.setItem(SESSION_KEY, "true"); setAuthenticated(true); } } catch { // Network / server error — show password UI (may still be gated server-side). } finally { if (!cancelled) setCheckingGate(false); } })(); return () => { cancelled = true; }; }, [authenticated]); const handleSubmit = useCallback(async () => { const trimmed = password.trim(); if (!trimmed || submitting) return; setSubmitting(true); setError(""); try { const res = await verifyTemplateStudioPassword(trimmed); if (res.ok) { sessionStorage.setItem(SESSION_KEY, "true"); setAuthenticated(true); setPassword(""); } else { setError("Could not verify access."); setShake(true); setTimeout(() => setShake(false), 400); } } catch (err) { setError(errorMessageFromAxios(err)); setShake(true); setTimeout(() => setShake(false), 400); setPassword(""); } finally { setSubmitting(false); } }, [password, submitting]); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") void handleSubmit(); }; if (authenticated) return <>{children}; if (checkingGate) { return (
Checking access…
); } return (
{/* Lock Icon */}
🔒
{/* Title */}

Template Studio

Enter the password to access the template studio.

{/* Label */} {/* Input */}
{ setPassword(e.target.value); setError(""); }} onKeyDown={handleKeyDown} className="w-full rounded-lg border border-gray-300 px-3 py-2 pr-10 text-sm focus:outline-none focus:ring-2 focus:ring-purple-400 focus:border-purple-400" />
{/* Error */}
{error}
{/* Button */} {/* Back */}
{/* Tailwind custom animation */}
); }