Spaces:
Sleeping
Sleeping
| // web/src/components/LoginScreen.tsx | |
| import React, { useState } from "react"; | |
| import { Button } from "./ui/button"; | |
| import { Input } from "./ui/input"; | |
| import { Label } from "./ui/label"; | |
| import { Card } from "./ui/card"; | |
| import clareAvatar from "../assets/dfe44dab3ad8cd93953eac4a3e68bd1a5f999653.png"; | |
| import type { User } from "../App"; | |
| import { apiLogin } from "../lib/api"; | |
| interface LoginScreenProps { | |
| onLogin: (user: User) => void; | |
| } | |
| export function LoginScreen({ onLogin }: LoginScreenProps) { | |
| const [showForm, setShowForm] = useState(false); | |
| const [name, setName] = useState(""); | |
| const [email, setEmail] = useState(""); | |
| const [submitting, setSubmitting] = useState(false); | |
| const [err, setErr] = useState<string | null>(null); | |
| const handleSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| setErr(null); | |
| const n = name.trim(); | |
| const u = email.trim(); | |
| if (!n || !u) return; | |
| setSubmitting(true); | |
| try { | |
| // backend expects: { name, user_id } | |
| const resp = await apiLogin({ name: n, user_id: u }); | |
| // api.ts returns { ok: true/false ... } | |
| if ((resp as any)?.ok !== true) { | |
| const msg = (resp as any)?.error || "Login failed"; | |
| setErr(msg); | |
| return; | |
| } | |
| onLogin({ name: n, email: u }); | |
| } catch (e: any) { | |
| setErr(e?.message || "Login failed"); | |
| } finally { | |
| setSubmitting(false); | |
| } | |
| }; | |
| return ( | |
| <div className="min-h-screen bg-background flex items-center justify-center p-4"> | |
| <Card className="w-full max-w-md p-8"> | |
| <div className="flex flex-col items-center space-y-6"> | |
| <div className="w-24 h-24 rounded-full overflow-hidden bg-white flex items-center justify-center"> | |
| <img src={clareAvatar} alt="Clare AI" className="w-full h-full object-cover" /> | |
| </div> | |
| <div className="text-center space-y-2"> | |
| <h1 className="text-2xl">Welcome to Clare</h1> | |
| <p className="text-sm text-muted-foreground"> | |
| Your AI teaching assistant for personalized learning | |
| </p> | |
| </div> | |
| {!showForm ? ( | |
| <Button onClick={() => setShowForm(true)} className="w-full" size="lg"> | |
| Sign In | |
| </Button> | |
| ) : ( | |
| <form onSubmit={handleSubmit} className="w-full space-y-4"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="login-name">Name</Label> | |
| <Input | |
| id="login-name" | |
| value={name} | |
| onChange={(e) => setName(e.target.value)} | |
| placeholder="Enter your name" | |
| required | |
| disabled={submitting} | |
| /> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="login-email">Email / Student ID</Label> | |
| <Input | |
| id="login-email" | |
| type="email" | |
| value={email} | |
| onChange={(e) => setEmail(e.target.value)} | |
| placeholder="Enter your email or ID" | |
| required | |
| disabled={submitting} | |
| /> | |
| </div> | |
| {err && ( | |
| <div className="text-sm text-destructive bg-destructive/10 border border-destructive/20 rounded-md p-2"> | |
| {err} | |
| </div> | |
| )} | |
| <div className="flex gap-2"> | |
| <Button type="submit" className="flex-1" disabled={submitting}> | |
| {submitting ? "Signing in..." : "Enter"} | |
| </Button> | |
| <Button | |
| type="button" | |
| variant="outline" | |
| onClick={() => { | |
| if (submitting) return; | |
| setShowForm(false); | |
| setErr(null); | |
| }} | |
| disabled={submitting} | |
| > | |
| Cancel | |
| </Button> | |
| </div> | |
| </form> | |
| )} | |
| </div> | |
| </Card> | |
| </div> | |
| ); | |
| } | |