diff options
Diffstat (limited to 'makima/frontend/src/routes/login.tsx')
| -rw-r--r-- | makima/frontend/src/routes/login.tsx | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/makima/frontend/src/routes/login.tsx b/makima/frontend/src/routes/login.tsx new file mode 100644 index 0000000..63b3af3 --- /dev/null +++ b/makima/frontend/src/routes/login.tsx @@ -0,0 +1,150 @@ +import { useState, type FormEvent } from "react"; +import { useNavigate } from "react-router"; +import { useAuth } from "../contexts/AuthContext"; +import { Masthead } from "../components/Masthead"; + +type AuthMode = "signin" | "signup"; + +export default function LoginPage() { + const navigate = useNavigate(); + const { signIn, signUp, isAuthConfigured, isAuthenticated } = useAuth(); + + const [mode, setMode] = useState<AuthMode>("signin"); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState<string | null>(null); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState<string | null>(null); + + // Redirect if already authenticated + if (isAuthenticated && isAuthConfigured) { + navigate("/mesh"); + return null; + } + + const handleEmailAuth = async (e: FormEvent) => { + e.preventDefault(); + setError(null); + setMessage(null); + setLoading(true); + + try { + if (mode === "signin") { + const { error } = await signIn(email, password); + if (error) { + setError(error.message); + } else { + navigate("/mesh"); + } + } else if (mode === "signup") { + const { error } = await signUp(email, password); + if (error) { + setError(error.message); + } else { + setMessage("Check your email for a confirmation link."); + } + } + } finally { + setLoading(false); + } + }; + + // If auth is not configured, show a message + if (!isAuthConfigured) { + return ( + <div className="relative z-10 min-h-screen flex flex-col"> + <Masthead /> + <main className="flex-1 flex items-center justify-center p-4"> + <div className="w-full max-w-md text-center"> + <h1 className="text-2xl font-bold mb-4">Authentication Required</h1> + <p className="text-zinc-400 mb-4"> + Authentication is not configured. Please configure Supabase authentication to use this application. + </p> + <p className="text-zinc-500 text-sm"> + For API access, use an API key in request headers instead. + </p> + </div> + </main> + </div> + ); + } + + return ( + <div className="relative z-10 min-h-screen flex flex-col"> + <Masthead /> + <main className="flex-1 flex items-center justify-center p-4"> + <div className="w-full max-w-md"> + <div className="text-center mb-8"> + <h1 className="text-2xl font-bold mb-2">Sign In</h1> + <p className="text-zinc-400"> + {mode === "signin" && "Sign in to your account"} + {mode === "signup" && "Create a new account"} + </p> + </div> + + {/* Mode switcher */} + <div className="flex border-b border-zinc-800 mb-6"> + <button + onClick={() => setMode("signin")} + className={`flex-1 py-2 text-sm transition-colors ${ + mode === "signin" + ? "text-white border-b-2 border-white" + : "text-zinc-500 hover:text-zinc-300" + }`} + > + Sign In + </button> + <button + onClick={() => setMode("signup")} + className={`flex-1 py-2 text-sm transition-colors ${ + mode === "signup" + ? "text-white border-b-2 border-white" + : "text-zinc-500 hover:text-zinc-300" + }`} + > + Sign Up + </button> + </div> + + {/* Email/password form */} + <form onSubmit={handleEmailAuth} className="space-y-4"> + <div> + <label className="block text-sm text-zinc-400 mb-1">Email</label> + <input + type="email" + value={email} + onChange={(e) => setEmail(e.target.value)} + placeholder="you@example.com" + className="w-full px-3 py-2 bg-zinc-900 border border-zinc-800 rounded text-white placeholder-zinc-600 focus:outline-none focus:border-zinc-600" + required + /> + </div> + <div> + <label className="block text-sm text-zinc-400 mb-1">Password</label> + <input + type="password" + value={password} + onChange={(e) => setPassword(e.target.value)} + placeholder="********" + className="w-full px-3 py-2 bg-zinc-900 border border-zinc-800 rounded text-white placeholder-zinc-600 focus:outline-none focus:border-zinc-600" + required + minLength={6} + /> + </div> + + {error && <div className="text-red-400 text-sm">{error}</div>} + {message && <div className="text-green-400 text-sm">{message}</div>} + + <button + type="submit" + disabled={loading} + className="w-full py-2 bg-white text-black rounded font-medium hover:bg-zinc-200 transition-colors disabled:opacity-50" + > + {loading ? "Loading..." : mode === "signin" ? "Sign In" : "Sign Up"} + </button> + </form> + </div> + </main> + </div> + ); +} |
