152 lines
5.4 KiB
Vue
152 lines
5.4 KiB
Vue
<template>
|
|
<section class="flex min-h-dvh flex-col justify-center px-5 py-6 md:mx-auto md:max-w-sm">
|
|
<div class="mb-6">
|
|
<p class="text-sm font-semibold text-brand-700">Money</p>
|
|
<h1 class="mt-2 text-2xl font-bold tracking-normal text-ink">빠르게 기록하는 개인 가계부</h1>
|
|
<p class="mt-2 text-sm leading-6 text-muted">계정을 만들면 내 자산, 카테고리, 거래 내역을 따로 관리할 수 있습니다.</p>
|
|
</div>
|
|
|
|
<div class="mb-4 grid grid-cols-2 gap-2">
|
|
<button
|
|
type="button"
|
|
class="segmented-button border border-line py-2.5"
|
|
:class="{ 'border-red-500 text-red-600': mode === 'login' }"
|
|
@click="mode = 'login'"
|
|
>
|
|
로그인
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="segmented-button border border-line py-2.5"
|
|
:class="{ 'border-red-500 text-red-600': mode === 'register' }"
|
|
@click="mode = 'register'"
|
|
>
|
|
회원가입
|
|
</button>
|
|
</div>
|
|
|
|
<form class="space-y-3" @submit.prevent="submit">
|
|
<div v-if="mode === 'register'">
|
|
<label class="label" for="name">이름</label>
|
|
<input id="name" v-model="name" class="field" autocomplete="name" placeholder="표시 이름">
|
|
</div>
|
|
<div>
|
|
<label class="label" for="email">이메일</label>
|
|
<input id="email" v-model="email" class="field" type="email" autocomplete="email" placeholder="you@example.com" required>
|
|
</div>
|
|
<div>
|
|
<label class="label" for="password">비밀번호</label>
|
|
<input
|
|
id="password"
|
|
v-model="password"
|
|
class="field"
|
|
type="password"
|
|
:autocomplete="mode === 'login' ? 'current-password' : 'new-password'"
|
|
placeholder="8자 이상"
|
|
required
|
|
>
|
|
</div>
|
|
<p v-if="authNotice && !error" class="rounded-md bg-brand-50 px-3 py-2 text-sm font-semibold text-brand-700">
|
|
{{ authNotice }}
|
|
</p>
|
|
<p v-if="error" class="rounded-md bg-red-50 px-3 py-2 text-sm font-semibold text-red-600">
|
|
{{ error }}
|
|
</p>
|
|
<button class="btn-primary w-full" type="submit" :disabled="pending">
|
|
{{ pending ? '확인 중...' : mode === 'login' ? '로그인' : '가입하고 시작하기' }}
|
|
</button>
|
|
</form>
|
|
|
|
<button
|
|
v-if="isDev"
|
|
type="button"
|
|
class="btn-secondary mt-3 w-full border-dashed text-muted"
|
|
:disabled="pending"
|
|
@click="devLogin"
|
|
>
|
|
개발용 테스트 로그인
|
|
</button>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
type AuthMode = 'login' | 'register'
|
|
|
|
const mode = ref<AuthMode>('login')
|
|
const name = ref('')
|
|
const email = ref('')
|
|
const password = ref('')
|
|
const error = ref('')
|
|
const pending = ref(false)
|
|
const session = useState('session')
|
|
const route = useRoute()
|
|
const isDev = import.meta.dev
|
|
const redirectPath = computed(() => typeof route.query.redirect === 'string' ? route.query.redirect : '/')
|
|
const authNotice = computed(() => {
|
|
if (route.query.reason === 'expired') return '로그인 세션이 만료되었습니다. 다시 로그인해 주세요.'
|
|
if (route.query.reason === 'required') return '가계부를 보려면 먼저 로그인해 주세요.'
|
|
return ''
|
|
})
|
|
|
|
async function submit() {
|
|
const normalizedEmail = email.value.trim().toLowerCase()
|
|
if (!normalizedEmail || !normalizedEmail.includes('@')) {
|
|
error.value = '이메일 형식을 확인해 주세요.'
|
|
return
|
|
}
|
|
if (password.value.length < 8) {
|
|
error.value = '비밀번호는 8자 이상 입력해 주세요.'
|
|
return
|
|
}
|
|
|
|
pending.value = true
|
|
error.value = ''
|
|
try {
|
|
const endpoint = mode.value === 'login' ? '/api/auth/login' : '/api/auth/register'
|
|
session.value = await $fetch(endpoint, {
|
|
method: 'POST',
|
|
body: { name: name.value, email: normalizedEmail, password: password.value }
|
|
})
|
|
await navigateTo(redirectPath.value)
|
|
} catch (err: any) {
|
|
const status = err?.statusCode || err?.response?.status
|
|
const statusMessage = err?.statusMessage || err?.response?._data?.statusMessage || ''
|
|
if (mode.value === 'register' && status === 409) {
|
|
error.value = '이미 가입된 이메일입니다.'
|
|
} else if (mode.value === 'register' && statusMessage) {
|
|
error.value = statusMessage === 'Invalid registration'
|
|
? '이메일과 8자 이상 비밀번호를 확인해 주세요.'
|
|
: '가입 처리 중 문제가 생겼습니다. 잠시 후 다시 시도해 주세요.'
|
|
} else if (mode.value === 'register') {
|
|
error.value = '가입 처리 중 문제가 생겼습니다. 잠시 후 다시 시도해 주세요.'
|
|
} else if (!status) {
|
|
error.value = '서버에 연결할 수 없습니다. 네트워크 상태를 확인해 주세요.'
|
|
} else {
|
|
error.value = '이메일 또는 비밀번호를 확인해 주세요.'
|
|
}
|
|
} finally {
|
|
pending.value = false
|
|
}
|
|
}
|
|
|
|
async function devLogin() {
|
|
pending.value = true
|
|
error.value = ''
|
|
try {
|
|
session.value = await $fetch('/api/auth/dev-login', { method: 'POST' })
|
|
await navigateTo(redirectPath.value)
|
|
} catch (err: any) {
|
|
const status = err?.statusCode || err?.response?.status
|
|
error.value = status === 404
|
|
? '개발 환경에서만 사용할 수 있는 로그인입니다.'
|
|
: '개발용 로그인에 실패했습니다.'
|
|
} finally {
|
|
pending.value = false
|
|
}
|
|
}
|
|
|
|
watch(mode, () => {
|
|
error.value = ''
|
|
})
|
|
</script>
|