'use client'; import { useEffect, useState, useCallback } from 'react'; import { toast } from 'sonner'; import { Eye, EyeOff, Copy, CheckCircle2, XCircle, Loader2, ExternalLink, Zap, AlertTriangle, RotateCcw, } from 'lucide-react'; import { createClient } from '@/lib/supabase/client'; import { useAuth } from '@/hooks/use-auth'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Accordion, AccordionItem, AccordionTrigger, AccordionContent, } from '@/components/ui/accordion'; import type { WhatsAppConfig as WhatsAppConfigType } from '@/types'; const MASKED_TOKEN = '••••••••••••••••'; type ConnectionStatus = 'connected' | 'disconnected' | 'unknown'; type ResetReason = 'token_corrupted' | 'meta_api_error' | null; export function WhatsAppConfig() { const supabase = createClient(); const { user, loading: authLoading } = useAuth(); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [testing, setTesting] = useState(false); const [resetting, setResetting] = useState(false); const [showToken, setShowToken] = useState(false); const [config, setConfig] = useState(null); const [connectionStatus, setConnectionStatus] = useState('unknown'); const [resetReason, setResetReason] = useState(null); const [statusMessage, setStatusMessage] = useState(''); const [phoneNumberId, setPhoneNumberId] = useState(''); const [wabaId, setWabaId] = useState(''); const [accessToken, setAccessToken] = useState(''); const [verifyToken, setVerifyToken] = useState(''); const [tokenEdited, setTokenEdited] = useState(false); const webhookUrl = typeof window !== 'undefined' ? `${window.location.origin}/api/whatsapp/webhook` : ''; const fetchConfig = useCallback(async (userId: string) => { setLoading(true); try { // Load form values from Supabase (shows what's in DB) const { data, error } = await supabase .from('whatsapp_config') .select('*') .eq('user_id', userId) .maybeSingle(); if (error) { console.error('Failed to load config row:', error); } if (data) { setConfig(data); setPhoneNumberId(data.phone_number_id || ''); setWabaId(data.waba_id || ''); setAccessToken(MASKED_TOKEN); setVerifyToken(''); setTokenEdited(false); } else { setConfig(null); setPhoneNumberId(''); setWabaId(''); setAccessToken(''); setVerifyToken(''); setTokenEdited(false); } // Then verify health via the API (decrypts token + pings Meta) if (data) { try { const res = await fetch('/api/whatsapp/config', { method: 'GET' }); const payload = await res.json(); if (payload.connected) { setConnectionStatus('connected'); setResetReason(null); setStatusMessage(''); } else { setConnectionStatus('disconnected'); setResetReason(payload.needs_reset ? 'token_corrupted' : payload.reason === 'meta_api_error' ? 'meta_api_error' : null); setStatusMessage(payload.message || ''); } } catch (err) { console.error('Health check failed:', err); setConnectionStatus('disconnected'); } } else { setConnectionStatus('disconnected'); setResetReason(null); setStatusMessage(''); } } catch (err) { console.error('fetchConfig error:', err); toast.error('Failed to load WhatsApp configuration'); } finally { setLoading(false); } }, [supabase]); useEffect(() => { if (authLoading) return; if (!user) { setLoading(false); return; } fetchConfig(user.id); }, [authLoading, user, fetchConfig]); async function handleSave() { if (!phoneNumberId.trim()) { toast.error('Phone Number ID is required'); return; } if (!config && (!accessToken.trim() || !tokenEdited)) { toast.error('Access Token is required for initial setup'); return; } try { setSaving(true); // Always POST through the API — it verifies with Meta and encrypts // the access_token server-side with ENCRYPTION_KEY. Skipping this // and writing direct to Supabase stores the token in plaintext, // which then fails decryption on every subsequent health check. const payload: Record = { phone_number_id: phoneNumberId.trim(), waba_id: wabaId.trim() || null, verify_token: verifyToken.trim() || null, }; if (tokenEdited && accessToken !== MASKED_TOKEN && accessToken.trim()) { payload.access_token = accessToken.trim(); } else if (config) { // Existing config — reuse stored encrypted token by decrypting on the // server. But our POST handler requires an access_token to verify // with Meta. If the user didn't change the token, we need to signal // that. Simplest: require token re-entry if they're updating. toast.error('Please re-enter the Access Token to save changes'); setSaving(false); return; } const res = await fetch('/api/whatsapp/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await res.json(); if (!res.ok) { toast.error(data.error || 'Failed to save configuration'); setSaving(false); return; } toast.success( data.phone_info?.verified_name ? `Connected to ${data.phone_info.verified_name}` : 'Configuration saved successfully' ); if (user) await fetchConfig(user.id); } catch (err) { console.error('Save error:', err); toast.error('Failed to save configuration'); } finally { setSaving(false); } } async function handleTestConnection() { try { setTesting(true); const res = await fetch('/api/whatsapp/config', { method: 'GET' }); const payload = await res.json(); if (payload.connected) { setConnectionStatus('connected'); setResetReason(null); setStatusMessage(''); toast.success( payload.phone_info?.verified_name ? `Connected to ${payload.phone_info.verified_name}` : 'API connection successful' ); } else { setConnectionStatus('disconnected'); setResetReason(payload.needs_reset ? 'token_corrupted' : payload.reason === 'meta_api_error' ? 'meta_api_error' : null); setStatusMessage(payload.message || ''); toast.error(payload.message || 'API connection failed'); } } catch (err) { console.error('Test connection error:', err); setConnectionStatus('disconnected'); toast.error('Connection test failed. Check network and try again.'); } finally { setTesting(false); } } async function handleReset() { if (!confirm('This will delete the current WhatsApp config so you can re-enter it. Continue?')) { return; } try { setResetting(true); const res = await fetch('/api/whatsapp/config', { method: 'DELETE' }); const data = await res.json(); if (!res.ok) { toast.error(data.error || 'Failed to reset configuration'); return; } toast.success('Configuration cleared. You can now re-enter your credentials.'); setConfig(null); setPhoneNumberId(''); setWabaId(''); setAccessToken(''); setVerifyToken(''); setTokenEdited(false); setConnectionStatus('disconnected'); setResetReason(null); setStatusMessage(''); } catch (err) { console.error('Reset error:', err); toast.error('Failed to reset configuration'); } finally { setResetting(false); } } function handleCopyWebhookUrl() { navigator.clipboard.writeText(webhookUrl); toast.success('Webhook URL copied to clipboard'); } if (loading) { return (
); } const showResetBanner = resetReason === 'token_corrupted'; return (
{/* Main config form */}
{/* Corrupted-token reset banner */} {showResetBanner && (
Stored token can't be decrypted {statusMessage}
)} {/* Connection Status */}
{connectionStatus === 'connected' ? ( ) : ( )} {connectionStatus === 'connected' ? 'Connected' : 'Not Connected'}
{connectionStatus === 'connected' ? 'Your WhatsApp Business API is connected and ready to send/receive messages.' : statusMessage || 'Configure your Meta API credentials below to connect your WhatsApp Business account.'}
{/* API Credentials */} API Credentials Enter your Meta WhatsApp Business API credentials.
setPhoneNumberId(e.target.value)} className="bg-slate-800 border-slate-700 text-white placeholder:text-slate-500" />
setWabaId(e.target.value)} className="bg-slate-800 border-slate-700 text-white placeholder:text-slate-500" />
{ setAccessToken(e.target.value); setTokenEdited(true); }} onFocus={() => { if (accessToken === MASKED_TOKEN) { setAccessToken(''); setTokenEdited(true); } }} className="bg-slate-800 border-slate-700 text-white placeholder:text-slate-500 pr-10" />
{config && !tokenEdited && (

Token is hidden for security. Re-enter it to update configuration.

)}
setVerifyToken(e.target.value)} className="bg-slate-800 border-slate-700 text-white placeholder:text-slate-500" />

A custom string you create. Must match the token you set in Meta webhook settings.

{/* Webhook URL */} Webhook Configuration Use this URL as your webhook callback in the Meta App Dashboard.
{/* Action Buttons */}
{config && ( )}
{/* Setup Instructions Sidebar */}
Setup Instructions Follow these steps to connect your WhatsApp Business API. 1 Create a Meta App
  1. Go to developers.facebook.com
  2. Click "My Apps" and then "Create App"
  3. Select "Business" as the app type
  4. Fill in app details and create
2 Add WhatsApp Product
  1. In your app dashboard, click "Add Product"
  2. Find "WhatsApp" and click "Set Up"
  3. Follow the setup wizard to link your business
3 Get API Credentials
  1. Go to WhatsApp > API Setup
  2. Copy your Phone Number ID
  3. Copy your WhatsApp Business Account ID
  4. Generate a Permanent Access Token from Business Settings > System Users
4 Configure Webhooks
  1. Go to WhatsApp > Configuration
  2. Click "Edit" on the Webhook section
  3. Paste the Webhook Callback URL from above
  4. Enter the same Verify Token you set here
  5. Subscribe to "messages" webhook field
); }