import {createBrowserRouter, redirect} from 'react-router'; import {initAuth} from '../features/auth/service'; import {isLoggedIn, hasRole} from '../stores/auth'; import {hasSeenOnboarding} from '../features/onboarding/storage'; import AuthenticatedLayout from '@/components/layout/AuthenticatedLayout'; import LoginPage from '../pages/login'; import DashboardPage from '../pages/dashboard'; import StatisticsPage from '../pages/statistics'; import ForbiddenPage from '../pages/forbidden'; import DevDesignPage from '../pages/dev-design'; import RenegotiationPage from '../pages/renegotiation'; import NotFoundPage from '../pages/not-found'; import ProductsPage from '../pages/products'; import PartnersPage from '../pages/partners'; import QuotationPage from '../pages/quotation'; import CardsPage from '../pages/cards'; import MembersPage from '../pages/members'; import SettingsPage from '../pages/settings'; import DevSettingsPage from '../pages/dev-settings'; import NotificationsPage from '../pages/notifications'; import OnboardingPage from '../pages/onboarding'; export const router = createBrowserRouter([ { index: true, loader: () => redirect('/dashboard'), }, { path: 'login', loader: async () => { await initAuth(); if (isLoggedIn()) return redirect('/dashboard'); return null; }, Component: LoginPage, }, { path: 'forbidden', Component: ForbiddenPage, }, { path: 'onboarding', loader: async ({request}) => { await initAuth(); if (!isLoggedIn()) { const url = new URL(request.url); const from = encodeURIComponent(url.pathname + url.search); return redirect(`/login?redirect=${from}`); } return null; }, Component: OnboardingPage, }, { loader: async ({request}) => { await initAuth(); if (!isLoggedIn()) { const url = new URL(request.url); const from = encodeURIComponent(url.pathname + url.search); return redirect(`/login?redirect=${from}`); } // 최초 로그인 유저는 온보딩으로. 완료·건너뛰기 시 flag 저장되어 이후엔 통과. if (!hasSeenOnboarding()) { return redirect('/onboarding'); } return null; }, Component: AuthenticatedLayout, children: [ {path: 'dashboard', Component: DashboardPage}, {path: 'statistics', Component: StatisticsPage}, {path: 'products', Component: ProductsPage}, {path: 'partners', Component: PartnersPage}, {path: 'quotation', Component: QuotationPage}, {path: 'cards', Component: CardsPage}, {path: 'renegotiation', Component: RenegotiationPage}, {path: 'notifications', Component: NotificationsPage}, { // 최고관리자 전용. 자식 loader 는 부모와 병렬 실행되므로 여기서도 initAuth 를 기다린다(멱등). path: 'members', loader: async () => { await initAuth(); return hasRole('최고관리자', '개발자') ? null : redirect('/forbidden'); }, Component: MembersPage, }, { // 개발자 전용. 디자인 토큰·컴포넌트 기준 화면. path: 'dev/design', loader: async () => { await initAuth(); return hasRole('개발자') ? null : redirect('/forbidden'); }, Component: DevDesignPage, }, { // 최고관리자 전용. 공급사에게 보이는 브랜딩·안내 문구. (자식 loader 는 부모와 병렬 → initAuth 대기 필수) path: 'settings', loader: async () => { await initAuth(); return hasRole('최고관리자', '개발자') ? null : redirect('/forbidden'); }, Component: SettingsPage, }, { // 개발자 전용 고급 설정. 용어·커스텀 필드는 협상 동작·목표가 산정에 영향을 줘 관리자에게 열지 않는다. path: 'dev/settings', loader: async () => { await initAuth(); return hasRole('개발자') ? null : redirect('/forbidden'); }, Component: DevSettingsPage, }, ], }, { path: '*', Component: NotFoundPage, }, ]);