/* blueyetutor — App shell + hash router. * * Layout: left sidebar (brand + nav + user) + main content area. * Visual: white surfaces, navy used only on active-item indicator, * CTAs, links, and key numeric values. * * Routes: * #/login LoginPage (anonymous only) * #/upload UploadPage (default authed landing) * #/library LibraryPage * #/workbook WorkbookPage * #/history HistoryPage * #/students StudentsPage */ const { useState: appUseState, useEffect: appUseEffect } = React; // History 는 Workbook 안 sub-tab, 배정 현황은 Students 안 sub-tab 으로 통합 (2026-05-20). const TEACHER_NAV = [ { route: "upload", label: "Upload", icon: "Upload" }, { route: "library", label: "Library", icon: "Library" }, { route: "workbook", label: "Workbook", icon: "Workbook" }, { route: "students", label: "Students", icon: "Students" }, ]; const STUDENT_NAV = [ { route: "assignments", label: "내 과제", icon: "Notebook" }, ]; function navFor(role) { return role === "student" ? STUDENT_NAV : TEACHER_NAV; } function homeFor(role) { return role === "student" ? "assignments" : "upload"; } function useHashRoute() { const [r, setR] = appUseState(() => parseHash()); appUseEffect(() => { const onHash = () => setR(parseHash()); window.addEventListener("hashchange", onHash); return () => window.removeEventListener("hashchange", onHash); }, []); return r; } function parseHash() { const h = (location.hash || "").replace(/^#\/?/, ""); const q = h.indexOf("?"); return (q >= 0 ? h.slice(0, q) : h) || "upload"; } // 페이지가 sub-tab 직접 링크(`#/workbook?tab=history`)를 읽을 때 사용. function hashQueryParam(name) { const h = (location.hash || ""); const q = h.indexOf("?"); if (q < 0) return null; const sp = new URLSearchParams(h.slice(q + 1)); return sp.get(name); } window.hashQueryParam = hashQueryParam; function initial(email) { if (!email) return "·"; const at = email.indexOf("@"); const local = at > 0 ? email.slice(0, at) : email; return (local[0] || "·").toUpperCase(); } // 사이드바 footer 의 "비밀번호 변경" 진입점 — modal 로 옛/새 비번 받아서 // /api/auth/change-password 호출. 성공 시 사용자에게 통보만, 세션은 유지. function ChangePasswordButton() { const [open, setOpen] = appUseState(false); const [oldPw, setOldPw] = appUseState(""); const [newPw, setNewPw] = appUseState(""); const [newPw2, setNewPw2] = appUseState(""); const [busy, setBusy] = appUseState(false); const [msg, setMsg] = appUseState(null); const reset = () => { setOldPw(""); setNewPw(""); setNewPw2(""); setMsg(null); }; const submit = async () => { if (newPw.length < 8) { setMsg({ ok: false, text: "새 비밀번호는 최소 8자 이상이어야 합니다." }); return; } if (newPw !== newPw2) { setMsg({ ok: false, text: "새 비밀번호가 일치하지 않습니다." }); return; } setBusy(true); setMsg(null); const r = await window.storeActions.changePassword(oldPw, newPw); setBusy(false); if (r && r.ok) { setMsg({ ok: true, text: "변경 완료." }); setOldPw(""); setNewPw(""); setNewPw2(""); } else setMsg({ ok: false, text: (r && r.message) || "변경에 실패했습니다." }); }; return ( <> setOpen(false)} footer={<> }>
setOldPw(e.target.value)} autoFocus /> setNewPw(e.target.value)} /> setNewPw2(e.target.value)} /> {msg && {msg.text}}
); } function App() { const store = useStore(); const route = useHashRoute(); const authed = !!store.user; const role = (store.user && store.user.role) || "teacher"; const NAV = navFor(role); const home = homeFor(role); const allowed = NAV.some(n => n.route === route); // 인증 없이 접근 가능한 라우트 — 가입 메일·비번 reset 메일의 링크 클릭. const anon = route === "login" || route === "verify" || route === "reset" || route === "forgot"; appUseEffect(() => { if (!authed && !anon) { location.hash = "#/login"; return; } if (authed && anon) { location.hash = `#/${home}`; return; } // Legacy URL fallback — old links still work after the nav merge. if (authed && role === "teacher") { if (route === "history") { location.hash = "#/workbook?tab=history"; return; } if (route === "assignments") { location.hash = "#/students?tab=assigned"; return; } } // Students only ever see their own area; bounce any teacher route. if (authed && role === "student" && !allowed && route !== "login") { location.hash = "#/assignments"; } }, [authed, route, role, allowed, home, anon]); if (!authed || anon) { return ; } return (
{/* Students 만 #/assignments 에서 AssignmentsPage. teacher 는 합쳐졌음. */} {role === "student" && route === "assignments" && } {role === "teacher" && route === "upload" && } {role === "teacher" && route === "library" && } {role === "teacher" && route === "workbook" && } {role === "teacher" && route === "students" && } {!allowed && route !== "assignments" && (
location.hash = `#/${home}`}>홈으로} />
)}
); } ReactDOM.createRoot(document.getElementById("root")).render();