feat: 운영 안정성과 세션 음성 경험 개선

This commit is contained in:
Yun Chan 2026-07-31 00:13:08 +09:00
parent facc4ad2d9
commit c788343467
95 changed files with 8431 additions and 1785 deletions

View file

@ -1,7 +1,8 @@
import { useEffect, type ReactNode } from "react";
import { useEffect, useLayoutEffect, useRef, type ReactNode } from "react";
import { useLocation } from "react-router-dom";
import { Topbar } from "./Topbar";
import { Sidebar } from "./Sidebar";
import { designRoleOf, useAuth, type Role } from "../../lib/auth";
import { canAccessRole, designRoleOf, useAuth, type Role } from "../../lib/auth";
export interface AppShellProps {
children: ReactNode;
@ -28,6 +29,8 @@ export interface AppShellProps {
*/
export function AppShell({ children, className, contextLabel, hideNav, bleed, wide, hideTopbar, navRole }: AppShellProps) {
const { user } = useAuth();
const { pathname } = useLocation();
const mainRef = useRef<HTMLElement | null>(null);
const showNav = !hideNav && !!user;
const shellRole = navRole ?? user?.role;
const mainClassName = [
@ -47,6 +50,34 @@ export function AppShell({ children, className, contextLabel, hideNav, bleed, wi
};
}, [shellRole, user]);
useLayoutEffect(() => {
const resetMainScroll = () => {
const main = mainRef.current;
if (!main) return;
main.scrollTop = 0;
main.scrollLeft = 0;
};
resetMainScroll();
const frame = window.requestAnimationFrame(resetMainScroll);
return () => window.cancelAnimationFrame(frame);
}, [pathname]);
useEffect(() => {
const handlePageShow = () => {
const main = mainRef.current;
if (!main) return;
main.scrollTop = 0;
main.scrollLeft = 0;
window.requestAnimationFrame(() => {
if (!mainRef.current) return;
mainRef.current.scrollTop = 0;
mainRef.current.scrollLeft = 0;
});
};
window.addEventListener("pageshow", handlePageShow);
return () => window.removeEventListener("pageshow", handlePageShow);
}, []);
return (
<div
className={[
@ -60,8 +91,13 @@ export function AppShell({ children, className, contextLabel, hideNav, bleed, wi
>
{hideTopbar ? null : <Topbar contextLabel={contextLabel} />}
<div className={"vg-shell__body" + (showNav ? "" : " vg-shell__body--bare")}>
{showNav ? <Sidebar role={shellRole ?? user.role} /> : null}
<main className={mainClassName}>
{showNav ? (
<Sidebar
role={shellRole ?? user.role}
showAdminEntry={canAccessRole(user, "admin")}
/>
) : null}
<main ref={mainRef} className={mainClassName}>
<div className="vg-main__inner">{children}</div>
</main>
</div>