o2o-negosium-original/negodata/front/src/app/router.tsx
Mina Choi a416668256 [feat] negodata/front: 설정 화면 회사/개발자 분리 + 카드 변수 별칭·미리보기 정비 + 네고율 공통화
- 설정 분리: /settings(최고관리자)는 공급사에게 보이는 브랜딩·안내 문구만, /dev/settings(개발자 신설)는
  협상 동작·데이터 구조를 바꾸는 탭(용어·커스텀 필드 등) — 화면에 보이는 것과 동작을 바꾸는 것을 관리 주체로 가름
- 카드 변수: prev_customer_price(당사 직전가) 칩 추가, 시드/구버전 별칭(anchoring_price 등)은
  툴바 미노출·인식만 지원. SlateRenderer 는 평문에 박힌 미주입 토큰도 카탈로그에 있으면 한글 라벨로 표기
- 견적상세 협상대화의 사용 카드 미리보기를 서식 렌더(renderCardScriptPreview)로 — 마커 원문 노출 제거
- 네고율 차감을 매입가 전용 → 매입가·판매가 공통으로(문구·주석 정리)
2026-08-05 16:00:31 +09:00

133 lines
4.5 KiB
TypeScript

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([
// dev 전용: import.meta.env.DEV가 false인 프로덕션 빌드에선 이 배열 항목과
// 내부 import()가 통째로 트리셰이킹되어 번들/라우트에 포함되지 않는다.
...(import.meta.env.DEV
? [
{
path: 'dev/design',
lazy: async () => ({
Component: (await import('../pages/dev/dev-design')).default,
}),
},
]
: []),
{
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,
},
]);