diff --git a/package.json b/package.json index 25da85f..b7dd235 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "1.0.10", + "version": "1.0.11", "type": "module", "scripts": { "dev": "vite", diff --git a/server/email.js b/server/email.js index 2c649d6..dd516db 100644 --- a/server/email.js +++ b/server/email.js @@ -386,11 +386,19 @@ export function buildAdminReplyTemplate({ recipientName, message }) { } export function buildQuestionAnsweredEmailTemplate({ firstName, question, answer, questionUrl }) { + const cfg = state.cachedSiteContent ?? {} + const subject = cfg.qaAnsweredEmailSubject?.trim() || 'Your question has been answered — Verse by Verse with Nate' + const bodyText = cfg.qaAnsweredEmailBody?.trim() || 'Nate has answered your question on Verse by Verse with Nate.' + const ctaLabel = cfg.qaAnsweredEmailCtaLabel?.trim() || 'Read Full Answer →' + const signoff = cfg.qaAnsweredEmailSignoff?.trim() || 'Grace and peace,\nNate' + const safeName = escapeHtml(firstName || 'friend') const safeQuestion = escapeHtml(question) const safeAnswer = escapeHtml(answer.length > 600 ? answer.slice(0, 597) + '…' : answer).replace(/\n/g, '
') const safeUrl = escapeHtml(questionUrl) - const text = `Hi ${firstName || 'friend'},\n\nYour question has been answered on Verse by Verse with Nate.\n\nYour question: ${question}\n\nAnswer: ${answer}\n\nRead it at: ${questionUrl}\n\nGrace and peace,\nNate` + const safeBody = escapeHtml(bodyText) + const safeSignoff = escapeHtml(signoff).replace(/\n/g, '
') + const text = `Hi ${firstName || 'friend'},\n\n${bodyText}\n\nYour question: ${question}\n\nAnswer: ${answer}\n\nRead it at: ${questionUrl}\n\n${signoff}` const html = `
@@ -406,7 +414,7 @@ export function buildQuestionAnsweredEmailTemplate({ firstName, question, answer @@ -429,7 +438,7 @@ export function buildQuestionAnsweredEmailTemplate({ firstName, question, answer

Hi ${safeName},

-

Nate has answered your question on Verse by Verse with Nate.

+

${safeBody}

Your question

${safeQuestion}

@@ -415,11 +423,12 @@ export function buildQuestionAnsweredEmailTemplate({ firstName, question, answer

Answer

${safeAnswer}

- Read Full Answer → + ${escapeHtml(ctaLabel)}
+

${safeSignoff}

You received this because you requested a notification when this question was answered.

` - return { subject: 'Your question has been answered — Verse by Verse with Nate', text, html } + return { subject, text, html } } // ── Transactional email senders ──────────────────────────────────────────── diff --git a/src/AdminPage.tsx b/src/AdminPage.tsx index f686789..1e13f7c 100644 --- a/src/AdminPage.tsx +++ b/src/AdminPage.tsx @@ -482,6 +482,12 @@ function EmailTemplatesPanel({ fieldKeys={['twoFaOtpEmailSubject','twoFaOtpEmailBody','twoFaOtpEmailExpiry']} openKey="2fa-otp" /> + + {renderSaveStatus()} ) @@ -990,6 +996,10 @@ const FIELDS: Array<{ key: StringField; label: string; multiline?: boolean; sect { key: 'twoFaOtpEmailSubject', label: 'Subject Line', section: 'email-templates' }, { key: 'twoFaOtpEmailBody', label: 'Body Text (shown above the code)', multiline: true, section: 'email-templates' }, { key: 'twoFaOtpEmailExpiry', label: 'Expiry / Security Note (shown below the code)', multiline: true, section: 'email-templates' }, + { key: 'qaAnsweredEmailSubject', label: 'Subject Line', section: 'email-templates' }, + { key: 'qaAnsweredEmailBody', label: 'Body Text (paragraph shown below the greeting)', multiline: true, section: 'email-templates' }, + { key: 'qaAnsweredEmailCtaLabel', label: 'Button Label', section: 'email-templates' }, + { key: 'qaAnsweredEmailSignoff', label: 'Signoff', multiline: true, section: 'email-templates' }, ] function normalizeStudies(siteContent: SiteContent): StudyProgram[] { diff --git a/src/content.ts b/src/content.ts index 542b18c..b5bea26 100644 --- a/src/content.ts +++ b/src/content.ts @@ -239,6 +239,11 @@ export interface SiteContent { twoFaOtpEmailSubject: string twoFaOtpEmailBody: string twoFaOtpEmailExpiry: string + // ── Q&A Answered Notification ── + qaAnsweredEmailSubject: string + qaAnsweredEmailBody: string + qaAnsweredEmailCtaLabel: string + qaAnsweredEmailSignoff: string // ── Header ── headerFollowLabel: string // ── Cookie banner ── @@ -430,6 +435,10 @@ export const DEFAULTS: SiteContent = { twoFaOtpEmailSubject: 'Your sign-in code — Verse by Verse with Nate', twoFaOtpEmailBody: 'Your two-factor sign-in code is below. Enter it to complete sign-in.', twoFaOtpEmailExpiry: 'This code expires in 10 minutes. If you did not request this, you can ignore this message.', + qaAnsweredEmailSubject: 'Your question has been answered — Verse by Verse with Nate', + qaAnsweredEmailBody: 'Nate has answered your question on Verse by Verse with Nate.', + qaAnsweredEmailCtaLabel: 'Read Full Answer →', + qaAnsweredEmailSignoff: 'Grace and peace,\nNate', headerFollowLabel: 'Follow on Spotify', cookieBannerText: 'We use optional analytics cookies to better understand how people find and engage with this content. By accepting, you allow us to measure how you interact with the site — no personal data is sold or shared.', customLinks: [],