Add intelligent auto-archiving system that automatically moves sermons to the "Previous Sermons" list when they are 1 day past their most recent date. Features: - Auto-archive logic that checks both primary and additional sermon dates - Finds the most recent date across all dates for a sermon - Archives sermon 1 day after the most recent date has passed - Manual trigger via "Run Auto-Archive Now" button on admin page - Automatic daily execution via scheduled cleanup task - Clear admin UI with explanatory text and status messages - Manual archive/unarchive functionality preserved Implementation: - Added getMostRecentSermonDate() helper to find latest date from primary and additional dates - Added autoArchiveOldSermons() function to database utils - Created /api/sermons/auto-archive endpoint for manual triggering - Integrated into daily cleanup plugin schedule - Updated admin UI with auto-archive button and status indicators - Added unarchiveSermon() function for completeness The system runs automatically every 24 hours and can be manually triggered by admins. Sermons are moved to the previous sermons dropdown on the home page exactly 1 day after their final presentation date, ensuring the main page always shows current and upcoming content while preserving access to past sermons. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
832 lines
30 KiB
Vue
832 lines
30 KiB
Vue
<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>
|
||
<ClientOnly>
|
||
<Menu
|
||
:is-authenticated="isAuthenticated"
|
||
:is-admin="isAdmin"
|
||
:show-home="true"
|
||
:current-path="route.fullPath"
|
||
/>
|
||
</ClientOnly>
|
||
</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 space-y-4">
|
||
<div>
|
||
<button
|
||
@click="handleAutoArchive"
|
||
:disabled="autoArchiving"
|
||
class="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"
|
||
>
|
||
{{ autoArchiving ? 'Auto-Archiving...' : 'Run Auto-Archive Now' }}
|
||
</button>
|
||
<p class="text-xs text-gray-500 mt-2">
|
||
Automatically archive sermons that are 1 day past their most recent date.
|
||
This moves them to the "Previous Sermons" list on the home page.
|
||
This also runs automatically on a daily schedule.
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<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>
|
||
|
||
<div v-if="autoArchiveError" class="text-red-600 text-sm">
|
||
{{ autoArchiveError }}
|
||
</div>
|
||
<div v-if="autoArchiveSuccess" class="text-green-600 text-sm">
|
||
{{ autoArchiveSuccess }}
|
||
</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'
|
||
})
|
||
|
||
// Check authentication status
|
||
const { data: authData } = await useFetch('/api/auth/verify')
|
||
const isAuthenticated = computed(() => authData.value?.authenticated || false)
|
||
const isAdmin = computed(() => authData.value?.isAdmin || false)
|
||
|
||
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 autoArchiving = ref(false)
|
||
const autoArchiveError = ref('')
|
||
const autoArchiveSuccess = 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 handleAutoArchive() {
|
||
autoArchiveError.value = ''
|
||
autoArchiveSuccess.value = ''
|
||
autoArchiving.value = true
|
||
|
||
try {
|
||
const result = await $fetch('/api/sermons/auto-archive', {
|
||
method: 'POST'
|
||
})
|
||
|
||
autoArchiveSuccess.value = result.message
|
||
|
||
// Refresh sermon list after auto-archive
|
||
await refreshSermons()
|
||
} catch (e: any) {
|
||
autoArchiveError.value = e.data?.message || 'Failed to run auto-archive'
|
||
} finally {
|
||
autoArchiving.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>
|