음성 재생과 운영 배포 정리
This commit is contained in:
parent
8ed185ce6c
commit
ac7db95542
1020 changed files with 46863 additions and 2175 deletions
|
|
@ -6,6 +6,8 @@
|
|||
/learn/session/:sessionId -> Session
|
||||
/learn/session/:sessionId/review -> SessionReview
|
||||
/teach -> Professor (teacher → data-role=instructor)
|
||||
/teach/personas -> PersonaStudio (teacher/admin)
|
||||
/teach/session/:sessionId/review -> SessionReview (teacher read-only)
|
||||
/admin -> Admin (admin)
|
||||
/settings -> Settings
|
||||
/ -> Navigate(역할 홈, 미인증이면 /login)
|
||||
|
|
@ -14,15 +16,18 @@
|
|||
|
||||
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 { AuthProvider, canAccessRole, useAuth, roleHomePath, type Role } from "./lib/auth";
|
||||
|
||||
import Login from "./pages/Login";
|
||||
import Onboarding from "./pages/Onboarding";
|
||||
import PendingApproval from "./pages/PendingApproval";
|
||||
import LearnerHome from "./pages/LearnerHome";
|
||||
import AvatarExpressionLab from "./pages/AvatarExpressionLab";
|
||||
import AvatarPreview from "./pages/AvatarPreview";
|
||||
import Session from "./pages/Session";
|
||||
import SessionReview from "./pages/SessionReview";
|
||||
import Professor from "./pages/Professor";
|
||||
import PersonaStudio from "./pages/PersonaStudio";
|
||||
import Admin from "./pages/Admin";
|
||||
import Settings from "./pages/Settings";
|
||||
|
||||
|
|
@ -48,7 +53,13 @@ function BootScreen() {
|
|||
* RequireAuth — 미인증이면 /login 으로. (선택) roles 로 역할 제한.
|
||||
* 권한 불일치 시 자신의 역할 홈으로 보냄(빈 화면/에러 대신).
|
||||
*/
|
||||
function RequireAuth({ children, roles }: { children: ReactNode; roles?: Role[] }) {
|
||||
function RequireAuth({
|
||||
children,
|
||||
roles,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
roles?: Role[];
|
||||
}) {
|
||||
const { user, loading } = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
|
|
@ -56,95 +67,215 @@ function RequireAuth({ children, roles }: { children: ReactNode; roles?: Role[]
|
|||
if (!user) {
|
||||
return <Navigate to="/login" replace state={{ from: location.pathname }} />;
|
||||
}
|
||||
if (roles && !roles.includes(user.role)) {
|
||||
if (roles && !roles.some((role) => canAccessRole(user, role))) {
|
||||
return <Navigate to={roleHomePath(user.role)} replace />;
|
||||
}
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
function OnboardingGate({ children }: { children: ReactNode }) {
|
||||
const { user, loading } = useAuth();
|
||||
const location = useLocation();
|
||||
const path = location.pathname;
|
||||
|
||||
if (loading) return <BootScreen />;
|
||||
if (path === "/pending") return <>{children}</>;
|
||||
if (user && user.onboardingCompletedAt == null && path !== "/onboarding") {
|
||||
return <Navigate to="/onboarding" replace state={{ from: path }} />;
|
||||
}
|
||||
if (user && user.onboardingCompletedAt != null && (path === "/login" || path === "/onboarding")) {
|
||||
return <Navigate to={roleHomePath(user.role)} replace />;
|
||||
}
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
function PendingApprovalGate({ children }: { children: ReactNode }) {
|
||||
const { user, loading } = useAuth();
|
||||
const location = useLocation();
|
||||
const path = location.pathname;
|
||||
|
||||
if (loading) return <BootScreen />;
|
||||
if (user && user.accountStatus !== "approved" && path !== "/pending") {
|
||||
return <Navigate to="/pending" replace state={{ from: path }} />;
|
||||
}
|
||||
if (user && user.accountStatus === "approved" && path === "/pending") {
|
||||
return (
|
||||
<Navigate
|
||||
to={user.onboardingCompletedAt == null ? "/onboarding" : roleHomePath(user.role)}
|
||||
replace
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
/** 루트(/) — 인증되면 역할 홈, 아니면 /login. */
|
||||
function RootRedirect() {
|
||||
const { user, loading } = useAuth();
|
||||
if (loading) return <BootScreen />;
|
||||
if (user && user.accountStatus !== "approved") {
|
||||
return <Navigate to="/pending" replace />;
|
||||
}
|
||||
if (user && user.onboardingCompletedAt == null) {
|
||||
return <Navigate to="/onboarding" replace />;
|
||||
}
|
||||
return <Navigate to={user ? roleHomePath(user.role) : "/login"} replace />;
|
||||
}
|
||||
|
||||
function AppRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<PendingApprovalGate>
|
||||
<OnboardingGate>
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
|
||||
{/* dev: 인증 없는 아바타 컴포지션 튜닝 페이지 (실서비스 아님) */}
|
||||
<Route path="/dev/avatar-preview" element={<AvatarPreview />} />
|
||||
<Route
|
||||
path="/pending"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<PendingApproval />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* 학습자 */}
|
||||
<Route
|
||||
path="/learn"
|
||||
element={
|
||||
<RequireAuth roles={["learner"]}>
|
||||
<LearnerHome />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/learn/avatar-expressions"
|
||||
element={
|
||||
<RequireAuth roles={["learner"]}>
|
||||
<AvatarExpressionLab />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/learn/session/:sessionId"
|
||||
element={
|
||||
<RequireAuth roles={["learner"]}>
|
||||
<Session />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/learn/session/:sessionId/review"
|
||||
element={
|
||||
<RequireAuth roles={["learner"]}>
|
||||
<SessionReview />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
{/* dev: 인증 없는 아바타 컴포지션 튜닝 페이지 (실서비스 아님) */}
|
||||
<Route path="/dev/avatar-preview" element={<AvatarPreview />} />
|
||||
|
||||
{/* 교수자 */}
|
||||
<Route
|
||||
path="/teach"
|
||||
element={
|
||||
<RequireAuth roles={["teacher"]}>
|
||||
<Professor />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/onboarding"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Onboarding />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* 관리자 */}
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
<RequireAuth roles={["admin"]}>
|
||||
<Admin />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
{/* 학습자 */}
|
||||
<Route
|
||||
path="/learn"
|
||||
element={
|
||||
<RequireAuth roles={["learner"]}>
|
||||
<LearnerHome view="dashboard" />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/learn/practice"
|
||||
element={
|
||||
<RequireAuth roles={["learner"]}>
|
||||
<LearnerHome view="practice" />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/learn/history"
|
||||
element={
|
||||
<RequireAuth roles={["learner"]}>
|
||||
<LearnerHome view="history" />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/learn/avatar-expressions"
|
||||
element={
|
||||
<RequireAuth roles={["learner"]}>
|
||||
<AvatarExpressionLab />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/learn/session/:sessionId"
|
||||
element={
|
||||
<RequireAuth roles={["learner"]}>
|
||||
<Session />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/learn/session/:sessionId/review"
|
||||
element={
|
||||
<RequireAuth roles={["learner"]}>
|
||||
<SessionReview />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* 설정 — 3역할 공통 */}
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Settings />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
{/* 교수자 */}
|
||||
<Route
|
||||
path="/teach"
|
||||
element={
|
||||
<RequireAuth roles={["teacher"]}>
|
||||
<Professor />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/teach/personas"
|
||||
element={
|
||||
<RequireAuth roles={["teacher", "admin"]}>
|
||||
<PersonaStudio />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/teach/session/:sessionId/review"
|
||||
element={
|
||||
<RequireAuth roles={["teacher"]}>
|
||||
<SessionReview />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="/" element={<RootRedirect />} />
|
||||
{/* 미정의 경로 → 루트로 */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
{/* 관리자 */}
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
<RequireAuth roles={["admin"]}>
|
||||
<Admin section="overview" />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/users"
|
||||
element={
|
||||
<RequireAuth roles={["admin"]}>
|
||||
<Admin section="users" />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/access"
|
||||
element={
|
||||
<RequireAuth roles={["admin"]}>
|
||||
<Admin section="access" />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/tickets"
|
||||
element={
|
||||
<RequireAuth roles={["admin"]}>
|
||||
<Admin section="tickets" />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* 설정 — 3역할 공통 */}
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Settings />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="/" element={<RootRedirect />} />
|
||||
{/* 미정의 경로 → 루트로 */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</OnboardingGate>
|
||||
</PendingApprovalGate>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue