arcadiasuite/client/src/pages/WhatsApp.tsx

294 lines
11 KiB
TypeScript

import { BrowserFrame } from "@/components/Browser/BrowserFrame";
import { useState, useEffect, useRef } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import {
Smartphone,
QrCode,
Loader2,
CheckCircle,
XCircle,
RefreshCw,
Send,
Phone,
MessageSquare,
Wifi,
WifiOff,
LogOut
} from "lucide-react";
import { useAuth } from "@/hooks/use-auth";
import QRCode from "qrcode";
interface WhatsAppStatus {
status: "disconnected" | "connecting" | "qr_pending" | "connected";
qrCode: string | null;
phoneNumber: string | null;
}
async function fetchStatus(): Promise<WhatsAppStatus> {
const response = await fetch("/api/whatsapp/status", { credentials: "include" });
if (!response.ok) throw new Error("Failed to fetch status");
return response.json();
}
async function connectWhatsApp(): Promise<WhatsAppStatus> {
const response = await fetch("/api/whatsapp/connect", {
method: "POST",
credentials: "include",
});
if (!response.ok) throw new Error("Failed to connect");
return response.json();
}
async function disconnectWhatsApp(): Promise<void> {
const response = await fetch("/api/whatsapp/disconnect", {
method: "POST",
credentials: "include",
});
if (!response.ok) throw new Error("Failed to disconnect");
}
export default function WhatsApp() {
const queryClient = useQueryClient();
const { user } = useAuth();
const [qrDataUrl, setQrDataUrl] = useState<string | null>(null);
const [isPolling, setIsPolling] = useState(false);
const { data: status, isLoading, refetch } = useQuery({
queryKey: ["whatsapp-status"],
queryFn: fetchStatus,
refetchInterval: isPolling ? 2000 : false,
});
const connectMutation = useMutation({
mutationFn: connectWhatsApp,
onSuccess: (data) => {
setIsPolling(true);
if (data.qrCode) {
generateQrDataUrl(data.qrCode);
}
queryClient.invalidateQueries({ queryKey: ["whatsapp-status"] });
},
});
const disconnectMutation = useMutation({
mutationFn: disconnectWhatsApp,
onSuccess: () => {
setIsPolling(false);
setQrDataUrl(null);
queryClient.invalidateQueries({ queryKey: ["whatsapp-status"] });
},
});
const generateQrDataUrl = async (qr: string) => {
try {
const url = await QRCode.toDataURL(qr, {
width: 256,
margin: 2,
color: { dark: "#000000", light: "#ffffff" }
});
setQrDataUrl(url);
} catch (err) {
console.error("QR generation error:", err);
}
};
useEffect(() => {
if (status?.qrCode && status.status === "qr_pending") {
generateQrDataUrl(status.qrCode);
}
if (status?.status === "connected") {
setIsPolling(false);
setQrDataUrl(null);
}
}, [status?.qrCode, status?.status]);
const handleConnect = () => {
connectMutation.mutate();
};
const handleDisconnect = () => {
disconnectMutation.mutate();
};
const getStatusBadge = () => {
switch (status?.status) {
case "connected":
return <Badge className="bg-green-600"><Wifi className="w-3 h-3 mr-1" /> Conectado</Badge>;
case "connecting":
return <Badge className="bg-yellow-600"><Loader2 className="w-3 h-3 mr-1 animate-spin" /> Conectando</Badge>;
case "qr_pending":
return <Badge className="bg-blue-600"><QrCode className="w-3 h-3 mr-1" /> Aguardando QR</Badge>;
default:
return <Badge variant="secondary"><WifiOff className="w-3 h-3 mr-1" /> Desconectado</Badge>;
}
};
return (
<BrowserFrame>
<div className="h-full w-full flex bg-[#111b21]">
<div className="flex-1 flex flex-col">
<div className="h-16 bg-[#202c33] flex items-center justify-between px-6 border-b border-[#222d34]">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-[#00a884] flex items-center justify-center">
<Smartphone className="w-5 h-5 text-white" />
</div>
<div>
<h1 className="text-white font-semibold">WhatsApp Web</h1>
<div className="flex items-center gap-2">
{getStatusBadge()}
{status?.phoneNumber && (
<span className="text-xs text-[#8696a0]">+{status.phoneNumber}</span>
)}
</div>
</div>
</div>
{status?.status === "connected" && (
<Button
variant="ghost"
size="sm"
className="text-red-400 hover:text-red-300 hover:bg-red-900/20"
onClick={handleDisconnect}
disabled={disconnectMutation.isPending}
data-testid="button-disconnect"
>
<LogOut className="w-4 h-4 mr-2" />
Desconectar
</Button>
)}
</div>
<div className="flex-1 flex items-center justify-center p-8">
{isLoading ? (
<div className="text-center">
<Loader2 className="w-12 h-12 text-[#00a884] animate-spin mx-auto mb-4" />
<p className="text-[#8696a0]">Carregando...</p>
</div>
) : status?.status === "connected" ? (
<Card className="bg-[#202c33] border-[#222d34] max-w-lg w-full">
<CardHeader className="text-center">
<div className="w-20 h-20 rounded-full bg-green-600 flex items-center justify-center mx-auto mb-4">
<CheckCircle className="w-10 h-10 text-white" />
</div>
<CardTitle className="text-white">WhatsApp Conectado</CardTitle>
<CardDescription className="text-[#8696a0]">
Seu WhatsApp está conectado ao Arcádia Suite
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="bg-[#111b21] rounded-lg p-4">
<div className="flex items-center gap-3">
<Phone className="w-5 h-5 text-[#00a884]" />
<div>
<p className="text-sm text-[#8696a0]">Número conectado</p>
<p className="text-white font-medium">+{status.phoneNumber}</p>
</div>
</div>
</div>
<div className="bg-[#111b21] rounded-lg p-4">
<div className="flex items-center gap-3">
<MessageSquare className="w-5 h-5 text-[#00a884]" />
<div>
<p className="text-sm text-[#8696a0]">Status</p>
<p className="text-green-400 font-medium">Online e sincronizado</p>
</div>
</div>
</div>
<p className="text-xs text-[#8696a0] text-center">
As mensagens recebidas aparecerão automaticamente no chat interno.
</p>
</CardContent>
</Card>
) : status?.status === "qr_pending" && qrDataUrl ? (
<Card className="bg-[#202c33] border-[#222d34] max-w-lg w-full">
<CardHeader className="text-center">
<CardTitle className="text-white">Escaneie o QR Code</CardTitle>
<CardDescription className="text-[#8696a0]">
Abra o WhatsApp no seu celular e escaneie o código abaixo
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center gap-6">
<div className="bg-white p-4 rounded-lg">
<img
src={qrDataUrl}
alt="WhatsApp QR Code"
className="w-64 h-64"
data-testid="qr-code"
/>
</div>
<div className="text-center space-y-2">
<p className="text-sm text-[#8696a0]">
1. Abra o WhatsApp no seu celular
</p>
<p className="text-sm text-[#8696a0]">
2. Toque em <span className="text-white">Menu</span> ou <span className="text-white">Configurações</span>
</p>
<p className="text-sm text-[#8696a0]">
3. Selecione <span className="text-white">Aparelhos conectados</span>
</p>
<p className="text-sm text-[#8696a0]">
4. Toque em <span className="text-white">Conectar um aparelho</span>
</p>
<p className="text-sm text-[#8696a0]">
5. Aponte seu celular para esta tela
</p>
</div>
<Button
variant="outline"
onClick={() => refetch()}
className="border-[#00a884] text-[#00a884] hover:bg-[#00a884]/10"
data-testid="button-refresh-qr"
>
<RefreshCw className="w-4 h-4 mr-2" />
Atualizar QR Code
</Button>
</CardContent>
</Card>
) : (
<Card className="bg-[#202c33] border-[#222d34] max-w-lg w-full">
<CardHeader className="text-center">
<div className="w-20 h-20 rounded-full bg-[#00a884] flex items-center justify-center mx-auto mb-4">
<Smartphone className="w-10 h-10 text-white" />
</div>
<CardTitle className="text-white">Conectar WhatsApp</CardTitle>
<CardDescription className="text-[#8696a0]">
Conecte sua conta do WhatsApp para enviar e receber mensagens
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center gap-4">
<Button
onClick={handleConnect}
disabled={connectMutation.isPending}
className="bg-[#00a884] hover:bg-[#00a884]/90 text-white w-full"
data-testid="button-connect"
>
{connectMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Gerando QR Code...
</>
) : (
<>
<QrCode className="w-4 h-4 mr-2" />
Gerar QR Code
</>
)}
</Button>
<p className="text-xs text-[#8696a0] text-center">
Você precisará escanear um QR Code com seu celular para conectar.
</p>
</CardContent>
</Card>
)}
</div>
</div>
</div>
</BrowserFrame>
);
}