web (Vite+React19+TS, Cloudflare Pages 배포): - 디자인토큰(세이지틸/테라코타 SSOT), 앱셸, 공통 UI 프리미티브 - 7화면: 로그인/학습자홈/상담세션/회기리뷰/교수자/관리자/설정 - ClientAvatar: SVG 반구상 흉상 4상태 + RMS 립싱크 + 6파라미터 정서 - 회기리뷰는 외부 레퍼런스 디자인을 Vignette 토큰으로 리스킨 api (FastAPI): - 게이트웨이 /v1/generate·/v1/stream 어댑터(상주풀/EngineSession 보존) - services: 페르소나 L0~L6 빌더 / 결정론 상태머신 / 가드레일 / 턴 오케스트레이터 / 회기간 메모리 / 평가AI / 음성 / RAG - store: DB off 폴백(in-memory), sessions 실구현 검증: - web: node22 tsc+vite build 통과(node23 segfault 회피), Pages 배포 200 - api: app.main import 통과 - 핫픽스: Topbar initials undefined-safe (undefined.trim 크래시) - E2E: 서연(P1) 상담 1턴 — 좋은/나쁜 상담에 차등 반응 실증
146 lines
4.1 KiB
TypeScript
146 lines
4.1 KiB
TypeScript
/* =====================================================================
|
|
App — 라우터. AuthProvider 로 감싸고 역할 가드(RequireAuth) 적용.
|
|
라우트 (task 명세):
|
|
/login -> Login
|
|
/learn -> LearnerHome (learner)
|
|
/learn/session/:sessionId -> Session
|
|
/learn/session/:sessionId/review -> SessionReview
|
|
/teach -> Professor (teacher → data-role=instructor)
|
|
/admin -> Admin (admin)
|
|
/settings -> Settings
|
|
/ -> Navigate(역할 홈, 미인증이면 /login)
|
|
보호 라우트는 AuthContext 기반 간단 가드. 과설계 금지.
|
|
===================================================================== */
|
|
|
|
import type { ReactNode } from "react";
|
|
import { BrowserRouter, Navigate, Route, Routes, useLocation } from "react-router-dom";
|
|
import { AuthProvider, useAuth, roleHomePath, type Role } from "./lib/auth";
|
|
|
|
import Login from "./pages/Login";
|
|
import LearnerHome from "./pages/LearnerHome";
|
|
import Session from "./pages/Session";
|
|
import SessionReview from "./pages/SessionReview";
|
|
import Professor from "./pages/Professor";
|
|
import Admin from "./pages/Admin";
|
|
import Settings from "./pages/Settings";
|
|
|
|
/** 부트스트랩 로딩 동안 깜빡임 최소화용 중립 화면. */
|
|
function BootScreen() {
|
|
return (
|
|
<div
|
|
style={{
|
|
minHeight: "100vh",
|
|
display: "grid",
|
|
placeItems: "center",
|
|
background: "var(--bg-app)",
|
|
color: "var(--text-muted)",
|
|
fontSize: "var(--fs-sm)",
|
|
}}
|
|
>
|
|
불러오는 중…
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* RequireAuth — 미인증이면 /login 으로. (선택) roles 로 역할 제한.
|
|
* 권한 불일치 시 자신의 역할 홈으로 보냄(빈 화면/에러 대신).
|
|
*/
|
|
function RequireAuth({ children, roles }: { children: ReactNode; roles?: Role[] }) {
|
|
const { user, loading } = useAuth();
|
|
const location = useLocation();
|
|
|
|
if (loading) return <BootScreen />;
|
|
if (!user) {
|
|
return <Navigate to="/login" replace state={{ from: location.pathname }} />;
|
|
}
|
|
if (roles && !roles.includes(user.role)) {
|
|
return <Navigate to={roleHomePath(user.role)} replace />;
|
|
}
|
|
return <>{children}</>;
|
|
}
|
|
|
|
/** 루트(/) — 인증되면 역할 홈, 아니면 /login. */
|
|
function RootRedirect() {
|
|
const { user, loading } = useAuth();
|
|
if (loading) return <BootScreen />;
|
|
return <Navigate to={user ? roleHomePath(user.role) : "/login"} replace />;
|
|
}
|
|
|
|
function AppRoutes() {
|
|
return (
|
|
<Routes>
|
|
<Route path="/login" element={<Login />} />
|
|
|
|
{/* 학습자 */}
|
|
<Route
|
|
path="/learn"
|
|
element={
|
|
<RequireAuth roles={["learner"]}>
|
|
<LearnerHome />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/learn/session/:sessionId"
|
|
element={
|
|
<RequireAuth roles={["learner"]}>
|
|
<Session />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/learn/session/:sessionId/review"
|
|
element={
|
|
<RequireAuth roles={["learner"]}>
|
|
<SessionReview />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
|
|
{/* 교수자 */}
|
|
<Route
|
|
path="/teach"
|
|
element={
|
|
<RequireAuth roles={["teacher"]}>
|
|
<Professor />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
|
|
{/* 관리자 */}
|
|
<Route
|
|
path="/admin"
|
|
element={
|
|
<RequireAuth roles={["admin"]}>
|
|
<Admin />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
|
|
{/* 설정 — 3역할 공통 */}
|
|
<Route
|
|
path="/settings"
|
|
element={
|
|
<RequireAuth>
|
|
<Settings />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
|
|
<Route path="/" element={<RootRedirect />} />
|
|
{/* 미정의 경로 → 루트로 */}
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<BrowserRouter>
|
|
<AuthProvider>
|
|
<AppRoutes />
|
|
</AuthProvider>
|
|
</BrowserRouter>
|
|
);
|
|
}
|