Files
sodalive-backend-spring-boot/docs/20260929_크리에이터_선물하기/gift-detail-tracking-page-prompt.md
T

21 KiB

선물 상세 및 운송장 등록 페이지 생성 프롬프트

이 문서는 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 컴포넌트를 기본으로 사용한다.

모바일 앱 구현 프롬프트

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 예시:
{
  "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:
{
  "courierCompanyName": "한진택배",
  "trackingNumber": "123456789012"
}
  • Response envelope 예시:
{
  "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 예시:
{
  "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 문자열을 앱 표시 형식으로 변환해 표시하는지 확인한다.