Files
a301_client/docs/superpowers/specs/2026-03-23-wallet-ui-design.md
2026-03-23 13:24:52 +09:00

196 lines
7.2 KiB
Markdown

# 지갑 UI 설계
> 작성일: 2026-03-23
> 상태: 승인됨
---
## 개요
웹 클라이언트(`a301_client`)에 블록체인 지갑 UI를 추가한다. 단일 `/wallet` 페이지에 4개 탭(지갑, 자산, 인벤토리, 마켓)으로 구성하고, HomePage에 잔액 요약 카드를 배치한다.
---
## 페이지 구조
### 라우팅
| 경로 | 컴포넌트 | 인증 |
|------|----------|------|
| `/wallet` | WalletPage | 로그인 필수 |
`/wallet` 라우트 보호: 기존 `AdminRoute` 패턴을 참고하여 `PrivateRoute` 컴포넌트를 새로 만든다. 로그인하지 않은 유저는 `/login`으로 리다이렉트.
기존 라우트는 변경하지 않는다.
### HomePage 변경
1. **헤더**: "지갑" 링크 추가 → `/wallet`로 이동
2. **지갑 요약 카드**: 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 호출 전략:
1. `GET /api/chain/assets` → 자산 ID 배열 반환
2. 각 ID에 대해 `GET /api/chain/asset/:id``Promise.all`**병렬 호출**
3. 개별 자산 로드 실패 시: 해당 자산만 "로드 실패" 표시, 나머지는 정상 표시
- 로딩 중: 스피너 표시
### 자산 상세 (클릭 시 펼치기)
- 속성(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 추가 |