Files
sb-front/src/components/LoginModal.vue
T
ByungCheol 385f6c970e
CI / build (push) Failing after 15m6s
feat: 로그인 모달 '로그인 상태 유지' 체크박스 (자동 로그인)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 21:47:03 +09:00

202 lines
4.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { reactive, ref, watch, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useUiStore } from '@/stores/ui'
const auth = useAuthStore()
const ui = useUiStore()
const router = useRouter()
// 로그인 닫고 회원가입 팝업 열기
function goSignup() {
ui.openSignup()
}
const form = reactive({ loginId: '', password: '', rememberMe: true })
const loading = ref(false)
const error = ref(null)
// 팝업이 열릴 때마다 입력값 초기화 (로그인 상태 유지는 기본 켜둠)
watch(
() => ui.loginOpen,
(open) => {
if (open) {
form.loginId = ''
form.password = ''
form.rememberMe = true
error.value = null
}
},
)
async function handleLogin() {
error.value = null
if (!form.loginId || !form.password) {
error.value = '아이디와 비밀번호를 입력하세요.'
return
}
loading.value = true
try {
await auth.login(form.loginId, form.password, form.rememberMe)
const redirect = ui.redirectPath
ui.closeLogin()
if (redirect) router.push(redirect)
} catch (e) {
error.value = e.response?.data?.message || '로그인에 실패했습니다.'
} finally {
loading.value = false
}
}
function handleNaverLogin() {
alert('네이버 로그인은 준비 중입니다.')
}
// ESC 로 닫기
function onKeydown(e) {
if (e.key === 'Escape' && ui.loginOpen) ui.closeLogin()
}
onMounted(() => window.addEventListener('keydown', onKeydown))
onUnmounted(() => window.removeEventListener('keydown', onKeydown))
</script>
<template>
<Teleport to="body">
<Transition name="fade">
<div v-if="ui.loginOpen" class="modal-backdrop" @click.self="ui.closeLogin()">
<div class="modal" role="dialog" aria-modal="true" aria-label="로그인">
<button class="close" type="button" aria-label="닫기" @click="ui.closeLogin()">×</button>
<h2>로그인</h2>
<form class="form" @submit.prevent="handleLogin">
<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" />
<label class="remember">
<input v-model="form.rememberMe" type="checkbox" :disabled="loading" />
로그인 상태 유지
</label>
<button class="submit" type="submit" :disabled="loading">{{ loading ? '로그인 중...' : '로그인' }}</button>
</form>
<p v-if="error" class="msg error">{{ error }}</p>
<button type="button" class="naver" :disabled="loading" @click="handleNaverLogin">
네이버로 로그인 (준비 )
</button>
<p class="links">
계정이 없으신가요?
<a href="#" @click.prevent="goSignup">회원가입</a>
</p>
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
.modal-backdrop {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
padding: 1rem;
}
.modal {
position: relative;
width: 100%;
max-width: 360px;
padding: 1.75rem 1.5rem 1.5rem;
background: var(--color-background);
border: 1px solid var(--color-border);
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}
.close {
position: absolute;
top: 0.5rem;
right: 0.6rem;
width: 2rem;
height: 2rem;
border: 0;
background: transparent;
color: var(--color-text);
font-size: 1.5rem;
line-height: 1;
cursor: pointer;
}
h2 {
margin-bottom: 1.25rem;
font-size: 1.3rem;
text-align: center;
}
.form {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.form input {
padding: 0.6rem 0.75rem;
border: 1px solid var(--color-border);
border-radius: 4px;
background: var(--color-background-soft);
color: var(--color-text);
}
.remember {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: 0.88rem;
cursor: pointer;
}
.remember input {
width: auto;
padding: 0;
cursor: pointer;
}
.submit,
.naver {
padding: 0.6rem 1rem;
border: 1px solid var(--color-border);
border-radius: 4px;
background: var(--color-background-soft);
color: var(--color-text);
cursor: pointer;
}
.submit:disabled,
.naver:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.naver {
width: 100%;
margin-top: 0.75rem;
border-color: #03c75a;
color: #03c75a;
}
.links {
margin-top: 1.25rem;
text-align: center;
font-size: 0.9rem;
}
.msg {
margin: 0.75rem 0 0;
}
.msg.error {
color: #c0392b;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.18s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>