feat(student): wire roster identity and student session
New Platform Shell entry point (frontend/src/platform/) alongside the existing, completely unmodified Lab UI (App.jsx and everything under frontend/src/components/). main.jsx now splits on the current URL path — /lab/<token>/ renders the existing <App/> exactly as before; anything else renders the new <PlatformShell/>. Both live in the same built bundle (it's served both by the persistent identity instance and by every ephemeral Lab container), split purely at runtime. - EntryScreen.jsx — cohort dropdown -> name dropdown (existing GET /api/roster/cohorts, GET /api/roster?cohort=) -> POST /api/login. No password. Visibly labeled pilot identity, not production auth, per the governing requirement that this be stated explicitly, not just true in the architecture docs. - api.js — student_id lives in localStorage only, sent as X-Student-Id on every authenticated request. Every authenticated call throws if no student is stored rather than silently going out anonymously — this is what "prevent accidental anonymous progress writes" means in practice, not just a comment. - PlatformShell.jsx — entry screen if no stored session, dashboard otherwise. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { api, storeStudent } from './api'
|
||||
import styles from './Platform.module.css'
|
||||
|
||||
// A. Entry screen — RootNode Academy branding, choose your name from roster, no password.
|
||||
// Explicitly pilot/dev identity, not production auth — see
|
||||
// docs/PHASE2A_GATEWAY_AND_STUDENT_FLOW.md "Student identity limitation."
|
||||
export default function EntryScreen({ onLogin }) {
|
||||
const [cohorts, setCohorts] = useState([])
|
||||
const [cohort, setCohort] = useState('')
|
||||
const [roster, setRoster] = useState([])
|
||||
const [studentId, setStudentId] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
api.cohorts().then(setCohorts).catch((e) => setError(e.message))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!cohort) { setRoster([]); return }
|
||||
api.roster(cohort).then(setRoster).catch((e) => setError(e.message))
|
||||
}, [cohort])
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!studentId) return
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const student = await api.login(studentId)
|
||||
storeStudent(student)
|
||||
onLogin(student)
|
||||
} catch (e) {
|
||||
setError(e.message)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.entryWrap}>
|
||||
<div className={styles.entryCard}>
|
||||
<div className={styles.brand}>
|
||||
<span className={styles.brandIcon}>⎈</span> RootNode Academy
|
||||
</div>
|
||||
<p className={styles.tagline}>Real-world Kubernetes labs. Pick your name to continue.</p>
|
||||
<p className={styles.pilotNote}>Pilot identity — no password. Not production auth.</p>
|
||||
|
||||
{error && <div className={styles.errorBox}>{error}</div>}
|
||||
|
||||
<label className={styles.label}>Cohort</label>
|
||||
<select className={styles.select} value={cohort} onChange={(e) => { setCohort(e.target.value); setStudentId('') }}>
|
||||
<option value="">Select a cohort…</option>
|
||||
{cohorts.map((c) => <option key={c} value={c}>{c}</option>)}
|
||||
</select>
|
||||
|
||||
{cohort && (
|
||||
<>
|
||||
<label className={styles.label}>Your name</label>
|
||||
<select className={styles.select} value={studentId} onChange={(e) => setStudentId(e.target.value)}>
|
||||
<option value="">Select your name…</option>
|
||||
{roster.map((s) => <option key={s.student_id} value={s.student_id}>{s.display_name}</option>)}
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
|
||||
<button className={styles.primaryBtn} disabled={!studentId || loading} onClick={handleLogin}>
|
||||
{loading ? 'Signing in…' : 'Continue'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user