import { Loader2, Plus } from 'lucide-react' import { useEffect, useMemo, useState } from 'react' import { Link, useNavigate } from 'react-router-dom' import { waTemplateApi, whatsappApi, type WaManagedTemplate, type WhatsAppAccount } from '../../lib/api' const STATUS_LABEL: Record = { APPROVED: 'Approved', PENDING: 'Pending', REJECTED: 'Rejected', PAUSED: 'Paused', DISABLED: 'Disabled', IN_APPEAL: 'In appeal', } function statusLabel(status: string) { return STATUS_LABEL[status] || status.toLowerCase() } export function WhatsAppTemplatesPage() { const navigate = useNavigate() const [accounts, setAccounts] = useState([]) const [accountId, setAccountId] = useState('') const [rows, setRows] = useState([]) const [summary, setSummary] = useState({ total: 0, approved: 0, pending: 0, rejected: 0, paused: 0 }) const [warnings, setWarnings] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [guidelines, setGuidelines] = useState(false) const [search, setSearch] = useState('') const [category, setCategory] = useState('') const [language, setLanguage] = useState('') const [status, setStatus] = useState('') const [from, setFrom] = useState('') const [to, setTo] = useState('') function load(nextAccount = accountId) { setLoading(true) setError('') waTemplateApi.list(nextAccount || undefined) .then((payload) => { setRows(payload.templates) setSummary(payload.summary) setWarnings(payload.warnings) }) .catch((reason) => setError(reason instanceof Error ? reason.message : 'Templates could not be loaded')) .finally(() => setLoading(false)) } useEffect(() => { whatsappApi.accounts() .then(setAccounts) .catch(() => setAccounts([])) load('') // The first load is intentionally unfiltered. // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const filtered = useMemo(() => rows.filter((row) => { const haystack = `${row.name} ${row.meta_template_id || ''} ${(row.phones || []).join(' ')} ${row.display_phone || ''} ${row.connection_name || ''} ${row.account_name || ''}`.toLowerCase() if (search && !haystack.includes(search.toLowerCase())) return false if (category && row.category !== category) return false if (language && row.language !== language) return false if (status && row.status !== status) return false const updated = row.updated_at?.slice(0, 10) if (from && updated < from) return false if (to && updated > to) return false return true }), [rows, search, category, language, status, from, to]) const languages = [...new Set(rows.map((row) => row.language))].sort() const cards = [ ['Total templates', summary.total], ['Approved', summary.approved], ['Pending review', summary.pending], ['Rejected', summary.rejected], ['Paused', summary.paused], ] as const async function remove(row: WaManagedTemplate) { if (!window.confirm(`Delete ${row.name}? Meta removes it from this WhatsApp account.`)) return try { await waTemplateApi.remove(row.id) load() } catch (reason) { setError(reason instanceof Error ? reason.message : 'This template could not be deleted') } } return (
Communicate

Message templates

Create, manage, and monitor your WhatsApp message templates.

Create template
{guidelines && (

Template guidelines

  • Marketing is for offers and announcements. Utility is for an existing order or account. Authentication is only for one-time codes.
  • Names use lowercase letters, numbers, and underscores, and must be unique on the WhatsApp account for that language.
  • Meta reviews every template. A template stays pending until Meta approves it, and Meta can change the category.
  • Templates belong to the WhatsApp Business account, so every number on that account can use an approved template.
)} {error &&

{error}

} {warnings.map((warning) =>

{warning}

)}
{cards.map(([label, value]) => (
{label} {value}
))}
setSearch(event.target.value)} placeholder="Search by name or ID" aria-label="Search templates" /> setFrom(event.target.value)} aria-label="Updated from" /> setTo(event.target.value)} aria-label="Updated to" />
{loading &&
Loading templates…
} {!loading && accounts.length === 0 &&

Connect a WhatsApp number before creating templates. Open WhatsApp

} {!loading && accounts.length > 0 && filtered.length === 0 &&

{rows.length === 0 ? 'No templates yet. Create one and Meta will review it.' : 'No templates match these filters.'}

} {!loading && filtered.length > 0 && (
Template nameNumberConnectionCategoryLanguageStatusQualityMessages sentLast updatedActions
{filtered.map((row) => (
{(row.phones && row.phones.length ? row.phones : [row.display_phone]).filter(Boolean).join(', ') || 'Shared on the account'} {row.connection_name || row.account_name}{row.connection_method} {row.category.toLowerCase()} {row.language} {statusLabel(row.status)} {row.quality || '—'} {row.messages_sent} {row.updated_at ? new Date(row.updated_at).toLocaleDateString() : '—'} {['REJECTED', 'PAUSED', 'APPROVED'].includes(row.status) && }
))}
)}
) }