Saving notes and username fixes

This commit is contained in:
2025-10-07 08:58:38 -04:00
parent 6b33f79737
commit 7fc1d79eeb
6 changed files with 371 additions and 2 deletions

View File

@@ -158,6 +158,9 @@
<h2 class="text-2xl font-semibold text-gray-900 mb-4">My Notes</h2>
<ClientOnly>
<div v-if="isAuthenticated">
<p class="mb-3 text-sm text-red-600 font-medium">
Notes are deleted if a Sermon is deleted by an admin. Please email or download notes to have a copy sent to you.
</p>
<textarea
v-model="notes"
@input="handleNotesChange"
@@ -171,6 +174,28 @@
</p>
<p v-else class="text-sm text-gray-500">Notes are automatically saved</p>
</div>
<div class="mt-4 flex gap-3">
<button
@click="emailNotes"
:disabled="emailStatus === 'sending'"
class="flex-1 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-blue-400 disabled:cursor-not-allowed font-medium transition-colors"
>
<span v-if="emailStatus === 'sending'">Sending...</span>
<span v-else>📧 Email Notes</span>
</button>
<button
@click="downloadNotes"
class="flex-1 px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 font-medium transition-colors"
>
📥 Download Notes
</button>
</div>
<p v-if="emailStatus === 'success'" class="mt-2 text-sm text-green-600">
Email sent successfully!
</p>
<p v-if="emailStatus === 'error'" class="mt-2 text-sm text-red-600">
Failed to send email. Please try again.
</p>
</div>
<div v-else class="bg-gray-50 rounded-lg p-8 text-center border-2 border-dashed border-gray-300">
<svg class="mx-auto h-12 w-12 text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -236,6 +261,7 @@ const fontSize = ref('medium')
// Notes state
const notes = ref('')
const saveStatus = ref('')
const emailStatus = ref('')
let saveTimeout: NodeJS.Timeout | null = null
// Load notes when sermon is loaded and user is authenticated
@@ -354,4 +380,40 @@ function formatDateRange(sermon: any) {
// Format all dates and join with " - "
return dates.map(formatWithDayName).join(' - ')
}
// Email notes function
const emailNotes = async () => {
if (!sermon.value) return
emailStatus.value = 'sending'
try {
await $fetch(`/api/notes/email/${sermon.value.id}`, {
method: 'POST'
})
emailStatus.value = 'success'
setTimeout(() => {
emailStatus.value = ''
}, 3000)
} catch (error) {
console.error('Failed to email notes:', error)
emailStatus.value = 'error'
setTimeout(() => {
emailStatus.value = ''
}, 3000)
}
}
// Download notes function
const downloadNotes = () => {
if (!sermon.value) return
// Create a link and trigger download
const link = document.createElement('a')
link.href = `/api/notes/download/${sermon.value.id}`
link.download = `sermon-notes-${sermon.value.slug}.txt`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
</script>

View File

@@ -609,7 +609,7 @@ async function handleUnarchive() {
async function handleDelete() {
if (!selectedSermonId.value) return
if (!confirm('Are you sure you want to permanently delete this sermon?')) {
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
}

View File

@@ -32,7 +32,7 @@ export default defineEventHandler(async (event) => {
})
}
setAuthCookie(event, username)
setAuthCookie(event, user.username)
return {
success: true,

View File

@@ -0,0 +1,122 @@
import { getSermonNote, getUserByUsername, getDatabase } from '~/server/utils/database'
import { getUserFromCookie } from '~/server/utils/auth'
export default defineEventHandler(async (event) => {
const username = getUserFromCookie(event)
if (!username) {
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}
const sermonId = parseInt(event.context.params?.sermonId || '')
if (!sermonId) {
throw createError({
statusCode: 400,
message: 'Invalid sermon ID'
})
}
// Get user info
const user = getUserByUsername(username)
if (!user) {
throw createError({
statusCode: 400,
message: 'User not found'
})
}
// Get sermon info
const db = getDatabase()
const sermon = db.prepare('SELECT * FROM sermons WHERE id = ?').get(sermonId) as any
if (!sermon) {
throw createError({
statusCode: 404,
message: 'Sermon not found'
})
}
// Get user's notes
const noteRecord = getSermonNote(user.id!, sermonId)
const userNotes = noteRecord?.notes || 'No notes taken'
// Format bible references
let bibleReferencesText = ''
try {
const refs = JSON.parse(sermon.bible_references)
bibleReferencesText = refs.map((ref: any) =>
`${ref.reference} (${ref.version})\n${ref.text}`
).join('\n\n')
} catch {
bibleReferencesText = sermon.bible_references
}
// Format date
const formatDate = (dateString: string) => {
const date = new Date(dateString + 'T00:00:00')
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
const dates = [sermon.date]
if (sermon.dates) {
try {
const additionalDates = JSON.parse(sermon.dates)
dates.push(...additionalDates)
} catch {}
}
const sermonDate = dates.map(formatDate).join(' - ')
// Create formatted text content
const textContent = `
================================================================================
SERMON NOTES
================================================================================
Title: ${sermon.title}
Date: ${sermonDate}
================================================================================
BIBLE REFERENCES
================================================================================
${bibleReferencesText}
================================================================================
PERSONAL APPLIANCE
================================================================================
${sermon.personal_appliance}
================================================================================
PASTOR'S CHALLENGE
================================================================================
${sermon.pastors_challenge}
================================================================================
MY NOTES
================================================================================
${userNotes}
================================================================================
New Life Christian Church
================================================================================
`.trim()
// Set headers for file download
setResponseHeaders(event, {
'Content-Type': 'text/plain; charset=utf-8',
'Content-Disposition': `attachment; filename="sermon-notes-${sermon.slug}.txt"`
})
return textContent
})

View File

@@ -0,0 +1,102 @@
import { getSermonNote, getSermonBySlug, getUserByUsername } from '~/server/utils/database'
import { getUserFromCookie } from '~/server/utils/auth'
import { sendSermonNotesEmail } from '~/server/utils/email'
export default defineEventHandler(async (event) => {
const username = getUserFromCookie(event)
if (!username) {
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}
const sermonId = parseInt(event.context.params?.sermonId || '')
if (!sermonId) {
throw createError({
statusCode: 400,
message: 'Invalid sermon ID'
})
}
// Get user info
const user = getUserByUsername(username)
if (!user || !user.email) {
throw createError({
statusCode: 400,
message: 'User email not found'
})
}
// Get sermon info
const db = getDatabase()
const sermon = db.prepare('SELECT * FROM sermons WHERE id = ?').get(sermonId) as any
if (!sermon) {
throw createError({
statusCode: 404,
message: 'Sermon not found'
})
}
// Get user's notes
const noteRecord = getSermonNote(user.id!, sermonId)
const userNotes = noteRecord?.notes || ''
// Format bible references
let bibleReferencesText = ''
try {
const refs = JSON.parse(sermon.bible_references)
bibleReferencesText = refs.map((ref: any) =>
`${ref.reference} (${ref.version})\n${ref.text}`
).join('\n\n')
} catch {
bibleReferencesText = sermon.bible_references
}
// Format date
const formatDate = (dateString: string) => {
const date = new Date(dateString + 'T00:00:00')
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
const dates = [sermon.date]
if (sermon.dates) {
try {
const additionalDates = JSON.parse(sermon.dates)
dates.push(...additionalDates)
} catch {}
}
const sermonDate = dates.map(formatDate).join(' - ')
// Send email
try {
await sendSermonNotesEmail(
user.email,
user.first_name || user.username,
sermon.title,
sermonDate,
bibleReferencesText,
sermon.personal_appliance,
sermon.pastors_challenge,
userNotes
)
return {
success: true,
message: 'Notes emailed successfully'
}
} catch (error) {
console.error('Failed to send email:', error)
throw createError({
statusCode: 500,
message: 'Failed to send email'
})
}
})

View File

@@ -37,3 +37,86 @@ export async function sendPasswordResetEmail(email: string, code: string) {
export function generateResetCode(): string {
return Math.floor(100000 + Math.random() * 900000).toString()
}
export async function sendSermonNotesEmail(
email: string,
firstName: string,
sermonTitle: string,
sermonDate: string,
bibleReferences: string,
personalAppliance: string,
pastorsChallenge: string,
userNotes: string
) {
const config = useRuntimeConfig()
const transporter = nodemailer.createTransport({
host: config.emailHost,
port: parseInt(config.emailPort),
secure: parseInt(config.emailPort) === 465,
auth: {
user: config.emailUser,
pass: config.emailPassword,
},
})
const mailOptions = {
from: config.emailFrom,
to: email,
subject: `Sermon Notes: ${sermonTitle}`,
text: `
Sermon Notes for ${firstName}
Title: ${sermonTitle}
Date: ${sermonDate}
Bible References:
${bibleReferences}
Personal Appliance:
${personalAppliance}
Pastor's Challenge:
${pastorsChallenge}
My Notes:
${userNotes || 'No notes taken'}
`,
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<h1 style="color: #333; border-bottom: 3px solid #4CAF50; padding-bottom: 10px;">Sermon Notes</h1>
<div style="margin: 20px 0;">
<h2 style="color: #4CAF50; margin-bottom: 5px;">${sermonTitle}</h2>
<p style="color: #666; margin-top: 0;">${sermonDate}</p>
</div>
<div style="background-color: #E3F2FD; padding: 15px; border-radius: 5px; margin: 20px 0;">
<h3 style="color: #1976D2; margin-top: 0;">Bible References</h3>
<div style="color: #333; white-space: pre-wrap;">${bibleReferences}</div>
</div>
<div style="background-color: #E8F5E9; padding: 15px; border-radius: 5px; margin: 20px 0;">
<h3 style="color: #388E3C; margin-top: 0;">Personal Appliance</h3>
<div style="color: #333; white-space: pre-wrap;">${personalAppliance}</div>
</div>
<div style="background-color: #F3E5F5; padding: 15px; border-radius: 5px; margin: 20px 0;">
<h3 style="color: #7B1FA2; margin-top: 0;">Pastor's Challenge</h3>
<div style="color: #333; white-space: pre-wrap;">${pastorsChallenge}</div>
</div>
<div style="background-color: #FFF9C4; padding: 15px; border-radius: 5px; margin: 20px 0;">
<h3 style="color: #F57F17; margin-top: 0;">My Notes</h3>
<div style="color: #333; white-space: pre-wrap;">${userNotes || '<em>No notes taken</em>'}</div>
</div>
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 12px;">
<p>This email was sent from New Life Christian Church Sermon Notes</p>
</div>
</div>
`,
}
await transporter.sendMail(mailOptions)
}