whatsapp integration
This commit is contained in:
parent
d077292bbc
commit
141ca9fda3
15 changed files with 578 additions and 1 deletions
139
frontend/src/features/whatsapp/WhatsAppSetupPage.tsx
Normal file
139
frontend/src/features/whatsapp/WhatsAppSetupPage.tsx
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import { Check, Copy, ExternalLink, MessageCircle } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { whatsappApi, type WhatsAppSetup, type WhatsAppWebhookEvent } from '../../lib/api'
|
||||
|
||||
function ConfigBadge({ ok, label }: { ok: boolean; label: string }) {
|
||||
return (
|
||||
<span className={`whatsapp-config-badge ${ok ? 'ok' : 'missing'}`}>
|
||||
<i /> {label}: {ok ? 'Configured' : 'Missing'}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function WhatsAppSetupPage() {
|
||||
const [setup, setSetup] = useState<WhatsAppSetup | null>(null)
|
||||
const [events, setEvents] = useState<WhatsAppWebhookEvent[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
const [copied, setCopied] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([whatsappApi.setup(), whatsappApi.events()])
|
||||
.then(([setupPayload, eventPayload]) => {
|
||||
setSetup(setupPayload)
|
||||
setEvents(eventPayload)
|
||||
})
|
||||
.catch((reason) => setError(reason instanceof Error ? reason.message : 'Unable to load WhatsApp setup'))
|
||||
.finally(() => setLoading(false))
|
||||
}, [])
|
||||
|
||||
async function copyValue(key: string, value: string) {
|
||||
await navigator.clipboard.writeText(value)
|
||||
setCopied(key)
|
||||
window.setTimeout(() => setCopied(null), 2000)
|
||||
}
|
||||
|
||||
if (loading) return <p className="explorer-loading">Loading WhatsApp setup…</p>
|
||||
|
||||
return (
|
||||
<div className="whatsapp-setup-page">
|
||||
<div className="account-heading">
|
||||
<span className="eyebrow">Communicate</span>
|
||||
<h1>WhatsApp</h1>
|
||||
<p>Configure Meta webhooks so Urmi can receive messages and delivery updates on behalf of your customers.</p>
|
||||
</div>
|
||||
|
||||
{error && <p className="auth-error">{error}</p>}
|
||||
|
||||
{setup && (
|
||||
<>
|
||||
<section className="panel whatsapp-panel">
|
||||
<div className="panel-heading">
|
||||
<div>
|
||||
<h2><MessageCircle size={18} /> Meta webhook configuration</h2>
|
||||
<p>Paste these into <strong>App Dashboard → WhatsApp → Configuration → Configure Webhooks</strong></p>
|
||||
</div>
|
||||
<a className="ghost-button" href={setup.docs_url} target="_blank" rel="noreferrer">
|
||||
Meta docs <ExternalLink size={14} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="whatsapp-config-row">
|
||||
<div>
|
||||
<span>Callback URL</span>
|
||||
<code>{setup.webhook_url}</code>
|
||||
</div>
|
||||
<button className="secondary-button" onClick={() => copyValue('url', setup.webhook_url)}>
|
||||
<Copy size={14} /> {copied === 'url' ? 'Copied' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="whatsapp-config-row">
|
||||
<div>
|
||||
<span>Verify token</span>
|
||||
<code>{setup.verify_token_configured ? 'Set in server env (WHATSAPP_VERIFY_TOKEN)' : 'Not configured'}</code>
|
||||
<small>Use the exact same string in Meta and in your <code>.env</code> file.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="whatsapp-badges">
|
||||
<ConfigBadge ok={setup.verify_token_configured} label="Verify token" />
|
||||
<ConfigBadge ok={setup.app_secret_configured} label="App secret" />
|
||||
<ConfigBadge ok={setup.access_token_configured} label="Access token" />
|
||||
<ConfigBadge ok={Boolean(setup.phone_number_id)} label="Phone number ID" />
|
||||
<ConfigBadge ok={Boolean(setup.business_account_id)} label="WABA ID" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="panel whatsapp-panel">
|
||||
<div className="panel-heading">
|
||||
<div>
|
||||
<h2>Subscribe to webhook fields</h2>
|
||||
<p>In Meta, subscribe to these fields after verifying the callback URL.</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="whatsapp-field-list">
|
||||
{setup.recommended_fields.map((field) => (
|
||||
<li key={field}><Check size={14} /> <code>{field}</code></li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="relay-note">
|
||||
Minimum for messaging: <code>messages</code>. For a tech provider / BSP, also enable template and account fields.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="panel whatsapp-panel">
|
||||
<div className="panel-heading">
|
||||
<div>
|
||||
<h2>Recent webhook events</h2>
|
||||
<p>Incoming payloads from Meta (after verification succeeds).</p>
|
||||
</div>
|
||||
</div>
|
||||
{events.length === 0 ? (
|
||||
<p className="analytics-empty">No webhook events yet. Click <strong>Verify and save</strong> in Meta to send a test.</p>
|
||||
) : (
|
||||
<div className="logs-table">
|
||||
<div className="logs-table-head logs-cols-5">
|
||||
<span>Field</span>
|
||||
<span>Type</span>
|
||||
<span>From</span>
|
||||
<span>Status</span>
|
||||
<span>Time</span>
|
||||
</div>
|
||||
{events.map((event) => (
|
||||
<div className="logs-table-row logs-cols-5" key={event.id}>
|
||||
<span>{event.field}</span>
|
||||
<span>{event.event_type}</span>
|
||||
<span>{event.from_wa_id || '—'}</span>
|
||||
<span>{event.status || '—'}</span>
|
||||
<span>{new Date(event.created_at).toLocaleString()}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue