# 선물 상세 및 운송장 등록 페이지 생성 프롬프트 이 문서는 Android/iOS 앱의 `선물함` 리스트에서 진입하는 선물 상세 페이지와 운송장 등록 페이지를 생성할 때 사용하는 프롬프트다. 참고 Figma: - 선물 보내기 신청 상세 운송장 등록 전 UI: `2481:19011` - 선물 보내기 신청 상세 운송장 등록 후 UI: `2481:19152` - 선물 보내기 신청 상세 전달 완료 UI: `2481:19320` - 전달 불가 UI: `2481:19456` - 취소 버튼 다이얼로그 UI: `2481:19500` - 취소 완료 페이지 UI: `2481:19521` - 운송장 등록 UI: `2481:19066` - 운송장 정보를 채우고 난 후 UI: `2481:19109` 전제: - 선물함 리스트 item 탭 시 `applicationNo`를 전달받아 상세 페이지로 이동한다. - 새 디자인 시스템을 만들지 말고 Android/iOS 각각의 기존 디자인 시스템, 컴포넌트, API client, 상태 관리, toast/dialog, navigation 패턴을 재사용한다. - 모든 API 응답은 공통 envelope를 사용하며 실제 payload는 `response.data`다. - Figma의 진행 상태는 4단계처럼 보이지만 실제 구현은 서버의 `statusTimeline` 5개 상태를 모두 사용한다. - 운송장 등록의 택배사 선택지는 서버 API가 없으므로 앱 로컬 상수로 관리한다. - 취소 확인 팝업은 앱의 `v2 dialog` 컴포넌트를 기본으로 사용한다. --- ## 모바일 앱 구현 프롬프트 ```text Android/iOS 앱의 선물 상세 페이지와 운송장 등록 페이지를 Figma 기준으로 생성하고 API 연동을 구현해줘. 범위: - 선물함 리스트에서 `applicationNo`를 받아 선물 상세 페이지로 이동한다. - 상세 페이지는 `GET /api/v2/gifts/{applicationNo}` 응답으로 렌더링한다. - `trackingRequired=true`인 보낸 선물 상세에서는 운송장 등록 CTA를 표시하고 운송장 등록 페이지로 이동한다. - 운송장 등록 페이지는 같은 상세 API를 조회해 신청 요약과 보낼 주소를 표시한 뒤 `POST /api/v2/gifts/{applicationNo}/tracking`으로 운송장 정보를 등록한다. 중요 전제: - Figma의 visual structure와 문구를 우선 따르되, 상태 개수와 데이터 노출은 API 계약을 우선한다. - 상세 진행 단계는 Figma처럼 4단계로 고정하지 말고 API `statusTimeline`의 5개 정상 진행 상태를 모두 렌더링한다. - 택배사 목록은 API로 조회하지 않는다. 앱 로컬 상수로 관리하고 선택한 한글 표시명을 `courierCompanyName`으로 전송한다. - 운송장 등록 완료 후 수정 기능은 만들지 않는다. 상세 페이지 진입: - 선물함 리스트 item에서 받은 `applicationNo`로 진입한다. - 화면 진입 시 `GET /api/v2/gifts/{applicationNo}`를 호출한다. - 조회 결과 `status=CANCELED`이면 일반 신청 상세 화면을 렌더링하지 말고 취소 완료 페이지 UI를 보여준다. - 로딩 중에는 기존 앱 상세 화면 로딩/스켈레톤 패턴을 사용한다. - 실패 시 기존 앱 toast/dialog와 재시도 패턴을 사용한다. 상세 페이지 공통 레이아웃: 1. 상단 고정 내비게이션 - 타이틀: `신청 상세` - 뒤로가기 버튼은 기존 앱 패턴을 사용한다. 2. 스크롤 콘텐츠 - 배송 상태 hero - 진행 상태 progress - 상태별 action button 영역 - 선물 정보 - 필요한 경우 보내는 사람, 받는 사서함, 사유 섹션 3. 하단 safe area - 기존 앱 패턴을 따른다. 상태별 hero 문구: | status | chip | title | body | |---|---|---|---| | `RECEIVED` | 없음 또는 `접수 완료` | `선물 신청 완료!` | `선물이 정상적으로 접수되었어요!` | | `TRACKING_REGISTERED` | `발송 확인` | `사서함으로 이동 중이에요` | 없음 | | `ARRIVED_AT_MAILBOX` | `사서함 도착` | `선물이 사서함에 도착했어요` | 없음 | | `INSPECTION_COMPLETED` | `검수 완료` | `선물 검수를 완료했어요` | 없음 | | `DELIVERED` | `전달 완료` | `크리에이터에게 선물을 전달했어요` | 없음 | | `UNDELIVERABLE` | `전달 불가` | `전달할 수 없는 품목입니다` | `보내주신 선물은 SODALIVE 선물 정책에 따라 크리에이터에게 전달할 수 없는 품목으로 확인되었습니다.` | | `CANCELED` | `신청 취소` | `선물 신청이 취소되었어요` | `사용한 캔은 환불 처리됩니다.` | 진행 상태 progress: - `statusTimeline` 배열을 그대로 사용한다. - 항상 아래 5개 단계를 이 순서로 표시한다. 1. `RECEIVED`: `신청 접수` 2. `TRACKING_REGISTERED`: `발송 확인` 3. `ARRIVED_AT_MAILBOX`: `사서함 도착` 4. `INSPECTION_COMPLETED`: `검수 완료` 5. `DELIVERED`: `전달 완료` - `occurredAt`이 있으면 해당 단계는 완료 색상으로 표시하고 `MM.DD`를 표시한다. - `occurredAt`이 null이면 비활성 색상으로 표시하고 날짜는 표시하지 않는다. - `UNDELIVERABLE`, `CANCELED`는 정상 진행 단계가 아니므로 progress step으로 추가하지 않는다. - 종료 상태에서도 `statusTimeline`에 있는 정상 진행 이력은 그대로 표시한다. 상세 페이지 섹션 표시 규칙: - `선물 정보`는 항상 표시한다. - `direction=SENT`이면 크리에이터명은 `giftInfo.recipientCreatorNickname`을 사용한다. - `direction=RECEIVED`이면 보낸 팬명은 `giftInfo.senderNickname`을 사용한다. - `senderInfo`가 있으면 `보내는 사람` 섹션을 표시한다. - `mailbox`가 있으면 `받는 사서함` 또는 `보내실 주소` 섹션을 표시한다. - `recipientAddress`가 있으면 `받는 사람` 또는 `배송지` 섹션을 표시한다. - `delivery.undeliverableReason`이 있으면 `사유` 섹션을 표시한다. 선물 정보 표시 필드: - 크리에이터 또는 보낸 팬 닉네임 - 선물 사이즈: `giftInfo.sizeName` - 카테고리: `giftInfo.categoryName` - 신청번호: `applicationNo` 또는 `giftInfo.applicationNo` - 캔: `giftInfo.paidCan`이 null이 아닐 때만 표시 - 운송장 번호: `giftInfo.tracking`이 null이 아닐 때 표시 - 검수일: `statusTimeline`에서 `INSPECTION_COMPLETED.occurredAt`이 있으면 표시 - 전달 완료일: `statusTimeline`에서 `DELIVERED.occurredAt`이 있으면 표시 - 취소일: `delivery.canceledAt`이 있으면 표시 받는 사서함/보내실 주소 표시: - `mailbox.name`을 사서함/받는 분으로 표시한다. - `mailbox.address`를 주소로 표시한다. - `mailbox.phoneNumber`를 연락처로 표시한다. - `mailbox`는 관리자가 등록한 전역 단일 주소다. 상대방 주소가 아니다. - `mailbox`는 `direction=SENT`이고 `status`가 `RECEIVED`, `TRACKING_REGISTERED`, `ARRIVED_AT_MAILBOX`, `INSPECTION_COMPLETED`일 때 내려온다. - `DELIVERED`, `UNDELIVERABLE`, `CANCELED` 또는 `direction=RECEIVED`에서는 `mailbox=null`이다. - `mailbox=null`이면 해당 섹션을 표시하지 않는다. 보내는 사람 표시: - `senderInfo.name` - `senderInfo.phoneNumber` - `senderInfo.address` - 이 정보는 보낸 팬 본인이 신청 시 입력한 정보다. 전달 불가 표시: - `status=UNDELIVERABLE`이면 Figma `2481:19456` 패턴을 따른다. - 사유 섹션의 제목은 `delivery.undeliverableReason`을 사용한다. - `delivery.undeliverableReason`이 null이면 사유 섹션 제목은 `전달 불가`로 표시한다. - 안내 문구는 `해당 선물은 정책에 따라 폐기되며 반송되지 않습니다.`를 사용한다. 상세 페이지 action: - `trackingRequired=true`이면 하단 주요 CTA `운송장 작성하기`를 표시하고 운송장 등록 페이지로 이동한다. - `direction=SENT && status=RECEIVED`이면 보조 action `신청 취소`를 표시할 수 있다. - 신청 취소 버튼을 누르면 즉시 API를 호출하지 말고 `v2 dialog` 확인 팝업을 먼저 표시한다. - `status=DELIVERED && direction=SENT`이면 `리뷰 남기기` 버튼을 표시하고 기존 리뷰 작성 화면/플로우로 이동한다. - `문의하기`, `선물 정책 확인` 버튼은 기존 앱에 해당 이동 경로가 있으면 연결하고, 없으면 기존 정책에 맞춰 숨기거나 비활성 처리한다. 신청 취소 확인 다이얼로그: - Figma `2481:19500`을 참고한다. - 앱의 기본 `v2 dialog` 컴포넌트를 사용한다. 새 dialog 컴포넌트를 만들지 않는다. - 제목: `신청 취소` - 본문: - `{크리에이터}에게 보내는 선물을 취소할까요?` - `선물 보내기 규정에 따라 선물 취소 및 사용한 캔이 모두 환불됩니다.` - 왼쪽 action: `나가기` - dialog만 닫고 상세/운송장 등록 화면 상태를 유지한다. - 오른쪽 destructive action: `취소하기` - `POST /api/v2/gifts/{applicationNo}/cancel`을 호출한다. - 호출 중에는 중복 탭을 막고 loading 상태를 표시한다. - 실패 시 dialog를 닫지 말고 기존 앱 에러 toast/dialog 패턴으로 안내한다. - 성공 시 취소 완료 페이지로 replace navigation 한다. 취소 완료 페이지: - Figma `2481:19521`을 참고한다. - 취소 API 성공 직후뿐 아니라 상세 페이지 접근 시 `GET /api/v2/gifts/{applicationNo}` 응답이 `status=CANCELED`인 경우에도 이 화면을 보여준다. - 상단 타이틀: `취소 완료` - hero chip: `취소 완료` - hero title: `선물 신청이 취소되었어요` - hero body: `{크리에이터}에게 보내는 선물 신청({applicationNo})이 취소되었습니다. 취소한 신청은 되돌릴 수 없으며, 다시 보내려면 새로 신청해 주세요.` - 섹션 제목: `취소 상세` - 취소 상세 카드 표시 필드: - 크리에이터: `giftInfo.recipientCreatorNickname` - 선물 사이즈: `giftInfo.sizeName` - 카테고리: `giftInfo.categoryName` - 신청번호: `applicationNo` - 신청일: `statusTimeline`의 `RECEIVED.occurredAt` - 취소일: 취소 API response의 `canceledAt` 또는 상세 API 조회 시 `delivery.canceledAt` - 환불 캔: 취소 API response의 `priceCan` 또는 상세 API의 `giftInfo.paidCan` - 하단 또는 카드 아래에 `문의하기` 버튼을 표시한다. - 뒤로가기 동작은 취소 전 상세/운송장 등록 화면으로 돌아가지 않게 한다. 기존 앱 navigation 정책에 맞춰 선물함 리스트 또는 이전 안전 화면으로 이동한다. 운송장 등록 페이지 진입: - 상세 페이지에서 `trackingRequired=true`일 때만 진입시킨다. - 진입 시 `GET /api/v2/gifts/{applicationNo}`를 다시 조회한다. - 조회 결과에서 `trackingRequired=false`이면 이미 등록 불가 상태이므로 기존 오류 처리 후 상세 페이지로 돌아간다. 운송장 등록 페이지 레이아웃: 1. 상단 고정 내비게이션 - 타이틀: `운송장 등록` 2. 신청 건 요약 카드 - `applicationNo` - 크리에이터 닉네임: `giftInfo.recipientCreatorNickname` - `giftInfo.sizeName · giftInfo.categoryName · 신청일` - 신청일은 `statusTimeline`의 `RECEIVED.occurredAt`을 `YYYY.MM.DD`로 표시한다. 3. 보내실 주소 - `mailbox`를 사용한다. - `mailbox=null`이면 운송장 등록을 막고 `받을 주소가 등록되어 있지 않습니다.` 오류를 표시한다. 4. 운송장 정보 - 택배사 select - 운송장 번호 input 5. 등록 안내 - Figma의 안내 문구를 유지한다. 6. 하단 action bar - 왼쪽: `신청 취소` - 오른쪽: `등록 완료` 택배사 로컬 상수: - 서버에서 택배사 옵션을 조회하지 않는다. - 앱에 아래 로컬 옵션을 둔다. 이미 앱 공통 택배사 상수가 있으면 그것을 우선 사용한다. ```ts const giftCourierCompanyNames = [ "CJ대한통운", "우체국택배", "GS25 편의점택배", "CU 편의점택배", "한진택배", "롯데택배", "로젠택배", "경동택배", "대신택배", "일양로지스", "천일택배", "합동택배", "건영택배", "농협택배" ]; ``` - 선택 UI는 기존 앱의 bottom sheet, picker, dialog 중 이미 쓰는 방식을 사용한다. - API에는 선택된 한글 표시명을 그대로 `courierCompanyName`으로 보낸다. 운송장 등록 CTA 활성 조건: - 택배사를 선택했다. - 운송장 번호를 입력했다. - 운송장 번호는 공백만 입력할 수 없다. - submit 중에는 중복 탭을 막고 loading 상태를 표시한다. - 비활성 상태는 Figma `2481:19066`, 활성 상태는 Figma `2481:19109`를 따른다. 운송장 등록 성공 처리: - `POST /api/v2/gifts/{applicationNo}/tracking` 성공 후 성공 toast를 표시한다. - 상세 페이지로 돌아가거나 replace navigation으로 상세 페이지를 다시 연다. - 상세 페이지는 `GET /api/v2/gifts/{applicationNo}`를 다시 호출해 `TRACKING_REGISTERED` 상태를 표시한다. API 명세: 선물 상세 조회: - Method: `GET` - URL: `/api/v2/gifts/{applicationNo}` - 인증: 로그인 필요 - Response envelope 예시: ```json { "success": true, "data": { "applicationNo": "A-1002609300001", "direction": "SENT", "status": "RECEIVED", "statusName": "접수 완료", "giftInfo": { "recipientCreatorNickname": "달빛수집가", "senderNickname": null, "sizeName": "소형", "categoryName": "잡화", "applicationNo": "A-1002609300001", "paidCan": 100, "tracking": null, "shippingRequestedAt": null }, "senderInfo": { "name": "김소다", "phoneNumber": "01012345678", "address": "(04030) 서울특별시 마포구 양화로 000, 3층" }, "recipientAddress": null, "mailbox": { "name": "소다라이브 사서함", "address": "(06174) 서울특별시 강남구 테헤란로108길 8, 유민빌딩 4층", "phoneNumber": "02-2055-1477" }, "trackingRequired": true, "recipientAddressRequired": false, "recipientAddressDeadlineAt": null, "delivery": { "canceledAt": null, "undeliverableAt": null, "undeliverableReason": null }, "statusTimeline": [ { "status": "RECEIVED", "statusName": "접수 완료", "occurredAt": "2026-09-18T03:00:00Z" }, { "status": "TRACKING_REGISTERED", "statusName": "발송 확인", "occurredAt": null }, { "status": "ARRIVED_AT_MAILBOX", "statusName": "사서함 도착", "occurredAt": null }, { "status": "INSPECTION_COMPLETED", "statusName": "검수완료", "occurredAt": null }, { "status": "DELIVERED", "statusName": "전달완료", "occurredAt": null } ] }, "message": "" } ``` Response `data` 필드: | 필드 | 타입 | 설명 | |---|---|---| | `applicationNo` | string | 선물 신청번호 | | `direction` | string | `SENT` 또는 `RECEIVED` | | `status` | string | 선물 상태 코드 | | `statusName` | string | 서버 상태 표시명 | | `giftInfo` | object | 선물 기본 정보 | | `giftInfo.recipientCreatorNickname` | string 또는 null | 보낸 선물 관점에서 받는 크리에이터 닉네임 | | `giftInfo.senderNickname` | string 또는 null | 받은 선물 관점에서 보낸 팬 닉네임 | | `giftInfo.sizeName` | string | 선물 사이즈명 | | `giftInfo.categoryName` | string | 카테고리명 | | `giftInfo.applicationNo` | string 또는 null | 보낸 선물 관점 신청번호 | | `giftInfo.paidCan` | number 또는 null | 보낸 선물 관점 사용 캔 | | `giftInfo.tracking` | string 또는 null | 운송장 표시 문자열. 예: `한진택배_123456789012` | | `giftInfo.shippingRequestedAt` | string 또는 null | 받은 선물 관점 배송신청일. UTC ISO 문자열 | | `senderInfo` | object 또는 null | 보낸 팬 본인이 신청 시 입력한 보내는 사람 정보 | | `senderInfo.name` | string | 이름 | | `senderInfo.phoneNumber` | string | 휴대폰 번호 | | `senderInfo.address` | string | `(우편번호) 주소, 상세주소` 형식 주소 | | `recipientAddress` | object 또는 null | 받는 크리에이터 본인이 입력한 배송지 | | `mailbox` | object 또는 null | 관리자가 등록한 전역 선물 받을 주소. 보낸 선물의 `DELIVERED` 전 정상 진행 상태에서만 값이 있다 | | `mailbox.name` | string | 사서함/받는 분 이름 | | `mailbox.address` | string | `(우편번호) 주소, 상세주소` 형식 주소 | | `mailbox.phoneNumber` | string | 연락처 | | `trackingRequired` | boolean | 보낸 팬이 운송장 등록을 해야 하는 상태인지 여부 | | `recipientAddressRequired` | boolean | 받는 크리에이터가 배송지를 입력해야 하는 상태인지 여부 | | `recipientAddressDeadlineAt` | string 또는 null | 배송지 입력 마감일. UTC ISO 문자열 | | `delivery.canceledAt` | string 또는 null | 취소일. UTC ISO 문자열 | | `delivery.undeliverableAt` | string 또는 null | 전달 불가 처리일. UTC ISO 문자열 | | `delivery.undeliverableReason` | string 또는 null | 전달 불가 사유 | | `statusTimeline` | array | 정상 진행 5단계 타임라인 | | `statusTimeline[].status` | string | 단계 상태 코드 | | `statusTimeline[].statusName` | string | 단계 표시명 | | `statusTimeline[].occurredAt` | string 또는 null | 해당 단계 도달 시각. UTC ISO 문자열 | 운송장 등록: - Method: `POST` - URL: `/api/v2/gifts/{applicationNo}/tracking` - 인증: 로그인 필요 - Request: ```json { "courierCompanyName": "한진택배", "trackingNumber": "123456789012" } ``` - Response envelope 예시: ```json { "success": true, "data": { "applicationNo": "A-1002609300001", "status": "TRACKING_REGISTERED", "statusName": "발송 확인", "courierCompanyName": "한진택배", "trackingNumber": "123456789012", "trackingRegisteredAt": "2026-09-19T03:00:00Z", "recipientAddressDeadlineAt": "2026-09-26T03:00:00Z" }, "message": "" } ``` 운송장 등록 response `data` 필드: | 필드 | 타입 | 설명 | |---|---|---| | `applicationNo` | string | 선물 신청번호 | | `status` | string | 등록 후 상태. `TRACKING_REGISTERED` | | `statusName` | string | 서버 상태 표시명 | | `courierCompanyName` | string | 선택한 택배사 한글명 | | `trackingNumber` | string | 운송장 번호 | | `trackingRegisteredAt` | string | 운송장 등록일. UTC ISO 문자열 | | `recipientAddressDeadlineAt` | string | 크리에이터 배송지 입력 마감일. UTC ISO 문자열 | 선물 보내기 취소: - Method: `POST` - URL: `/api/v2/gifts/{applicationNo}/cancel` - Request: 없음 - 용도: `direction=SENT && status=RECEIVED`에서만 `신청 취소` action에 사용한다. - Response envelope 예시: ```json { "success": true, "data": { "applicationNo": "A-1002609300001", "status": "CANCELED", "statusName": "신청 취소", "priceCan": 100, "canceledAt": "2026-09-18T04:00:00Z" }, "message": "" } ``` 선물 보내기 취소 response `data` 필드: | 필드 | 타입 | 설명 | |---|---|---| | `applicationNo` | string | 선물 신청번호 | | `status` | string | 취소 후 상태. `CANCELED` | | `statusName` | string | 서버 상태 표시명 | | `priceCan` | number | 환불되는 캔 수량 | | `canceledAt` | string | 취소일. UTC ISO 문자열 | 테스트/검증: - 리스트 item 탭 시 `applicationNo`로 상세 페이지가 열리는지 확인한다. - 상세 진입 시 `GET /api/v2/gifts/{applicationNo}`가 호출되는지 확인한다. - 상세 조회 결과 `status=CANCELED`이면 일반 상세가 아니라 취소 완료 페이지가 표시되는지 확인한다. - `statusTimeline` 5개 단계가 모두 표시되는지 확인한다. - Figma처럼 4단계로 하드코딩하지 않았는지 확인한다. - `trackingRequired=true`이면 `운송장 작성하기`가 표시되는지 확인한다. - 신청 취소 버튼 탭 시 `v2 dialog` 확인 팝업이 뜨고 즉시 API를 호출하지 않는지 확인한다. - 취소 확인 dialog에서 `나가기`를 누르면 dialog만 닫히는지 확인한다. - 취소 확인 dialog에서 `취소하기`를 누르면 `POST /api/v2/gifts/{applicationNo}/cancel`을 호출하는지 확인한다. - 취소 성공 후 취소 완료 페이지가 표시되고 취소 전 상세/등록 화면으로 돌아가지 않는지 확인한다. - `mailbox`가 있을 때만 사서함 주소 섹션이 표시되는지 확인한다. - `direction=SENT`의 `RECEIVED`, `TRACKING_REGISTERED`, `ARRIVED_AT_MAILBOX`, `INSPECTION_COMPLETED` 상태에서 `mailbox`가 표시되는지 확인한다. - `DELIVERED`, `UNDELIVERABLE`, `CANCELED`, `direction=RECEIVED`에서는 `mailbox`가 표시되지 않는지 확인한다. - `UNDELIVERABLE`이면 전달 불가 hero와 사유 섹션이 표시되는지 확인한다. - 운송장 등록 페이지에서 택배사/운송장 번호 누락 시 API를 호출하지 않는지 확인한다. - 운송장 등록 시 선택한 택배사 한글명과 운송장 번호가 request body로 전송되는지 확인한다. - 운송장 등록 성공 후 상세 페이지가 재조회되어 `TRACKING_REGISTERED` 상태로 보이는지 확인한다. - 모든 날짜는 UTC ISO 문자열을 앱 표시 형식으로 변환해 표시하는지 확인한다. ```