Complete sermon management system with Nuxt 4, authentication, SQLite database, QR codes, and Docker deployment

This commit is contained in:
Ryderjj89
2025-09-29 18:59:31 -04:00
commit c033410c2e
25 changed files with 1510 additions and 0 deletions

154
pages/[slug].vue Normal file
View File

@@ -0,0 +1,154 @@
<template>
<div class="min-h-screen bg-gray-50">
<!-- Header -->
<header class="bg-white shadow-sm border-b">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center py-4">
<div class="flex items-center">
<img src="/logos/logo.png" alt="New Life Christian Church" class="h-10 w-auto mr-4" />
<UButton
@click="navigateTo('/')"
variant="ghost"
color="gray"
icon="i-heroicons-arrow-left"
>
Back to Sermons
</UButton>
</div>
<div>
<QRCodeButton :url="currentUrl" :title="sermon?.title" @click="showQRModal = $event" />
</div>
</div>
</div>
</header>
<main class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Loading State -->
<div v-if="loading" class="flex justify-center py-12">
<UIcon name="i-heroicons-arrow-path" class="animate-spin h-8 w-8 text-primary" />
</div>
<!-- Sermon Content -->
<div v-else-if="sermon">
<UCard>
<template #header>
<div class="text-center">
<h1 class="text-3xl font-bold text-gray-900">{{ sermon.title }}</h1>
<p class="text-lg text-gray-600 mt-2">
{{ formatDate(sermon.date) }}
</p>
</div>
</template>
<div class="space-y-8">
<!-- Bible References -->
<div v-if="sermon.bibleReferences && sermon.bibleReferences.length > 0">
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Bible References</h2>
<div class="grid gap-3 md:grid-cols-2">
<div
v-for="reference in sermon.bibleReferences"
:key="reference"
class="bg-red-50 border border-red-200 rounded-lg p-4"
>
<p class="text-red-800 font-medium">{{ reference }}</p>
</div>
</div>
</div>
<!-- Personal Application -->
<div v-if="sermon.personalApplication">
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Personal Application</h2>
<UCard class="bg-blue-50 border-blue-200">
<p class="text-blue-900 whitespace-pre-wrap">{{ sermon.personalApplication }}</p>
</UCard>
</div>
<!-- Pastor's Challenge -->
<div v-if="sermon.pastorChallenge">
<h2 class="text-2xl font-semibold text-gray-900 mb-4">Pastor's Challenge</h2>
<UCard class="bg-green-50 border-green-200">
<p class="text-green-900 whitespace-pre-wrap">{{ sermon.pastorChallenge }}</p>
</UCard>
</div>
</div>
</UCard>
</div>
<!-- Not Found -->
<div v-else class="text-center py-12">
<UIcon name="i-heroicons-exclamation-triangle" class="mx-auto h-12 w-12 text-gray-400" />
<h3 class="mt-2 text-sm font-medium text-gray-900">Sermon not found</h3>
<p class="mt-1 text-sm text-gray-500">
The sermon you're looking for doesn't exist.
</p>
<div class="mt-6">
<UButton @click="navigateTo('/')">
Back to Sermons
</UButton>
</div>
</div>
</main>
<!-- QR Code Modal -->
<QRCodeModal
v-model="showQRModal"
:url="currentUrl"
:title="sermon?.title"
/>
</div>
</template>
<script setup lang="ts">
import { format, parseISO } from 'date-fns'
interface Sermon {
id: number
title: string
date: string
slug: string
bibleReferences?: string[]
personalApplication?: string
pastorChallenge?: string
}
const route = useRoute()
const slug = route.params.slug as string
const sermon = ref<Sermon | null>(null)
const loading = ref(true)
const showQRModal = ref(false)
const currentUrl = computed(() => {
if (process.client) {
return window.location.pathname
}
return `/${slug}`
})
const formatDate = (dateString: string) => {
try {
return format(parseISO(dateString), 'MMMM d, yyyy')
} catch {
return dateString
}
}
const loadSermon = async () => {
loading.value = true
try {
const data = await $fetch(`/api/sermons/${slug}`)
sermon.value = data
} catch (error) {
console.error('Failed to load sermon:', error)
sermon.value = null
} finally {
loading.value = false
}
}
onMounted(() => {
if (slug) {
loadSermon()
}
})
</script>