[FE-48] 3단계 배선 (검토 대기 배너 · 딥링크)

작업 내용 (설계 의도)

근거: 지원 관리 확장 FE 웹 설계 S-18 지원 대시보드(검토 대기 배너) · 컴포넌트 트리 · 상태 관리(전역 승격을 반려한 후보 — 검토 대기 연락 건수) · 라우팅 · 내비게이션 > 3단계 라우팅 흐름 · Single Writer per File 검증 > 단계 3 wave 3 · Testing Plan > 반드시 커버할 실패·엣지 경로 #47, 지원 관리 확장 TDD 3단계 — 연락 검토 화면 (FR-90) · 검토 요청 알림(FR-94).

변경 사항

  • 3단계의 공통 파일 와이어업을 한 티켓으로 모읍니다. wave 2의 6개 티켓이 각자 대시보드·공고 상세를 건드리면 머지 충돌이 확정이므로, 화면 간 연결만 마지막 wave에 단독 배치합니다.
  • 대시보드(S-18)에 검토 대기 배너를 추가합니다 — “검토할 연락 3건이 있어요” + [연락 검토 열기]/contact-events(기본 PENDING 세그먼트)로 보냅니다. 디스코드 알림을 놓친 사용자가 앱에서 검토 대기를 발견하는 유일한 동선입니다.
  • 배너 건수는 FE-42의 useContactEvents PENDING 쿼리 totalCount를 재사용합니다. 목록 화면과 같은 쿼리 키를 공유하므로 연락을 반영·무시하면 배너가 자동으로 갱신됩니다. 별도 전역 상태(Zustand)를 만들지 않습니다 — 서버 상태를 스토어에 복사하면 두 값이 어긋납니다.
  • 검토 대기 0건이면 배너를 렌더하지 않습니다. 0건은 정상 상태이므로 “0건” 배너를 남기면 대시보드 상단이 항상 알림처럼 보입니다.
  • 배너 쿼리가 실패하거나 로딩 중이면 배너를 렌더하지 않습니다 — 대시보드 본문(칸반·면접·장기 미변경)이 부가 배너 때문에 막히면 안 됩니다(부분 실패 격리).
  • 공고 상세(S-04)에 3단계 컴포넌트를 배선합니다 — 추천도 섹션 안에 CapReleaseControl(FE-46), 매칭 박스 자리에 MatchEvidenceBox(FE-47). 두 컴포넌트는 props만 받는 순수 프레젠테이션이므로 데이터 조회·전달은 공고 상세 페이지가 합니다.
  • FE-47이 계약 미확정으로 미착수인 경우, 이 티켓은 MatchEvidenceBox 배선을 제외하고 배너·상한 해제 배선만 수행합니다. 공고 상세는 기존 matched: boolean 표시를 그대로 유지합니다 — 계약이 없다는 이유로 3단계 배선 전체를 막지 않습니다.
  • 디스코드 검토 요청 알림의 딥링크(/contact-events/:contactEventId) 가 인증 게이트를 통과해 원래 경로로 복귀하는지 확인합니다. 라우트 자체는 FE-41이 선언했고 게이트 규약은 1단계 FE-23 소유이므로, 이 티켓은 동작 확인과 회귀 테스트만 하고 게이트 구현을 수정하지 않습니다.
  • 연락 반영 후 ['dashboard'] 무효화가 실제로 배너와 칸반에 반영되는지 통합 관점에서 검증합니다 — 무효화 매핑은 FE-43이 선언했고, 이 티켓은 두 화면이 함께 갱신되는지를 봅니다.

의존

  • FE-42 — useContactEvents 훅과 PENDING 쿼리 키를 재사용합니다(새 훅을 만들지 않습니다).
  • FE-43 — 딥링크 도착 화면과 연락 결정 후 무효화 동작을 전제로 합니다.
  • FE-46 — CapReleaseControl을 공고 상세에 배선합니다.
  • FE-47 — MatchEvidenceBox를 공고 상세에 배선합니다. 미착수(계약 미확정)면 이 부분만 제외하고 진행합니다.
  • FE-40 · FE-41 — 타입·queryKey·목과 라우트 선언을 사용합니다.
  • BE-76 — 검토 요청 알림(딥링크 발신) 구현.

다이어그램

처리 흐름

sequenceDiagram
    participant User as 사용자
    participant Dash as DashboardPage
    participant Banner as PendingContactBanner
    participant Query as useContactEvents PENDING
    participant Detail as ContactEventDetailPage
    Dash->>Query: 검토 대기 조회 (목록과 동일 키)
    Query-->>Banner: totalCount
    Banner-->>User: 0건이면 미렌더, 1건 이상이면 배너
    User->>Banner: 연락 검토 열기
    Banner->>Detail: /contact-events 경유 상세 진입
    Detail->>Query: 반영 후 contact-events·dashboard 무효화
    Query-->>Banner: 갱신된 totalCount

컴포넌트 의존

flowchart LR
    Dash[DashboardPage] --> Banner[PendingContactBanner]
    Banner --> Hook[useContactEvents · FE-42]
    Hook --> Keys[contact-events queryKey]
    Detail[JobPostingDetailPage] --> Reco[RecommendationSection]
    Reco --> Cap[CapReleaseControl · FE-46]
    Detail --> Evidence[MatchEvidenceBox · FE-47]
    Discord[디스코드 딥링크] --> Gate[AuthGate · 미수정]
    Gate --> ContactDetail[ContactEventDetailPage]

테스트 케이스

  • 검토 대기 3건이면 대시보드에 “검토할 연락 3건” 배너가 보이고 [연락 검토 열기]/contact-events로 이동한다.
  • 검토 대기 0건이면 대시보드 배너가 렌더되지 않는다.
  • 배너와 연락 검토 목록이 같은 쿼리 키를 공유해, 연락을 반영하면 두 화면의 건수가 함께 줄어든다.
  • 배너용 조회가 실패해도 대시보드의 칸반·면접·장기 미변경 섹션은 정상 렌더된다.
  • 배너 조회가 로딩 중이면 배너 자리가 비어 있고 “0건” 문구가 보이지 않는다.
  • 미인증 상태에서 디스코드 딥링크 /contact-events/42로 진입하면 로그인 화면을 거쳐 로그인 성공 후 /contact-events/42로 복귀한다.
  • 공고 상세에서 capApplied인 공고에 상한 해제 컨트롤이 보이고, 해제하면 추천도 표시가 갱신된다.
  • 공고 상세에서 matchScore가 있으면 매칭 근거 박스가 보이고, null이면 기존 매칭 표시로 폴백한다.
  • 연락을 반영하면 대시보드 칸반의 해당 지원 카드 상태가 갱신된다.
  • 공고 상세의 3단계 섹션 중 하나가 실패해도 나머지 섹션은 정상 렌더된다.
  • 대시보드 배너와 공고 상세 3단계 섹션을 다크 모드로 렌더하면 시맨틱 토큰 class만 사용하고 하드코딩 색이 0건이다.