Skip to content

[#7] 백엔드 API 연동과 보드 및 마이페이지 개편 - #11

Merged
pug9483 merged 23 commits into
devfrom
feat/7-auth-api-integration
Aug 30, 2026
Merged

pug9483 merged 23 commits into
devfrom
feat/7-auth-api-integration

Conversation

@pug9483

@pug9483 pug9483 commented Aug 30, 2026

Copy link
Copy Markdown
Member

📄 작업 내용

  • mock 데이터를 걷어내고 Spring 백엔드 API와 연동 (인증, 공고, 서평, 알림, 문의)
  • 카테고리 값을 백엔드 enum과 1:1로 맞춤 — 한국어 라벨을 값으로 써서 필터 쿼리가 서버에서 실패하던 문제 해결
  • 신청 기록(자기 신고)과 공고 일정 캘린더 내보내기(.ics · 구글 캘린더) 추가
  • 보드를 모집 달력(/calendar)과 전체 모집 목록(/board) 두 화면으로 분리, 목록은 행 형식으로 변경
  • 관리자 계정으로 로그인한 사용자는 재로그인 없이 백오피스 진입 (진입 링크가 UI에 없어 주소를 아는 사람만 들어갈 수 있었음)
  • 서평 등록을 참여 후기로 전환 — 별점 대상을 책에서 모집 진행으로, 서평 원문 링크는 선택으로
  • 마이페이지 정리 — 탭마다 나타났다 사라지던 검색·정렬 툴바 제거, 계정 관리 재구성
  • 즐겨찾기 토스트 추가와 목록 즉시 갱신
  • 컨테이너 배포(nginx) 및 CI/CD 워크플로 추가

✅ PR 유형

  • Feature
  • Bug Fix
  • Refactor
  • Test
  • Chore
  • Docs

🧪 테스트 내용

  • 로컬 테스트 완료
  • API 테스트 완료
  • UI 테스트 완료

npm run lint · npm run build 통과. 375 / 768 / 1024 / 1440px에서 확인했습니다.
로그인이 필요한 화면(마이페이지, 후기 작성, 백오피스)은 직접 확인하지 못했습니다.

🙋🏻 참고 사항

  • 백엔드 PR과 함께 머지해야 합니다. 참여 후기 전환에 백엔드 변경(서평 링크 선택, 사전 승인 제거)이 걸려 있습니다.
  • 서평 → 참여 후기는 성격 변경입니다. 공고의 서평 의무(제출 기한·등록 채널)는 실제 계약이라 문구를 그대로 뒀습니다.
  • /board가 목록, /calendar가 달력입니다. 기존 /board 링크는 그대로 동작합니다.
  • 커밋 중 3개는 단독으로 타입 체크가 되지 않습니다(변경이 서로 얽혀 파일 단위로만 분리 가능). 최종 상태는 통과합니다.

🔗 관련 이슈

resolved: #7

pug9483 added 23 commits June 24, 2026 23:36
1. C001 검증 실패
  - errors.ts: AuthError에 fields?: FieldError[] 추가, 'VALIDATION' 코드 추가
  - client.ts: C001 → VALIDATION 매핑, toAuthError가 응답 data 배열을 parseFieldErrors로 파싱해 AuthError.fields에 담음
  - SignupPage.tsx: applyFieldErrors로 email/password/nickname 필드에 에러 매핑 (email 에러 시 인증 상태도 리셋)
2. 비밀번호 최대 길이(64자) 검증
3. 가입 성공 + 자동 로그인 실패 분기
  - api/auth.ts: signup에서 가입(201) 후 login()만 실패하면 SIGNUP_LOGIN_FAILED 코드로 구분
  - SignupPage.tsx: 이 경우 "가입 완료, 다시 로그인해주세요" 안내 + 로그인 페이지로 이동(redirect 보존). 재시도해서 EMAIL_EXISTS로 막히는 혼란 제거
- 비밀번호 변경 엔드포인트를 /me/password로 정렬 (GET /api/me와 일관)
1. 비밀번호 재설정(코드 방식) 엔드포인트 주석 확정
2. 백엔드 에러코드 매핑 추가
  - M002 : USER_NOT_FOUND (미가입 이메일 재설정 코드 발송)
  - M005 : PASSWORD_INCORRECT (현재 비밀번호 불일치)
  - M006 : EMAIL_NOT_VERIFIED (재설정 이메일 인증 미완료)
dev의 백오피스·공고 API(#9, #10)를 가져오면서, HTTP 클라이언트를 이 브랜치의
client.ts로 통일하고 그 위에 공고 도메인을 다시 세웠다. 아래 변경들은 서로
의존해 개별로는 빌드되지 않아 머지 커밋에 함께 담는다.

API 레이어 통합
- lib/api/http.ts 제거. campaigns·notices·inquiries·notifications·adminAuth를
  client.ts(토큰 재발급·서버 에러코드 매핑 보유) 위로 포팅
- client.ts에 쿼리 파라미터·토큰 오버라이드·PageResponse·multipart 업로드 추가
- errors.ts 코드 유니온을 인증 외 도메인까지 확장(NOT_FOUND/FORBIDDEN/SERVER)
- 관리자 세션은 apiRequest의 token 옵션으로 사용자 세션과 분리
- 프론트는 같은 오리진 '/api'로만 호출하고 Vite 프록시가 백엔드로 넘긴다

신청 퍼널 연결
- applyUrl을 타입·API·관리자 폼·상세 CTA까지 전 구간 연결. 그동안 '#'로
  하드코딩돼 공고를 찾아도 신청할 방법이 없었다

고정 날짜 제거
- TODAY_ISO를 실제 오늘로 전환. 서버가 daysRemaining을 내려주는 지금은
  캘린더가 항상 2026-05에서 열리고 신규 공고 기본 날짜가 어긋났다

로딩·에러 상태
- useAsyncData 도입. 조회 중 "결과 없음"이 먼저 보이던 문제를 스켈레톤으로 교체
- 상세는 목록 캐시 대신 단건 조회. 링크를 직접 열면 "존재하지 않는 공고"가
  뜨던 문제를 없애고, 404와 조회 실패를 구분해 표시

서버사이드 검색·페이지네이션
- 검색·필터·정렬을 서버로 넘기고 '더 보기'로 페이지를 잇는다. 앞 100건만 받아
  클라이언트에서 거르던 방식은 공고가 늘면 결과가 조용히 잘렸다
- 캘린더는 보고 있는 달만 별도 조회

공고 상세 정보 확장
- 모집 인원·제공 형태·서평 의무 채널·제출 기한·신청 자격 추가

서평 제출·인증 루프
- 참여자가 원문 링크로 서평을 제출하고 관리자가 승인/반려. 기존 리뷰 관리는
  localStorage에만 쌓여 사용자 화면에 전혀 노출되지 않았다

백오피스 확장
- 공지사항 작성·수정, 1:1 문의 답변 화면 추가(둘 다 받을 곳이 없었다)
- 검수 큐(draft) ↔ 게시(published) 탭, 모집 상태 수동 전환
- 표지 이미지를 data URL 대신 실제 업로드로 교체

알림
- 스텁이던 알림 큐를 실제 API로 연결(조회·읽음·모두 읽음)

크롤링 대비
- source·sourceUrl·collectedAt·publishStatus와 중복 감지 키를 지금 넣어둔다.
  수집기가 붙으면 draft로 들어와 같은 검수 화면을 그대로 쓴다

정리
- 아무 데서도 import되지 않던 mockRecruitments·mockNotices 삭제
- 캘린더 축에 발표일 추가
/me 응답에 emailSubscribed가 있는데 회원가입은 false로 고정해 보내고,
켤 수 있는 화면도 없었다.

- 회원가입에 선택 동의 체크박스 추가
- 알림 설정에 이메일 수신 토글 추가 (PATCH /me/email-subscription)
- 알림 구독 저장이 비동기로 바뀐 뒤에도 성공 토스트가 무조건 떴던 것을
  실패 시 편집 모드를 유지하도록 수정
공고 링크는 카톡·인스타로 퍼지는 게 주 유입 경로인데 index.html에 title
하나뿐이라 공유하면 미리보기가 백지로 떴다.

- index.html에 description·OG·트위터 카드 기본값 추가
- usePageMeta로 공고·공지 상세에서 제목·설명·이미지·canonical을 갱신
  (표지 이미지가 있을 때만 og:image를 걸어 깨진 미리보기를 피한다)
- 상세 페이지에 공유 버튼 추가 (모바일은 OS 공유 시트, 그 외 링크 복사)
- robots.txt 추가. 백오피스·개인 화면은 색인 제외
- vercel.json에 SPA rewrite 추가. 이게 없으면 /recruitments/:id를 직접 열거나
  새로고침할 때 404가 난다

크롤러가 자바스크립트를 실행하지 않으면 index.html 기본값만 읽으므로,
공고별 미리보기를 정확히 맞추려면 상세 경로에 프리렌더가 추가로 필요하다.
출판사는 이미 필터·알림 구독의 축이고 /campaigns/publishers 엔드포인트도
있는데, "이 출판사의 다른 모집"으로 이어지는 화면이 없었다.

- /publishers/:name 라우트 추가. 서버 검색에 publisher 파라미터를 얹어
  해당 출판사 공고만 정렬·페이지네이션한다
- 공고 상세의 출판사 이름을 이 페이지로 링크
- 보드에 '즐겨찾기만' 토글 추가. 켜면 즐겨찾기 목록을 통째로 받아 캘린더와
  목록을 같은 데이터로 그린다(개인의 유한한 목록이라 클라이언트에서 걸러도
  잘려나갈 일이 없다). 이 상태에서는 '더 보기'를 숨긴다
- 공지 카테고리 필터를 서버로 넘기고, 목록에 스켈레톤·에러 상태 추가
CLAUDE.md와 AGENTS.md가 여전히 "No backend — everything is mocked"라고
설명하고 있었다. 이 문서를 읽는 사람도, 에이전트도 계속 틀린 전제로 작업하게
된다.

- 단일 HTTP 클라이언트·타입드 에러·분리된 관리자 세션 설명으로 교체
- useAsyncData 기반 조회 상태와 "세 가지 상태를 모두 그린다" 규칙 명시
- 서버사이드 검색이 원칙이고 클라이언트 필터는 유한한 목록 전용임을 명시
- react-hooks v7에서 에러가 되는 두 패턴(effect 내 동기 setState, 렌더 중
  ref 쓰기)과 이 코드베이스의 회피 방법 기록
- README에 신청·서평·출판사·백오피스 확장과 크롤링 대비 스키마 반영
- .claude/launch.json 추가 (5199 고정 포트)
- 쓰이지 않는 EMPTY_PAGE·hasActiveFilter 제거
디자인 통일
- PageContainer(narrow/default/wide) 하나로 페이지 폭·여백을 통일.
  기존에는 화면마다 max-w가 2xl~7xl로 제각각이었다
- PageHeader로 제목 영역을 통일. h1이 있는 화면과 없는 화면이 섞여 있었다
- Button과 ButtonLink가 buttonStyles를 공유. 404·빈 상태의 오렌지 버튼을
  페이지마다 직접 만들어 크기와 radius가 달랐다
- Section이 혼자 gray 계열을 쓰던 것을 stone으로 맞춤
- 카드 radius 규칙 정리: 카드 rounded-xl, 패널·섹션 rounded-2xl

반응형
- 캘린더: md 미만에서는 제목 미리보기 대신 유형별 점과 건수만 보여주는
  compact 셀로 전환. 7열 × 150px 그리드는 모바일에서 쓸 수 없었다
- 캘린더 기준 탭은 좁은 화면에서 짧은 라벨 사용
- 백오피스: 사이드바가 숨는 모바일에 상단 탭 내비게이션 추가.
  그동안 모바일에서는 백오피스 내비게이션이 아예 없었다
- 관리자 목록·폼, 마이페이지 탭, 문의/공지 탭을 모바일에서 쌓이거나
  가로 스크롤되도록 조정 (scrollbar-none 유틸 추가)
- 공고 상세 헤더를 제목과 액션 두 줄로 분리
- 기본 여백을 px-4 → sm:px-6으로

정보 구조
- 공지사항을 헤더 '보드' 옆에서 푸터 '고객지원'으로 이동. 공지·문의는
  서비스를 쓰는 흐름이 아니라 도움말에 가깝다
- Footer 신설 (둘러보기 / 고객지원 / 저작권). 모바일 메뉴에도 함께 노출
- 마이페이지에 탭 내비게이션 추가. 하위 화면으로 가는 길이
  사용자 메뉴 드롭다운뿐이었다
PageContainer/PageHeader/ButtonLink를 쓰는 이유와, 팔레트·radius·가로
스크롤 탭·캘린더 두 가지 모드 같은 규칙을 적어둔다. 다음에 화면을 추가할 때
또 제각각이 되지 않도록.
한국어 라벨을 값으로 쓰다가 categories=IT/개발 쿼리가 서버 enum 변환에 실패했다.
값 공간을 CampaignCategory와 1:1로 맞추고, 화면 문구는 CATEGORY_LABELS로만 만든다.
홈 카테고리 타일은 검색어가 아니라 카테고리 필터로 보드를 연다.
- 마감 필터를 withinDays에서 deadline=WEEK|IMMINENT로 교체
- 프로필 수정을 PATCH /me로, 이미지 업로드를 관리자 엔드포인트와 관리자 토큰으로
- 백엔드 ErrorCode 전체를 매핑하고 CONFLICT를 신설. 409 폴백이 EMAIL_EXISTS라
  중복 즐겨찾기에 회원가입 문구가 떴다
- 홈 카테고리 건수를 집계 엔드포인트 한 번으로
신청은 외부 폼에서 일어나 서비스가 결과를 알 수 없다. 사용자가 직접 남긴 표시를
기준으로 마이페이지에서 발표일과 서평 제출 기한을 챙겨준다. 결과(당첨/미당첨)도
자기 신고값이다.

공고의 마감·발표·서평 마감을 .ics와 구글 캘린더로 내보낸다. 하나만 놓쳐도 참여가
끝나는데 지금은 그 날짜가 서비스 안에만 있다. 하루 전 알림을 기본으로 넣는다.
관리자 화면으로 가는 링크가 UI 어디에도 없어 /admin 주소를 아는 사람만 들어갈 수 있었다.
role이 ADMIN인 사용자에게 사용자 메뉴에 관리자 페이지를 노출한다.

관리자 로그인도 같은 /login을 호출하고 역할만 확인하므로, 이미 관리자로 로그인한
사용자는 재로그인 없이 그 세션을 이어받는다. 토큰은 복사하지 않는다 - 복사해 넘기면
그 요청이 401에도 /reissue 재발급을 타지 않아 사용자 토큰 갱신 시 낡는다.
세션을 이어받은 경우 끊을 관리자 세션이 없으므로 로그아웃 대신 서비스로 나가기를 둔다.

관리자 수정 화면이 공개 API를 써서 검수 대기 공고를 열 수 없었고 조회수까지 올렸다.
관리자 엔드포인트로 교체한다.
토글 성공 경로에 토스트가 아예 없었다. 실패 문구만 있었다.

즐겨찾기 ID 집합과 공고 목록이 서로 다른 저장소인데 토글이 집합만 갱신해서,
마이페이지에서 별을 누르고 즐겨찾기 탭으로 넘어가도 마운트 때 받은 목록이 그대로
남아 새로고침해야 보였다. 스냅샷에 version을 두고 목록 조회 deps에 넣는다.
version은 서버 반영 후에 올린다 - 낙관적 갱신 시점에 다시 받으면 아직 반영 전인
응답이 와서 방금 누른 공고가 빠진다.

조회 실패 시 catch가 없어 unhandled rejection이 터지던 것도 함께 막는다.
달력과 전체 목록을 한 화면에 쌓아 두니 빈 달에는 첫 화면이 통째로 빈 격자였다.
/calendar는 그 달만 다루고 날짜를 고르면 그 날로 좁힌다. /board는 달력 없이
검색·필터·정렬과 더 보기만 둔다. 기존 /board 링크가 전부 공고 보러 가기 성격이라
목록을 /board에 남겨 리다이렉트를 피했다.

목록은 카드 그리드 대신 한 줄에 하나씩 쌓아, 출판사·제목·도서·모집 기간을 같은
자리에서 위아래로 훑게 한다. 달력은 카드를 그대로 쓴다.

두 화면이 같은 조건 UI를 쓰므로 검색·필터 상태와 툴바를 공유 조각으로 뺐다.
헤더 내비게이션이 이미 어느 화면인지 알려주는데 제목과 설명이 첫 화면 위쪽을
그대로 차지했다. 뒤로가기나 버튼을 품은 헤더(상세·출판사·백오피스)는 남긴다.
nginx 정적 서빙 이미지와 GHCR 푸시·SSH 배포 워크플로.
빌드 시 API 절대 주소를 넣지 않아 같은 오리진 /api를 유지한다.
공고·서평 탭 네 개 중 두 곳에서만 검색·정렬 툴바가 나타났다 사라져 탭을 옮길 때마다
화면 구성이 바뀌었다. 최근 본 공고는 최대 50건, 즐겨찾기도 개인의 유한한 목록이라
검색·정렬·페이징을 걷어내고 네 탭을 같은 구조로 맞췄다. 목록 표현도 보드와 같은
행 형식으로 통일했다.

계정 관리는 바꿀 수 없는 이메일이 비활성 입력칸으로 폼 자리를 차지하고 있어
상단 계정 요약으로 옮기고, 가끔 쓰는 비밀번호 변경은 접어 두었다.
공고는 인스타그램 등에서 수집해 오고 신청도 외부 폼으로 나가는데, 책 서평까지 여기에
옮겨 적을 이유가 없다. 원본은 블로그·예스24에 있고 아무도 그걸 읽으러 오지 않는다.
반면 '발표가 공고대로 났는지, 책이 언제 왔는지, 조건이 달랐는지'는 어디에도 남지 않고
이 서비스만 쌓을 수 있다. 별점의 대상을 책에서 모집 진행으로 옮긴다.

- 서평 등록 -> 참여 후기, 별점 -> 모집 만족도
- 서평 원문 링크를 선택으로 두고 본문보다 아래로 내린다
- submissionStatus는 노출 게이트가 아니라 원문 확인 표시가 됐다.
  공개 목록에는 '서평 확인됨'만 배지로 달고 기본값인 '확인 전'은 알리지 않는다
- 마이페이지 '내 서평' -> '내 후기', 반려 시 숨겨진다는 안내 추가

공고의 서평 의무(제출 기한·등록 채널)는 실제 계약이라 문구를 그대로 둔다.
@pug9483 pug9483 self-assigned this Aug 30, 2026
@pug9483 pug9483 added feature 새로운 기능 추가 bug 버그 수정 및 오류 대응 refactor 기능 변경 없는 코드 구조 개선 chore 설정, 빌드, 패키지, 환경 변수 등 기타 작업 docs 문서 작성 및 수정 labels Aug 30, 2026
@pug9483
pug9483 merged commit 2ed4b61 into dev Aug 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug 버그 수정 및 오류 대응 chore 설정, 빌드, 패키지, 환경 변수 등 기타 작업 docs 문서 작성 및 수정 feature 새로운 기능 추가 refactor 기능 변경 없는 코드 구조 개선

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 실제 로그인 연동 (Spring 백엔드 API 연결)

1 participant