Repository navigation
Conversation
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>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
예시 이미지를 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가 플로우 끝까지 들고 가게 했다. 로그인·가입 퍼널과 같은 구성이다.
| setFailed(true); | ||
| return; | ||
| } | ||
| const opened = window.open( |
There was a problem hiding this comment.
[r] window.open에 "noopener"를 넘기면 HTML 스펙상 반환값이 항상 null이라, 토스 링크를 채우고 나면 실제로 토스가 열려도 opened가 null이 돼서 매번 실패 화면이 뜰 것 같습니다. 지금은 링크가 비어 있어서 드러나지 않지만, 링크를 넣는 순간 송금 확인 단계로 넘어갈 방법이 없어지는 것 같아요.
PR 한계에 적어주신 것처럼 딥링크는 반환값으로 열렸는지 알 수도 없어서, 반환값으로 판정하는 대신 아래 중 하나로 가면 어떨까요?
- 링크가 있으면 열고 바로 송금 확인 단계로 넘기기. 확인 단계에 이미 "다음에 할게요"가 있어서 송금하지 못했을 때도 빠져나갈 수 있고, 실패 화면은 링크가 없을 때만 띄웁니다.
- 앱 WebView에서는
lib/bridge로 네이티브에 열기를 맡기고, 열렸는지 결과를 받아서 판정하기
if (!FEE_EXTERNAL_LINKS.tossTransfer) {
setFailed(true);
return;
}
window.open(FEE_EXTERNAL_LINKS.tossTransfer, "_blank", "noopener");
onTransferred();| steps: FEE_TRANSFER_STEP_OPTIONS, | ||
| }); | ||
|
|
||
| const goHome = () => navigate("/", { replace: true }); |
There was a problem hiding this comment.
[c] 플로우를 끝내고 홈으로 나간 뒤 브라우저 뒤로가기를 누르면 퍼널 단계로 다시 들어오는 것 같습니다.
홈에서 들어와 끝까지 진행하면 히스토리가 [홈, 학기, 캡처, 토스, 확인]으로 쌓이고, 확인에서 replace("complete") → goHome(replace("/"))을 거치면 [홈, 학기, 캡처, 토스, 홈]이 됩니다. 여기서 뒤로가기를 누르면 토스 단계가 학기 수·파일 context째로 다시 열려서, 송금 확인을 이미 요청한 뒤에도 같은 흐름을 다시 탈 수 있을 것 같아요. 48번째 줄의 "다음에 할게요"(navigate("/"), push)도 뒤로가기를 누르면 송금 확인 단계로 돌아옵니다.
퍼널에 쌓인 단계 수만큼 되돌아간 뒤 나가고, 바로 들어와서 돌아갈 기록이 없으면 replace("/")로 보내는 식이면 어떨까요? AuthSignupScreen도 goHome = navigate("/", { replace: true })로 같은 구조라, lib/useFunnel 쪽에 퍼널을 나가는 공통 함수를 두면 두 퍼널이 같이 쓸 수 있을 것 같습니다.
| onChangeTranscript(nextFile ?? undefined); | ||
| }; | ||
|
|
||
| const handleGuideConfirm = () => { |
There was a problem hiding this comment.
[c] 외부 링크를 여는 로직이 단계 컴포넌트 안에 들어 있는 것 같습니다(FeeTransferTossStep.tsx의 handleOpenToss도 같아요). window.open이 이번 PR에서 두 곳 늘어서, main의 FeePaymentsScreen(카카오 채널)과 #97(설정)까지 합치면 다섯 곳이 되는 것 같아요.
PR 한계에 적어주신 "WebView에서 window.open이 막히거나 앱 안에 갇힐 수 있다"는 문제도 결국 한곳에서 풀어야 할 것 같아서, lib/에 브리지(isInAppShell·postBridgeMessage)를 고려한 함수 하나를 두고 단계 컴포넌트는 onOpenGuide·onOpenToss 같은 콜백만 받게 하면 어떨까요? 그러면 단계 컴포넌트는 화면을 그리는 일만 맡고, 어디로 어떻게 여는지는 FeeTransferScreen이 정할 수 있을 것 같습니다.
| <FeeTransferSemesterStep | ||
| initialSemester={context.semester} | ||
| // 첫 단계라 뒤로가기는 이전 단계가 아니라 플로우 밖(들어온 화면)으로 나간다 | ||
| onBack={() => navigate(-1)} |
There was a problem hiding this comment.
[c] /fee/transfer로 바로 들어온 경우(딥링크·앱 WebView 진입·주소 직접 입력)에는 navigate(-1)로 돌아갈 곳이 없어서 첫 단계의 뒤로가기가 동작하지 않을 것 같습니다. main에 useBackNavigation이 들어와서, rebase하실 때 아래처럼 바꾸면 바로 해결될 것 같아요!
const goBackFromFlow = useBackNavigation("/");
// ...
onBack={goBackFromFlow}| onChangeTranscript: (transcript: File | undefined) => void; | ||
| onNext: (transcript: File) => void; | ||
| }) { | ||
| const [file, setFile] = useState<File | null>(initialTranscript ?? null); |
There was a problem hiding this comment.
[a] 캡처 파일을 로컬 file state와 퍼널 context(49번째 줄의 onChangeTranscript)에 같이 들고 있어서 값이 두 군데에 있는 것 같습니다. history.replace로 context를 바꾸면 화면이 새 context로 다시 그려지니, transcript={context.transcript}처럼 context 하나만 읽으면 initialTranscript와 로컬 state 없이도 될 것 같아요.
남은 학기 수는 입력 중인 문자열("", "0")을 숫자 context로 나타낼 수 없어서 로컬 state가 필요한 게 맞는 것 같습니다!
|
|
||
| return ( | ||
| <label | ||
| className="flex h-[46px] w-full cursor-pointer items-center justify-center gap-1 rounded-[10px] border border-line-normal-neutral" |
There was a problem hiding this comment.
[a] px 임의값이 몇 군데 남아 있는 것 같습니다. 컴포넌트 컨벤션의 "Tailwind 단계 → 토큰 → 예외(주석)" 순서대로 바꾸면 좋을 것 같아요.
- 여기
h-[46px]→h-11.5, 25번째 줄size-[18px]→size-4.5 rounded-[10px]은 Tailwind 단계가 없어서, 어느 Figma 값인지 주석을 남기거나 토큰으로FeeTranscriptGuide.tsx의gap-[13px](20번째 줄) →gap-3.25,w-[280px](34번째 줄) →w-70
| aria-label="업로드한 이미지 미리보기" | ||
| aria-modal="true" | ||
| className="absolute inset-0 flex flex-col bg-background-normal" | ||
| role="dialog" |
There was a problem hiding this comment.
[a] role="dialog"·aria-modal·Esc 닫기는 들어가 있는데, 열릴 때 포커스를 오버레이로 옮기거나 닫힐 때 원래 자리(파일 행 버튼)로 돌려놓는 처리가 없어서, 키보드·스크린리더 사용자는 초점이 뒤 화면에 남을 것 같습니다. 열릴 때 닫기 버튼에 포커스를 주고, 닫힐 때 미리보기를 연 버튼으로 돌려주면 좋을 것 같아요.
| // - kSmartMentor: 학점이수현황을 확인하는 국민대 K-스마트멘토 주소 | ||
| // - tossTransfer: 계좌·금액이 채워진 토스 송금 딥링크. 학생회 계좌와 납부 금액 규칙이 정해져야 | ||
| // 만들 수 있다(남은 학기 수에 따라 금액이 달라진다). | ||
| export const FEE_EXTERNAL_LINKS = { |
There was a problem hiding this comment.
[a] main에도 #113에서 만든 features/fee/constants/fee.ts가 있어서 rebase하면 이 파일이 add/add로 충돌할 것 같습니다. 합치실 때 main의 STUDENT_COUNCIL_KAKAO_CHANNEL_URL도 외부 연동 주소라, FEE_EXTERNAL_LINKS 하나로 묶으면 좋을 것 같아요.
그리고 8번째 줄 주석의 SETTINGS_EXTERNAL_LINKS는 아직 머지되지 않은 #97에만 있어서, 지금 main 기준으로는 찾을 수 없는 이름인 것 같습니다.
| // 용어 사전(terminology.md)의 `회비 납부 → fee`와 "프론트 라우트 경로: /{코드 용어}" 규칙으로 정했다. | ||
| // 단계(남은 학기 수·캡처 업로드·토스 이동·송금 확인·완료)는 이 경로 하나 안에서 useFunnel이 | ||
| // `?fee.step=`으로 나눈다 — 로그인·온보딩 퍼널(`/auth`)과 같은 방식이다. | ||
| export const FEE_TRANSFER_PATH = "/fee/transfer"; |
There was a problem hiding this comment.
[a] #117이 머지되면서 main의 HomeInfoList에 "학생회비 계좌 송금 화면(#105)이 머지되면 onSelect로 연결한다"는 TODO가 남아 있습니다. rebase하시면 이 경로로 onSelect만 이어주면 돼서, 리뷰 포인트에 적어주신 홈 진입점 연결도 이번 PR에 같이 넣어도 될 것 같아요!
| background: "normal", | ||
| hasBottomNav: false, | ||
| } satisfies ScreenRouteHandle, | ||
| path: "/fee/transfer", |
There was a problem hiding this comment.
[a] 마지막 커밋에서 라우트 5개가 /fee/transfer 하나 + useFunnel 퍼널로 바뀌었는데, PR 본문은 아직 이전 구조(라우트 5개, navigate(..., { state })로 학기 수 전달, FeeTransferTranscriptScreen)로 적혀 있는 것 같습니다. 리뷰하는 분들이 헷갈리지 않도록 본문도 지금 구조로 맞춰주시면 감사하겠습니다!
그리고 main이 5커밋 앞서 있어서 이 파일과 fee.ts에서 충돌이 나는 것 같습니다. 라우트 경로 /fee/transfer 자체는 용어 사전 규칙(회비 납부 → fee)에 맞는 것 같아요.
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로 연결한다.
#️⃣연관된 이슈
🎯 해결하려는 문제가 무엇인가요?
홈의 "학생회비 납부하기"(
3147:146285)를 눌렀을 때 갈 화면이 코드에 하나도 없었다. Figma에는 계좌 송금 플로우가 9개 프레임으로 그려져 있다.PG 결제가 아니라 토스 딥링크로 송금 → 학생회가 3일 이내 수동 확인하는 방식이고, 남은 학기 수에 따라 납부 금액이 달라져서 학점이수현황 캡처를 함께 제출받는다.
구현 화면
❓ 왜 해결해야 하나요?
학생회비 납부는 홈 진입점이 이미 디자인에 있는 주요 플로우인데, 눌러도 갈 데가 없다.
⭐ 어떻게 해결했나요?
FeeTransferStepLayout으로 다섯 화면을 묶었다. 전부뒤로가기 헤더 → 12px → (일러스트) 제목·설명 → 본문 → 하단 고정 버튼구조가 같고 간격과 문구만 다르다. 간격은 Figma가 화면마다 8/12/16으로 다르게 잡아둬서contentGap유니온 타입으로 받는다.ResultScreen을 쓴다. 체크 모션이 신청 완료 화면들과 같은 Figma 메인 컴포넌트(Circle Check Motion 1712:192849)의 인스턴스라CompleteCheck를 그대로 재사용했다.ConfirmModal이다. Figma 인스턴스(3562:162972)가 기존Modal(995:34701)과 같은 311×190이다.라우트를 새로 정한 근거
화면설계서(
687:20584)에는 경로 컬럼이 아예 없다. 7개 페이지 텍스트 노드 약 19,000개를/경로패턴으로 훑어도 라우트 문자열이 0건이다. 그래서 용어 사전(terminology.md)의회비 납부 → fee와 "프론트 라우트 경로:/{코드 용어}" 규칙으로 정했다.🧩 이 PR의 한계 & 트레이드오프
FEE_EXTERNAL_LINKS상수에 빈 문자열로 두고 값이 있을 때만 열도록 했다(빈 창이 뜨지 않는다). 토스 딥링크는 학생회 계좌와 납부 금액 규칙(남은 학기 수에 따라 달라진다)이 정해져야 만들 수 있다.objectURL로 띄운다(언마운트 시revokeObjectURL). 연동 때FeeTransferTranscriptScreen에서 업로드 요청을 보내면 된다.navigate(..., { state })로 넘기지만 받는 쪽이 없다. API가 붙으면 그 자리에서 쓴다.window.open반환값으로만 판정한다. 실패 화면은 그 경우와 링크가 비어 있는 경우에 뜬다.1~99로 뒀다. 실제 학적 기준이 있으면 맞춰야 한다.⛓️ 기존 기능에 미치는 영향
ConfirmModal·ResultScreen설명에whitespace-pre-line을 붙여\n을 살렸다. 기존 사용처(행사·사물함 결과 화면, 빌릴게·열린피드백 모달)는 모두 한 줄 문구라 렌더링 변화가 없다(description=에\n을 쓰는 곳이 0건인 것을 grep으로 확인).index.css에--color-blue-99토큰이 하나 늘었다. 기존 토큰은 건드리지 않았다.router.tsx에 라우트 5개가 추가됐다.🔀 Edge Case & 실패 시나리오
0이면 "다음"이 비활성이다.input.value를 비워서change가 다시 터지게 했다).📋 검토한 대안과 선택 이유
/fee/transfer하나에서 step state)도 검토했지만, 뒤로가기를 직접 다뤄야 하고 중간 화면을 열어볼 수 없어서 단계별 라우트로 갔다.Button의 outlined로 대체하는 것도 봤지만 radius·높이가 달라서 Stream 로컬로 만들었다. 파일 행 아이콘도 WDSIconImage는 외곽선 스타일이라 글리프가 달라 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]납부내역 화면 3종(3147:147841/3147:147860/3147:147879)과 홈 진입점·상태 뱃지는 이 PR에서 뺐습니다. 별도 이슈로 이어서 하겠습니다.