--- status: stable page_id: A06_Login related_pages: ["[[A06_Login/A06_backend]]", "[[A07_Register/A07_frontend]]", "[[auth_rbac]]"] last_updated: 2026-07-12 --- # A06_Login — Frontend 로그인 화면. 이메일+비밀번호 1차 → 필요 시 OTP 입력으로 전환하는 단일 폼. ## 파일 구조 | 파일 | 위치 | 역할 | |---|---|---| | `A06_Login_UI_Auth_Page.ts` | `A06_Login/A06_Login_UI_Auth_Page.ts:1-80` | 로그인 폼 렌더링 + 제출 로직 | | `A06_Login_UI_Style.css` | `A06_Login/A06_Login_UI_Style.css:1-52` | 스타일 (theme.css 변수만 사용) | | `A06_Login_Api_Fetch.ts` | `A06_Login/A06_Login_Api_Fetch.ts:1-57` | 인증 API 클라이언트 (`credentials: "include"`) | ## 컴포넌트 / 함수 | 함수 | 위치 | 역할 | |---|---|---| | `renderA06Login(root)` | `A06_Login/A06_Login_UI_Auth_Page.ts:17` | 카드+폼(email/password/otp/submit) 조립 | | `onA06_Login_Submit_Click(event)` | `A06_Login/A06_Login_UI_Auth_Page.ts:34` | 폼 제출 핸들러(검증→요청→OTP 전환/성공) | ## API 클라이언트 함수 | 함수 | 위치 | 역할 | |---|---|---| | `requestLogin(email, password)` | `A06_Login/A06_Login_Api_Fetch.ts:30` | `POST /auth/login/request` | | `verifyLogin(email, otpCode)` | `A06_Login/A06_Login_Api_Fetch.ts:34` | `POST /auth/login/verify` | | `fetchSession()` | `A06_Login/A06_Login_Api_Fetch.ts:38` | `GET /auth/session` → boolean(로그인 여부) | | `fetchSessionUser()` | `A06_Login/A06_Login_Api_Fetch.ts:43` | `GET /auth/session` → SessionUser 객체 | | `logout()` | `A06_Login/A06_Login_Api_Fetch.ts:54` | `POST /auth/logout` | - `SessionUser` 타입: id, email, name?, role?, company_id?, is_master? - `fetchSession`/`fetchSessionUser`/`logout`은 [[a00_app_shell_framework]]에서도 사용 (공유 자원) ## 제출 로직 (2단계 폼 전환) 1. 이메일 유효성 검사 (isBlank/isValidEmail) → 실패 시 필드 에러 2. OTP 미요구 상태면 비밀번호 필수, OTP 요구 상태면 6자리 숫자 검증 3. `otpRequired` false → `requestLogin`, true → `verifyLogin` 4. 응답 `status === "otp_required"`: - otp 필드 노출, password 필드 숨김 - 제출 버튼 라벨 → `A06_Login_Verify` - 토스트 안내(`A06_Login_OtpSent`) 5. 로그인 성공 → 토스트 후 `navigateTo(ROUTES.B01_ACCOUNT)` ## 로컬라이제이션 모든 텍스트는 [[ui_templates]]에서 로드. 하드코딩 금지. | UI 요소 | 로케일 키 | |---|---| | 제목 | `A06_Login_Title` | | 이메일/비번/OTP 필드 | `A06_Login_Field_Email`, `A06_Login_Field_Password`, `A06_Login_Field_Otp` | | 제출/인증 버튼 | `A06_Login_Submit`, `A06_Login_Verify` | | 회원가입 이동 | `A06_Login_ToRegister` | | 안내/성공 | `A06_Login_OtpSent`, `A06_Login_Success` | | 오류 | `A06_Login_Error_Email`, `A06_Login_Error_Required`, `A06_Login_Error_Request` | ## 스타일 (CSS) - `.a06-login` — 페이지 래퍼 (중앙 정렬, 상단 정렬) - `.a06-login__card` — 로그인 카드 (max-width: 400px, surface 배경, border) - `.a06-login__title` — heading-sm, deep-iris, 중앙 정렬 - `.a06-login__form` — flex 열 (gap: spacing-16) - `.a06-login__submit` / `.a06-login__link` — 전체 너비 버튼 ## 이벤트 핸들러 | 함수 | 위치 | 동작 | |---|---|---| | `onA06_Login_Submit_Click()` | `A06_Login/A06_Login_UI_Auth_Page.ts:34` | 로그인/OTP 제출 | | (인라인) 회원가입 버튼 | `A06_Login/A06_Login_UI_Auth_Page.ts:75` | A07_REGISTER로 이동 | ## 의존성 - [[a00_app_shell_framework]] — `navigateTo()` 라우팅 - `config_frontend.ts` — `ROUTES`, `API_BASE_URL` - `common_util_validate` — `isBlank`, `isValidEmail` - [[ui_templates]] — `createButton`, `createInputField`, `showLoadingOverlay`/`hideLoadingOverlay`, `showToast` - 백엔드 API → [[A06_Login/A06_backend]]