Skip to content

[Feat/#105] 학생회비 계좌 송금 플로우 화면 구현 - #107

Merged
xeoxxn merged 22 commits into
mainfrom
feat/#105-fee-transfer-flow
Oct 29, 2026
Merged

xeoxxn merged 22 commits into
mainfrom
feat/#105-fee-transfer-flow

Conversation

@xeoxxn

@xeoxxn xeoxxn commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

#️⃣연관된 이슈

🎯 해결하려는 문제가 무엇인가요?

홈의 "학생회비 납부하기"(3147:146285)를 눌렀을 때 갈 화면이 코드에 하나도 없었다. Figma에는 계좌 송금 플로우가 9개 프레임으로 그려져 있다.

PG 결제가 아니라 토스 딥링크로 송금 → 학생회가 3일 이내 수동 확인하는 방식이고, 남은 학기 수에 따라 납부 금액이 달라져서 학점이수현황 캡처를 함께 제출받는다.

구현 화면

❓ 왜 해결해야 하나요?

학생회비 납부는 홈 진입점이 이미 디자인에 있는 주요 플로우인데, 눌러도 갈 데가 없다.

⭐ 어떻게 해결했나요?

라우트 하나(/fee/transfer) + useFunnel 퍼널 하나로 묶었다. 단계는 주소의 ?fee.step=으로 나뉜다 — 로그인·온보딩 퍼널(AuthSignupScreen, /auth)과 같은 구성이다.

?fee.step 없음(진입)   남은 학기 수          3562:162833
?fee.step=transcript   학점이수현황 업로드    3562:162845 · 3562:162891
?fee.step=toss         토스 이동 안내        3562:163025 · 3562:163046
?fee.step=confirm      송금하셨나요?         3562:162973
?fee.step=complete     확인요청 완료         3562:163013
features/fee/
  FeeTransferScreen.tsx              퍼널 본체 — 단계 전환만 맡는다
  constants/feeTransferFunnel.ts     단계·context 타입 + guard
  steps/FeeTransfer{Semester,Transcript,Toss,Confirm,Complete}Step.tsx
  components/FeeTransferStepLayout.tsx
  • 단계 사이 값은 퍼널 context가 들고 간다. 남은 학기 수와 캡처 이미지가 플로우 끝까지 이어지고, 토스 단계에서 뒤로 돌아와도 입력이 남는다. 학기 수 입력·파일 선택은 단계를 바꾸지 않고 history.replace로 context만 갈아 끼운다(히스토리가 늘지 않음).
  • 앞 단계 값 없이 중간 단계로 들어오면 guard가 첫 단계로 돌려보낸다. 새로고침·주소 직접 입력에도 학기 수나 캡처 없이 토스 화면이 열리지 않는다.
  • FeeTransferStepLayout으로 네 단계를 묶었다. 전부 뒤로가기 헤더 → 12px → (일러스트) 제목·설명 → 본문 → 하단 고정 버튼 구조가 같고 간격과 문구만 다르다. 간격은 Figma가 화면마다 8/12/16으로 다르게 잡아둬서 contentGap 유니온 타입으로 받는다. 뒤로가기는 onBack prop으로 받아서 퍼널이 단계에 맞는 동작을 넘긴다.
  • 업로드 전/후와 토스 안내/실패는 퍼널 단계로 올리지 않았다. 각각 한 단계 안의 상태 차이고, 뒤로가기로 돌아갈 지점이 아니다.
  • 확인요청 완료는 공용 ResultScreen을 쓴다. 체크 모션이 신청 완료 화면들과 같은 Figma 메인 컴포넌트(Circle Check Motion 1712:192849)의 인스턴스라 CompleteCheck를 그대로 재사용했다. 뒤로가기(←)가 아니라 X로 닫는 화면이라 이 단계만 FeeTransferStepLayout을 쓰지 않는다.
  • K-스마트멘토 이동 확인 모달도 공용 ConfirmModal이다. Figma 인스턴스(3562:162972)가 기존 Modal(995:34701)과 같은 311×190이다.
  • 이미지 미리보기는 단계가 아니라 포털 오버레이다. 뒤로가기로 업로드 단계가 사라지면 안 된다.
  • 외부 링크는 lib/openExternalLink로 모았다. 단계 화면은 어디로 어떻게 여는지 모르고 onOpenGuide·onOpenToss만 부른다. 앱 WebView에서 시스템 브라우저로 넘기는 브리지 분기도 이 자리에 들어간다.
  • 캡처 이미지는 File을 context에 그대로 담는다. 구조화 복제가 되는 값이라 history.state에 실린다. guard가 instanceof File로 확인해서, 되살아나지 못하면 첫 단계로 돌아간다.

라우트를 새로 정한 근거

화면설계서(687:20584)에는 경로 컬럼이 아예 없다. 7개 페이지 텍스트 노드 약 19,000개를 /경로 패턴으로 훑어도 라우트 문자열이 0건이다. 그래서 용어 사전(terminology.md)의 회비 납부 → fee와 "프론트 라우트 경로: /{코드 용어}" 규칙으로 정했다.

🧩 이 PR의 한계 & 트레이드오프

  • 외부 연동 주소가 전부 비어 있다 — 토스 송금 딥링크, K-스마트멘토 주소. FEE_EXTERNAL_LINKS 상수에 빈 문자열로 두고 값이 있을 때만 열도록 했다(빈 창이 뜨지 않는다). 토스 딥링크는 학생회 계좌와 납부 금액 규칙(남은 학기 수에 따라 달라진다)이 정해져야 만들 수 있다.
  • 업로드 API가 없다. 고른 파일을 퍼널 context에 담아 두기만 하고, 미리보기는 objectURL로 띄운다(언마운트 시 revokeObjectURL). 연동할 때 FeeTransferTranscriptStep에서 업로드 요청을 보내면 된다. 그때는 File 대신 서버가 준 식별자를 context에 담는 쪽이 안전하다 — history.state는 브라우저마다 크기 상한이 있다(Firefox 16MiB).
  • 토스가 열렸는지 알 수 없다. 딥링크 특성상 브라우저가 결과를 안 알려준다. 그래서 열기만 하고 송금 확인 단계로 넘긴다 — 송금하지 못했으면 그 단계의 "다음에 할게요"로 빠져나갈 수 있다. 이동 실패 화면은 링크 주소가 아직 없을 때만 띄운다. 앱 WebView에서 열기가 막히는 경우까지 가려내려면 네이티브 브리지로 결과를 받아야 한다.
  • 남은 학기 수 → 납부 금액 계산이 없다. 규칙을 못 받아서 context에 학기 수만 담아 둔다.
  • 남은 학기 수 검증을 1~99로 뒀다. 실제 학적 기준이 있으면 맞춰야 한다.

⛓️ 기존 기능에 미치는 영향

  • router.tsx에 라우트가 1개 추가됐다(/fee/transfer).
  • ConfirmModal·ResultScreen 설명에 whitespace-pre-line을 붙여 \n을 살렸다. 기존 사용처(행사·사물함 결과 화면, 빌릴게·열린피드백 모달)는 모두 한 줄 문구라 렌더링 변화가 없다(description=에 \n을 쓰는 곳이 0건인 것을 grep으로 확인).
  • index.css에 --color-blue-99 토큰이 하나 늘었다. 기존 토큰은 건드리지 않았다.
  • lib/useFunnel.ts에 useFunnelExit을 추가했다. 로그인·가입 퍼널도 같은 문제(끝내고 나간 뒤 뒤로가기로 단계 복귀)가 있어서 이어서 쓸 수 있지만, 이 PR에서는 계좌 송금만 쓴다.
  • lib/openExternalLink.ts를 새로 만들고 납부내역 화면의 카카오 채널 열기도 여기로 바꿨다. window.open 직접 호출은 이제 이 파일 하나뿐이다.
  • HomeInfoList의 미납 상태 "학생회비 납부하기" 줄에 onSelect를 연결했다(#117이 남긴 TODO 해소). 나머지 줄은 그대로다.
  • main을 머지했다 — features/fee/constants/fee.ts가 add/add로 충돌해서 양쪽을 합치고, 카카오 채널 주소도 외부 연동 주소라 FEE_EXTERNAL_LINKS로 묶었다.

🔀 Edge Case & 실패 시나리오

  • 남은 학기 수에 문자를 섞어 쳐도 숫자만 걸러 두 자리까지만 받는다. 비었거나 0이면 "다음"이 비활성이다.
  • 같은 파일을 연속으로 다시 고를 수 있다(input.value를 비워서 change가 다시 터지게 했다).
  • 업로드한 이미지를 지우면 "다음"이 다시 비활성으로 돌아간다.
  • 중간 단계 주소로 바로 들어오거나 새로고침하면 guard가 첫 단계로 돌려보낸다.
  • 토스 링크가 비어 있으면 이동 실패 화면으로 간다.
  • 미리보기는 Esc로도 닫히고, 열릴 때 초점이 닫기 버튼으로 옮겨가고 닫히면 파일 행으로 돌아온다.
  • 플로우로 바로 들어온 경우(딥링크·앱 WebView 진입·주소 직접 입력) 첫 단계의 뒤로가기는 홈으로 보낸다(useBackNavigation).
  • 플로우를 끝내거나 "다음에 할게요"로 나갈 때는 쌓인 단계 수만큼 되돌아간다(useFunnelExit) — 나간 뒤 뒤로가기를 눌러도 단계가 되살아나지 않는다.

📋 검토한 대안과 선택 이유

  • 처음에는 단계마다 라우트를 따로 뒀다(/fee/transfer/semester 등 5개). 그러면 단계 사이 값을 navigate(..., { state })로 넘겨야 해서 남은 학기 수가 캡처 단계에서 읽히지 않고 고른 파일도 다음 단계로 넘어가지 않았다. 퍼널 context로 바꾸면서 두 값이 플로우 끝까지 이어진다.
  • 업로드 버튼을 WDS Button의 outlined로 대체하는 것도 봤지만 radius·높이가 달라서 Stream 로컬로 만들었다. 파일 행 아이콘도 WDS IconImage는 외곽선 스타일이라 글리프가 달라 Figma 것을 받았다.
  • 카드 테두리를 border로 그리면 box-sizing 때문에 Figma의 335×104 / 303px 행이 333×102 / 301px가 된다 — outline-1 -outline-offset-1로 바꿨다.

💬 리뷰 포인트

  • [c] 일러스트 모션의 반복 설정을 확인 부탁드립니다. Figma는 둘 다 2초 loop인데 1회 재생(loop={false})으로 맞췄습니다 — 기존 완료 체크 모션과 같은 판단입니다. 계속 반복되는 게 의도면 알려주세요.
  • [c] 라우트 경로(/fee/transfer)를 새로 정했습니다. 설계서에 근거가 없어 용어 사전 규칙으로 정한 거라, 백엔드 엔드포인트와 맞는지 확인 부탁드립니다.
  • [c] 납부 금액 규칙과 토스 계좌가 정해지면 알려주세요. 딥링크를 만들려면 둘 다 필요합니다.
  • [a] 리뷰 지적 9건을 반영했습니다(토스 열림 판정, 퍼널 탈출 히스토리, fee.ts 충돌 통합, 첫 단계 뒤로가기, 캡처 파일 단일 출처, 외부 링크 lib 통합, px 임의값 → Tailwind 단계, 미리보기 초점, 홈 진입점 연결). 작업 단위로 커밋을 나눴습니다.
  • [a] 외부 링크를 앱 셸로 넘기는 브리지 메시지는 넣지 않았습니다 — 앱 쪽 핸들러가 같이 들어가야 동작해서(lib/bridge/bridge.ts 메시지 추가 절차) 별도 이슈로 이어가는 게 맞아 보입니다.

xeoxxn and others added 3 commits October 8, 2026 16:33
Figma 설명 문구는 줄바꿈 위치까지 디자인에 포함돼 있는데 `\n`이 공백으로
접혀서 의도와 다른 자리에서 줄이 바뀌었다. 기존 사용처는 모두 한 줄 문구라
렌더링 변화가 없다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
48px 일러스트 둘은 svgAssets가 레이어별로 쪼개져 나와 export(svg)로 받고,
같이 붙어 나온 캔버스 배경 rect와 조상 path를 걷어냈다. 파일 행 아이콘은
WDS IconImage와 글리프가 달라서 Figma 것을 그대로 받았다.

업로드한 파일 행 배경(Atomic/Blue/99)은 기존 primary-subtle(Blue/95)보다
훨씬 옅은 별개 값이라 토큰을 새로 연결한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
홈의 "학생회비 납부하기"에서 시작하는 5단계 송금 플로우다. PG 결제가 아니라
토스 딥링크로 송금하고 학생회가 수동으로 입금을 확인하는 방식이라, 남은 학기
수와 학점이수현황 캡처를 받은 뒤 토스로 보냈다가 돌아와 확인을 받는다.

다섯 화면이 `뒤로가기 헤더 → 12px → (일러스트) 제목·설명 → 본문 → 하단 고정
버튼` 구조가 같고 간격과 문구만 달라서 FeeTransferStepLayout으로 묶었다.
업로드 전/후와 토스 안내/실패는 각각 한 화면의 상태 차이라 라우트를 나누지
않았고, 확인요청 완료 화면은 체크 모션이 신청 완료 화면들과 같은 Figma 메인
컴포넌트라 공용 ResultScreen을 쓴다.

경로는 화면설계서에 표기가 없어 용어 사전의 `회비 납부 → fee`로 정했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b66e0945-3cd9-4d0e-ae06-d07a0351a5cb
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

xeoxxn and others added 6 commits October 8, 2026 16:52
예시 이미지를 1배(280x205)로 받아 고해상도 화면에서 흐리게 보였다. 3배(840x615)로
다시 받고, 280px 그림이 335px 카드 안에 정확히 들어가도록 좌우 여백을 27.5px로 맞춘다.

"캡처 예시" 라벨은 Figma가 Caption 2/Regular(11px)인데 caption1(12px)로 썼었다.

이 그림은 실제 K-스마트멘토 스크린샷 위에 가림 박스와 가짜 값을 얹어 만든 것이라
레이어가 합쳐진 export만 커밋해야 한다. 가림 박스가 전부 불투명해 밑글씨가 비치지
않는 것을 픽셀로 확인했고, 그 전제를 컴포넌트 주석에 남겼다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
supertoss://send 딥링크로 토스를 열고, 앱에 다녀오면 송금 확인 단계로 넘긴다.

브라우저는 커스텀 스킴이 실제로 열렸는지 알려주지 않는다 — window.open은 앱이
없어도 창 객체를 돌려준다. 그래서 "앱으로 전환되면 탭이 가려진다"는 성질을 쓴다:
1.5초 안에 화면이 가려지면 성공으로 보고 복귀를 기다리고, 가려지지 않은 채
타이머가 울리면 이동 실패 화면을 띄운다.

입금 계좌는 저장소가 public이라 코드에 넣지 않고 VITE_FEE_TOSS_* 환경 변수로
받는다(.env는 .gitignore에 있다). 값이 없으면 빈 URL이라 곧바로 실패 화면이 된다.
금액은 남은 학기 수별 규칙을 아직 못 받아서 목업 상수로 보낸다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
실기기에서만 동작을 확인할 수 있어 검증이 안 된 채로 들어가는 걸 피한다.
토스 화면은 다시 "누르면 바로 이동 실패"로 돌아간다.

This reverts commit 7a6cceb.
화면 프레임이 w-full / sm:w-[480px]라 카드 폭이 변하는데 카드 높이를 375 기준
248px로 박고 그림을 w-full로 둬서, 폭이 넓어지면 그림이 같이 커져 아래가 잘렸다
(430px에서 40px, 480px 프레임에서 77px). 반대로 좁으면 아래에 빈 공간이 생겼다.

높이를 내용이 정하게 두고 그림은 Figma 크기인 280px로 고정한다 — 카드가 더 좁으면
preflight의 max-width:100%가 줄여준다. 설계 폭에서는 그대로 248px이 나온다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
get_motion_context를 다섯 프레임 전부 돌려보니 ₩ 아이콘(3562:163025)에도 Figma
Motion이 있었다. 송금 확인 화면만 확인하고 넘어가 놓쳤던 것이다.

둘 다 Lottie를 받아야 붙일 수 있어 지금은 정지 SVG다 — 어떤 키프레임이 빠져
있는지 주석에 남겨서 재export 때 무엇을 받아야 하는지 알 수 있게 한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
디자이너가 LottieFiles 플러그인으로 내보낸 json을 받아 정지 SVG를 대체한다.
둘 다 48x48 / 60fps / 120프레임(2초)이고 텍스트 레이어는 없다.

재생기는 두 아이콘이 공유한다(FeeLottieIcon). 타임라인은 2초지만 실제 움직임은
앞부분에서 끝나서, 모션 줄이기일 때 세울 프레임을 json에서 직접 확인해 넘긴다 —
₩는 키프레임이 66프레임(1.1초)에서 끝나고, 종이비행기는 플러그인이 이징을 매
프레임으로 구워놔서 값이 실제로 고정되는 49프레임(0.82초)을 찾아야 했다.

브라우저에서 재생도 확인했다. ₩는 path d가 11단계로 변하며 획이 그려지고,
종이비행기는 scale 0.816 → 1.024(overshoot) → 1.0으로 잦아들며 770ms에 멈춘다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	docs/conventions/wds-component-usage.md
단계마다 라우트를 따로 두면 값을 navigate state로 넘겨야 해서 남은 학기 수가
캡처 단계에서 읽히지 않고 고른 파일도 다음 단계로 넘어가지 않았다. 라우트를
/fee/transfer 하나로 합치고 단계는 ?fee.step=으로 나눠, 두 값을 퍼널 context가
플로우 끝까지 들고 가게 했다. 로그인·가입 퍼널과 같은 구성이다.
Comment thread src/features/fee/FeeTransferScreen.tsx Resolved
Comment thread src/features/fee/FeeTransferScreen.tsx Resolved
Comment thread src/features/fee/constants/fee.ts Resolved
Comment thread src/features/fee/constants/fee.ts Resolved
Comment thread src/app/router.tsx Resolved
fee.ts가 add/add로 충돌해서 양쪽을 합쳤다. main의 카카오 채널 주소도 외부 연동
주소라 FEE_EXTERNAL_LINKS로 묶었다(#107 리뷰).
window.open에 noopener를 주면 스펙상 반환값이 항상 null이라, 토스 링크를 채우면
실제로 열려도 매번 실패 화면이 떴다. 반환값 판정을 버리고 열기만 한 뒤 송금 확인
단계로 넘긴다 — 실패 화면은 링크 주소가 없을 때만 띄운다.

플로우를 끝내고 홈으로 나간 뒤 뒤로가기를 누르면 중간 단계가 context째 되살아나서
송금 확인을 다시 요청할 수 있었다. lib/useFunnel에 useFunnelExit을 두고 쌓인 단계
수만큼 되돌아가게 했다. "다음에 할게요"도 같은 경로를 쓴다.
토스 딥링크·K-스마트멘토·학생회 카카오 채널이 각자 window.open을 부르고 있었다.
lib/openExternalLink로 모아서 여는 방식이 바뀔 때 한곳만 고치면 되게 했다 —
앱 WebView에서 시스템 브라우저로 넘기는 브리지 분기도 이 자리에 들어간다.

단계 화면은 어디로 어떻게 여는지 모르고 onOpenGuide·onOpenToss만 부른다.
토스 단계는 링크 유무를 hasTossLink로 받아 실패 화면만 판단한다.
고른 캡처를 단계의 로컬 state와 퍼널 context에 같이 두고 있었다. context를
갈아 끼우면 화면이 새 값으로 다시 그려지니 로컬 state가 필요 없다 — 진실의
출처를 하나로 줄여서 나중에 퍼널 밖에서 context를 바꿔도 어긋나지 않는다.
첫 단계의 뒤로가기가 navigate(-1)이라, 딥링크·앱 WebView 진입·주소 직접
입력으로 들어오면 돌아갈 기록이 없어 아무 일도 하지 않았다. 다른 화면들과
같이 useBackNavigation으로 바꿔서 그런 경우 홈으로 보낸다.
컴포넌트 컨벤션의 "Tailwind 단계 → 토큰 → 구조 → 주석 달린 예외" 순서를
따른다. h-[46px]→h-11.5, size-[18px]→size-4.5, gap-[13px]→gap-3.25,
w-[280px]→w-70. rounded-[10px]만 단계가 없어서 임의값으로 남기고 주석을 달았다.
오버레이가 떠 있는 동안 초점이 뒤 화면에 그대로 있어서, 키보드·스크린리더로는
가려진 화면을 계속 조작할 수 있었다. 열릴 때 닫기 버튼으로 초점을 옮기고
닫힐 때 미리보기를 연 파일 행으로 되돌린다.
미납 상태의 "학생회비 납부하기" 줄이 누를 수 없는 채로 있었다(화면이 없어서
남겨 둔 TODO). 계좌 송금 플로우가 들어왔으니 onSelect로 연결한다.
@tnals0924
tnals0924 self-requested a review October 28, 2026 08:40
openExternalLink와 ConfirmModal의 줄바꿈 주석을 #96 브랜치와 같은 문구로 둔다.
기능은 양쪽이 이미 같고 예시로 든 Figma 노드만 달라서, 먼저 머지되는 쪽이
무엇이든 나머지 한쪽이 충돌 없이 들어온다.
설정 화면(#97)이 머지되면서 wds-component-usage.md가 충돌했다. 양쪽이 문서 끝에
각자 섹션을 덧붙인 것이라 둘 다 살렸다(설정 화면 섹션 뒤에 계좌 송금 섹션).

openExternalLink·ConfirmModal은 미리 같은 문구로 맞춰둬서 충돌 없이 합쳐졌다.
@xeoxxn
xeoxxn merged commit f6cc58c into main Oct 29, 2026
1 check passed
@xeoxxn
xeoxxn deleted the feat/#105-fee-transfer-flow branch October 29, 2026 03:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

학생회비 계좌 송금 플로우 화면 구현

2 participants