Spaces:
Sleeping
Sleeping
| 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 { toast } from 'sonner'; | |
| import clareAvatar from '../assets/dfe44dab3ad8cd93953eac4a3e68bd1a5f999653.png'; | |
| import type { User } from '../App'; | |
| interface LoginScreenProps { | |
| onLogin: (user: User) => void; | |
| } | |
| export function LoginScreen({ onLogin }: LoginScreenProps) { | |
| const [showForm, setShowForm] = useState(false); | |
| const [name, setName] = useState(''); | |
| const [emailOrId, setEmailOrId] = useState(''); | |
| const [submitting, setSubmitting] = useState(false); | |
| const handleSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| const n = name.trim(); | |
| const uid = emailOrId.trim(); | |
| if (!n || !uid) return; | |
| setSubmitting(true); | |
| try { | |
| // HF Space: same-origin call is correct (your FastAPI serves the SPA) | |
| const resp = await fetch('/api/login', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ name: n, user_id: uid }), | |
| }); | |
| const data = await resp.json().catch(() => ({})); | |
| if (!resp.ok || !data?.ok) { | |
| const msg = data?.error || `Login failed (HTTP ${resp.status})`; | |
| throw new Error(msg); | |
| } | |
| // Keep your existing App flow: user = { name, email } | |
| onLogin({ name: data.user?.name ?? n, email: data.user?.user_id ?? uid }); | |
| toast.success('Signed in'); | |
| } catch (err: any) { | |
| toast.error(err?.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"> | |
| {/* Clare Avatar */} | |
| <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> | |
| {/* Welcome Text */} | |
| <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-userid">Email / Student ID</Label> | |
| <Input | |
| id="login-userid" | |
| // IMPORTANT: allow non-email IDs | |
| type="text" | |
| value={emailOrId} | |
| onChange={(e) => setEmailOrId(e.target.value)} | |
| placeholder="Enter your email or ID" | |
| required | |
| disabled={submitting} | |
| /> | |
| </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={() => setShowForm(false)} | |
| disabled={submitting} | |
| > | |
| Cancel | |
| </Button> | |
| </div> | |
| <div className="text-xs text-muted-foreground"> | |
| This sign-in is for session identification only (name + ID), used to personalize tutoring and log events. | |
| </div> | |
| </form> | |
| )} | |
| </div> | |
| </Card> | |
| </div> | |
| ); | |
| } | |