feat: P1 풀빌드 — React 프론트 7화면 + 백엔드 상담루프·평가·음성·RAG
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턴 — 좋은/나쁜 상담에 차등 반응 실증
This commit is contained in:
parent
859ab26314
commit
24b1b7a6e1
84 changed files with 19645 additions and 107 deletions
146
apps/web/src/App.tsx
Normal file
146
apps/web/src/App.tsx
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
/* =====================================================================
|
||||
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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue