[FE-08] 회사 등록 위저드 — 소스 탐색 · 후보 확정 · 후보 0건 분기

작업 내용 (설계 의도)

근거 설계: 20260722-공고알림앱-design-fe-web.md — “S-02 회사 등록” 근거 요구: FR-1(회사명 입력) · FR-2(slug 프로빙 + URL 직접 입력 + 샘플 3건) · FR-4(복수 소스 1:N) · FR-5(후보 0건 → 수동 등록 전용) · 시나리오 1 · 시나리오 5

변경 사항

이 앱의 첫인상을 만드는 화면입니다. 회사명 입력 → 후보 탐색(동기·수 초 소요) → 후보 + 샘플 공고 3건 미리보기 → 사용자 확정의 반자동 흐름을 3스텝 위저드로 구현합니다.

핵심 설계 의도:

  • 한 스텝에 질문 하나(토스 1 thing per 1 page). 회사명·URL을 받는 Step 1, 탐색 로딩 Step 2, 후보 확정 Step 3로 나눕니다. 한 화면에 다 넣으면 로딩 중 무엇을 기다리는지가 흐려집니다.
  • 탐색 로딩을 전면화합니다. 수 초~수십 초가 걸리는 동기 작업이므로 스피너만 두지 않고 무엇을 하는 중인지(“채용 플랫폼 3곳을 확인하는 중”)와 예상 소요(“최대 30초”)를 함께 보여줍니다. 대기가 실패처럼 느껴지지 않게 하는 장치입니다.
  • 로딩을 취소 불가로 두지 않습니다 — 헤더 로 Step 1 복귀가 가능하고, 진행 중 요청은 AbortController로 취소합니다.
  • 샘플 공고 3건 미리보기가 후보 확정의 근거입니다(FR-2). 미리보기가 없으면 잘못된 slug를 확정해 엉뚱한 회사 공고를 수집하는 사고가 납니다. 후보 카드마다 제목 3건을 반드시 렌더합니다.
  • 복수 선택을 지원합니다(FR-4) — 우리은행처럼 인크루트와 잡코리아를 함께 갖는 사례가 조사에서 확인됐습니다. 체크박스 다중 선택 + CTA에 선택 개수를 표시합니다.
  • 후보 0건이 막다른 길이 아니라 분기가 되어야 합니다(FR-5, 시나리오 5). Step 3b에서 ① “채용 페이지 주소로 다시 찾기”(Step 1 URL 입력 복귀) ② “수동 등록 전용으로 저장”(주 CTA) 두 탈출구를 제공하고, “공고를 직접 추가하면 지원 관리는 똑같이 사용할 수 있어요”(FR-21 보장)를 명시해 등록이 헛되지 않음을 알립니다.
  • 등록 성공 토스트에 시딩 사실을 담습니다 — “오늘 자정부터 공고를 확인해요”. 첫 수집은 시딩이라 알림이 오지 않는데(FR-10) 이를 모르면 사용자는 시스템이 고장 났다고 판단합니다.
  • 위저드 상태는 라우트 지역 상태(useReducer)입니다. 전역 승격 반려 근거: 라우트 이탈 후 되살아나는 것이 오히려 버그이고, 중단된 등록의 복원은 요구사항이 아닙니다.

범위: src/pages/company/RegisterCompanyPage.tsx(스텁 대체), 전용 스텝 컴포넌트 4종, src/api/company/discovery.ts, src/api/company/registration.ts, src/hooks/company/useSourceDiscovery.ts, useRegisterCompany.ts.

Single Writer 주의: src/api/company/query.tsuseCompaniesFE-09가 소유합니다. 이 티켓은 건드리지 않습니다.

의존

  • FE-02, FE-03, FE-04
  • BE 의존: BE-17 (회사 등록·소스 탐색 API)

다이어그램

처리 흐름

sequenceDiagram
    participant U as 사용자
    participant W as RegisterCompanyPage
    participant D as useSourceDiscovery
    participant R as useRegisterCompany
    participant S as 서버
    U->>W: 회사명 입력 후 "공고 찾아보기"
    W->>D: mutate(companyName, siteUrl?)
    D->>S: POST /api/companies/source-discoveries
    alt 후보 1건 이상
        S-->>D: candidates + samplePostings[3]
        W-->>U: Step 3a 후보 목록 (복수 선택)
        U->>W: 후보 확정
        W->>R: mutate(name, sources)
        R->>S: POST /api/companies
        S-->>R: {companyId}
        W-->>U: 공고 목록으로 이동 + 시딩 안내 토스트
    else 후보 0건
        S-->>D: candidates 빈 배열
        W-->>U: Step 3b — URL 재시도 / 수동 등록 전용 저장
        U->>W: 수동 등록 전용 저장
        W->>R: mutate(name, sources=[])
        S-->>R: {registrationType: MANUAL_ONLY}
    end

클래스 의존

flowchart LR
    subgraph Page["pages/company"]
        Wiz[RegisterCompanyPage]
        S1[CompanyNameStep]
        S2[DiscoveringStep]
        S3[CandidateSelectStep]
        S4[NoCandidateStep]
        CC[SourceCandidateCard]
    end
    subgraph Hooks["hooks/company"]
        HD[useSourceDiscovery]
        HR[useRegisterCompany]
    end
    subgraph Api["api/company"]
        AD[discovery.ts]
        AR[registration.ts]
    end
    Wiz --> S1
    Wiz --> S2
    Wiz --> S3
    Wiz --> S4
    S3 --> CC
    Wiz --> HD
    Wiz --> HR
    HD --> AD
    HR --> AR

테스트 케이스

  • 회사명이 비어 있으면 “공고 찾아보기” 버튼이 비활성이다
  • 회사명을 입력하고 제출하면 탐색 로딩 스텝으로 전환되고 소요 시간 안내가 보인다
  • 탐색이 성공하면 후보 목록과 각 후보의 샘플 공고 3건이 렌더된다
  • 후보를 2개 선택하면 CTA에 선택 개수가 반영되고 등록 시 소스 2건이 전송된다
  • 아무 후보도 선택하지 않으면 등록 CTA가 비활성이다
  • 탐색 결과가 0건이면 “수동 등록 전용으로 저장” 경로와 URL 재시도 경로가 모두 보인다
  • 후보 0건 상태로 저장하면 빈 소스 배열로 등록 요청이 전송된다
  • 후보 0건 화면에 “지원 관리는 똑같이 사용할 수 있어요” 안내가 렌더된다
  • URL을 직접 입력하고 탐색하면 URL이 요청에 포함된다
  • 잘못된 형식의 URL을 입력하면 인라인 검증 에러가 보이고 요청이 전송되지 않는다
  • 탐색이 실패(500)하면 재시도 버튼과 수동 등록 전용 분기가 함께 렌더된다
  • 로딩 중 뒤로가기를 누르면 Step 1로 복귀하고 진행 중 요청이 취소된다
  • 회사명 중복(409)이면 인라인 에러와 기존 회사로 이동하는 링크가 보인다
  • 등록에 성공하면 회사 공고 목록으로 이동하고 시딩 안내 토스트가 표시된다
  • 등록 실패(500) 시 후보 선택 상태가 유지된 채 에러 토스트가 표시된다