Clarify auth states

This commit is contained in:
2026-08-14 13:29:26 +09:00
parent cbd43e16c2
commit c4072ed86b
5 changed files with 45 additions and 8 deletions

View File

@@ -86,3 +86,5 @@
- 내역 헤더의 검색과 필터 버튼을 `assets/icons/search.svg`, `assets/icons/tune.svg` 인라인 SVG로 교체했다.
- CSV 내보내기 컬럼을 한글 컬럼명과 앱의 거래 흐름에 맞는 순서로 정리하고 Excel 호환 BOM을 추가했다.
- 빈 데이터 상태 화면을 공통 컴포넌트로 분리하고 내역, 통계, 자산, 설정, 공유 화면에 적용했다.
- 로그인 화면에 세션 만료/로그인 필요 안내를 추가하고, 로그인 후 원래 접근하려던 화면으로 돌아가도록 했다.
- 로그인/회원가입 입력값을 요청 전에 먼저 검증하고 네트워크 오류 메시지를 더 명확하게 표시하도록 했다.

View File

@@ -56,3 +56,4 @@ user.email: zenn.message@gmail.com
- 의미 있는 수정 단위마다 변경 내역을 문서에 남기고 커밋/푸시한다.
- 원격 저장소는 `https://git.sori.studio/zenn/money.sori.studio.git`를 사용한다.
- 인증이 필요한 화면에서 로그인 화면으로 이동할 때는 로그인 필요/세션 만료처럼 사용자가 이유를 이해할 수 있는 안내를 함께 제공한다.

View File

@@ -2,6 +2,8 @@ export default defineNuxtRouteMiddleware(async (to) => {
if (to.path === '/login' || to.path.startsWith('/share/')) return
const session = useState<{ id: string; email: string; name: string } | null>('session')
const sessionCookie = useCookie<string | null>('money_session')
const authReason = sessionCookie.value ? 'expired' : 'required'
if (session.value === undefined) {
try {
@@ -12,6 +14,12 @@ export default defineNuxtRouteMiddleware(async (to) => {
}
if (!session.value) {
return navigateTo('/login')
return navigateTo({
path: '/login',
query: {
reason: authReason,
redirect: to.fullPath === '/' ? undefined : to.fullPath
}
})
}
})

View File

@@ -46,7 +46,12 @@
required
>
</div>
<p v-if="error" class="text-sm font-medium text-red-600">{{ error }}</p>
<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>
@@ -74,18 +79,35 @@ 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: email.value, password: password.value }
body: { name: name.value, email: normalizedEmail, password: password.value }
})
await navigateTo('/')
await navigateTo(redirectPath.value)
} catch (err: any) {
const status = err?.statusCode || err?.response?.status
const statusMessage = err?.statusMessage || err?.response?._data?.statusMessage || ''
@@ -97,6 +119,8 @@ async function submit() {
: '가입 처리 중 문제가 생겼습니다. 잠시 후 다시 시도해 주세요.'
} else if (mode.value === 'register') {
error.value = '가입 처리 중 문제가 생겼습니다. 잠시 후 다시 시도해 주세요.'
} else if (!status) {
error.value = '서버에 연결할 수 없습니다. 네트워크 상태를 확인해 주세요.'
} else {
error.value = '이메일 또는 비밀번호를 확인해 주세요.'
}
@@ -110,9 +134,12 @@ async function devLogin() {
error.value = ''
try {
session.value = await $fetch('/api/auth/dev-login', { method: 'POST' })
await navigateTo('/')
} catch {
error.value = '개발용 로그인에 실패했습니다.'
await navigateTo(redirectPath.value)
} catch (err: any) {
const status = err?.statusCode || err?.response?.status
error.value = status === 404
? '개발 환경에서만 사용할 수 있는 로그인입니다.'
: '개발용 로그인에 실패했습니다.'
} finally {
pending.value = false
}

View File

@@ -35,7 +35,6 @@
## 7. 인증과 사용자 경험
- 회원 탈퇴 또는 데이터 삭제 정책을 정한다.
- 로그인 실패, 세션 만료, 네트워크 오류 상태를 화면에서 더 명확히 보여준다.
## 8. 공유