Files
nlcc-itinerary/pages/admin.vue
Joshua Ryder 1cb0a4e9a2 feat: Add consistent mobile hamburger menu across all pages
- Updated profile.vue to use MobileMenu on mobile, desktop buttons on desktop
- Updated admin.vue to use MobileMenu on mobile, desktop buttons on desktop
- Updated users.vue to use MobileMenu on mobile, desktop buttons on desktop
- All pages with header navigation now have consistent mobile UX
- Mobile menu provides clean, organized navigation with all options
- Desktop retains traditional button layout for familiarity

This ensures consistent navigation experience across the entire application,
with the hamburger menu appearing on all pages with headers when viewed
on mobile devices.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 09:29:34 -05:00

799 lines
28 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 flex flex-col">
<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>
<!-- Mobile Menu -->
<div class="md:hidden">
<ClientOnly>
<MobileMenu
:is-authenticated="true"
:is-admin="true"
:show-home="true"
:current-path="route.fullPath"
/>
</ClientOnly>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center gap-4">
<NuxtLink
to="/"
class="px-4 py-2 text-sm font-medium text-blue-700 bg-blue-50 rounded-md hover:bg-blue-100"
>
Home
</NuxtLink>
<button
@click="handleLogout"
class="px-4 py-2 text-sm font-medium text-red-700 bg-red-50 rounded-md hover:bg-red-100"
>
Log Out
</button>
</div>
</div>
<h1 class="text-2xl font-bold text-gray-900">Manage Sermons</h1>
</div>
</header>
<main class="flex-1 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12 w-full">
<!-- 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) }}){{ sermon.archived ? ' [ARCHIVED]' : '' }}
</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
v-if="!isSelectedSermonArchived"
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>
<button
v-else
type="button"
@click="handleUnarchive"
:disabled="!selectedSermonId || archiving"
class="flex-1 px-6 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed font-medium"
>
{{ archiving ? 'Unarchiving...' : 'Unarchive' }}
</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">
Primary 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"
/>
<p class="text-xs text-gray-500 mt-1">This date will be used for the sermon URL and sorting</p>
</div>
<!-- Additional Dates Section -->
<div class="border-t pt-6">
<h3 class="text-lg font-semibold text-gray-900 mb-2">Additional Dates</h3>
<p class="text-sm text-gray-600 mb-4">Add more dates if this sermon will be presented multiple times (e.g., Saturday and Sunday services)</p>
<div class="space-y-3">
<div v-for="(date, index) in sermonDates" :key="index" class="flex gap-3">
<input
v-model="sermonDates[index]"
type="date"
class="flex-1 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
/>
<button
v-if="sermonDates.length > 1"
type="button"
@click="removeDate(index)"
class="px-3 py-2 bg-red-100 text-red-700 rounded-md hover:bg-red-200"
>
</button>
</div>
<button
type="button"
@click="addDate"
class="px-4 py-2 bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200 font-medium"
>
+ Add Date
</button>
</div>
</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>
<!-- Worship Songs Section -->
<div class="border-t pt-6">
<h2 class="text-xl font-semibold text-gray-900 mb-4">Worship Songs</h2>
<p class="text-sm text-gray-600 mb-4">Add songs that were sung during this service (optional)</p>
<div class="space-y-4">
<div v-for="(song, index) in worshipSongs" :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="flex-1">
<label :for="`song-name-${index}`" class="block text-sm font-medium text-gray-700 mb-1">
Song Name
</label>
<input
:id="`song-name-${index}`"
v-model="song.name"
type="text"
placeholder="e.g., Amazing Grace"
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 class="flex-1">
<label :for="`song-artist-${index}`" class="block text-sm font-medium text-gray-700 mb-1">
Artist/Composer
</label>
<input
:id="`song-artist-${index}`"
v-model="song.artist"
type="text"
placeholder="e.g., John Newton"
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="worshipSongs.length > 1"
type="button"
@click="removeSong(index)"
class="self-end px-3 py-2 bg-red-100 text-red-700 rounded-md hover:bg-red-200"
>
</button>
</div>
</div>
<button
type="button"
@click="addSong"
class="px-4 py-2 bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200 font-medium"
>
+ Add Song
</button>
</div>
</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>
<!-- Database Settings Section -->
<div class="bg-white shadow-lg rounded-lg p-8 mt-8">
<h2 class="text-2xl font-bold text-gray-900 mb-6">Database Settings</h2>
<div class="space-y-4">
<div>
<label for="retention-policy" class="block text-sm font-medium text-gray-700 mb-2">
Sermon Retention Policy
</label>
<p class="text-sm text-gray-600 mb-3">
Choose how long to keep sermons in the database. Older sermons will be automatically deleted based on this policy.
</p>
<select
id="retention-policy"
v-model="retentionPolicy"
@change="handleRetentionPolicyChange"
:disabled="savingRetentionPolicy"
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 disabled:opacity-50"
>
<option value="forever">Keep Forever</option>
<option value="1_month">1 Month</option>
<option value="3_months">3 Months</option>
<option value="6_months">6 Months</option>
<option value="1_year">1 Year</option>
<option value="3_years">3 Years</option>
<option value="5_years">5 Years</option>
<option value="10_years">10 Years</option>
</select>
</div>
<div v-if="retentionPolicyError" class="text-red-600 text-sm">
{{ retentionPolicyError }}
</div>
<div v-if="retentionPolicySuccess" class="text-green-600 text-sm">
{{ retentionPolicySuccess }}
</div>
<div class="border-t pt-4 mt-4">
<button
@click="handleManualCleanup"
:disabled="cleaningUp"
class="px-6 py-2 bg-orange-600 text-white rounded-md hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 disabled:opacity-50 disabled:cursor-not-allowed font-medium"
>
{{ cleaningUp ? 'Running Cleanup...' : 'Run Cleanup Now' }}
</button>
<p class="text-xs text-gray-500 mt-2">
Manually trigger the cleanup process to delete sermons according to the retention policy.
This will also run automatically on a daily schedule.
</p>
</div>
<div v-if="cleanupError" class="text-red-600 text-sm">
{{ cleanupError }}
</div>
<div v-if="cleanupSuccess" class="text-green-600 text-sm">
{{ cleanupSuccess }}
</div>
</div>
</div>
</main>
<Footer />
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
const route = useRoute()
// 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 sermonDates = ref([''])
const worshipSongs = ref([{ name: '', artist: '' }])
const formData = ref({
date: '',
title: '',
personal_appliance: '',
pastors_challenge: ''
})
const error = ref('')
const success = ref('')
const loading = ref(false)
// Database settings state
const retentionPolicy = ref('forever')
const savingRetentionPolicy = ref(false)
const retentionPolicyError = ref('')
const retentionPolicySuccess = ref('')
const cleaningUp = ref(false)
const cleanupError = ref('')
const cleanupSuccess = ref('')
// Fetch current retention policy
const { data: retentionPolicyData } = await useFetch('/api/settings/retention-policy')
if (retentionPolicyData.value) {
retentionPolicy.value = retentionPolicyData.value.retentionPolicy
}
// Check if selected sermon is archived
const isSelectedSermonArchived = computed(() => {
if (!selectedSermonId.value || !allSermons.value) return false
const sermon = allSermons.value.find((s: any) => s.id === parseInt(selectedSermonId.value))
return sermon?.archived === 1
})
function addReference() {
bibleReferences.value.push({ version: 'ESV', reference: '', text: '' })
}
function removeReference(index: number) {
bibleReferences.value.splice(index, 1)
}
function addDate() {
sermonDates.value.push('')
}
function removeDate(index: number) {
sermonDates.value.splice(index, 1)
}
function addSong() {
worshipSongs.value.push({ name: '', artist: '' })
}
function removeSong(index: number) {
worshipSongs.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)
// Filter valid dates and serialize as JSON
const validDates = sermonDates.value.filter(d => d)
const dates = validDates.length > 0 ? JSON.stringify(validDates) : null
// Filter valid songs and serialize as JSON
const validSongs = worshipSongs.value.filter(s => s.name)
const worship_songs = validSongs.length > 0 ? JSON.stringify(validSongs) : null
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,
dates,
worship_songs
}
if (editingSermonId.value) {
// Update existing sermon
await $fetch(`/api/sermons/update/${editingSermonId.value}`, {
method: 'PUT',
body
})
success.value = 'Sermon updated successfully!'
// Refresh sermon list
await refreshSermons()
// Don't redirect or reset form when editing - stay on page with success message
} 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: '' }]
sermonDates.value = ['']
worshipSongs.value = [{ name: '', artist: '' }]
}
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: '' }]
}
// Parse and load additional dates
try {
sermonDates.value = sermon.dates ? JSON.parse(sermon.dates) : ['']
} catch {
sermonDates.value = ['']
}
// Parse and load worship songs
try {
worshipSongs.value = sermon.worship_songs ? JSON.parse(sermon.worship_songs) : [{ name: '', artist: '' }]
} catch {
worshipSongs.value = [{ name: '', artist: '' }]
}
// 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 hidden from the main page.')) {
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 handleUnarchive() {
if (!selectedSermonId.value) return
if (!confirm('Are you sure you want to unarchive this sermon? It will be visible on the main page again.')) {
return
}
archiveError.value = ''
archiveSuccess.value = ''
archiving.value = true
try {
await $fetch(`/api/sermons/unarchive/${selectedSermonId.value}`, {
method: 'POST'
})
archiveSuccess.value = 'Sermon unarchived successfully!'
selectedSermonId.value = ''
await refreshSermons()
} catch (e: any) {
archiveError.value = e.data?.message || 'Failed to unarchive sermon'
} finally {
archiving.value = false
}
}
async function handleDelete() {
if (!selectedSermonId.value) return
if (!confirm('Are you sure you want to permanently delete this sermon?\n\n⚠ WARNING: Deleting this sermon will also delete everyone\'s sermon notes for this sermon. This action cannot be undone.')) {
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) {
// Add T00:00:00 to ensure the date is interpreted as local time, not UTC
const date = new Date(dateString + 'T00:00:00')
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
async function handleLogout() {
await $fetch('/api/auth/logout', { method: 'POST' })
await navigateTo('/login')
}
async function handleRetentionPolicyChange() {
retentionPolicyError.value = ''
retentionPolicySuccess.value = ''
savingRetentionPolicy.value = true
try {
await $fetch('/api/settings/retention-policy', {
method: 'POST',
body: {
retentionPolicy: retentionPolicy.value
}
})
retentionPolicySuccess.value = 'Retention policy saved successfully!'
// Clear success message after 3 seconds
setTimeout(() => {
retentionPolicySuccess.value = ''
}, 3000)
} catch (e: any) {
retentionPolicyError.value = e.data?.message || 'Failed to save retention policy'
} finally {
savingRetentionPolicy.value = false
}
}
async function handleManualCleanup() {
if (!confirm('Are you sure you want to run the cleanup process?\n\nThis will permanently delete all sermons older than the retention policy. This action cannot be undone.')) {
return
}
cleanupError.value = ''
cleanupSuccess.value = ''
cleaningUp.value = true
try {
const result = await $fetch('/api/sermons/cleanup', {
method: 'POST'
})
cleanupSuccess.value = result.message + ` (${result.deletedCount} sermon${result.deletedCount !== 1 ? 's' : ''} deleted)`
// Refresh sermon list after cleanup
await refreshSermons()
} catch (e: any) {
cleanupError.value = e.data?.message || 'Failed to run cleanup'
} finally {
cleaningUp.value = false
}
}
</script>