diff --git a/docs/superpowers/specs/2026-03-23-wallet-ui-design.md b/docs/superpowers/specs/2026-03-23-wallet-ui-design.md new file mode 100644 index 0000000..e40da15 --- /dev/null +++ b/docs/superpowers/specs/2026-03-23-wallet-ui-design.md @@ -0,0 +1,195 @@ +# 지갑 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 추가 |