Extract tabs nav, templates grid, editor split view, settings form, logs table, and data-loading/actions hook into _components/ and _hooks/. page.tsx reduced from 816 to 88 LOC. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
228 lines
6.8 KiB
TypeScript
228 lines
6.8 KiB
TypeScript
'use client'
|
|
|
|
import { useCallback, useEffect, useState } from 'react'
|
|
import {
|
|
API_BASE,
|
|
EmailTemplate,
|
|
SendLog,
|
|
Settings,
|
|
TabId,
|
|
TemplateType,
|
|
TemplateVersion,
|
|
getHeaders,
|
|
} from '../_types'
|
|
|
|
export function useEmailTemplates(activeTab: TabId) {
|
|
const [templates, setTemplates] = useState<EmailTemplate[]>([])
|
|
const [templateTypes, setTemplateTypes] = useState<TemplateType[]>([])
|
|
const [settings, setSettings] = useState<Settings | null>(null)
|
|
const [logs, setLogs] = useState<SendLog[]>([])
|
|
const [logsTotal, setLogsTotal] = useState(0)
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
|
|
|
|
// Editor state
|
|
const [selectedTemplate, setSelectedTemplate] = useState<EmailTemplate | null>(null)
|
|
const [editorSubject, setEditorSubject] = useState('')
|
|
const [editorHtml, setEditorHtml] = useState('')
|
|
const [editorVersion, setEditorVersion] = useState<TemplateVersion | null>(null)
|
|
const [saving, setSaving] = useState(false)
|
|
const [previewHtml, setPreviewHtml] = useState<string | null>(null)
|
|
|
|
// Settings form
|
|
const [settingsForm, setSettingsForm] = useState<Settings | null>(null)
|
|
const [savingSettings, setSavingSettings] = useState(false)
|
|
|
|
const loadTemplates = useCallback(async () => {
|
|
try {
|
|
const url = selectedCategory ? `${API_BASE}?category=${selectedCategory}` : API_BASE
|
|
const res = await fetch(url, { headers: getHeaders() })
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
const data = await res.json()
|
|
setTemplates(Array.isArray(data) ? data : [])
|
|
} catch (e: any) {
|
|
setError(e.message)
|
|
}
|
|
}, [selectedCategory])
|
|
|
|
const loadTypes = useCallback(async () => {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/types`, { headers: getHeaders() })
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setTemplateTypes(Array.isArray(data) ? data : [])
|
|
}
|
|
} catch { /* ignore */ }
|
|
}, [])
|
|
|
|
const loadSettings = useCallback(async () => {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/settings`, { headers: getHeaders() })
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setSettings(data)
|
|
setSettingsForm(data)
|
|
}
|
|
} catch { /* ignore */ }
|
|
}, [])
|
|
|
|
const loadLogs = useCallback(async () => {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/logs?limit=50`, { headers: getHeaders() })
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setLogs(data.logs || [])
|
|
setLogsTotal(data.total || 0)
|
|
}
|
|
} catch { /* ignore */ }
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
setLoading(true)
|
|
Promise.all([loadTemplates(), loadTypes(), loadSettings()])
|
|
.finally(() => setLoading(false))
|
|
}, [loadTemplates, loadTypes, loadSettings])
|
|
|
|
useEffect(() => {
|
|
if (activeTab === 'logs') loadLogs()
|
|
}, [activeTab, loadLogs])
|
|
|
|
useEffect(() => {
|
|
loadTemplates()
|
|
}, [selectedCategory, loadTemplates])
|
|
|
|
const openEditor = useCallback(async (template: EmailTemplate) => {
|
|
setSelectedTemplate(template)
|
|
setPreviewHtml(null)
|
|
|
|
if (template.latest_version) {
|
|
setEditorSubject(template.latest_version.subject)
|
|
setEditorHtml(template.latest_version.body_html)
|
|
setEditorVersion(template.latest_version)
|
|
} else {
|
|
// Load default content
|
|
try {
|
|
const res = await fetch(`${API_BASE}/default/${template.template_type}`, { headers: getHeaders() })
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setEditorSubject(data.default_subject || '')
|
|
setEditorHtml(data.default_body_html || '')
|
|
}
|
|
} catch { /* ignore */ }
|
|
setEditorVersion(null)
|
|
}
|
|
}, [])
|
|
|
|
const saveVersion = useCallback(async () => {
|
|
if (!selectedTemplate) return
|
|
setSaving(true)
|
|
try {
|
|
const res = await fetch(`${API_BASE}/${selectedTemplate.id}/versions`, {
|
|
method: 'POST',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify({
|
|
version: editorVersion ? `${parseFloat(editorVersion.version) + 0.1}` : '1.0',
|
|
language: 'de',
|
|
subject: editorSubject,
|
|
body_html: editorHtml,
|
|
}),
|
|
})
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
const version = await res.json()
|
|
setEditorVersion(version)
|
|
await loadTemplates()
|
|
} catch (e: any) {
|
|
setError(e.message)
|
|
} finally {
|
|
setSaving(false)
|
|
}
|
|
}, [selectedTemplate, editorSubject, editorHtml, editorVersion, loadTemplates])
|
|
|
|
const publishVersion = useCallback(async () => {
|
|
if (!editorVersion) return
|
|
setSaving(true)
|
|
try {
|
|
const res = await fetch(`${API_BASE}/versions/${editorVersion.id}/publish`, {
|
|
method: 'POST',
|
|
headers: getHeaders(),
|
|
})
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
const updated = await res.json()
|
|
setEditorVersion(updated)
|
|
await loadTemplates()
|
|
} catch (e: any) {
|
|
setError(e.message)
|
|
} finally {
|
|
setSaving(false)
|
|
}
|
|
}, [editorVersion, loadTemplates])
|
|
|
|
const loadPreview = useCallback(async () => {
|
|
if (!editorVersion) return
|
|
try {
|
|
const res = await fetch(`${API_BASE}/versions/${editorVersion.id}/preview`, {
|
|
method: 'POST',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify({ variables: {} }),
|
|
})
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setPreviewHtml(data.body_html)
|
|
}
|
|
} catch { /* ignore */ }
|
|
}, [editorVersion])
|
|
|
|
const saveSettings2 = useCallback(async () => {
|
|
if (!settingsForm) return
|
|
setSavingSettings(true)
|
|
try {
|
|
const res = await fetch(`${API_BASE}/settings`, {
|
|
method: 'PUT',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(settingsForm),
|
|
})
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setSettings(data)
|
|
setSettingsForm(data)
|
|
}
|
|
} catch (e: any) {
|
|
setError(e.message)
|
|
} finally {
|
|
setSavingSettings(false)
|
|
}
|
|
}, [settingsForm])
|
|
|
|
const initializeDefaults = useCallback(async () => {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/initialize`, {
|
|
method: 'POST',
|
|
headers: getHeaders(),
|
|
})
|
|
if (res.ok) {
|
|
await loadTemplates()
|
|
}
|
|
} catch (e: any) {
|
|
setError(e.message)
|
|
}
|
|
}, [loadTemplates])
|
|
|
|
return {
|
|
// Data
|
|
templates, templateTypes, settings, logs, logsTotal, loading, error,
|
|
selectedCategory,
|
|
// Editor
|
|
selectedTemplate, editorSubject, editorHtml, editorVersion, saving, previewHtml,
|
|
// Settings
|
|
settingsForm, savingSettings,
|
|
// Setters
|
|
setError, setSelectedCategory,
|
|
setEditorSubject, setEditorHtml,
|
|
setSettingsForm,
|
|
// Actions
|
|
openEditor, saveVersion, publishVersion, loadPreview,
|
|
saveSettings2, initializeDefaults,
|
|
}
|
|
}
|