import { useEffect, useMemo, useState } from "react"; import { Alert, Badge, Button, Code, Container, Group, Paper, Select, SimpleGrid, Stack, Table, Text, TextInput, Textarea, Title, } from "@mantine/core"; import { notifications } from "@mantine/notifications"; import { IconAlertCircle, IconBuildingStore, IconPhoneCall } from "@tabler/icons-react"; import { AxiosError } from "axios"; import { apiClient } from "../lib/api"; import type { BusinessProfile, PhoneNumberListItem, WhatsAppAccountConfig, } from "../types"; function toPrettyJSON(value: unknown): string { return JSON.stringify(value, null, 2); } function formatError(error: unknown): string { if (error instanceof AxiosError) { if (typeof error.response?.data === "string") return error.response.data; if (error.response?.data && typeof error.response.data === "object") { return toPrettyJSON(error.response.data); } return error.message; } if (error instanceof Error) return error.message; return "Request failed"; } function extractErrorPayload(error: unknown): unknown { if (error instanceof AxiosError) { return error.response?.data ?? { message: error.message }; } if (error instanceof Error) return { message: error.message }; return { message: "Request failed" }; } function websitesToText(websites?: string[]): string { return (websites || []).join("\n"); } function parseWebsitesInput(input: string): string[] { return input .split(/[\n,]/) .map((entry) => entry.trim()) .filter((entry) => entry.length > 0); } type ResultPanelProps = { title: string; payload: unknown; }; function ResultPanel({ title, payload }: ResultPanelProps) { return ( {title} {toPrettyJSON(payload)} ); } export default function WhatsAppBusinessPage() { const [accounts, setAccounts] = useState([]); const [loadingAccounts, setLoadingAccounts] = useState(true); const [accountId, setAccountId] = useState(""); const [phoneNumbers, setPhoneNumbers] = useState([]); const [phoneNumberIdForCode, setPhoneNumberIdForCode] = useState(""); const [codeMethod, setCodeMethod] = useState<"SMS" | "VOICE">("SMS"); const [language, setLanguage] = useState("en_US"); const [phoneNumberIdForVerify, setPhoneNumberIdForVerify] = useState(""); const [verificationCode, setVerificationCode] = useState(""); const [phoneNumberIdForRegister, setPhoneNumberIdForRegister] = useState(""); const [pin, setPin] = useState(""); const [businessProfile, setBusinessProfile] = useState(null); const [about, setAbout] = useState(""); const [address, setAddress] = useState(""); const [description, setDescription] = useState(""); const [email, setEmail] = useState(""); const [websites, setWebsites] = useState(""); const [vertical, setVertical] = useState(""); const [actionLoading, setActionLoading] = useState>({}); const [responseHistory, setResponseHistory] = useState< Array<{ id: string; title: string; status: "success" | "error"; payload: unknown; createdAt: string; }> >([]); const businessAccounts = useMemo( () => accounts .filter((entry) => entry.type === "business-api" && !entry.disabled) .sort((a, b) => a.id.localeCompare(b.id, undefined, { sensitivity: "base" })), [accounts], ); const accountOptions = businessAccounts.map((entry) => ({ value: entry.id, label: `${entry.id} (business-api)`, })); const phoneNumberOptions = phoneNumbers.map((entry) => ({ value: entry.id, label: `${entry.display_phone_number || entry.phone_number} (${entry.id})`, })); useEffect(() => { const loadAccounts = async () => { try { setLoadingAccounts(true); const result = await apiClient.getAccountConfigs(); setAccounts(result || []); } catch (error) { notifications.show({ title: "Error", message: "Failed to load WhatsApp accounts", color: "red", }); console.error(error); } finally { setLoadingAccounts(false); } }; loadAccounts(); }, []); useEffect(() => { if (!accountId && businessAccounts.length > 0) { setAccountId(businessAccounts[0].id); } }, [businessAccounts, accountId]); const appendResponse = (entry: { title: string; status: "success" | "error"; payload: unknown; }) => { const item = { id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, createdAt: new Date().toISOString(), ...entry, }; setResponseHistory((prev) => [item, ...prev].slice(0, 20)); if (entry.status === "success") { console.info(`[WhatsApp Business] ${entry.title} success`, entry.payload); } else { console.error(`[WhatsApp Business] ${entry.title} error`, entry.payload); } }; const runAction = async (key: string, actionTitle: string, action: () => Promise) => { if (!accountId) { notifications.show({ title: "Validation Error", message: "Select a business account first", color: "red", }); return undefined; } try { setActionLoading((prev) => ({ ...prev, [key]: true })); const result = await action(); appendResponse({ title: actionTitle, status: "success", payload: result, }); return result; } catch (error) { appendResponse({ title: actionTitle, status: "error", payload: extractErrorPayload(error), }); notifications.show({ title: "Request Failed", message: formatError(error), color: "red", }); console.error(error); return undefined; } finally { setActionLoading((prev) => ({ ...prev, [key]: false })); } }; const handleListPhoneNumbers = async () => { const result = await runAction("listPhoneNumbers", "List Phone Numbers", async () => { return apiClient.listPhoneNumbers(accountId); }); if (result) { setPhoneNumbers(result.data || []); notifications.show({ title: "Success", message: `Loaded ${result.data?.length || 0} phone number(s)`, color: "green", }); } }; const handleRequestVerificationCode = async (e: React.FormEvent) => { e.preventDefault(); const response = await runAction("requestCode", "Request Verification Code", async () => { return apiClient.requestVerificationCode({ account_id: accountId, phone_number_id: phoneNumberIdForCode.trim(), code_method: codeMethod, language: language.trim() || "en_US", }); }); if (response) { notifications.show({ title: "Success", message: "Verification code requested", color: "green", }); } }; const handleVerifyCode = async (e: React.FormEvent) => { e.preventDefault(); const response = await runAction("verifyCode", "Verify Code", async () => { return apiClient.verifyPhoneCode({ account_id: accountId, phone_number_id: phoneNumberIdForVerify.trim(), code: verificationCode.trim(), }); }); if (response) { notifications.show({ title: "Success", message: "Verification code accepted", color: "green", }); } }; const handleRegisterPhoneNumber = async (e: React.FormEvent) => { e.preventDefault(); const response = await runAction("registerPhoneNumber", "Register Phone Number", async () => { return apiClient.registerPhoneNumber({ account_id: accountId, phone_number_id: phoneNumberIdForRegister.trim(), pin: pin.trim(), }); }); if (response) { notifications.show({ title: "Success", message: "Phone number registration submitted", color: "green", }); } }; const handleGetBusinessProfile = async () => { const profile = await runAction("getBusinessProfile", "Get Business Profile", async () => { return apiClient.getBusinessProfile(accountId); }); if (profile) { setBusinessProfile(profile); setAbout(profile.about || ""); setAddress(profile.address || ""); setDescription(profile.description || ""); setEmail(profile.email || ""); setWebsites(websitesToText(profile.websites)); setVertical(profile.vertical || ""); notifications.show({ title: "Success", message: "Business profile loaded", color: "green", }); } }; const handleUpdateBusinessProfile = async (e: React.FormEvent) => { e.preventDefault(); const payload = { account_id: accountId, about: about.trim(), address: address.trim(), description: description.trim(), email: email.trim(), websites: parseWebsitesInput(websites), vertical: vertical.trim(), }; const response = await runAction("updateBusinessProfile", "Update Business Profile", async () => { const result = await apiClient.updateBusinessProfile(payload); return { payload, response: result }; }); if (response) { notifications.show({ title: "Success", message: "Business profile updated", color: "green", }); } }; return (
WhatsApp Business Management Select a business account, then manage phone number verification/registration and business profile details.
Business Account {businessAccounts.length} business account(s) setPhoneNumberIdForCode(value || "")} searchable clearable /> setPhoneNumberIdForCode(e.currentTarget.value)} required /> setPhoneNumberIdForVerify(value || "")} searchable clearable /> setPhoneNumberIdForVerify(e.currentTarget.value)} required /> setVerificationCode(e.currentTarget.value)} required />
Register Phone Number