Setup.tsx (4285B)
1 import { useState, type FormEvent } from "react"; 2 import { api } from "../../lib/api"; 3 import { useAuthStore } from "../../store/auth"; 4 5 export function Setup() { 6 const { checkStatus } = useAuthStore(); 7 const [password, setPassword] = useState(""); 8 const [confirmPassword, setConfirmPassword] = useState(""); 9 const [apiKey, setApiKey] = useState(""); 10 const [error, setError] = useState<string | null>(null); 11 const [loading, setLoading] = useState(false); 12 13 const handleSubmit = async (e: FormEvent) => { 14 e.preventDefault(); 15 setError(null); 16 17 if (password !== confirmPassword) { 18 setError("Passwords do not match"); 19 return; 20 } 21 22 if (!password.trim()) { 23 setError("Password is required"); 24 return; 25 } 26 27 setLoading(true); 28 try { 29 await api.post("/auth/setup", { password, api_key: apiKey || undefined }); 30 await checkStatus(); 31 window.location.href = "/"; 32 } catch (err) { 33 const message = 34 err instanceof Error ? err.message : "Setup failed"; 35 setError(message); 36 } finally { 37 setLoading(false); 38 } 39 }; 40 41 return ( 42 <div className="flex min-h-screen items-center justify-center bg-gray-950"> 43 <div className="w-full max-w-sm rounded-xl border border-gray-800 bg-gray-900 p-8 shadow-2xl"> 44 <h1 className="mb-2 text-center text-2xl font-bold text-gray-100"> 45 Initial Setup 46 </h1> 47 <p className="mb-6 text-center text-sm text-gray-400"> 48 Create your admin account to get started. 49 </p> 50 51 <form onSubmit={handleSubmit} className="space-y-4"> 52 <div> 53 <label 54 htmlFor="setup-password" 55 className="mb-1 block text-sm font-medium text-gray-300" 56 > 57 Password 58 </label> 59 <input 60 id="setup-password" 61 type="password" 62 value={password} 63 onChange={(e) => setPassword(e.target.value)} 64 className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-100 placeholder-gray-500 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" 65 placeholder="Choose a password" 66 autoFocus 67 /> 68 </div> 69 70 <div> 71 <label 72 htmlFor="confirm-password" 73 className="mb-1 block text-sm font-medium text-gray-300" 74 > 75 Confirm Password 76 </label> 77 <input 78 id="confirm-password" 79 type="password" 80 value={confirmPassword} 81 onChange={(e) => setConfirmPassword(e.target.value)} 82 className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-100 placeholder-gray-500 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" 83 placeholder="Confirm password" 84 /> 85 </div> 86 87 <div> 88 <label 89 htmlFor="api-key" 90 className="mb-1 block text-sm font-medium text-gray-300" 91 > 92 API Key{" "} 93 <span className="text-gray-500">(optional)</span> 94 </label> 95 <input 96 id="api-key" 97 type="password" 98 value={apiKey} 99 onChange={(e) => setApiKey(e.target.value)} 100 className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-100 placeholder-gray-500 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" 101 placeholder="LLM API key" 102 /> 103 </div> 104 105 {error && ( 106 <div className="rounded-lg bg-red-900/50 px-3 py-2 text-sm text-red-300"> 107 {error} 108 </div> 109 )} 110 111 <button 112 type="submit" 113 disabled={loading || !password.trim() || !confirmPassword.trim()} 114 className="w-full rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" 115 > 116 {loading ? "Setting up..." : "Create Account"} 117 </button> 118 </form> 119 </div> 120 </div> 121 ); 122 }