FeaturesDesign System 컴포넌트Shells (운영사 셸 · AuthShell)

Shells — 운영사 셸 · AuthShell

운영사 셸은 두 군데로 나뉜다:

  • AuthShell@linkmusic/ui 의 인증 화면 셸 (packages/ui/src/components/auth-shell.tsx).
  • 운영사 백오피스 셸@linkmusic/ui 에 단일 OpsShell 컴포넌트로 존재하지 않는다. apps/admin(protected) layout 이 Topbar + Sidebar 를 조합해 구성한다.

운영사 백오피스 셸 (admin (protected) layout)

운영사 백오피스 (Surface 10) 전역 셸 — sticky Topbar + Sidebar + content area. 단일 ui 컴포넌트가 아니라 apps/admin/src/app/(protected)/layout.tsx 가 server-side 에서 /auth/me 를 fetch 한 뒤 다음 admin 셸 컴포넌트들을 조립한다 (apps/admin/src/components/shell/):

컴포넌트파일
Topbartopbar.tsx
Sidebarsidebar.tsx
AccountMenuaccount-menu.tsx
ThemeToggletheme-toggle.tsx
ToastHostProvidertoast-host.tsx
OpsStepperops-stepper.tsx (등록 화면 stepper)
PageHeaderpage-header.tsx (페이지 상단 헤더)

구조

// apps/admin/src/app/(protected)/layout.tsx (요약)
<ToastHostProvider>
  <div data-surface="ops">
    <Topbar email={email} name={name} />
    <div>
      <Sidebar />
      <main>{children}</main>
    </div>
  </div>
</ToastHostProvider>

사이드바 메뉴 (sidebar.tsx NAV_TOP · NAV_BOTTOM 단일 소스)

상단 9 항목 + 구분선 + 하단 3 항목. ⭐ 표식(우측 도트)은 본사·협약.

항목href비고
대시보드/placeholder
본사 ⭐/hqSPEC #013 도입
매장/storesSPEC #019 도입 (목록). sub-route /stores/new(등록) 도 active 매칭
음원/music후속 SPEC
라이브러리/libraries후속 SPEC
협약 ⭐/contracts후속 SPEC
정산/billing후속 SPEC
CS 티켓/tickets후속 SPEC
감사 로그/audit/impersonation후속 SPEC
통계 (하단)/stats후속 SPEC
계정 (하단)/users후속 SPEC
설정 (하단)/settings/policiesSPEC #023 도입 — 약관 게시. sub-route 도 active 매칭. lucide Settings 아이콘 (임시 — 시안 부재)

탑바 (topbar.tsx)

  • 로고 + 구분선 + “운영사 백오피스 · BETA” 라벨 (좌)
  • ⌘K 글로벌 검색 (placeholder — 클릭 시 “준비 중” toast)
  • ThemeToggle (light/dark/system)
  • 장애벨 (incidentCount count badge — incident 도메인 미존재라 기본 0, 배지 숨김. SPEC #017 §3)
  • 동기화 펄스 — 가짜 “12초 전” 펄스는 backend 상태 endpoint 가 없어 제거(SPEC #017)
  • 계정 메뉴 (AccountMenuemail/name)

임퍼소네이션 모드 분기

  • 본사 모드(/admin/*)는 운영사 (protected) 셸이 아니라 별도 HQShell 로 진입한다 (아래 참고).

HQShell — 본사 모드 셸 (admin /admin layout · 실 HQ_MANAGER 전용)

이 절은 apps/admin 의 본사 모드 셸이다. 운영사→본사 임퍼소네이션의 도착지가 apps/space /admin/*(진짜 본사 기능)로 이전되면서, admin HQShell 은 실 HQ_MANAGER 직접 로그인 전용으로 단순화됐다 — 임퍼소네이션 배너·카운트다운·만료 화면이 없다. 임퍼소네이션 표면은 아래 apps/space HQShell 절 참고.

본사 모드 (Surface 11) 전역 셸 — HQTopBar + HQSidebar + content area. OpsShell 과 동일하게 단일 ui 컴포넌트가 아니라 apps/admin 소속이며, apps/admin/src/app/admin/layout.tsx(server component) 가 세션을 읽어 다음 컴포넌트들을 조립한다 (apps/admin/src/components/hq-shell/):

컴포넌트파일비고
HQShellhq-shell.tsxTopBar+Sidebar 조립 (server component · 배너 없음)
HQTopBarhq-topbar.tsx로고 + 본사명 + ThemeToggle (plan·storeCount 후속)
HQSidebarhq-sidebar.tsx10항목 — 대시보드만 active, 나머지 disabled(“준비 중”)

ImpersonationBanner(impersonation-banner.tsx)는 apps/admin 에서 제거됐다 — apps/space 로 이전.

진입·가드

  • /admin/*middleware.ts 가 bypass 하고 admin/layout.tsx 가 유일한 가드:
    1. 세션 없음(lm_session) → /login.
    2. lm_session role=HQ_MANAGER → 실 HQ_MANAGER 모드 (refresh-aware, passwordMustChange → change-password, 약관/개인정보 재동의 → reagree).
    3. lm_session role=OPERATOR → redirect("/") (교차 리디렉션).
  • hqName 은 /auth/me 응답. 세션 만료는 layout 의 refresh-aware(loadMeRefreshAware)가 처리한다 (클라이언트 카운트다운/만료 화면 없음).

사이드바 메뉴 (hq-sidebar.tsx HQ_NAV 단일 소스)

대시보드 · 안내방송 · CM송 · 플레이리스트 · 라이브러리 · 매장 · 구독·결제 · 고객지원 · LLM 자동멘트 · 설정. 대시보드(/admin) 만 링크 활성, 나머지 9 항목은 aria-disabled + “준비 중” (후속 SPEC).

HQShell — 매장 클라이언트 본사 모드 셸 (apps/space /admin layout)

신규 매장 클라이언트(apps/space · space.linkmusic.io)의 본사 모드(Surface 11) 셸. 진짜 본사 기능이 단일 소스로 있는 곳이며, 운영사→본사 임퍼소네이션의 도착지이기도 하다. apps/space/src/app/admin/ layout.tsx(server)가 두 세션을 해석해 조립한다 (apps/space/src/components/hq-shell/):

컴포넌트파일비고
HQShellhq-shell.tsximpersonation prop 으로 배너 분기, TopBar+Sidebar 조립 (client)
ImpersonationBannerimpersonation-banner.tsx빨간 배너 + 60분 카운트다운 + [운영사 백오피스로 돌아가기] (client, 임퍼소네이션만)
HQTopBarhq-topbar.tsx로고 + 본사명 + PlanBadge(plan) + 산하 매장 수(storeCount) + ThemeToggle (client)
PlanBadgeplan-badge.tsxAI / TRUST 배지 (시안 hq-shared §PlanBadge). plan=null 이면 생략
HQSidebarhq-sidebar.tsx본사 기능 항목 — 출시된 항목 active

두 진입 모드

HQShellimpersonation: boolean prop 으로 두 모드를 분기한다. layout 이 어느 세션으로 진입했는지에 따라 prop 을 정한다 (우선순위: 임퍼소네이션 우선).

모드세션 cookieimpersonation배너hqName 소스plan·storeCount세션 관리
임퍼소네이션lm_space_impersonation_sessiontrueO (빨강 + 60분 카운트다운)sealed 세션null·0 폴백(0-backend-call)v1 고정, refresh 없음
실 HQ_MANAGERlm_space_session role=HQ_MANAGERfalseXHqMeResponse.namegetHqMerefresh-aware

진입·가드

  • apps/space middleware.ts/impersonate-exchange(임퍼소네이션 새 탭 도착지)·/admin/* 를 bypass 한다 — /admin 은 두 세션 중 하나로 진입할 수 있어 단일 cookie presence 검사로는 임퍼소네이션 진입이 부당하게 튕긴다. layout 이 단독 가드(두 세션 모두 검사·둘 다 없으면 server-side /login, fail-closed):
    1. lm_space_impersonation_session 존재 → 임퍼소네이션 모드 (배너 O · plan/storeCount 폴백 · pmc/재동의 체크 불필요).
    2. lm_space_session role=HQ_MANAGER → loadMeRefreshAware(pmc 가드) → loadHqMeRefreshAwareGET /api/v1/hq/me(HqMeResponse) → HQShell 에 hqName·plan·storeCount 주입.
    3. role=STORE_MANAGER → /store, role≠HQ_MANAGER(OPERATOR·누락) → /login (fail-closed).
    4. 세션 없음 → /login.
  • 임퍼소네이션 배너 컨텍스트(operatorEmail·hqName)는 exchange 시 sealed 세션에 봉인 → layout 이 prop 전달 (페이지마다 backend 호출 0). 실 HQ_MANAGER topbar 값은 getHqMe 단일 호출에서 온다 (data=null 이면 폴백값 — 본사 / null plan / 0개).
  • data-surface="hq" HQ scale(comfortable · 15px)은 apps/space/src/app/globals.css 가 선언 (packages/ui 무변경 — 시안 design/tokens.css §HQ MODE 기준).

임퍼소네이션 배너 (① Binding Constraint · impersonation=true 일 때만)

  • 모든 apps/space /admin/* 최상단 고정, --imp-alert·--imp-alert-fg 토큰(@linkmusic/ui) 직접 참조.
  • 세션 잔여 카운트다운 (MM:SS, 1초) — session.refreshExpiresAt(절대 ms) 기준. v1 고정 60분, 자동 refresh 없음.
  • [운영사 백오피스로 돌아가기] → POST /api/auth/impersonation-exit(space) (세션 destroy) → window.close() + 안내.
  • 0:00 도달 → 셸이 만료 화면(danger 배너 + 탭 닫기 안내)으로 전환.

StoreShell — 점장 모드 (apps/space /store · placeholder · SPEC #050)

apps/space/src/app/store/ — STORE_MANAGER 가드 + placeholder. 셸·화면(home·즉시방송 등)은 후속.

AuthShell

로그인 · 비밀번호 변경 등 pre-auth 화면 전용. @linkmusic/ui export.

구조

사이드바·topbar 없는 단순 centered layout (강제 비밀번호 변경 동안 다른 곳으로 이동하지 못하도록 제한). 헤더(로고 + “운영사 백오피스 · BETA” 라벨 + 우측 옵셔널 headerAction) + center 정렬 main + 옵셔널 footer.

import { AuthShell } from "@linkmusic/ui";
 
<AuthShell headerAction={<ThemeToggle />} footer={<>지원 · 버전</>}>
  <AuthCard>{/* form */}</AuthCard>
</AuthShell>

사용처

  • /login
  • /onboarding/change-password
  • (impersonate-exchange 는 redirect 만, 시각 없음)

위치

packages/ui/src/components/auth-shell.tsx

Constraints

  • 모두 토큰만 사용 — data-surface="ops" Layer 3 ops compact.
  • 운영사 셸: Server Component 가 /auth/me fetch + Client Component (Topbar/Sidebar) 가 interactive.

Roadmap

  • StoreShell 본구현 (apps/space /store — Surface 12 점장 모드 home·즉시방송 등)
  • apps/space HQShell 기능 페이지 22개 · HQSidebar 활성화 · 검색/알림/계정 메뉴 (후속 SPEC)
  • HQ TopBar ⌘K 검색 활성화 · 사이드바 collapse / pin
  • auth/BFF 공유 패키지 추출 (admin·space 중복 제거 — SPEC #050 §F4)

References

  • SPEC #006 §2-3·§9 · SPEC #012 PR-A/PR-B · SPEC #015 (admin HQShell) · SPEC #016 (이중 세션·실 HQ_MANAGER 모드) · SPEC #050 (apps/space 본사 모드 셸·점장 placeholder)
  • handoff 10-surface-ops-backoffice.md §4 · design/hq-shared.jsx (HQShell·HQTopBar·HQSidebar·PlanBadge 시안)
  • linkmusic-frontend-space/packages/ui/src/components/auth-shell.tsx
  • linkmusic-frontend-space/apps/admin/src/app/(protected)/layout.tsx
  • linkmusic-frontend-space/apps/admin/src/components/shell/
  • linkmusic-frontend-space/apps/admin/src/app/admin/layout.tsx
  • linkmusic-frontend-space/apps/admin/src/components/hq-shell/
  • linkmusic-frontend-space/apps/space/src/app/admin/layout.tsx · apps/space/src/app/store/
  • linkmusic-frontend-space/apps/space/src/components/hq-shell/