Initial commit: Sarthi Lab desktop application
This commit is contained in:
120
src/screens/StudentLoginScreen.jsx
Normal file
120
src/screens/StudentLoginScreen.jsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import useLabStore from '../store/labStore';
|
||||
import { studentLogin, getRoomClasses } from '../services/labApi';
|
||||
|
||||
export default function StudentLoginScreen() {
|
||||
const { serverUrl, roomToken, sessionToken, labInfo, setStudent } = useLabStore();
|
||||
const [rollNumber, setRollNumber] = useState('');
|
||||
const [className, setClassName] = useState('');
|
||||
const [classes, setClasses] = useState([]);
|
||||
const [loadingClasses, setLoadingClasses] = useState(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchClasses() {
|
||||
try {
|
||||
const list = await getRoomClasses(serverUrl, roomToken);
|
||||
setClasses(list);
|
||||
if (list.length === 1) setClassName(list[0]);
|
||||
} catch {
|
||||
} finally {
|
||||
setLoadingClasses(false);
|
||||
}
|
||||
}
|
||||
fetchClasses();
|
||||
}, [serverUrl, roomToken]);
|
||||
|
||||
const handleLogin = async (e) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const student = await studentLogin(serverUrl, sessionToken, rollNumber.trim(), className);
|
||||
setStudent(student);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-mesh flex flex-col items-center justify-center p-6">
|
||||
<div className="w-full max-w-sm animate-slide-up">
|
||||
<div className="text-center mb-8">
|
||||
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl avatar-gradient flex items-center justify-center accent-glow-sm">
|
||||
<svg className="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-white tracking-tight">{labInfo?.lab_name}</h2>
|
||||
<p className="text-slate-400 text-sm mt-1">{labInfo?.school_name}</p>
|
||||
<p className="text-slate-500 text-xs mt-3">Enter your details to start learning</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleLogin} className="glass-strong rounded-2xl p-7 space-y-5">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-slate-400 uppercase tracking-wider mb-2">Roll Number</label>
|
||||
<input
|
||||
type="text"
|
||||
value={rollNumber}
|
||||
onChange={e => setRollNumber(e.target.value)}
|
||||
className="w-full bg-white/[0.05] border border-white/[0.08] text-white rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--accent)]/40 focus:border-[var(--accent)]/40 transition-all placeholder:text-slate-600"
|
||||
placeholder="e.g. 42"
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-slate-400 uppercase tracking-wider mb-2">Class</label>
|
||||
{!loadingClasses && classes.length > 0 ? (
|
||||
<select
|
||||
value={className}
|
||||
onChange={e => setClassName(e.target.value)}
|
||||
className="w-full bg-white/[0.05] border border-white/[0.08] text-white rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--accent)]/40 focus:border-[var(--accent)]/40 transition-all appearance-none"
|
||||
required
|
||||
>
|
||||
<option value="" className="bg-slate-900">Select your class...</option>
|
||||
{classes.map(c => (
|
||||
<option key={c} value={c} className="bg-slate-900">{c}</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
value={className}
|
||||
onChange={e => setClassName(e.target.value)}
|
||||
className="w-full bg-white/[0.05] border border-white/[0.08] text-white rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--accent)]/40 focus:border-[var(--accent)]/40 transition-all placeholder:text-slate-600"
|
||||
placeholder={loadingClasses ? 'Loading classes...' : 'e.g. Grade 10 - A'}
|
||||
required
|
||||
disabled={loadingClasses}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-500/10 border border-red-500/20 text-red-400 rounded-xl px-4 py-3 text-sm animate-fade-in">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading || loadingClasses}
|
||||
className="w-full accent-gradient accent-glow disabled:opacity-50 text-white font-semibold rounded-xl py-3.5 transition-all"
|
||||
>
|
||||
{loading ? (
|
||||
<span className="flex items-center justify-center gap-2">
|
||||
<span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||||
Logging in...
|
||||
</span>
|
||||
) : 'Start Learning'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user