[FE-18] 애그리게이터 소스 등록 화면

작업 내용 (설계 의도)

근거 설계: 20260722-공고알림앱-design-fe-web.md — “S-12 애그리게이터 소스 등록”, “방안 8” 근거 요구: FR-60(애그리게이터 소스 = 플랫폼 + 검색 조건) · 시나리오 9

변경 사항

애그리게이터(사람인·점핏·원티드·리멤버·잡코리아·서핏 — 4차 갱신으로 6종) 소스를 등록하는 화면입니다. 회사 등록(회사명 → 탐색 → 확정)과 흐름이 다릅니다 — 탐색 단계가 없고, 플랫폼을 고르고 검색 조건(직무 카테고리·키워드)을 지정하는 단일 폼입니다.

핵심 설계 의도:

  • 위저드가 아니라 단일 폼입니다(토스 1 thing per 1 page + Minimum Input). 탐색·확정 단계가 없어 회사 등록 위저드(FE-08)와 컴포넌트를 공유하지 않습니다.
  • 직무 카테고리는 플랫폼별 코드(사람인 cat_kewd, 점핏 jobCategory)입니다. 사용자에게 코드를 노출하지 않고 한글 라벨 Select로 제시하며, 플랫폼 전환 시 선택지가 바뀝니다. 매핑은 FE-03의 aggregatorCategories 상수를 소비합니다(계약 요청 12로 BE 확정 대기).
  • 키워드는 선택입니다 — 비우면 카테고리 전체 수집(Minimum Input).
  • 발견 회사의 성격을 등록 시점에 안내합니다 — “관심 회사와 구분 / 일일 요약 / 승격 가능”(FR-62). 이 안내가 없으면 사용자는 왜 개별 알림이 안 오는지 의아해합니다.
  • 규약 경고를 띄우지 않습니다 (플랫폼 2종 한정)4차 갱신에서 무효 — 회색지대 4종이 P0로 편입돼 플랫폼이 6종이 됐고, 회색지대 선택 시 안내 배너를 노출합니다. 아래 “4차 갱신 반영” 참조.
  • 등록 성공 시 홈 발견 탭으로 이동(/?companyOrigin=DISCOVERED)하고 “오늘 자정부터 이 조건으로 공고를 찾아요” 토스트를 표시합니다 — 첫 수집이 다음 자정이라 즉시 회사가 보이지 않음을 알립니다.
  • 제출 실패(5xx) 시 입력값을 보존합니다.

진입점: 홈 발견 탭 헤더의 [+ 발견 소스 추가](FE-09가 링크를 렌더). 라우트 /aggregator-sources/new는 FE-01이 스텁으로 선언하며, 이 티켓이 스텁을 대체합니다.

계약: POST /api/aggregator-sources는 존재합니다. 등록 소스 목록 조회·삭제 API는 없어(계약 요청 #10) P0는 등록 전용으로 두고 소스 확인·해제 UI를 만들지 않습니다.

범위: src/pages/aggregator/AggregatorSourcePage.tsx(스텁 대체), 전용 컴포넌트(PlatformSegment·CategorySelect), src/api/aggregator.ts, src/hooks/aggregator/useRegisterAggregatorSource.ts.

의존

  • FE-02 (UI 프리미티브), FE-03 (카테고리 상수·검증)
  • BE 의존: BE-17 (회사·소스 등록 — 애그리게이터 소스 등록 API 포함) 또는 신규 BE 티켓. 실 연동은 그 이후, 개발·테스트는 MSW 목으로 선행

다이어그램

처리 흐름

sequenceDiagram
    participant U as 사용자
    participant P as AggregatorSourcePage
    participant C as aggregatorCategories
    participant M as useRegisterAggregatorSource
    participant S as 서버
    U->>P: 플랫폼 "사람인" 선택
    P->>C: 사람인 카테고리 목록
    C-->>P: 한글 라벨 Select 채움
    U->>P: 카테고리 선택 · 키워드 입력 후 제출
    P->>M: mutate(platform, searchCategoryCode, searchKeyword?)
    M->>S: POST /api/aggregator-sources
    alt 성공
        S-->>M: {jobSourceId, platform}
        M-->>U: 홈 발견 탭으로 이동 + 토스트
    else 실패
        S-->>M: 400 또는 5xx
        M-->>U: 인라인/토스트 + 입력값 보존
    end

클래스 의존

flowchart LR
    subgraph Page["pages/aggregator"]
        Main[AggregatorSourcePage]
        Plat[PlatformSegment]
        Cat[CategorySelect]
    end
    subgraph Hooks["hooks/aggregator"]
        H[useRegisterAggregatorSource]
    end
    subgraph Api["api"]
        A[aggregator.ts]
    end
    subgraph Const["constants"]
        Cats[aggregatorCategories]
    end
    subgraph Ui["components/ui"]
        Seg[Segment]
        Sel[Select]
        TF[TextField]
        Btn[Button]
    end
    Main --> Plat
    Main --> Cat
    Main --> H
    Plat --> Seg
    Cat --> Sel
    Cat --> Cats
    Main --> TF
    Main --> Btn
    H --> A

테스트 케이스

  • 플랫폼 Select에 6종(사람인·점핏·원티드·리멤버·잡코리아·서핏)이 렌더된다 (4차 갱신)
  • 플랫폼을 점핏으로 바꾸면 카테고리 Select 선택지가 점핏 카테고리로 교체된다
  • 카테고리를 선택하지 않으면 “소스 추가하기” 버튼이 비활성이다
  • 키워드 없이(카테고리만) 제출하면 searchKeyword가 미전송된다
  • 카테고리·키워드를 모두 입력해 제출하면 세 값이 전송된다
  • 발견 회사 성격 안내(일일 요약·승격 가능)가 화면에 렌더된다
  • 등록 성공 시 홈 발견 탭(?companyOrigin=DISCOVERED)으로 이동하고 안내 토스트가 표시된다
  • 등록이 5xx면 입력값이 보존된 채 에러 토스트가 표시된다
  • 제출 중 CTA에 로딩이 표시되고 폼이 비활성화된다
  • 청정 플랫폼(사람인·점핏) 선택 시 회색지대 안내 배너가 렌더되지 않는다 (4차 갱신)
  • 다크 모드에서 세그먼트·Select·안내 박스가 오류 없이 렌더된다

3차 갱신 반영 (2026-07-22, BE 최종 확정)

이 티켓이 애그리게이터 도메인의 등록(S-12) + 관리(S-13) 두 화면을 소유합니다. 파일 소유 경계(pages/aggregator/**·api/aggregator.ts·hooks/aggregator/**)는 그대로라 wave·티켓 수 무변경.

1. 카테고리를 API로 채움 (상수화 금지)

  • 직무 카테고리 Select를 GET /api/aggregator-sources/categories?platform=으로 채웁니다({categories:[{code, label}]}). FE 상수화는 어댑터 지원 코드와 드리프트가 나므로 금지.
  • 플랫폼 세그먼트가 바뀌면 카테고리를 재조회합니다 — 의존 쿼리(useAggregatorCategories(platform), queryKey ['aggregator-categories', platform], enabled: !!platform). 카테고리 옵션은 서버 상태(Query)라 useState에 복사하지 않습니다.
  • 카테고리 로딩 중 Select는 “불러오는 중” placeholder + CTA 비활성. 조회 실패 시 Select 자리에 에러 + [다시 시도].
  • 등록 성공 시 소스 관리 화면(S-13)으로 이동(기존 “홈 발견 탭”에서 변경) — 방금 추가한 소스가 목록에 나타나 반영을 즉시 확인.

2. 소스 관리 화면 S-13 추가 (AggregatorSourceListPage)

  • GET /api/aggregator-sources(활성·비활성 함께) 목록 + [발견 소스 추가] CTA(→ S-12).
  • 비활성 소스는 disabled=true로 흐리게(text-tertiary + 점선 테두리 + “중지된 소스예요”). 소프트 삭제라 이력이 보존됩니다.
  • DELETE /api/aggregator-sources/{id}(소프트 삭제)는 확인 다이얼로그 없이 낙관적 비활성 처리 → 실패 시 롤백(onMutate 스냅샷 → onError 복원 → onSettled ['aggregator-sources'] 무효화).
  • 진입점: 홈 발견 탭 헤더 [발견 소스 관리] → S-13. S-13이 S-12의 부모.
  • 4상태: loading(카드 스켈레톤) / empty(“아직 등록한 발견 소스가 없어요” + 중앙 CTA) / error(목록 에러 대체, CTA 유지) / success.

추가 컴포넌트·훅

  • AggregatorSourceListPage, AggregatorSourceItem(활성/비활성 위계), CategorySelect(API 소비)
  • useAggregatorCategories(platform), useAggregatorSources(), useDeleteAggregatorSource()

추가 테스트 케이스

  • 플랫폼을 사람인→점핏으로 바꾸면 카테고리를 재조회하고 선택지가 교체된다
  • 카테고리 조회 중 Select가 로딩 상태이고 CTA가 비활성이다
  • 카테고리 조회 실패 시 에러와 [다시 시도]가 렌더된다
  • 카테고리가 0건이면 안내 문구가 렌더되고 CTA가 비활성이다
  • 등록 성공 시 소스 관리 화면(S-13)으로 이동한다
  • S-13 목록에 활성 소스와 비활성(disabled) 소스가 흐리게 함께 렌더된다
  • 비활성 소스에 “중지된 소스예요” 표기가 렌더된다
  • [삭제] 클릭 시 낙관적으로 비활성 처리되고 실패 시 원복된다
  • S-13에서 [발견 소스 추가] 클릭 시 S-12(/aggregator-sources/new)로 이동한다
  • S-13 소스 0건이면 empty 안내와 중앙 CTA가 렌더된다
  • 다크 모드에서 활성/비활성 위계가 오류 없이 렌더된다

4차 갱신 반영 (2026-07-22, 회색지대 애그리게이터 P0 편입)

  • 플랫폼 선택지 2→6종(사람인·점핏 + 원티드·리멤버·잡코리아·서핏). 6개라 세그먼트가 아니라 Select(PlatformSelect)로 제시합니다.
  • 회색지대 4종 선택 시 안내 배너 — warning-subtle 배너로 “이 플랫폼은 서비스 약관상 자동 수집에 제약이 있어요. 개인 열람 용도로만 사용됩니다”. 청정 2종(사람인·점핏)은 배너 미노출로 시각 구분. 회색지대 판정은 FE-03 isGrayZonePlatform에 위임. 배너는 등록을 막지 않습니다(안내 전용).
  • 카테고리는 여전히 GET .../categories?platform=로 동적 구성 — 플랫폼이 늘어도 카테고리 로직은 무변경(플랫폼 변경 시 재조회 그대로).

추가 테스트 케이스

  • 플랫폼 Select에 6종이 모두 렌더된다
  • 회색지대 플랫폼(원티드) 선택 시 안내 배너가 노출된다
  • 청정 플랫폼(사람인) 선택 시 안내 배너가 노출되지 않는다
  • 회색지대 배너가 있어도 카테고리·키워드가 정상이면 등록 CTA가 활성이다(등록을 막지 않음)