7.2 KiB
7.2 KiB
지갑 UI 설계
작성일: 2026-03-23 상태: 승인됨
개요
웹 클라이언트(a301_client)에 블록체인 지갑 UI를 추가한다. 단일 /wallet 페이지에 4개 탭(지갑, 자산, 인벤토리, 마켓)으로 구성하고, HomePage에 잔액 요약 카드를 배치한다.
페이지 구조
라우팅
| 경로 | 컴포넌트 | 인증 |
|---|---|---|
/wallet |
WalletPage | 로그인 필수 |
/wallet 라우트 보호: 기존 AdminRoute 패턴을 참고하여 PrivateRoute 컴포넌트를 새로 만든다. 로그인하지 않은 유저는 /login으로 리다이렉트.
기존 라우트는 변경하지 않는다.
HomePage 변경
- 헤더: "지갑" 링크 추가 →
/wallet로 이동 - 지갑 요약 카드: DownloadSection 위에 배치
- TOL 잔액 (큰 글씨)
- 보유 자산 수, 장착 아이템 수
- 클릭 시
/wallet로 이동 - 로그인하지 않은 상태에서는 숨김
- 3개 API (
/balance,/assets,/inventory)Promise.all로 병렬 호출 - 일부 API 실패 시: 실패한 항목만 "--"로 표시, 카드 자체는 숨기지 않음
탭 1: 지갑
잔액, 지갑 주소, 키 내보내기.
잔액 카드
- TOL 잔액을 크게 표시
- API:
GET /api/chain/balance
지갑 정보
- 공개키: 축약 표시 (
a3b4...e1f2) + 복사 버튼 - 주소: 축약 표시 (
a3b4c5d6...e9f0a1b2) + 복사 버튼 - 클릭 시 클립보드에 전체 값 복사, 토스트로 "복사됨" 알림
- API:
GET /api/chain/wallet
키 내보내기
- 비밀번호 입력 필드 + "내보내기" 버튼
- 성공 시: 개인키 hex를 화면에 표시 (복사 버튼 포함)
- 경고 텍스트: "개인키를 안전하게 보관하세요"
- 탭 이탈 시(다른 탭 클릭): 개인키 표시 상태 초기화 (보안)
- 에러 처리:
- HTTP 401 → "비밀번호가 올바르지 않습니다"
- 기타 에러 → 토스트로 서버 에러 메시지 표시
- API:
POST /api/chain/wallet/export(body:{"password": "..."})
탭 2: 자산
보유 NFT 목록 + 상세 정보.
자산 목록
- 아이템 이름, 템플릿 이름, 자산 ID 표시
- 거래 가능 여부 표시 ("거래 가능" / "거래 불가")
- API 호출 전략:
GET /api/chain/assets→ 자산 ID 배열 반환- 각 ID에 대해
GET /api/chain/asset/:id를Promise.all로 병렬 호출 - 개별 자산 로드 실패 시: 해당 자산만 "로드 실패" 표시, 나머지는 정상 표시
- 로딩 중: 스피너 표시
자산 상세 (클릭 시 펼치기)
- 속성(properties) 키-값 표시
- 거래 가능 여부
- 마켓 등록 상태 (등록됨 / 미등록)
- 마켓 등록 버튼: 미등록 + 거래 가능한 자산에만 표시
- 클릭 시: 가격 입력 인라인 UI (펼쳐진 상세 영역 내 숫자 input + "등록" 버튼)
POST /api/chain/market/list(body:{"asset_id": "...", "price": N})- Idempotency-Key 헤더 필요
- 성공 시: 토스트 알림 + 해당 자산 상태 갱신 (마켓 등록됨으로 변경)
탭 3: 인벤토리
장착 슬롯 현황 (조회 전용).
슬롯 목록
- 슬롯 이름 + 장착된 아이템 이름/ID 표시
- 빈 슬롯은 "비어있음"으로 표시 (점선 테두리)
- 장착/해제 버튼 없음 (게임 내에서만 조작)
- API:
GET /api/chain/inventory
탭 4: 마켓
NFT 마켓플레이스. 구매, 판매(자산 탭에서 연결), 취소.
리스팅 목록
- 전체 / 내 리스팅 필터 토글
- 각 리스팅: 아이템 이름, 판매자 (축약 주소), 가격
- "내 리스팅" 필터: 클라이언트에서
/api/chain/wallet으로 가져온 내 지갑 주소와 리스팅의 seller 필드를 비교하여 필터링 - API:
GET /api/chain/market→ 활성 리스팅 목록 - 미사용:
GET /api/chain/market/:id— 리스팅 상세 모달이 없으므로 의도적으로 사용하지 않음
구매
- 타인의 리스팅에 "구매" 버튼
- 확인 다이얼로그 (useConfirm 사용): "화염 활을 500 TOL에 구매하시겠습니까?"
POST /api/chain/market/buy(body:{"listing_id": "..."})- Idempotency-Key 헤더 필요
- 성공 시: 토스트 알림 + 리스팅 목록 새로고침 + 잔액 갱신
- 실패 시: 서버 응답 에러 메시지를 토스트로 표시 (잔액 부족 등 포함)
내 리스팅 취소
- 내 리스팅에 "취소" 버튼 (빨간 테두리)
- 확인 다이얼로그: "리스팅을 취소하시겠습니까?"
POST /api/chain/market/cancel(body:{"listing_id": "..."})- Idempotency-Key 헤더 필요
- 성공 시: 토스트 알림 + 리스팅 목록 새로고침
API 호출 패턴
기존 API 클라이언트 활용
- 모든 API 호출은
src/api/client.js의apiFetch()경유 - JWT 토큰 자동 첨부, 401 시 자동 refresh
- 새 파일:
src/api/chain.js— 체인 관련 API 래퍼 함수 모음
Idempotency-Key
- 마켓 구매/등록/취소 등 트랜잭션 API에는
Idempotency-Key헤더 필요 chain.js래퍼 내부에서crypto.randomUUID()로 자동 생성하여 헤더에 추가- 호출 측(컴포넌트)에서는 Idempotency-Key를 신경 쓸 필요 없음
에러 처리
- API 에러 시 토스트로 에러 메시지 표시
- 네트워크 에러 시 기존 retry 로직 적용 (GET만)
로딩 상태
- 각 탭 진입 시: 중앙 스피너 표시 (기존 로딩 패턴과 동일)
- WalletSummary: 인라인 스피너 또는 "--" placeholder
컴포넌트 구조
src/
├── api/
│ └── chain.js # 체인 API 래퍼 (신규)
├── pages/
│ ├── WalletPage.jsx # /wallet 페이지 (탭 컨테이너) (신규)
│ └── WalletPage.css # 지갑 페이지 스타일 (신규)
├── components/
│ └── wallet/ # 지갑 관련 컴포넌트 (신규)
│ ├── WalletTab.jsx # 탭 1: 잔액, 주소, 키 내보내기
│ ├── AssetsTab.jsx # 탭 2: 자산 목록 + 상세
│ ├── InventoryTab.jsx # 탭 3: 인벤토리 조회
│ ├── MarketTab.jsx # 탭 4: 마켓
│ └── WalletSummary.jsx # HomePage 요약 카드
스타일링
- 기존 프로젝트 패턴 그대로: plain CSS, dark 테마
- 색상: 기존
#BACDB0(sage green) accent 사용 - 탭 UI: 하단 보더로 활성 탭 표시
- 복사 버튼: 클릭 시
navigator.clipboard.writeText()+ 토스트 - 반응형:
@media (max-width: 768px)대응
변경 파일 요약
| 파일 | 변경 |
|---|---|
src/api/chain.js |
신규: 체인 API 래퍼 (Idempotency-Key 자동 생성) |
src/pages/WalletPage.jsx |
신규: 지갑 페이지 (탭 컨테이너) |
src/pages/WalletPage.css |
신규: 지갑 페이지 스타일 |
src/components/wallet/WalletTab.jsx |
신규: 잔액/주소/키 내보내기 |
src/components/wallet/AssetsTab.jsx |
신규: 자산 목록 + 상세 + 마켓 등록 |
src/components/wallet/InventoryTab.jsx |
신규: 인벤토리 조회 |
src/components/wallet/MarketTab.jsx |
신규: 마켓 (구매/취소) |
src/components/wallet/WalletSummary.jsx |
신규: 홈 요약 카드 |
src/App.jsx |
수정: /wallet 라우트 + PrivateRoute 추가 |
src/pages/HomePage.jsx |
수정: 헤더 링크 + WalletSummary 추가 |