Spaces:
Sleeping
Sleeping
File size: 4,337 Bytes
198828b 652b4c9 507f3f7 198828b 652b4c9 198828b 652b4c9 198828b 652b4c9 198828b 507f3f7 198828b 507f3f7 652b4c9 507f3f7 652b4c9 198828b 652b4c9 198828b 652b4c9 198828b 652b4c9 507f3f7 652b4c9 507f3f7 652b4c9 507f3f7 652b4c9 507f3f7 652b4c9 507f3f7 652b4c9 507f3f7 652b4c9 507f3f7 652b4c9 507f3f7 652b4c9 507f3f7 652b4c9 507f3f7 652b4c9 198828b 652b4c9 198828b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 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>
);
}
|