taboola
question generation feature, date selection, changes to UI login page and icons
c2c3b04
Raw
History Blame Contribute Delete
6.79 kB
import { useState } from "react";
import { apiPost, setToken } from "../lib/api";
import { CLASSLENS_ICON } from "../lib/icon";
import type { User } from "../types";
interface AuthResponse {
token: string;
user: User;
}
interface LoginFormProps {
onLogin: (user: User) => void;
}
export function LoginForm({ onLogin }: LoginFormProps) {
const [mode, setMode] = useState<"login" | "register">("login");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [fullName, setFullName] = useState("");
const [school, setSchool] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setLoading(true);
try {
const endpoint = mode === "login" ? "/api/auth/login" : "/api/auth/register";
const body =
mode === "login"
? { email, password }
: { email, password, full_name: fullName, school };
const result = await apiPost<AuthResponse>(endpoint, body);
setToken(result.token);
onLogin(result.user);
} catch (err: unknown) {
setError(err instanceof Error ? err.message : "An error occurred");
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-[var(--color-background)] px-4">
{/* Subtle decorative stripe at the very top */}
<div
className="fixed top-0 left-0 right-0 h-1"
style={{ background: "var(--color-primary)" }}
/>
<div className="w-full max-w-md">
{/* Logo + wordmark above the card */}
<div className="text-center mb-8">
<img
src={CLASSLENS_ICON}
alt="ClassLens"
className="w-16 h-16 mx-auto mb-4 rounded-xl shadow-sm"
/>
<h1 className="font-display text-3xl font-bold text-[var(--color-primary)]">
ClassLens
</h1>
<p className="text-sm text-[var(--color-text-muted)] mt-1">
AI-Powered Exam Analysis
</p>
</div>
<div className="card p-8">
{/* Mode toggle */}
<div className="flex mb-6 bg-[var(--color-background)] rounded-lg p-1 gap-1">
<button
type="button"
onClick={() => { setMode("login"); setError(""); }}
className={`flex-1 py-2 text-sm font-medium rounded-md transition-all ${
mode === "login"
? "bg-[var(--color-surface)] text-[var(--color-primary)] shadow-sm font-semibold border border-[var(--color-border)]"
: "text-[var(--color-text-muted)] hover:text-[var(--color-text)]"
}`}
>
Log in
</button>
<button
type="button"
onClick={() => { setMode("register"); setError(""); }}
className={`flex-1 py-2 text-sm font-medium rounded-md transition-all ${
mode === "register"
? "bg-[var(--color-surface)] text-[var(--color-primary)] shadow-sm font-semibold border border-[var(--color-border)]"
: "text-[var(--color-text-muted)] hover:text-[var(--color-text)]"
}`}
>
Register
</button>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-[var(--color-text)] mb-1.5">
Email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email address"
required
className="input"
autoFocus
/>
</div>
{mode === "register" && (
<>
<div>
<label className="block text-sm font-medium text-[var(--color-text)] mb-1.5">
Full name
</label>
<input
type="text"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
placeholder="Your name"
required
className="input"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--color-text)] mb-1.5">
School
</label>
<input
type="text"
value={school}
onChange={(e) => setSchool(e.target.value)}
placeholder="School name"
required
className="input"
/>
</div>
</>
)}
<div>
<label className="block text-sm font-medium text-[var(--color-text)] mb-1.5">
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
minLength={4}
className="input"
/>
</div>
{error && (
<p className="text-sm text-red-500 flex items-center gap-1.5 bg-red-50 border border-red-200 rounded-lg px-3 py-2">
<svg className="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{error}
</p>
)}
<button
type="submit"
disabled={loading}
className="w-full btn btn-primary py-3 text-base disabled:opacity-50 mt-2"
>
{loading
? <span className="flex items-center justify-center gap-2">
<span className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
{mode === "login" ? "Logging in..." : "Creating account..."}
</span>
: mode === "login" ? "Log in" : "Create account"
}
</button>
</form>
</div>
<p className="text-center text-xs text-[var(--color-text-muted)] mt-6">
ClassLens &bull; AI-Powered Teaching Assistant
</p>
</div>
</div>
);
}