# 설계서: 프론트 - 로그인 메뉴 (#283)
> **상태**: Approved
> **작성**: [AI] Architect · **최종수정**: 2026-06-15
> **추적성** — Redmine: #283 · 관련 ADR: 없음
> · 구현 파일: `frontend/src/components/LoginMenu.tsx` · 테스트: TBD (현재 없음)
## 1. 목적 (Why)
헤더에서 비로그인 사용자가 한 번의 클릭으로 Google 소셜 로그인 모달을 띄워 가입/로그인을 완료할 수 있도록, 다른 UI(지도·바텀시트)와 겹치지 않는 **z-index 안전한 모달** 진입점을 제공한다.
## 2. 범위 (Scope)
- **포함**:
- "로그인" 버튼 (헤더용)
- 클릭 시 `
`에 Portal로 마운트되는 모달 (백드롭 + 카드)
- Google 로그인 위젯(`@react-oauth/google`)의 콜백을 부모에 전달
- 백드롭 클릭 / "✕" 버튼으로 닫기
- 다크모드 색상 토큰 대응
- **제외 (out of scope)**:
- 토큰 저장(`localStorage`), 백엔드 검증, 세션 갱신 — 부모(`onGoogleSuccess`)의 책임
- 카카오/네이버/이메일 로그인 등 추가 프로바이더
- 로그아웃 UI (별도 컴포넌트)
- GoogleOAuthProvider 설정 (`_app`/`layout.tsx`에서 처리)
## 3. 인수조건 (이미 구현된 동작 기준)
- [x] "로그인" 버튼이 헤더 스타일(보더+호버 brand 색)로 노출된다.
- [x] 클릭 시 모달이 열리고, 화면 전체를 덮는 백드롭(`bg-black/40 backdrop-blur-sm`)이 깔린다.
- [x] 모달은 `createPortal`로 `document.body`에 마운트되어 부모의 stacking context 영향을 받지 않는다.
- [x] 모달 z-index는 `99999`로 다른 모든 오버레이(지도, 바텀시트)보다 위에 위치한다.
- [x] 모달 안에 "소셜 계정으로 간편 로그인" 안내와 Google 로그인 버튼(`size=large, width=260`)이 표시된다.
- [x] Google 로그인 성공 시 `credential`을 부모 `onGoogleSuccess(credential)`로 전달하고 모달을 닫는다.
- [x] Google 로그인 실패 시 `console.error("Google login failed")` 로깅(UX는 위젯이 처리).
- [x] 백드롭 영역(자식이 아닌 본인 클릭)에서만 모달이 닫힌다 (`e.target === e.currentTarget`).
- [x] 우상단 "✕" 버튼으로 모달을 닫을 수 있다.
- [x] 다크모드 토큰(`dark:text-gray-300`, `dark:border-gray-600`, `dark:hover:border-brand-500`)으로 색이 적응한다.
## 4. 컨텍스트 & 제약
- **프레임워크**: Next.js 16 App Router, Client Component (`"use client"`)
- **라이브러리**:
- `@react-oauth/google` — `` 위젯 사용, 상위 어딘가에 `` 마운트 필요
- `react-dom`의 `createPortal`
- **스타일**: Tailwind, Saffron 디자인 토큰 — `bg-surface`, `text-brand-600`, `border-brand-400`
- **z-index 제약**: 지도/바텀시트/Drawer 등 기존 오버레이가 다수 존재 → Portal + inline style `z-index: 99999` 사용 (Tailwind 클래스가 아닌 인라인으로 적용해 명시적 우선순위 보장)
- **SSR 안전성**: `createPortal`이 `document.body`를 참조하므로 `"use client"` 필수. SSR 단계에서는 `open=false` 초기 상태로 모달 미렌더 → 안전.
- **가정**:
- 부모는 `onGoogleSuccess`에서 백엔드 `/api/auth/google` 호출 및 토큰 저장을 책임진다.
- GoogleOAuthProvider clientId는 환경 변수(`NEXT_PUBLIC_GOOGLE_CLIENT_ID`)로 주입된다.
## 5. 아키텍처 개요
- 파일:
- `LoginMenu.tsx` — 단일 컴포넌트
- 외부:
- 부모: 헤더 (예: `Header.tsx`, `page.tsx`) — `onGoogleSuccess` 콜백 제공
- 상위: `` 마운트 (layout)
```
[Header]
└─
│
│ state: open: boolean
│
├─