Files
nlcc-itinerary/pages/admin.vue

471 lines
16 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.
<template>
<div class="min-h-screen bg-gray-50">
<header class="bg-white shadow-sm">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="flex items-center justify-between mb-4">
<NuxtLink to="/">
<img src="/logos/logo.png" alt="New Life Christian Church" class="h-16 w-auto cursor-pointer hover:opacity-80" />
</NuxtLink>
<button
@click="handleLogout"
class="text-sm font-medium text-red-600 hover:text-red-700"
>
Logout
</button>
</div>
<h1 class="text-2xl font-bold text-gray-900">Manage Sermons</h1>
</div>
</header>
<main class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<!-- Manage Existing Sermons Section -->
<div class="bg-white shadow-lg rounded-lg p-8 mb-8">
<h2 class="text-2xl font-bold text-gray-900 mb-6">Select Sermon to Manage</h2>
<div v-if="allSermons && allSermons.length > 0" class="space-y-4">
<div class="space-y-4">
<div>
<label for="sermon-select" class="block text-sm font-medium text-gray-700 mb-2">
Choose a Sermon
</label>
<select
id="sermon-select"
v-model="selectedSermonId"
class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
>
<option value="">-- Choose a sermon --</option>
<option v-for="sermon in allSermons" :key="sermon.id" :value="sermon.id">
{{ sermon.title }} ({{ formatDate(sermon.date) }})
</option>
</select>
</div>
<div class="space-y-3">
<div class="flex gap-4">
<button
type="button"
@click="handleEdit"
:disabled="!selectedSermonId"
class="flex-1 px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed font-medium"
>
Edit
</button>
<button
type="button"
@click="handleArchive"
:disabled="!selectedSermonId || archiving"
class="flex-1 px-6 py-2 bg-yellow-600 text-white rounded-md hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 disabled:opacity-50 disabled:cursor-not-allowed font-medium"
>
{{ archiving ? 'Archiving...' : 'Archive' }}
</button>
</div>
<button
type="button"
@click="handleDelete"
:disabled="!selectedSermonId || deleting"
class="w-full px-6 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 disabled:opacity-50 disabled:cursor-not-allowed font-medium"
>
{{ deleting ? 'Deleting...' : 'Delete' }}
</button>
</div>
</div>
<div v-if="deleteError || archiveError" class="text-red-600 text-sm">
{{ deleteError || archiveError }}
</div>
<div v-if="deleteSuccess || archiveSuccess" class="text-green-600 text-sm">
{{ deleteSuccess || archiveSuccess }}
</div>
</div>
<div v-else class="text-gray-500 text-sm">
No sermons available to manage.
</div>
</div>
<!-- Create/Edit Sermon Form -->
<form @submit.prevent="handleSubmit" class="bg-white shadow-lg rounded-lg p-8 space-y-8">
<div class="flex items-center justify-between">
<h2 class="text-2xl font-bold text-gray-900">
{{ editingSermonId ? 'Edit Sermon' : 'Create New Sermon' }}
</h2>
<button
v-if="editingSermonId"
type="button"
@click="cancelEdit"
class="text-sm text-gray-600 hover:text-gray-800"
>
Cancel Edit
</button>
</div>
<!-- Basic Info -->
<div class="space-y-6">
<div>
<label for="date" class="block text-sm font-medium text-gray-700 mb-2">
Sermon Date
</label>
<input
id="date"
v-model="formData.date"
type="date"
required
class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label for="title" class="block text-sm font-medium text-gray-700 mb-2">
Sermon Title
</label>
<input
id="title"
v-model="formData.title"
type="text"
required
placeholder="Enter sermon title"
class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
<!-- Section 1: Bible References -->
<div class="border-t pt-6">
<h2 class="text-xl font-semibold text-gray-900 mb-4">Section 1: Bible References</h2>
<div class="space-y-6">
<div v-for="(ref, index) in bibleReferences" :key="index" class="border border-gray-200 rounded-lg p-4 space-y-3">
<div class="flex flex-col md:flex-row gap-3">
<div class="w-full md:w-64">
<label :for="`version-${index}`" class="block text-sm font-medium text-gray-700 mb-1">
Bible Version
</label>
<select
:id="`version-${index}`"
v-model="ref.version"
class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
>
<option value="ESV">English Standard Version (ESV)</option>
<option value="NKJV">New King James Version (NKJV)</option>
<option value="NIV">New International Version (NIV)</option>
<option value="KJV">King James Version (KJV)</option>
<option value="NLT">New Living Translation (NLT)</option>
<option value="NASB">New American Standard Bible (NASB)</option>
<option value="CSB">Christian Standard Bible (CSB)</option>
</select>
</div>
<div class="flex-1 flex gap-3">
<div class="flex-1">
<label :for="`reference-${index}`" class="block text-sm font-medium text-gray-700 mb-1">
Book & Verses
</label>
<input
:id="`reference-${index}`"
v-model="ref.reference"
type="text"
placeholder="e.g., John 3:16 or Romans 8:28-30"
class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<button
v-if="bibleReferences.length > 1"
type="button"
@click="removeReference(index)"
class="self-end px-3 py-2 bg-red-100 text-red-700 rounded-md hover:bg-red-200 md:block"
>
</button>
</div>
</div>
<div>
<label :for="`text-${index}`" class="block text-sm font-medium text-gray-700 mb-1">
Verse Text
</label>
<textarea
:id="`text-${index}`"
v-model="ref.text"
rows="3"
placeholder="Paste the verse text here..."
class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
></textarea>
</div>
</div>
<button
type="button"
@click="addReference"
class="px-4 py-2 bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200 font-medium"
>
+ Add Reference
</button>
</div>
</div>
<!-- Section 2: Personal Appliance -->
<div class="border-t pt-6">
<h2 class="text-xl font-semibold text-gray-900 mb-4">Section 2: Personal Appliance</h2>
<textarea
v-model="formData.personal_appliance"
rows="6"
required
placeholder="Enter personal appliance content..."
class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
></textarea>
</div>
<!-- Section 3: Pastor's Challenge -->
<div class="border-t pt-6">
<h2 class="text-xl font-semibold text-gray-900 mb-4">Section 3: Pastor's Challenge</h2>
<textarea
v-model="formData.pastors_challenge"
rows="6"
required
placeholder="Enter pastor's challenge content..."
class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
></textarea>
</div>
<!-- Error/Success Messages -->
<div v-if="error" class="text-red-600 text-sm">
{{ error }}
</div>
<div v-if="success" class="text-green-600 text-sm">
{{ success }}
</div>
<!-- Submit Button -->
<div class="flex gap-4">
<button
type="submit"
:disabled="loading"
class="flex-1 py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
>
{{ loading ? (editingSermonId ? 'Updating...' : 'Creating...') : (editingSermonId ? 'Update Sermon' : 'Create Sermon') }}
</button>
<NuxtLink
to="/"
class="px-6 py-3 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
>
Cancel
</NuxtLink>
</div>
</form>
</main>
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
// Fetch all sermons for management (including archived)
const { data: allSermons, refresh: refreshSermons } = await useFetch('/api/sermons?includeArchived=true')
// Sermon management state
const selectedSermonId = ref('')
const deleteError = ref('')
const deleteSuccess = ref('')
const deleting = ref(false)
const archiving = ref(false)
const archiveError = ref('')
const archiveSuccess = ref('')
// Create sermon form state
const editingSermonId = ref<number | null>(null)
const bibleReferences = ref([{ version: 'ESV', reference: '', text: '' }])
const formData = ref({
date: '',
title: '',
personal_appliance: '',
pastors_challenge: ''
})
const error = ref('')
const success = ref('')
const loading = ref(false)
function addReference() {
bibleReferences.value.push({ version: 'ESV', reference: '', text: '' })
}
function removeReference(index: number) {
bibleReferences.value.splice(index, 1)
}
function formatDateToSlug(date: string) {
// Convert YYYY-MM-DD to MMDDYYYY
const [year, month, day] = date.split('-')
return `sermon-${month}${day}${year}`
}
async function handleSubmit() {
error.value = ''
success.value = ''
loading.value = true
try {
const slug = formatDateToSlug(formData.value.date)
// Filter out empty references and serialize as JSON
const validReferences = bibleReferences.value.filter(ref =>
ref.version && ref.reference && ref.text
)
const bible_references = JSON.stringify(validReferences)
const body = {
slug,
title: formData.value.title,
date: formData.value.date,
bible_references,
personal_appliance: formData.value.personal_appliance,
pastors_challenge: formData.value.pastors_challenge
}
if (editingSermonId.value) {
// Update existing sermon
await $fetch(`/api/sermons/update/${editingSermonId.value}`, {
method: 'PUT',
body
})
success.value = 'Sermon updated successfully!'
} else {
// Create new sermon
await $fetch('/api/sermons', {
method: 'POST',
body
})
success.value = 'Sermon created successfully!'
}
// Reset form
editingSermonId.value = null
formData.value = {
date: '',
title: '',
personal_appliance: '',
pastors_challenge: ''
}
bibleReferences.value = [{ version: 'ESV', reference: '', text: '' }]
// Refresh sermon list
await refreshSermons()
// Redirect after 2 seconds
setTimeout(() => {
navigateTo('/')
}, 2000)
} catch (e: any) {
error.value = e.data?.message || `Failed to ${editingSermonId.value ? 'update' : 'create'} sermon`
} finally {
loading.value = false
}
}
function cancelEdit() {
editingSermonId.value = null
formData.value = {
date: '',
title: '',
personal_appliance: '',
pastors_challenge: ''
}
bibleReferences.value = [{ version: 'ESV', reference: '', text: '' }]
}
function handleEdit() {
if (!selectedSermonId.value) return
const sermon = allSermons.value?.find((s: any) => s.id === parseInt(selectedSermonId.value))
if (!sermon) return
// Set editing mode
editingSermonId.value = sermon.id
// Load sermon data into form
formData.value = {
date: sermon.date,
title: sermon.title,
personal_appliance: sermon.personal_appliance,
pastors_challenge: sermon.pastors_challenge
}
// Parse and load Bible references
try {
bibleReferences.value = JSON.parse(sermon.bible_references)
} catch {
// Fallback for old format
bibleReferences.value = [{ version: 'ESV', reference: sermon.bible_references, text: '' }]
}
// Scroll to form (just below the management section)
const formElement = document.querySelector('form')
if (formElement) {
formElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
async function handleArchive() {
if (!selectedSermonId.value) return
if (!confirm('Are you sure you want to archive this sermon? It will be moved to the previous sermons list.')) {
return
}
archiveError.value = ''
archiveSuccess.value = ''
archiving.value = true
try {
await $fetch(`/api/sermons/archive/${selectedSermonId.value}`, {
method: 'POST'
})
archiveSuccess.value = 'Sermon archived successfully!'
selectedSermonId.value = ''
await refreshSermons()
} catch (e: any) {
archiveError.value = e.data?.message || 'Failed to archive sermon'
} finally {
archiving.value = false
}
}
async function handleDelete() {
if (!selectedSermonId.value) return
if (!confirm('Are you sure you want to permanently delete this sermon?')) {
return
}
deleteError.value = ''
deleteSuccess.value = ''
deleting.value = true
try {
await $fetch(`/api/sermons/delete/${selectedSermonId.value}`, {
method: 'DELETE'
})
deleteSuccess.value = 'Sermon deleted successfully!'
selectedSermonId.value = ''
// Refresh the sermon list
await refreshSermons()
} catch (e: any) {
deleteError.value = e.data?.message || 'Failed to delete sermon'
} finally {
deleting.value = false
}
}
function formatDate(dateString: string) {
const date = new Date(dateString)
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
async function handleLogout() {
await $fetch('/api/auth/logout', { method: 'POST' })
await navigateTo('/login')
}
</script>