import { useCallback, useEffect, useMemo, useState } from 'react'; import { EmbeddableShell } from '../components/shared/EmbeddableShell'; import { NavIcon } from '../components/NavIcon'; import { ButtonPrimary, ButtonSecondary, LinkButtonSecondary } from '../components/shared/buttons'; import { ApiErrorPanel } from '../components/shared/ApiErrorPanel'; import { getSikshyaApi, SIKSHYA_ENDPOINTS } from '../api'; import { appViewHref } from '../lib/appUrl'; import { useAdminRouting } from '../lib/adminRouting'; import { useSikshyaDialog } from '../components/shared/SikshyaDialogContext'; import type { SikshyaReactConfig } from '../types'; import type { EmailTemplateApi } from '../types/emailTemplate'; import { ToggleSwitch } from '../components/email/EmailTemplateForms'; import { TableSkeleton } from '../components/shared/Skeleton'; import { ListEmptyState } from '../components/shared/list'; import { RowActionsMenu, type RowActionItem } from '../components/shared/list/RowActionsMenu'; import { __ } from '../lib/i18n'; function categoryStyle(cat: string): string { const c = cat.toLowerCase(); if (c.includes('enroll')) { return 'bg-sky-100 text-sky-900 dark:bg-sky-950/50 dark:text-sky-200'; } if (c.includes('complet')) { return 'bg-emerald-100 text-emerald-900 dark:bg-emerald-950/50 dark:text-emerald-200'; } if (c.includes('cert')) { return 'bg-violet-100 text-violet-900 dark:bg-violet-950/50 dark:text-violet-200'; } if (c.includes('auto') || c.includes('account')) { return 'bg-amber-100 text-amber-950 dark:bg-amber-950/40 dark:text-amber-200'; } if (c.includes('commerce') || c.includes('payment') || c.includes('order')) { return 'bg-teal-100 text-teal-900 dark:bg-teal-950/50 dark:text-teal-200'; } return 'bg-slate-200/90 text-slate-800 dark:bg-slate-800 dark:text-slate-200'; } /** * Transactional email templates — list + bulk actions (matches Courses-style admin patterns). */ export function EmailTemplatesListPage(props: { config: SikshyaReactConfig; title: string; embedded?: boolean }) { const { config, title, embedded } = props; const dialog = useSikshyaDialog(); const { navigateHref } = useAdminRouting(); const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [actionBusy, setActionBusy] = useState(null); const [selected, setSelected] = useState>(() => new Set()); const [bulkBusy, setBulkBusy] = useState(false); const load = useCallback(async () => { setLoading(true); setError(null); try { const res = await getSikshyaApi().get<{ templates: EmailTemplateApi[] }>(SIKSHYA_ENDPOINTS.admin.emailTemplates); setRows(Array.isArray(res.templates) ? res.templates : []); setSelected(new Set()); } catch (e) { setError(e); setRows([]); } finally { setLoading(false); } }, []); useEffect(() => { void load(); }, [load]); const allIds = useMemo(() => rows.map((r) => r.id), [rows]); const allSelected = rows.length > 0 && selected.size === rows.length; const someSelected = selected.size > 0; const toggleSelectAll = () => { if (allSelected) { setSelected(new Set()); } else { setSelected(new Set(allIds)); } }; const toggleRow = (id: string) => { setSelected((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); }; const patchTemplate = async (id: string, body: Record) => { setActionBusy(id); try { const updated = await getSikshyaApi().patch(SIKSHYA_ENDPOINTS.admin.emailTemplate(id), body); setRows((prev) => prev.map((t) => (t.id === id ? { ...t, ...updated } : t))); return updated; } finally { setActionBusy(null); } }; const onToggleEnabled = async (row: EmailTemplateApi) => { if (row.locked) { return; } try { await patchTemplate(row.id, { enabled: !row.enabled }); } catch { /* ignore */ } }; const runBulk = async (action: 'enable' | 'disable' | 'delete') => { const ids = Array.from(selected); if (ids.length === 0) { return; } if (action === 'delete') { const ok = await dialog.confirm({ title: `Delete ${ids.length} template(s)?`, message: __('Only custom templates are removed. System templates cannot be deleted and will be skipped if selected.', 'sikshya'), confirmLabel: __('Delete', 'sikshya'), variant: 'danger', }); if (!ok) { return; } } setBulkBusy(true); try { await getSikshyaApi().post(SIKSHYA_ENDPOINTS.admin.emailTemplateBulk, { action, ids }); await load(); } catch (e) { setError(e); } finally { setBulkBusy(false); } }; const editHref = (id: string) => appViewHref(config, 'email-template-edit', { template_id: id }); const newHref = appViewHref(config, 'email-template-edit', { template_id: 'new' }); const addonsHref = appViewHref(config, 'addons'); return ( {__('Email delivery', 'sikshya')} void load()}> Refresh navigateHref(newHref)}> Add template } > {error ? (
void load()} />
) : null} {someSelected ? (
{selected.size} selected void runBulk('enable')}> {__('Enable', 'sikshya')} void runBulk('disable')}> {__('Disable', 'sikshya')} void runBulk('delete')} className="border-rose-200 text-rose-700 hover:bg-rose-50 dark:border-rose-900/50 dark:text-rose-200 dark:hover:bg-rose-950/40" > {__('Delete custom', 'sikshya')}
) : null}
{loading ? ( ) : ( {rows.map((row, idx) => ( ))} )}
Template Event Subject Category Send to Status {__('Actions', 'sikshya')}
toggleRow(row.id)} aria-label={`Select ${row.name}`} />
{ e.preventDefault(); navigateHref(editHref(row.id)); }} className="font-semibold text-brand-600 hover:underline dark:text-brand-400" > {row.name}
{row.template_type === 'system' ? __('System', 'sikshya') : __('Custom', 'sikshya')} {row.locked ? ( Add-on off ) : null}
{row.event} {row.subject} {row.category} {row.recipient_to || '{{student_email}}'} void onToggleEnabled(row)} label="Enabled" /> {(() => { const items: RowActionItem[] = [ { key: 'edit', label: row.locked ? __('View (locked)', 'sikshya') : __('Edit', 'sikshya'), onClick: () => navigateHref(editHref(row.id)), }, ]; if (row.locked) { items.push({ key: 'addons', label: __('Unlock via add-ons', 'sikshya'), href: addonsHref, }); } return ; })()}
{!loading && rows.length === 0 ? ( ) : null}
); }