feat: Implement dedicated login page and authentication middleware

This commit is contained in:
Ryderjj89
2025-10-01 19:55:49 -04:00
parent 4aa7198406
commit ce867de7bc
5 changed files with 115 additions and 131 deletions

View File

@@ -1,118 +0,0 @@
<template>
<Teleport to="body">
<UModal v-model="isOpen" :ui="{ width: 'sm:max-w-md' }" class="login-modal" :persistent="false">
<UCard>
<template #header>
<h3 class="text-lg font-semibold text-gray-900">Admin Login</h3>
</template>
<form @submit.prevent="handleSubmit" class="space-y-4">
<UFormGroup label="Username" name="username">
<UInput
v-model="form.username"
ref="usernameInput"
placeholder="Enter username"
:disabled="loading"
class="w-full"
/>
</UFormGroup>
<UFormGroup label="Password" name="password">
<UInput
v-model="form.password"
type="password"
placeholder="Enter password"
:disabled="loading"
class="w-full"
/>
</UFormGroup>
<div v-if="error" class="text-red-600 text-sm p-3 bg-red-50 border border-red-200 rounded-md">
{{ error }}
</div>
<div class="flex justify-end space-x-3 pt-4">
<UButton
type="button"
variant="outline"
color="gray"
:disabled="loading"
@click="close"
>
Cancel
</UButton>
<UButton
type="submit"
variant="solid"
color="primary"
:loading="loading"
>
Login
</UButton>
</div>
</form>
</UCard>
</UModal>
</Teleport>
</template>
<script setup lang="ts">
interface Props {
modelValue: boolean
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'success'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const isOpen = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
const form = reactive({
username: '',
password: ''
})
const loading = ref(false)
const error = ref('')
const handleSubmit = async () => {
if (!form.username || !form.password) {
error.value = 'Please enter both username and password'
return
}
loading.value = true
error.value = ''
try {
await $fetch('/api/auth/login', {
method: 'POST',
body: {
username: form.username,
password: form.password
}
})
emit('success')
close()
} catch (err: any) {
error.value = err.data?.statusMessage || 'Login failed'
} finally {
loading.value = false
}
}
const close = () => {
isOpen.value = false
form.username = ''
form.password = ''
error.value = ''
}
</script>

21
middleware/auth.ts Normal file
View File

@@ -0,0 +1,21 @@
export default defineNuxtRouteMiddleware(async (to, from) => {
const token = useCookie('auth_token')
if (!token.value && to.path === '/admin') {
// Redirect to login page if not authenticated and trying to access admin
return navigateTo('/login')
}
if (token.value) {
try {
// Verify token with server
await $fetch('/api/auth/verify')
} catch {
// If token is invalid, clear it and redirect to login
token.value = ''
if (to.path === '/admin') {
return navigateTo('/login')
}
}
}
})

View File

@@ -159,6 +159,10 @@
</template>
<script setup lang="ts">
definePageMeta({
middleware: ['auth']
})
const form = reactive({
title: '',
date: '',

View File

@@ -23,7 +23,7 @@
<div class="flex items-center space-x-4">
<UButton
v-if="!isLoggedIn"
@click="showLoginModal = true"
@click="navigateTo('/login')"
variant="outline"
color="primary"
>
@@ -78,12 +78,6 @@
</div>
</main>
<!-- Login Modal - Only show when triggered -->
<LoginModal
v-if="showLoginModal"
v-model="showLoginModal"
@success="handleLoginSuccess"
/>
</div>
</template>
@@ -93,7 +87,6 @@ import { format, parseISO, isAfter, subMonths } from 'date-fns'
const timeFilter = ref('3months')
const sermons = ref([])
const loading = ref(true)
const showLoginModal = ref(false)
const isLoggedIn = ref(false)
const timeOptions = [
@@ -134,9 +127,4 @@ onMounted(async () => {
await loadSermons()
})
const handleLoginSuccess = () => {
showLoginModal.value = false
isLoggedIn.value = true
}
</script>

89
pages/login.vue Normal file
View File

@@ -0,0 +1,89 @@
<template>
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<UCard class="w-full max-w-md">
<template #header>
<div class="flex flex-col items-center">
<img src="/logos/logo.png" alt="New Life Christian Church" class="h-16 mb-4" />
<h2 class="text-2xl font-bold text-gray-900">Admin Login</h2>
</div>
</template>
<form @submit.prevent="handleSubmit" class="space-y-6">
<UFormGroup label="Username" name="username" required>
<UInput
v-model="form.username"
placeholder="Enter username"
icon="i-heroicons-user"
size="lg"
variant="outline"
:disabled="loading"
/>
</UFormGroup>
<UFormGroup label="Password" name="password" required>
<UInput
v-model="form.password"
type="password"
placeholder="Enter password"
icon="i-heroicons-lock-closed"
size="lg"
variant="outline"
:disabled="loading"
/>
</UFormGroup>
<div v-if="error" class="text-red-600 text-sm p-3 bg-red-50 border border-red-200 rounded-md">
{{ error }}
</div>
<UButton
type="submit"
variant="solid"
color="primary"
size="lg"
block
:loading="loading"
>
Login
</UButton>
</form>
</UCard>
</div>
</template>
<script setup lang="ts">
const form = reactive({
username: '',
password: ''
})
const loading = ref(false)
const error = ref('')
const handleSubmit = async () => {
if (!form.username || !form.password) {
error.value = 'Please enter both username and password'
return
}
loading.value = true
error.value = ''
try {
await $fetch('/api/auth/login', {
method: 'POST',
body: {
username: form.username,
password: form.password
}
})
// Redirect to admin page on successful login
await navigateTo('/admin')
} catch (err: any) {
error.value = err.data?.statusMessage || 'Login failed'
} finally {
loading.value = false
}
}
</script>