feat: 구글 로그인 전용 — 아이디 로그인 폼·회원가입 UI 숨김
CI / build (push) Failing after 14m50s

- src/config/features.js ID_LOGIN_ENABLED 플래그(기본 false)로 게이트
- LoginModal: 아이디/비번 폼·'또는' 구분선·회원가입 링크 숨김(구글 버튼만)
- HomeView: 랜딩 회원가입 버튼 숨김, 안내문구 정리
- 백엔드 /api/auth/login·signup 은 비상용으로 유지(플래그 true 로 복구 가능)
- LoginModal.spec: 아이디 로그인 플로우는 플래그 mock 으로 계속 검증

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-27 19:41:04 +09:00
parent b8c1440e4d
commit 8254460751
4 changed files with 16 additions and 5 deletions
+4
View File
@@ -6,10 +6,14 @@ import { setActivePinia, createPinia } from 'pinia'
const { pushMock } = vi.hoisted(() => ({ pushMock: vi.fn() })) const { pushMock } = vi.hoisted(() => ({ pushMock: vi.fn() }))
vi.mock('vue-router', () => ({ useRouter: () => ({ push: pushMock }) })) vi.mock('vue-router', () => ({ useRouter: () => ({ push: pushMock }) }))
// 아이디 로그인 플로우 검증용 — 운영에선 숨김(ID_LOGIN_ENABLED=false)이지만 로직은 유지되므로 켜서 테스트
vi.mock('@/config/features', () => ({ ID_LOGIN_ENABLED: true }))
vi.mock('@/api/authApi', () => ({ vi.mock('@/api/authApi', () => ({
authApi: { authApi: {
login: vi.fn(), login: vi.fn(),
signupEnabled: vi.fn(() => Promise.resolve({ enabled: true })), signupEnabled: vi.fn(() => Promise.resolve({ enabled: true })),
googleClientId: vi.fn(() => Promise.resolve({ clientId: '' })),
}, },
})) }))
+4 -3
View File
@@ -5,6 +5,7 @@ import { useAuthStore } from '@/stores/auth'
import { useUiStore } from '@/stores/ui' import { useUiStore } from '@/stores/ui'
import { authApi } from '@/api/authApi' import { authApi } from '@/api/authApi'
import { isNativeGoogle, nativeGoogleIdToken } from '@/native/googleAuth' import { isNativeGoogle, nativeGoogleIdToken } from '@/native/googleAuth'
import { ID_LOGIN_ENABLED } from '@/config/features'
const auth = useAuthStore() const auth = useAuthStore()
const ui = useUiStore() const ui = useUiStore()
@@ -173,7 +174,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKeydown))
<button class="close" type="button" aria-label="닫기" @click="ui.closeLogin()">×</button> <button class="close" type="button" aria-label="닫기" @click="ui.closeLogin()">×</button>
<h2>로그인</h2> <h2>로그인</h2>
<form class="form" @submit.prevent="handleLogin"> <form v-if="ID_LOGIN_ENABLED" class="form" @submit.prevent="handleLogin">
<input v-model="form.loginId" type="text" placeholder="아이디" autocomplete="username" :disabled="loading" /> <input v-model="form.loginId" type="text" placeholder="아이디" autocomplete="username" :disabled="loading" />
<input v-model="form.password" type="password" placeholder="비밀번호" autocomplete="current-password" :disabled="loading" /> <input v-model="form.password" type="password" placeholder="비밀번호" autocomplete="current-password" :disabled="loading" />
<label class="remember"> <label class="remember">
@@ -187,7 +188,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKeydown))
<!-- 구글 로그인 서버에 클라이언트 ID 설정된 경우에만 노출 --> <!-- 구글 로그인 서버에 클라이언트 ID 설정된 경우에만 노출 -->
<div v-show="googleClientId" class="google-wrap"> <div v-show="googleClientId" class="google-wrap">
<div class="divider"><span>또는</span></div> <div v-if="ID_LOGIN_ENABLED" class="divider"><span>또는</span></div>
<!-- (안드로이드): 네이티브 버튼 / ·PC: GIS 렌더 버튼 --> <!-- (안드로이드): 네이티브 버튼 / ·PC: GIS 렌더 버튼 -->
<button <button
v-if="isNative" v-if="isNative"
@@ -211,7 +212,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKeydown))
네이버로 로그인 (준비 ) 네이버로 로그인 (준비 )
</button> </button>
<p class="links"> <p v-if="ID_LOGIN_ENABLED" class="links">
<template v-if="ui.signupEnabled"> <template v-if="ui.signupEnabled">
계정이 없으신가요? 계정이 없으신가요?
<a href="#" @click.prevent="goSignup">회원가입</a> <a href="#" @click.prevent="goSignup">회원가입</a>
+5
View File
@@ -0,0 +1,5 @@
// 로그인 방식 기능 플래그.
// 구글 로그인만 운영하므로 아이디 로그인 폼 / 회원가입 UI 를 숨긴다.
// 백엔드(/api/auth/login, /api/auth/signup)는 비상용으로 그대로 유지 —
// 되돌리려면 이 값을 true 로 바꾸면 아이디 로그인/회원가입이 다시 노출된다.
export const ID_LOGIN_ENABLED = false
+3 -2
View File
@@ -4,6 +4,7 @@ import { RouterLink } from 'vue-router'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { useUiStore } from '@/stores/ui' import { useUiStore } from '@/stores/ui'
import { accountApi } from '@/api/accountApi' import { accountApi } from '@/api/accountApi'
import { ID_LOGIN_ENABLED } from '@/config/features'
const auth = useAuthStore() const auth = useAuthStore()
const ui = useUiStore() const ui = useUiStore()
@@ -296,9 +297,9 @@ onMounted(load)
<p class="tagline">슬림하게 관리하는 가계부 · 자산 · 예산</p> <p class="tagline">슬림하게 관리하는 가계부 · 자산 · 예산</p>
<div class="cta"> <div class="cta">
<button type="button" class="btn primary" @click="ui.openLogin('/account')">로그인</button> <button type="button" class="btn primary" @click="ui.openLogin('/account')">로그인</button>
<button v-if="ui.signupEnabled" type="button" class="btn" @click="ui.openSignup()">회원가입</button> <button v-if="ID_LOGIN_ENABLED && ui.signupEnabled" type="button" class="btn" @click="ui.openSignup()">회원가입</button>
</div> </div>
<p class="cta-note">{{ ui.signupEnabled ? '로그인하면 나의 가계부 요약을 볼 수 있어요.' : '현재 회원가입이 제한되어 있습니다.' }}</p> <p class="cta-note">{{ !ID_LOGIN_ENABLED || ui.signupEnabled ? '로그인하면 나의 가계부 요약을 볼 수 있어요.' : '현재 회원가입이 제한되어 있습니다.' }}</p>
</div> </div>
<ul class="features"> <ul class="features">