import { Loader2 } from 'lucide-react' import { useEffect, useState, type FormEvent } from 'react' import { Link, useSearchParams } from 'react-router-dom' import { whatsappApi, type WhatsAppAccount, type WhatsAppTemplateRow } from '../../lib/api' const TEAM_TEMPLATE_NAME = 'urmi_team_aircheck' const TEAM_TEMPLATE_BODY = "Team, the broadcast desk is on air. This is the first note Urmi is sending through Meta's marketing line, and it is written for the people building it. Read it on your phone, then bring it to standup so we can close the test." type BroadcastResult = { template_name: string sent: number failed: number results: { to: string; status: string; error?: string; message_id?: string | null }[] } type CreatedTemplate = { name: string status: string } export function WhatsAppBroadcastPage() { const [params] = useSearchParams() const [accounts, setAccounts] = useState([]) const [accountId, setAccountId] = useState('') const [templates, setTemplates] = useState([]) const [templatesReady, setTemplatesReady] = useState(false) const [templateName, setTemplateName] = useState(TEAM_TEMPLATE_NAME) const [templateLanguage, setTemplateLanguage] = useState('en') const [draftName, setDraftName] = useState(TEAM_TEMPLATE_NAME) const [draftBody, setDraftBody] = useState(TEAM_TEMPLATE_BODY) const [creating, setCreating] = useState(false) const [created, setCreated] = useState(null) const [templateTick, setTemplateTick] = useState(0) const [recipients, setRecipients] = useState('') const [bodyValues, setBodyValues] = useState('') const [strict, setStrict] = useState(true) const [loading, setLoading] = useState(true) const [busy, setBusy] = useState(false) const [error, setError] = useState('') const [result, setResult] = useState(null) useEffect(() => { whatsappApi.accounts() .then((rows) => { const ready = rows.filter((row) => row.messaging_status === 'active' && row.phones.some((phone) => phone.status === 'connected')) setAccounts(ready) if (ready[0]) setAccountId(ready[0].id) }) .catch((reason) => setError(reason instanceof Error ? reason.message : 'Could not load WhatsApp accounts')) .finally(() => setLoading(false)) }, []) useEffect(() => { if (!accountId) return setTemplatesReady(false) whatsappApi.templates(accountId) .then((payload) => { const marketing = payload.templates.filter((item) => (item.category || '').toUpperCase() === 'MARKETING') setTemplates(marketing) setTemplatesReady(payload.available) const requested = params.get('template') const requestedLanguage = params.get('language') const approved = marketing.find((item) => item.name === requested) || marketing.find((item) => (item.status || '').toUpperCase() === 'APPROVED' && item.name) if (requested) setTemplateName(requested) if (requestedLanguage) setTemplateLanguage(requestedLanguage) if (approved?.name && !requested) { setTemplateName(approved.name) setTemplateLanguage(approved.language || 'en') } }) .catch(() => { setTemplates([]) setTemplatesReady(false) }) }, [accountId, templateTick]) async function createTemplate(event: FormEvent) { event.preventDefault() setCreating(true) setError('') setCreated(null) try { const payload = await whatsappApi.createMarketingTemplate(accountId, { name: draftName.trim(), language: 'en', body: draftBody.trim(), }) setCreated(payload) setTemplateName(payload.name) setTemplateLanguage(payload.language || 'en') setTemplateTick((value) => value + 1) } catch (reason) { setError(reason instanceof Error ? reason.message : 'Meta did not accept this template') } finally { setCreating(false) } } async function submit(event: FormEvent) { event.preventDefault() setBusy(true) setError('') setResult(null) const numbers = recipients.split(/[\n,]/).map((item) => item.trim()).filter(Boolean) const parameters = bodyValues.split('\n').map((item) => item.trim()).filter(Boolean) try { const payload = await whatsappApi.sendBroadcast(accountId, { template_name: templateName.trim(), template_language: templateLanguage.trim() || 'en', recipients: numbers, body_parameters: parameters, product_policy: strict ? 'STRICT' : 'CLOUD_API_FALLBACK', }) setResult(payload) } catch (reason) { setError(reason instanceof Error ? reason.message : 'Meta did not accept this broadcast') } finally { setBusy(false) } } const approved = templates.filter((item) => (item.status || '').toUpperCase() === 'APPROVED' && item.name) const pending = templates.filter((item) => (item.status || '').toUpperCase() !== 'APPROVED' && item.name) return (
Messages

WhatsApp broadcast

Sends a marketing template through Meta’s marketing endpoint. Chat replies stay on the WhatsApp page.

{!loading && accounts.length > 0 && (

Create the template here

Urmi submits this to Meta as a Marketing template on the connected account. Meta still has to approve it before a broadcast can send. That usually takes a few minutes.