Contracts (3층위 계약)Critical States (빈/에러/로딩)

Critical States (빈/에러/로딩/상태 모델 분기)

handoff brief 의 각 surface §6 정합. 모든 화면이 반드시 처리해야 할 상태 카탈로그.

Overview

UI 가 “기본 동작” 외 반드시 그려야 할 상태들. 어떤 디자인이든 빠뜨리면 데이터 강제 ①을 어긴 것.

1. Empty 상태

화면Empty 케이스처리
/ 대시보드본사 0건, 장애 0건 (녹색 정상)권장 액션 카드
/hq 본사 목록본사 0건, 검색 0건CTA (신규 본사 등록) + 검색 reset
/stores 매장 목록매장 0건CTA (INDEPENDENT 매장 등록)
/music 음원 풀음원 0건 (운영 초기)업로드 CTA
/libraries 라이브러리라이브러리 0건복제 / 새로 만들기 CTA
/tickets 티켓티켓 0건”처리할 티켓 없음” 휴식 메시지
/stats 통계데이터 없음 (베타 초기)“데이터 수집 중” 안내

2. Loading 상태

화면Loading처리
모든 목록초기 fetchSkeleton (3~5 rows)
임퍼소네이션 진입exchange token 발급 + 새 탭 + 첫 페이지 로드”진입 중…” overlay + 새 탭
폼 submitmutation pending버튼 spinner + form disabled
refreshaccess token 만료 → BFF refreshinvisible (UI 가 끊김 없음)

3. Error 상태

3-1. 인증 실패

  • 401 (AUTH_INVALID_CREDENTIALS) — 로그인 폼: “이메일 또는 비밀번호가 일치하지 않습니다.”
  • 401 backend 재인증 실패 (refresh 도 만료) — BFF 가 session.destroy() + /login 으로 redirect
  • 5xx (서버 장애)BACKEND_ERROR — session 유지 + Toast “잠시 후 다시 시도”
  • 네트워크 도달 실패BACKEND_UNREACHABLE — 동일하게 session 유지 + Toast

3-2. 검증 실패

  • 400 (VALIDATION_FAILED) — backend 가 field errors 반환. 폼 inline 표시 (field 별 error 메시지).
  • 409 (예: EMAIL_DUPLICATE) — Toast + field highlight.

3-3. 권한 실패

  • 403/audit/impersonation 등 OPERATOR-only 페이지에 HQ_MANAGER 접근 — 빈 화면 + “권한 없음”.

4. 상태 모델 분기 (데이터 강제 ①)

4-1. HQ status

statusUI 상태
활성 (가상)정상. 임퍼소네이션 가능 (FRANCHISE만)
SUSPENDED본사 row 회색 표시 + “이용 정지” 배지. 임퍼소네이션 비활성 (사유 표시).
INDEPENDENT임퍼소네이션 자체 비활성 — confirm dialog 안 뜸, 버튼 disabled + 사유

현재 도입: Hq.status enum 4종 (ACTIVE / ONBOARDING / UNPAID / SUSPENDED) — BE v0.7.0 (SPEC #013). 가상 본사는 ACTIVE 고정 + INDEPENDENT 분기로 임퍼소네이션 차단. 정지/복구 전이 + 관리 UI 도입 완료 (SPEC #018, BE v0.11.0) — hq-list 행 [정지]/[복구] + suspend/reactivate endpoint + 세션 무효화·로그인 차단. 자동 전환·사유 저장은 후속.

4-2. Store status (StoreStatus enum)

statusUI 상태
ACTIVE정상
SUSPENDED”이용 정지” 배지. 송출 차단 (후속 SPEC)
INACTIVE”장기 미사용” 배지 (90일 자동 전환, 후속 SPEC)

4-3. AccountStatus

statusUI 상태
ACTIVE정상
SUSPENDED즉시 세션 무효화 + 다음 로그인 차단 (구현 후속 SPEC)
WITHDRAWN탈퇴. 로그인 차단 + soft-delete

4-4. PlanType 분기

PlanUI 분기
AIAI 라이브러리만 노출
TRUSTAI + TRUST 라이브러리 노출

4-5. StoreType / HqType 분기

분기UI
FRANCHISE 본사산하 매장 (DIRECT / FRANCHISE) 묶음 표시. 결제는 본사 단위.
INDEPENDENT 매장가상 본사 산하. 결제는 매장 단위. UI 에서 본사 정보 hide.

5. 임퍼소네이션 진입 분기 (① 가장 중요)

진입 시도결과
FRANCHISE + non-SUSPENDED (ACTIVE·ONBOARDING·UNPAID)confirm dialog → 새 탭 + exchange → 본사 모드 + 빨간 배너
FRANCHISE + SUSPENDEDconfirm dialog 안 뜸. 버튼 disabled + “이용 정지된 본사”
INDEPENDENTconfirm dialog 안 뜸. 버튼 disabled + “개인 매장 가상 본사”
exchange token 만료 (60초 초과)새 탭에 “토큰 만료, 다시 시도”
exchange 중 운영사 세션 만료exchange 실패 + 로그인 redirect

6. 로그인 후 role 라우팅 · 교차 리디렉션 (SPEC #016)

6-1. 로그인 role 분기 (AuthResponse.role)

role결과
OPERATORsafeNext(next) (기본 /) — ops 셸
HQ_MANAGER/admin — 본사 모드 셸 (배너 없음, MeResponse.hqName 표시)
STORE_MANAGERredirect 없음 — “지원되지 않는 역할” 안내 + POST /api/auth/logout(세션 destroy). 앱 접근 차단

passwordMustChange=true 는 (로그인이 지원되는 OPERATOR·HQ_MANAGER 레이아웃 내에서) role 분기보다 우선 → /onboarding/change-password. STORE_MANAGER 는 위 표대로 로그인 자체가 차단(점장 모드 미구축)되어 이 FE redirect 에 도달하지 않는다. 반면 BE PasswordChangeEnforcementFilterrole 무관하게 pmc=true 계정의 allowlist 밖 보호 endpoint 호출을 403 PASSWORD_CHANGE_REQUIRED 로 차단해 직접 API 우회까지 방어한다 (이중, SPEC #022).

6-2. 교차 리디렉션 (레이아웃 가드 — defense-in-depth)

진입가드결과
ops (protected)/* 에 HQ_MANAGER me(protected)/layout.tsxredirect("/admin")
/admin/* 에 OPERATOR lm_session(임퍼소네이션 아님)app/admin/layout.tsxredirect("/")
/admin/* 에 임퍼소네이션 + 실 HQ_MANAGER 세션 공존app/admin/layout.tsx임퍼소네이션 우선
/admin/* 세션 없음app/admin/layout.tsx/login

순서: 두 layout 모두 passwordMustChange → role 라우팅 순. 변경 완료 후 role home 으로 바운스(루프 없음 — onboarding layout 은 me 재호출 안 함).

References

  • handoff 10-surface-ops-backoffice.md §6 Critical states
  • handoff 11/12-surface-*.md §6
  • /features/hq/impersonation — 진입 흐름 상세