email templates
This commit is contained in:
+154
-3
@@ -358,6 +358,114 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
|
||||
)
|
||||
}
|
||||
|
||||
function EmailTemplatesPanel({
|
||||
form,
|
||||
handleChange,
|
||||
renderSaveStatus,
|
||||
}: {
|
||||
form: SiteContent
|
||||
handleChange: (key: StringField, value: string) => void
|
||||
renderSaveStatus: () => React.ReactNode
|
||||
}) {
|
||||
const [expandedEmail, setExpandedEmail] = useState<string | null>(null)
|
||||
|
||||
function EmailTemplateCard({
|
||||
type, icon, title, trigger, autoVars, fieldKeys, openKey,
|
||||
}: {
|
||||
type: 'newsletter' | 'study' | 'transactional'
|
||||
icon: string
|
||||
title: string
|
||||
trigger: string
|
||||
autoVars?: string
|
||||
fieldKeys: string[]
|
||||
openKey: string
|
||||
}) {
|
||||
const isOpen = expandedEmail === openKey
|
||||
return (
|
||||
<div className={`admin-et-card admin-et-card--${type}${isOpen ? ' admin-et-card--open' : ''}`}>
|
||||
<button
|
||||
type="button"
|
||||
className="admin-et-card-header"
|
||||
onClick={() => setExpandedEmail(isOpen ? null : openKey)}
|
||||
>
|
||||
<span className="admin-et-card-icon" aria-hidden="true">{icon}</span>
|
||||
<div className="admin-et-card-meta">
|
||||
<div className="admin-et-card-title-row">
|
||||
<span className={`admin-et-badge admin-et-badge--${type}`}>{type}</span>
|
||||
<strong className="admin-et-card-title">{title}</strong>
|
||||
</div>
|
||||
<p className="admin-et-card-trigger">⚡ {trigger}</p>
|
||||
</div>
|
||||
<span className="admin-et-card-chevron">{isOpen ? '▲' : '▼'}</span>
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div className="admin-et-card-body">
|
||||
{autoVars && (
|
||||
<p className="admin-et-card-vars">
|
||||
<span className="admin-et-card-vars-label">Auto-inserted:</span> {autoVars}
|
||||
</p>
|
||||
)}
|
||||
{fieldKeys.map(key => {
|
||||
const field = FIELDS.find(f => f.key === key && f.section === 'email-templates')
|
||||
if (!field) return null
|
||||
return (
|
||||
<div className="admin-field" key={key}>
|
||||
<label htmlFor={`field-et-${key}`}>{field.label}</label>
|
||||
{field.multiline
|
||||
? <textarea id={`field-et-${key}`} rows={3} value={form[key as StringField] as string} onChange={e => handleChange(key as StringField, e.target.value)} />
|
||||
: <input id={`field-et-${key}`} type="text" value={form[key as StringField] as string} onChange={e => handleChange(key as StringField, e.target.value)} />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="admin-panel-section" aria-label="Email Templates">
|
||||
<div className="admin-panel-head">
|
||||
<h2>Email Templates</h2>
|
||||
<p>Every automated email the site sends — edit subject lines, body copy, and sign-offs. Publish to apply changes.</p>
|
||||
</div>
|
||||
|
||||
<EmailTemplateCard type="newsletter" icon="📬" title="Newsletter Welcome"
|
||||
trigger="Someone subscribes via the homepage form or contact page"
|
||||
autoVars="Subscriber's first name, Spotify / Apple / Amazon links"
|
||||
fieldKeys={['welcomeEmailSubject','welcomeEmailGreetingPrefix','welcomeEmailIntro','welcomeEmailCurrentSeries','welcomeEmailStartHereTitle','welcomeEmailStartHereSummary','welcomeEmailStartHereUrl','welcomeEmailStartHereLinkLabel','welcomeEmailWhatToExpect1','welcomeEmailWhatToExpect2','welcomeEmailWhatToExpect3','welcomeEmailScripture','welcomeEmailScriptureRef','welcomeEmailSignoff','welcomeEmailSpotifyUrl','welcomeEmailSpotifyBtnLabel','welcomeEmailAppleUrl','welcomeEmailAppleBtnLabel','welcomeEmailAmazonUrl']}
|
||||
openKey="newsletter-welcome" />
|
||||
|
||||
<EmailTemplateCard type="study" icon="📖" title="Study Account — New Account"
|
||||
trigger="A student creates a study account"
|
||||
autoVars="Student's display name, link to study hub, link to account page"
|
||||
fieldKeys={['studyWelcomeEmailSubject','studyWelcomeEmailBody','studyWelcomeEmailCtaLabel','studyWelcomeEmailCtaPath','studyWelcomeEmailSignoff']}
|
||||
openKey="study-welcome" />
|
||||
|
||||
<EmailTemplateCard type="study" icon="🗑️" title="Study Account — Account Deleted"
|
||||
trigger="A student (or admin) deletes a study account"
|
||||
autoVars="Student's display name, link to create a new account"
|
||||
fieldKeys={['studyDeletedEmailSubject','studyDeletedEmailBody','studyDeletedEmailCtaLabel','studyDeletedEmailCtaPath','studyDeletedEmailSignoff']}
|
||||
openKey="study-deleted" />
|
||||
|
||||
<EmailTemplateCard type="study" icon="🔔" title="Study Account — New Lesson Unlocked"
|
||||
trigger="A lesson releases for a student who has reminders turned on"
|
||||
autoVars="Student's name, lesson title, scripture reference, study track name — button links directly to the lesson (auto-generated, not editable)"
|
||||
fieldKeys={['studyReminderEmailSubjectPrefix','studyReminderEmailBody','studyReminderEmailCtaLabel','studyReminderEmailSignoff']}
|
||||
openKey="study-reminder" />
|
||||
|
||||
<EmailTemplateCard type="transactional" icon="🔐" title="Account Security — Email Change Confirmation"
|
||||
trigger="A student requests to change their account email address"
|
||||
autoVars="Button links to a one-time verification URL (auto-generated per request, not editable)"
|
||||
fieldKeys={['emailChangeSubject','emailChangeBody','emailChangeCtaLabel']}
|
||||
openKey="email-change" />
|
||||
|
||||
{renderSaveStatus()}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
interface Props {
|
||||
content: SiteContent
|
||||
onSave: (c: SiteContent) => void
|
||||
@@ -557,7 +665,7 @@ type AdminView =
|
||||
| 'podcast' | 'current-series' | 'episode-highlights' | 'podcast-checklist' | 'archived-series'
|
||||
| 'downloads' | 'custom-links' | 'content-blocks'
|
||||
| 'questions' | 'analytics' | 'assets' | 'colossians-study'
|
||||
| 'emails' | 'subscribers' | 'contacts' | 'study-users'
|
||||
| 'emails' | 'subscribers' | 'contacts' | 'study-users' | 'email-templates'
|
||||
| 'seo' | 'legal' | 'security' | 'brand' | 'global'
|
||||
|
||||
interface AdminSectionLink {
|
||||
@@ -597,6 +705,7 @@ const ADMIN_VIEW_OPTIONS: Array<{ group: string; options: Array<{ value: AdminVi
|
||||
{ value: 'contacts', label: 'Contacts' },
|
||||
{ value: 'subscribers', label: 'Subscribers' },
|
||||
{ value: 'study-users', label: 'Study Users' },
|
||||
{ value: 'email-templates', label: 'Email Templates' },
|
||||
{ value: 'analytics', label: 'Analytics' },
|
||||
{ value: 'assets', label: 'Asset Manager' },
|
||||
],
|
||||
@@ -661,7 +770,7 @@ const ADMIN_SECTION_LINKS: Partial<Record<AdminView, AdminSectionLink[]>> = {
|
||||
|
||||
type PodcastTab = 'current-series' | 'episode-highlights' | 'podcast-checklist' | 'archived-series'
|
||||
|
||||
type MainContentSection = 'hero' | 'start-here' | 'about' | 'contact' | 'series' | 'share' | 'prism' | 'global'
|
||||
type MainContentSection = 'hero' | 'start-here' | 'about' | 'contact' | 'series' | 'share' | 'prism' | 'global' | 'email-templates'
|
||||
|
||||
const BRAND_KIT_SWATCHES = [
|
||||
{ name: 'Rich Black', hex: '#0a0a08', role: 'Primary background' },
|
||||
@@ -792,7 +901,44 @@ const FIELDS: Array<{ key: StringField; label: string; multiline?: boolean; sect
|
||||
{ key: 'welcomeEmailWhatToExpect3', label: 'Welcome Email — What to Expect 3', multiline: true, section: 'global' },
|
||||
{ key: 'welcomeEmailScripture', label: 'Welcome Email — Scripture Text', multiline: true, section: 'global' },
|
||||
{ key: 'welcomeEmailScriptureRef', label: 'Welcome Email — Scripture Reference', section: 'global' },
|
||||
{ key: 'welcomeEmailSignoff', label: 'Welcome Email — Signoff HTML', multiline: true, section: 'global' },
|
||||
{ key: 'welcomeEmailSignoff', label: 'Signoff', multiline: true, section: 'global' },
|
||||
// ── Email Templates section ──
|
||||
{ key: 'welcomeEmailSubject', label: 'Subject Line', section: 'email-templates' },
|
||||
{ key: 'welcomeEmailGreetingPrefix', label: 'Greeting Prefix (before subscriber name)', section: 'email-templates' },
|
||||
{ key: 'welcomeEmailIntro', label: 'Intro Paragraph', multiline: true, section: 'email-templates' },
|
||||
{ key: 'welcomeEmailCurrentSeries', label: 'Current Series Blurb', multiline: true, section: 'email-templates' },
|
||||
{ key: 'welcomeEmailStartHereTitle', label: 'Start Here Episode Title', section: 'email-templates' },
|
||||
{ key: 'welcomeEmailStartHereSummary', label: 'Start Here Episode Summary', multiline: true, section: 'email-templates' },
|
||||
{ key: 'welcomeEmailStartHereUrl', label: 'Start Here Episode URL', section: 'email-templates' },
|
||||
{ key: 'welcomeEmailWhatToExpect1', label: 'What to Expect — Line 1', multiline: true, section: 'email-templates' },
|
||||
{ key: 'welcomeEmailWhatToExpect2', label: 'What to Expect — Line 2', multiline: true, section: 'email-templates' },
|
||||
{ key: 'welcomeEmailWhatToExpect3', label: 'What to Expect — Line 3', multiline: true, section: 'email-templates' },
|
||||
{ key: 'welcomeEmailScripture', label: 'Closing Scripture Text', multiline: true, section: 'email-templates' },
|
||||
{ key: 'welcomeEmailScriptureRef', label: 'Closing Scripture Reference', section: 'email-templates' },
|
||||
{ key: 'welcomeEmailSignoff', label: 'Signoff', multiline: true, section: 'email-templates' },
|
||||
{ key: 'welcomeEmailSpotifyUrl', label: 'Spotify URL', section: 'email-templates' },
|
||||
{ key: 'welcomeEmailSpotifyBtnLabel', label: '"Listen on Spotify" button label', section: 'email-templates' },
|
||||
{ key: 'welcomeEmailAppleUrl', label: 'Apple Podcasts URL', section: 'email-templates' },
|
||||
{ key: 'welcomeEmailAppleBtnLabel', label: '"Apple Podcasts" button label', section: 'email-templates' },
|
||||
{ key: 'welcomeEmailAmazonUrl', label: 'Amazon Music URL', section: 'email-templates' },
|
||||
{ key: 'welcomeEmailStartHereLinkLabel', label: '"Start Here" link label', section: 'email-templates' },
|
||||
{ key: 'studyWelcomeEmailSubject', label: 'Subject Line', section: 'email-templates' },
|
||||
{ key: 'studyWelcomeEmailBody', label: 'Body Paragraph', multiline: true, section: 'email-templates' },
|
||||
{ key: 'studyWelcomeEmailCtaLabel', label: 'Button Label', section: 'email-templates' },
|
||||
{ key: 'studyWelcomeEmailCtaPath', label: 'Button Link (path or full URL)', section: 'email-templates' },
|
||||
{ key: 'studyWelcomeEmailSignoff', label: 'Signoff', multiline: true, section: 'email-templates' },
|
||||
{ key: 'studyDeletedEmailSubject', label: 'Subject Line', section: 'email-templates' },
|
||||
{ key: 'studyDeletedEmailBody', label: 'Body Paragraph', multiline: true, section: 'email-templates' },
|
||||
{ key: 'studyDeletedEmailCtaLabel', label: 'Button Label', section: 'email-templates' },
|
||||
{ key: 'studyDeletedEmailCtaPath', label: 'Button Link (path or full URL)', section: 'email-templates' },
|
||||
{ key: 'studyDeletedEmailSignoff', label: 'Signoff', multiline: true, section: 'email-templates' },
|
||||
{ key: 'studyReminderEmailSubjectPrefix', label: 'Subject Prefix (lesson title auto-appended)', section: 'email-templates' },
|
||||
{ key: 'studyReminderEmailBody', label: 'Body Paragraph', multiline: true, section: 'email-templates' },
|
||||
{ key: 'studyReminderEmailCtaLabel', label: 'Button Label', section: 'email-templates' },
|
||||
{ key: 'studyReminderEmailSignoff', label: 'Signoff', multiline: true, section: 'email-templates' },
|
||||
{ key: 'emailChangeSubject', label: 'Subject Line', section: 'email-templates' },
|
||||
{ key: 'emailChangeBody', label: 'Body Text', multiline: true, section: 'email-templates' },
|
||||
{ key: 'emailChangeCtaLabel', label: 'Button Label', section: 'email-templates' },
|
||||
]
|
||||
|
||||
function normalizeStudies(siteContent: SiteContent): StudyProgram[] {
|
||||
@@ -4480,6 +4626,11 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* EMAIL TEMPLATES */}
|
||||
{adminView === 'email-templates' && (
|
||||
<EmailTemplatesPanel form={form} handleChange={handleChange} renderSaveStatus={renderSaveStatus} />
|
||||
)}
|
||||
|
||||
{/* QUESTIONS */}
|
||||
{adminView === 'questions' && (
|
||||
<section className="admin-panel-section" aria-label="Q&A Management">
|
||||
|
||||
Reference in New Issue
Block a user