import { useEffect, useMemo, useState } from 'react'; import { EmbeddableShell } from '../components/shared/EmbeddableShell'; import { EmailDeliverySettings } from '../components/EmailDeliverySettings'; import { ButtonPrimary } from '../components/shared/buttons'; import { NavIcon } from '../components/NavIcon'; import { getSikshyaApi, SIKSHYA_ENDPOINTS } from '../api'; import { AsyncBoundary } from '../components/shared/AsyncBoundary'; import { SkeletonCard } from '../components/shared/Skeleton'; import { useAsyncData } from '../hooks/useAsyncData'; import { ApiErrorPanel } from '../components/shared/ApiErrorPanel'; import { useSikshyaDialog } from '../components/shared/SikshyaDialogContext'; import { SIKSHYA_ADMIN_PAGE_FULL_WIDTH } from '../constants/shellLayout'; import type { SikshyaReactConfig } from '../types'; import type { SettingsField, SettingsSection } from '../types/settingsSchema'; import { renderSettingsField } from './settingsRenderField'; import { normalizeTabSections } from './settingsTabUtils'; import { TopRightToast, useTopRightToast } from '../components/shared/TopRightToast'; import { __ } from '../lib/i18n'; type SettingsSchema = Record; function normalizeForDirtyCompare(v: unknown): string { if (v === null || v === undefined) return ''; if (typeof v === 'boolean') return v ? '1' : '0'; if (typeof v === 'number') return String(v); return String(v); } function stableNormalizeRecord(obj: Record): Record { const out: Record = {}; const keys = Object.keys(obj).sort(); for (const k of keys) { out[k] = normalizeForDirtyCompare(obj[k]); } return out; } /** * Full-width transactional email settings (moved out of Settings for breathing room). */ export function EmailPage(props: { config: SikshyaReactConfig; title: string; embedded?: boolean }) { const { config, title, embedded } = props; const { confirm } = useSikshyaDialog(); const tab = 'email'; const schema = useAsyncData(async () => { const res = await getSikshyaApi().get<{ success: boolean; data?: { tabs?: SettingsSchema } }>( SIKSHYA_ENDPOINTS.settings.schema ); if (!res.success) { throw new Error(__('Could not load settings schema.', 'sikshya')); } return { tabs: res.data?.tabs || {} }; }, []); const values = useAsyncData(async () => { const res = await getSikshyaApi().get<{ success: boolean; data?: { values?: Record } }>( SIKSHYA_ENDPOINTS.settings.values(tab) ); if (!res.success) { throw new Error(__('Could not load settings values.', 'sikshya')); } return res.data?.values || {}; }, [tab]); const [draft, setDraft] = useState>({}); const [saving, setSaving] = useState(false); const [saveError, setSaveError] = useState(null); const [saveMsg, setSaveMsg] = useState(null); const toast = useTopRightToast(3800); const [initialValues, setInitialValues] = useState>({}); useEffect(() => { if (!values.data) return; setDraft(values.data); setInitialValues(values.data); setSaveMsg(null); setSaveError(null); toast.clear(); }, [values.data]); const tabSchema = normalizeTabSections((schema.data?.tabs || {})[tab]); const dirty = useMemo(() => { try { return JSON.stringify(stableNormalizeRecord(draft)) !== JSON.stringify(stableNormalizeRecord(initialValues)); } catch { return true; } }, [draft, initialValues]); const onSave = async () => { setSaving(true); setSaveError(null); setSaveMsg(null); toast.clear(); try { const res = await getSikshyaApi().post<{ success: boolean; message?: string; data?: { values?: Record } }>( SIKSHYA_ENDPOINTS.settings.save, { tab, values: draft } ); if (!res.success) { throw new Error(res.message || 'Save failed.'); } const next = res.data?.values || {}; setDraft(next); setInitialValues(next); const msg = res.message || 'Settings saved.'; setSaveMsg(msg); toast.success(__('Saved', 'sikshya'), msg); } catch (e) { setSaveError(e); toast.error(__('Save failed', 'sikshya'), e instanceof Error ? e.message : 'Could not save settings. Please try again.'); } finally { setSaving(false); } }; const onReset = async () => { const ok = await confirm({ title: __('Reset email settings?', 'sikshya'), message: __('Reset email settings to their default values?', 'sikshya'), variant: 'danger', confirmLabel: __('Reset', 'sikshya'), }); if (!ok) { return; } setSaving(true); setSaveError(null); setSaveMsg(null); toast.clear(); try { const res = await getSikshyaApi().post<{ success: boolean; message?: string }>(SIKSHYA_ENDPOINTS.settings.reset, { tab, }); if (!res.success) { throw new Error(res.message || 'Reset failed.'); } await values.refetch(); const msg = res.message || 'Settings reset.'; setSaveMsg(msg); toast.success(__('Reset', 'sikshya'), msg); } catch (e) { setSaveError(e); toast.error(__('Reset failed', 'sikshya'), e instanceof Error ? e.message : 'Could not reset settings. Please try again.'); } finally { setSaving(false); } }; const renderField = (f: SettingsField) => renderSettingsField(draft, setDraft, f); return (
{ schema.refetch(); values.refetch(); }} skeleton={
} >
Email

{__('Email delivery', 'sikshya')}

Configure how mail is sent and wrapped. Enable or edit individual messages on the Email templates screen.

{saveMsg ?

{saveMsg}

: null}
void onSave()}> {saving ? __('Saving…', 'sikshya') : __('Save email settings', 'sikshya')}
{saveError ? (
setSaveError(null)} />
) : null}
{tabSchema.length ? ( ) : (

{__('No email settings defined.', 'sikshya')}

)}
); }