# HQ 콘솔 Phase 1.5 (2/2) — `hq-ui` 프론트엔드 지침서

> 설계 원본: `HQ_CONSOLE_DESIGN.md` §D2 · §F · §7
> 백엔드 지침: `HQ_PHASE1_5_AUTH_GUIDE.md` (**구현 완료**, 테스트 16건 통과)
> 대상: **신규 SPA `cryptoments-admin/hq-ui`**
> 범위: **로그인 + 인증 셸까지.** 대시보드·수익·통계 화면은 다음 증분.

---

## 0. 완료 기준

1. `npm run build` (vue-tsc 포함) 통과
2. `/hq/login` 에서 로그인 → `/hq/dashboard` 진입 (대시보드는 **자리표시자**)
3. 일반 파트너 계정으로 로그인 시 **"이 콘솔은 상위 총판 전용입니다"** + 파트너 콘솔 링크
4. 2FA 계정은 OTP 화면 경유
5. 새로고침해도 세션 유지, 만료/401 시 로그인으로 복귀

---

## 1. 위치와 생성 방식

```
/Users/dudgh/work/Cryptoments/cryptoments-admin/hq-ui/
```

**`partner-ui` 를 그대로 복제하지 마라.** 필요한 파일만 옮겨 최소 셸로 시작한다
(partner-ui 는 30+ 화면이 딸려 있어 복제하면 죽은 코드가 대량으로 들어온다).

`partner-ui` 에서 **그대로 가져올 것** (경로 유지, 내용 거의 그대로):

| 파일 | 조치 |
|---|---|
| `src/lib/utils.ts` | 그대로 |
| `src/lib/chunkReload.ts` | 그대로 — ⚠️ **반드시 포함**. §5 참조 |
| `src/components/ui/{button,input,card,badge}` | 그대로 (table 은 이번 증분 불필요) |
| `src/assets/main.css`, `tailwind.config.js`, `postcss.config.js`, `components.json` | 그대로 |
| `tsconfig*.json`, `vite.config.ts` | 포트만 변경 (아래) |
| `src/api/client.ts` | **개조** — §3 |

`package.json` 은 partner-ui 의 **의존성 부분집합**으로 새로 쓴다. 이번 증분에 불필요한
`xlsx`, `jspdf`, `html2canvas`, `qrcode`, `chart.js`, `vue-chartjs`, `@tanstack/vue-table` 는
**넣지 않는다** (다음 증분에서 필요할 때 추가).

`vite.config.ts` 는 `server.port: 5175` (partner-ui 5173 과 충돌 회피), 프록시 target 동일
(`VITE_PROXY_TARGET || http://localhost:8081`).

---

## 2. 라우팅

```
/hq/login          LoginView        public
/hq/login/2fa      TwoFaView        public
/hq/dashboard      DashboardView    requiresAuth   ← 이번 증분은 자리표시자
/                  → /hq/dashboard 리다이렉트
```

`router.beforeEach` 는 partner-ui 패턴을 따르되 **`distributorOnly` 분기는 없다**
(HQ 콘솔 전체가 이미 상위 총판 전용).

⚠️ partner-ui 의 **"세션 살아 있는데 로그인 화면이면 대시보드로"** 가드는 **반드시 가져올 것**
(2026-08-18 운영 이슈 — 없으면 "로그인이 안 된다" 로 보인다).

---

## 3. API 클라이언트 — `src/api/client.ts` 개조

partner-ui 것을 베이스로 하되 **아래를 바꾼다.**

| 항목 | partner-ui | hq-ui |
|---|---|---|
| 토큰 localStorage 키 | `accessToken` | **`hq_accessToken`** |
| 세션 저장 키 | `partner_session` | **`hq_session`** |
| 401 리다이렉트 | `/partner/login` | `/hq/login` |
| env 변수 | `VITE_PARTNER_API_URL` | `VITE_HQ_API_URL` |

☠️ **토큰 키를 partner-ui 와 반드시 다르게 한다.** 같은 브라우저에서 두 콘솔을 동시에 쓰면
같은 키를 공유해 **서로의 토큰을 덮어쓴다**. 설계 D6(완전 분리)이 클라이언트에서 깨진다.

헤더는 partner-ui 와 동일하게 **두 개 모두** 보낸다 — 백엔드가 둘 다 필요로 한다.
```
Authorization: Bearer {token}   → HqSecurityFilter
Access-Token:  {token}          → XSessionController.getSession()
```

401 인터셉터의 `isOptionalProbe`(대시보드 요약 예외) 는 **이번 증분엔 불필요** — 제거하고
단순하게 간다. `/auth/login`·`/auth/2fa` 예외만 유지.

---

## 4. 인증 API·스토어

### `src/api/auth.ts`

| 함수 | 엔드포인트 |
|---|---|
| `login(email, password)` | `POST /api/hq/auth/login` |
| `verify2Fa(tempToken, otpCode)` | `POST /api/hq/auth/2fa/verify` |
| `logout()` | `POST /api/hq/auth/logout` |
| `getMe()` | `GET /api/hq/auth/me` |

⚠️ **비밀번호 재설정 API 를 만들지 마라** (설계 D8 — 백엔드에도 없다).

**응답 필드는 백엔드 DTO 를 직접 확인하고 맞춰라.** 추측 금지:
`partner-api/src/main/java/com/cryptoments/partnerapi/dto/response/HqLoginResponse.java`
`.../HqProfileResponse.java`

### `src/stores/auth.ts`

partner-ui 패턴(Pinia setup store) 을 따르되 노출값은 다음만:
`hq`(프로필), `isAuthenticated`, `twoFactorEnabled`, `hqName`, `hqCode`,
`login()`, `verify2Fa()`, `logout()`, `clear2FaPending()`.

`isDistributor` / `isMerchant` 같은 파트너 개념은 **넣지 않는다.**

---

## 5. 화면

### `LoginView.vue` — 설계 §F-1

| 요소 | 비고 |
|---|---|
| 이메일 · 비밀번호 | |
| 로그인 버튼 | 처리 중 disable |
| **콘솔 식별** | "상위 총판 콘솔" 명시 + 파트너 콘솔과 **시각적으로 구분되는 색/뱃지** |
| 비밀번호 찾기 | **링크만** — partner-ui 재설정 페이지로 이동 (`VITE_PARTNER_UI_URL` env). 자체 폼 금지 |

**에러 표시 — 설계 §F-2 를 그대로 따른다. 백엔드가 이미 구분해 내려준다.**

| 백엔드 | 화면 |
|---|---|
| 401 (자격증명·상태) | 서버 메시지 그대로 표시 |
| **403 `NOT_HQ_PARTNER`** | 서버 메시지 + **파트너 콘솔로 가는 버튼** 노출 |
| 403 그 외 | 서버 메시지 그대로 |

☠️ **에러 문구를 프론트에서 새로 만들지 마라.** 백엔드가 계정 열거 방지를 고려해 문구를
설계했다(§F-2). 프론트가 "비밀번호가 틀렸습니다" 같은 걸 지어내면 그 설계가 무너진다.

### `TwoFaView.vue`
OTP 6자리 입력 → `verify2Fa`. `tempToken` 없이 직접 진입하면 `/hq/login` 으로 돌려보낸다.

### `DashboardView.vue` — **자리표시자**
로그인 확인용. 표시할 것: HQ 이름·코드, 로그아웃 버튼,
"수익 대시보드는 다음 단계에서 구현됩니다" 안내.
⚠️ **가짜 숫자·목업 카드를 넣지 마라.** 실제 데이터인지 오인된다.

### `HqLayout.vue`
헤더(HQ 이름 + 로그아웃) + `<RouterView/>`. 사이드바 메뉴는 화면이 하나뿐이라 이번 증분엔 불필요.

---

## 6. ☠️ 배포 시 반드시 (설계 §F-3)

`index.html` 에 **`Cache-Control: no-cache`** 가 걸려야 한다. 안 걸면 배포 후
"로그인했는데 다시 로그인 화면" 증상이 난다 (stale SPA 셸 — 전 콘솔이 2026-07-30 에 겪음).
`chunkReload.ts` 는 그 **다음 단계 방어**(옛 청크 404)이므로 **둘 다** 필요하다.

nginx 설정은 이번 증분 범위 밖이지만, **`hq-ui/DEPLOY_NOTES.md`** 에 아래를 적어 남겨라.

```nginx
location = /index.html { add_header Cache-Control "no-cache"; }
```

---

## 7. 하지 말 것

- partner-ui 디렉토리 통째 복사
- 비밀번호 재설정 / 2FA 설정 / 프로필 수정 화면 (D8)
- 토큰 키를 partner-ui 와 공유
- 백엔드에 없는 엔드포인트 호출 (`/api/hq/auth/password/**` 등)
- 대시보드에 목업 숫자
- `any` 남용 — `vue-tsc` 를 통과시켜야 한다
- **커밋·push**

---

## 8. 참고

| 목적 | 경로 |
|---|---|
| 프론트 원형 전체 | `cryptoments-admin/partner-ui/` |
| API 클라이언트 | `partner-ui/src/api/client.ts` |
| 인증 API | `partner-ui/src/api/auth.ts` |
| 인증 스토어 | `partner-ui/src/stores/auth.ts` |
| 로그인 화면 | `partner-ui/src/views/partner/LoginView.vue` |
| 라우터 가드 | `partner-ui/src/router/index.ts` |
| 백엔드 응답 DTO | `cryptoments/partner-api/.../dto/response/Hq*.java` |
| 백엔드 컨트롤러 | `cryptoments/partner-api/.../controller/HqAuthController.java` |
