summaryrefslogtreecommitdiff
path: root/makima/frontend/src/routes/login.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/routes/login.tsx')
-rw-r--r--makima/frontend/src/routes/login.tsx150
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>
+ );
+}