Reverted to commit b8d0db98bb
This commit is contained in:
parent
ba5d3c92a3
commit
90b3030ebb
8
package-lock.json
generated
8
package-lock.json
generated
@ -41,7 +41,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"embla-carousel-react": "^8.3.0",
|
||||
"input-otp": "^1.2.4",
|
||||
"lucide-react": "^0.462.0",
|
||||
@ -4196,9 +4196,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"embla-carousel-react": "^8.3.0",
|
||||
"input-otp": "^1.2.4",
|
||||
"lucide-react": "^0.462.0",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import Index from '@/pages/Index';
|
||||
import Transacoes from '@/pages/Transacoes';
|
||||
@ -10,7 +11,6 @@ import WhatsApp from '@/pages/WhatsApp';
|
||||
import { Toaster } from '@/components/ui/toaster';
|
||||
import './App.css';
|
||||
import ProtectedRoute from '@/components/auth/ProtectedRoute';
|
||||
import RodrigoAudio from './pages/RodrigoAudio';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@ -49,7 +49,6 @@ function App() {
|
||||
} />
|
||||
<Route path="/login" element={<Auth />} />
|
||||
<Route path="/auth" element={<Auth />} />
|
||||
<Route path="/rodrigo-audio" element={<RodrigoAudio />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
<Toaster />
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Sidebar, defaultItems } from '@/components/layout/Sidebar';
|
||||
import Sidebar from '@/components/layout/Sidebar';
|
||||
import Header from '@/components/layout/Header';
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { Menu } from 'lucide-react';
|
||||
@ -39,11 +39,11 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="p-0 w-[250px]">
|
||||
<Sidebar items={defaultItems} className="w-full border-none" />
|
||||
<Sidebar className="w-full border-none" />
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
) : (
|
||||
<Sidebar items={defaultItems} />
|
||||
<Sidebar />
|
||||
)}
|
||||
<div className="flex flex-col flex-1 overflow-hidden">
|
||||
<Header />
|
||||
|
||||
@ -1,74 +1,99 @@
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Users,
|
||||
Settings,
|
||||
MessageSquare,
|
||||
Headphones
|
||||
} from "lucide-react";
|
||||
import React from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight, Home, CalendarDays, DollarSign, PieChart, Flag, MessageCircle } from 'lucide-react';
|
||||
|
||||
import { MainNavItem } from "@/types";
|
||||
import { siteConfig } from "@/config/site";
|
||||
// Updated navItems with the new WhatsApp connection page
|
||||
const navItems = [
|
||||
{
|
||||
name: 'Dashboard',
|
||||
path: '/dashboard',
|
||||
icon: <Home className="mr-2 h-5 w-5" />
|
||||
},
|
||||
{
|
||||
name: 'Transações',
|
||||
path: '/transacoes',
|
||||
icon: <DollarSign className="mr-2 h-5 w-5" />
|
||||
},
|
||||
{
|
||||
name: 'Categorias',
|
||||
path: '/categorias',
|
||||
icon: <PieChart className="mr-2 h-5 w-5" />
|
||||
},
|
||||
{
|
||||
name: 'Metas',
|
||||
path: '/metas',
|
||||
icon: <Flag className="mr-2 h-5 w-5" />
|
||||
},
|
||||
{
|
||||
name: 'Calendário',
|
||||
path: '/calendario',
|
||||
icon: <CalendarDays className="mr-2 h-5 w-5" />
|
||||
},
|
||||
{
|
||||
name: 'Conectar WhatsApp',
|
||||
path: '/whatsapp',
|
||||
icon: <MessageCircle className="mr-2 h-5 w-5" />
|
||||
},
|
||||
];
|
||||
|
||||
interface SidebarProps {
|
||||
items?: MainNavItem[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Sidebar({ items, className = "" }: SidebarProps) {
|
||||
const Sidebar = ({ className }: SidebarProps) => {
|
||||
const location = useLocation();
|
||||
const [collapsed, setCollapsed] = React.useState(false);
|
||||
|
||||
// Improved function to verify if the item is active based on exact path
|
||||
const isItemActive = (path: string) => {
|
||||
return location.pathname === path;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col space-y-6 w-full ${className}`}>
|
||||
<a href="/" className="flex items-center space-x-2">
|
||||
<img
|
||||
src="/logo.png"
|
||||
alt="Logo"
|
||||
className="h-8 w-8 rounded-md"
|
||||
/>
|
||||
<span className="font-bold">{siteConfig.name}</span>
|
||||
</a>
|
||||
<div className="flex flex-col space-y-1">
|
||||
{items?.map((item) => (
|
||||
item.href ? (
|
||||
<a
|
||||
key={item.title}
|
||||
href={item.href}
|
||||
className="flex items-center space-x-2 px-4 py-2 rounded-md hover:bg-secondary"
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
) : null
|
||||
))}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col h-full bg-sidebar border-r border-border transition-all duration-300 ease-in-out",
|
||||
collapsed ? "w-[60px]" : "w-[250px]",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between p-4">
|
||||
{!collapsed && (
|
||||
<h1 className="font-bold text-xl text-primary">FinDash</h1>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
className="ml-auto"
|
||||
>
|
||||
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 py-4 space-y-1">
|
||||
{navItems.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.path}
|
||||
className={cn(
|
||||
"flex items-center px-4 py-2 text-sm font-medium rounded-md transition-colors",
|
||||
isItemActive(item.path)
|
||||
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
||||
: "text-sidebar-foreground hover:bg-sidebar-accent/50",
|
||||
collapsed ? "justify-center" : "justify-start"
|
||||
)}
|
||||
>
|
||||
<span className={collapsed ? "mr-0" : "mr-2"}>{item.icon}</span>
|
||||
{!collapsed && <span>{item.name}</span>}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultItems: MainNavItem[] = [
|
||||
{
|
||||
title: "Dashboard",
|
||||
href: "/",
|
||||
icon: <LayoutDashboard className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
title: "Usuários",
|
||||
href: "/usuarios",
|
||||
icon: <Users className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
title: "WhatsApp",
|
||||
href: "/whatsapp",
|
||||
icon: <MessageSquare className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
title: "Configurações",
|
||||
href: "/configuracoes",
|
||||
icon: <Settings className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
title: "Áudios Rodrigo",
|
||||
href: "/rodrigo-audio",
|
||||
icon: <Headphones className="h-5 w-5" />
|
||||
},
|
||||
];
|
||||
export default Sidebar;
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
|
||||
import React from "react";
|
||||
import UsersList from "./UsersList";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { UserRoundSearch } from "lucide-react";
|
||||
|
||||
interface UsersDialogProps {
|
||||
trigger?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function UsersDialog({ trigger }: UsersDialogProps) {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
{trigger || (
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
<UserRoundSearch className="h-4 w-4" />
|
||||
<span>Usuários</span>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[700px] max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Gerenciamento de Usuários</DialogTitle>
|
||||
<DialogDescription>
|
||||
Visualize e gerencie os usuários cadastrados na plataforma
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<UsersList />
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default UsersDialog;
|
||||
@ -1,143 +0,0 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { format } from 'date-fns';
|
||||
import { ptBR } from 'date-fns/locale';
|
||||
import { UserInfo, getUsersRegisteredToday, getAllUsers } from '@/services/userService';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
|
||||
const UsersList: React.FC = () => {
|
||||
const [usersToday, setUsersToday] = useState<UserInfo[]>([]);
|
||||
const [allUsers, setAllUsers] = useState<UserInfo[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [activeTab, setActiveTab] = useState<string>("today");
|
||||
const { toast } = useToast();
|
||||
|
||||
const loadUsers = async (tab: string = activeTab) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (tab === "today") {
|
||||
const users = await getUsersRegisteredToday();
|
||||
setUsersToday(users);
|
||||
} else {
|
||||
const users = await getAllUsers();
|
||||
setAllUsers(users);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro ao carregar usuários:", error);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: "Falha ao carregar dados de usuários.",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers();
|
||||
}, []);
|
||||
|
||||
const handleTabChange = (value: string) => {
|
||||
setActiveTab(value);
|
||||
loadUsers(value);
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
try {
|
||||
return format(new Date(dateString), "dd/MM/yyyy HH:mm", { locale: ptBR });
|
||||
} catch (error) {
|
||||
return "Data inválida";
|
||||
}
|
||||
};
|
||||
|
||||
const displayUsers = activeTab === "today" ? usersToday : allUsers;
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle>Usuários Cadastrados</CardTitle>
|
||||
<CardDescription>
|
||||
{activeTab === "today"
|
||||
? `${usersToday.length} usuários cadastrados hoje`
|
||||
: `${allUsers.length} usuários cadastrados no total`}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="today" onValueChange={handleTabChange}>
|
||||
<TabsList className="mb-4">
|
||||
<TabsTrigger value="today">Cadastrados Hoje</TabsTrigger>
|
||||
<TabsTrigger value="all">Todos os Usuários</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="today" className="space-y-4">
|
||||
{loading ? (
|
||||
<div className="text-center py-4">Carregando...</div>
|
||||
) : usersToday.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{usersToday.map((user) => (
|
||||
<div key={user.id} className="border p-4 rounded-md">
|
||||
<div className="font-semibold">{user.nome}</div>
|
||||
<div className="text-sm text-muted-foreground">Email: {user.email}</div>
|
||||
{user.empresa && (
|
||||
<div className="text-sm text-muted-foreground">Empresa: {user.empresa}</div>
|
||||
)}
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Cadastrado em: {formatDate(user.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-4">Nenhum usuário cadastrado hoje.</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="all" className="space-y-4">
|
||||
{loading ? (
|
||||
<div className="text-center py-4">Carregando...</div>
|
||||
) : allUsers.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{allUsers.map((user) => (
|
||||
<div key={user.id} className="border p-4 rounded-md">
|
||||
<div className="font-semibold">{user.nome}</div>
|
||||
<div className="text-sm text-muted-foreground">Email: {user.email}</div>
|
||||
{user.empresa && (
|
||||
<div className="text-sm text-muted-foreground">Empresa: {user.empresa}</div>
|
||||
)}
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Cadastrado em: {formatDate(user.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-4">Nenhum usuário cadastrado.</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => loadUsers(activeTab)}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "Carregando..." : "Atualizar"}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsersList;
|
||||
@ -8,20 +8,20 @@ import { RefreshCw } from 'lucide-react';
|
||||
interface InstanceListProps {
|
||||
instances: WhatsAppInstance[];
|
||||
onViewQrCode: (instance: WhatsAppInstance) => void;
|
||||
onDeleteInstance: (instanceId: string) => void; // Only requires instanceId
|
||||
onRestartInstance: (instance: WhatsAppInstance) => Promise<void>;
|
||||
onLogoutInstance: (instance: WhatsAppInstance) => Promise<void>;
|
||||
onDelete: (instanceId: string) => void;
|
||||
onRestart: (instance: WhatsAppInstance) => Promise<void>;
|
||||
onLogout: (instance: WhatsAppInstance) => Promise<void>;
|
||||
onSetPresence: (instance: WhatsAppInstance, presence: 'online' | 'offline') => Promise<void>;
|
||||
onRefreshInstances?: () => Promise<void>;
|
||||
isRefreshing?: boolean;
|
||||
onRefreshInstances: () => Promise<void>;
|
||||
isRefreshing: boolean;
|
||||
}
|
||||
|
||||
const InstanceList = ({
|
||||
instances,
|
||||
onViewQrCode,
|
||||
onDeleteInstance,
|
||||
onRestartInstance,
|
||||
onLogoutInstance,
|
||||
onDelete,
|
||||
onRestart,
|
||||
onLogout,
|
||||
onSetPresence,
|
||||
onRefreshInstances,
|
||||
isRefreshing
|
||||
@ -43,17 +43,15 @@ const InstanceList = ({
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl font-semibold">Instâncias Criadas</h2>
|
||||
{onRefreshInstances && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onRefreshInstances}
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
{isRefreshing ? 'Atualizando...' : 'Atualizar Lista'}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onRefreshInstances}
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
{isRefreshing ? 'Atualizando...' : 'Atualizar Lista'}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{instances.map((instance) => (
|
||||
@ -61,9 +59,9 @@ const InstanceList = ({
|
||||
key={instance.instanceId}
|
||||
instance={instance}
|
||||
onViewQrCode={onViewQrCode}
|
||||
onDelete={onDeleteInstance}
|
||||
onRestart={onRestartInstance}
|
||||
onLogout={onLogoutInstance}
|
||||
onDelete={onDelete}
|
||||
onRestart={onRestart}
|
||||
onLogout={onLogout}
|
||||
onSetPresence={onSetPresence}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -1,103 +0,0 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { AudioMessage, storeAudioMessageForRodrigo } from '@/services/audioMessageService';
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
|
||||
interface MessageWebhookProps {
|
||||
instance?: WhatsAppInstance;
|
||||
}
|
||||
|
||||
const MessageWebhook: React.FC<MessageWebhookProps> = ({ instance }) => {
|
||||
const { toast } = useToast();
|
||||
const [webhookUrl, setWebhookUrl] = useState<string>('');
|
||||
|
||||
const handleSetWebhook = async () => {
|
||||
if (!instance) {
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: "Selecione uma instância primeiro",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Here you would set the webhook URL for the Evolution API
|
||||
// This is a placeholder for the actual implementation
|
||||
toast({
|
||||
title: "Webhook Configurado",
|
||||
description: `Webhook configurado para ${instance.instanceName}`,
|
||||
});
|
||||
|
||||
// Simulate receiving an audio message (for testing purposes)
|
||||
const testAudioMessage: AudioMessage = {
|
||||
sender_id: '120363420212322973@g.us',
|
||||
sender_name: 'Teste',
|
||||
instance_id: instance.instanceId,
|
||||
audio_url: 'https://example.com/audio.mp3',
|
||||
duration: 30,
|
||||
metadata: {
|
||||
test: true
|
||||
}
|
||||
};
|
||||
|
||||
await storeAudioMessageForRodrigo(testAudioMessage);
|
||||
} catch (error) {
|
||||
console.error('Error setting webhook:', error);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: "Erro ao configurar webhook",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Configurar Webhook para Mensagens</CardTitle>
|
||||
<CardDescription>
|
||||
Configure um webhook para receber mensagens da Evolution API.
|
||||
Os áudios enviados para o grupo do Rodrigo serão armazenados automaticamente.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="webhook-url">URL do Webhook</Label>
|
||||
<Input
|
||||
id="webhook-url"
|
||||
placeholder="https://seu-servidor.com/webhook"
|
||||
value={webhookUrl}
|
||||
onChange={(e) => setWebhookUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{instance ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Configurando para: <strong>{instance.instanceName}</strong>
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-sm text-yellow-600">
|
||||
Selecione uma instância primeiro.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button
|
||||
onClick={handleSetWebhook}
|
||||
disabled={!instance || !webhookUrl}
|
||||
>
|
||||
Configurar Webhook
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageWebhook;
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@ -10,22 +10,21 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
import { fetchQrCode } from '@/services/whatsAppService';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
interface QrCodeDialogProps {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
instanceName: string;
|
||||
phoneNumber: string;
|
||||
onStatusCheck?: () => void;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
activeInstance: WhatsAppInstance | null;
|
||||
onStatusCheck: () => void;
|
||||
}
|
||||
|
||||
const QrCodeDialog = ({
|
||||
open,
|
||||
setOpen,
|
||||
instanceName,
|
||||
phoneNumber,
|
||||
onOpenChange,
|
||||
activeInstance,
|
||||
onStatusCheck
|
||||
}: QrCodeDialogProps) => {
|
||||
const { toast } = useToast();
|
||||
@ -33,30 +32,23 @@ const QrCodeDialog = ({
|
||||
const [qrCodeData, setQrCodeData] = useState<string | null>(null);
|
||||
const [qrError, setQrError] = useState<string | null>(null);
|
||||
|
||||
// Load QR code when dialog opens
|
||||
useEffect(() => {
|
||||
if (open && instanceName) {
|
||||
handleRefreshQrCode();
|
||||
}
|
||||
}, [open, instanceName]);
|
||||
|
||||
const handleOpenChange = (newOpen: boolean) => {
|
||||
setOpen(newOpen);
|
||||
onOpenChange(newOpen);
|
||||
|
||||
// After dialog closes, trigger status check to update connection state
|
||||
if (!newOpen && onStatusCheck) {
|
||||
if (!newOpen) {
|
||||
onStatusCheck();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefreshQrCode = async () => {
|
||||
if (!instanceName) return;
|
||||
if (!activeInstance) return;
|
||||
|
||||
setLoadingQR(true);
|
||||
setQrError(null);
|
||||
|
||||
try {
|
||||
const data = await fetchQrCode(instanceName);
|
||||
const data = await fetchQrCode(activeInstance.instanceName);
|
||||
console.log('QR Code API response:', data);
|
||||
|
||||
// Using the "base64" field from the response as the QR code data
|
||||
@ -83,7 +75,7 @@ const QrCodeDialog = ({
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Conectar WhatsApp - {instanceName}</DialogTitle>
|
||||
<DialogTitle>Conectar WhatsApp - {activeInstance?.instanceName}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Escaneie o QR Code com seu WhatsApp para finalizar a conexão
|
||||
</DialogDescription>
|
||||
@ -117,7 +109,7 @@ const QrCodeDialog = ({
|
||||
)}
|
||||
|
||||
<div className="flex space-x-2">
|
||||
{instanceName && (
|
||||
{activeInstance && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleRefreshQrCode}
|
||||
@ -128,7 +120,7 @@ const QrCodeDialog = ({
|
||||
Atualizar QR Code
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost" onClick={() => setOpen(false)}>
|
||||
<Button variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
Fechar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
|
||||
// Export all WhatsApp components for easier imports
|
||||
export { default as CreateInstanceForm } from './CreateInstanceForm';
|
||||
export { default as InstanceList } from './InstanceList';
|
||||
export { default as InstanceStats } from './InstanceStats';
|
||||
export { default as QrCodeDialog } from './QrCodeDialog';
|
||||
export { default as InstanceCard } from './InstanceCard';
|
||||
export { default as MessageWebhook } from './MessageWebhook';
|
||||
@ -1,9 +0,0 @@
|
||||
|
||||
export const siteConfig = {
|
||||
name: "WhatsApp Dashboard",
|
||||
description: "Gerenciamento de instâncias de WhatsApp",
|
||||
mainNav: [],
|
||||
links: {
|
||||
github: "https://github.com/",
|
||||
},
|
||||
};
|
||||
@ -1,3 +1,186 @@
|
||||
|
||||
// Re-export from the refactored location
|
||||
export { useWhatsAppActions } from './whatsapp/useWhatsAppActions';
|
||||
import { useState } from 'react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
import {
|
||||
restartInstance,
|
||||
logoutInstance,
|
||||
deleteInstance,
|
||||
setInstancePresence,
|
||||
fetchQrCode
|
||||
} from '@/services/whatsAppService';
|
||||
|
||||
export const useWhatsAppActions = (
|
||||
updateInstance: (instance: WhatsAppInstance) => void,
|
||||
removeInstance: (instanceId: string) => void,
|
||||
checkAllInstancesStatus: () => Promise<void>
|
||||
) => {
|
||||
const { toast } = useToast();
|
||||
const [activeInstance, setActiveInstance] = useState<WhatsAppInstance | null>(null);
|
||||
const [qrDialogOpen, setQrDialogOpen] = useState(false);
|
||||
|
||||
// Handler for quando uma instância é reiniciada
|
||||
const handleRestartInstance = async (instance: WhatsAppInstance) => {
|
||||
try {
|
||||
console.log(`Attempting to restart instance ${instance.instanceName}`);
|
||||
await restartInstance(instance.instanceName);
|
||||
|
||||
// Atualiza o estado da instância para "connecting"
|
||||
const updatedInstance = {
|
||||
...instance,
|
||||
connectionState: 'connecting' as const
|
||||
};
|
||||
console.log(`Instance ${instance.instanceName} restart initiated, setting state to connecting`, updatedInstance);
|
||||
updateInstance(updatedInstance);
|
||||
|
||||
toast({
|
||||
title: "Sucesso",
|
||||
description: `Instância ${instance.instanceName} reiniciada com sucesso`
|
||||
});
|
||||
|
||||
// Verifica o status após um breve delay para dar tempo de atualizar
|
||||
setTimeout(() => checkAllInstancesStatus(), 3000);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error restarting instance ${instance.instanceName}:`, error);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: `Falha ao reiniciar a instância ${instance.instanceName}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Handler for quando uma instância é desconectada
|
||||
const handleLogoutInstance = async (instance: WhatsAppInstance) => {
|
||||
try {
|
||||
console.log(`Attempting to logout instance ${instance.instanceName}`);
|
||||
await logoutInstance(instance.instanceName);
|
||||
|
||||
// Atualiza o estado da instância para "closed"
|
||||
const updatedInstance = {
|
||||
...instance,
|
||||
connectionState: 'closed' as const,
|
||||
status: 'disconnected'
|
||||
};
|
||||
console.log(`Instance ${instance.instanceName} logout successful, updating instance state`, updatedInstance);
|
||||
updateInstance(updatedInstance);
|
||||
|
||||
toast({
|
||||
title: "Sucesso",
|
||||
description: `Instância ${instance.instanceName} desconectada com sucesso`
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error logging out instance ${instance.instanceName}:`, error);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: `Falha ao desconectar a instância ${instance.instanceName}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Handler for quando a presença é alterada
|
||||
const handleSetPresence = async (instance: WhatsAppInstance, presence: 'online' | 'offline') => {
|
||||
try {
|
||||
console.log(`Setting presence to ${presence} for instance ${instance.instanceName}`);
|
||||
await setInstancePresence(instance.instanceName, presence);
|
||||
|
||||
const updatedInstance = {
|
||||
...instance,
|
||||
presence: presence
|
||||
};
|
||||
console.log(`Updated instance with new presence status`, updatedInstance);
|
||||
updateInstance(updatedInstance);
|
||||
|
||||
toast({
|
||||
title: "Sucesso",
|
||||
description: `Instância ${instance.instanceName} agora está ${presence === 'online' ? 'Online' : 'Offline'}`
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error setting presence to ${presence} for instance ${instance.instanceName}:`, error);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: `Falha ao definir presença ${presence} para ${instance.instanceName}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Handler for when an instance is deleted
|
||||
const handleDeleteInstance = async (instanceId: string, instanceName: string) => {
|
||||
console.log(`Deleting instance with ID: ${instanceId}, name: ${instanceName}`);
|
||||
|
||||
try {
|
||||
// Call API to delete instance
|
||||
const response = await deleteInstance(instanceName);
|
||||
console.log(`Deletion API response for instance ${instanceName}:`, response);
|
||||
|
||||
// Remove from local state
|
||||
removeInstance(instanceId);
|
||||
|
||||
// If we're viewing QR code for this instance, close the dialog
|
||||
if (activeInstance?.instanceId === instanceId) {
|
||||
setQrDialogOpen(false);
|
||||
setActiveInstance(null);
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "Instância removida",
|
||||
description: "A instância do WhatsApp foi removida com sucesso.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error deleting instance with ID ${instanceId}:`, error);
|
||||
|
||||
// Even if the API call fails, we still want to remove the instance from local storage
|
||||
// This handles the case when instances are in inconsistent state with the server
|
||||
removeInstance(instanceId);
|
||||
|
||||
if (activeInstance?.instanceId === instanceId) {
|
||||
setQrDialogOpen(false);
|
||||
setActiveInstance(null);
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "Aviso",
|
||||
description: "Instância removida localmente, mas pode haver falha na comunicação com o servidor.",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Handler for when QR code dialog is requested
|
||||
const handleViewQrCode = async (instance: WhatsAppInstance) => {
|
||||
console.log(`Opening QR code dialog for instance: ${instance.instanceName}`);
|
||||
setActiveInstance(instance);
|
||||
setQrDialogOpen(true);
|
||||
|
||||
try {
|
||||
const data = await fetchQrCode(instance.instanceName);
|
||||
console.log('QR Code API response:', data);
|
||||
|
||||
// If the fetchQrCode call was successful, QrCodeDialog will handle showing the QR code
|
||||
} catch (error) {
|
||||
console.error("Error initiating QR code fetch:", error);
|
||||
toast({
|
||||
title: "Erro ao obter QR Code",
|
||||
description: "Falha ao iniciar obtenção de QR Code. Tente novamente.",
|
||||
variant: "destructive",
|
||||
});
|
||||
setQrDialogOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
activeInstance,
|
||||
qrDialogOpen,
|
||||
setQrDialogOpen,
|
||||
handleRestartInstance,
|
||||
handleLogoutInstance,
|
||||
handleSetPresence,
|
||||
handleDeleteInstance,
|
||||
handleViewQrCode
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,3 +1,249 @@
|
||||
|
||||
// Re-export from the refactored location
|
||||
export { useWhatsAppInstances } from './whatsapp/useWhatsAppInstances';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
import {
|
||||
saveInstancesToLocalStorage,
|
||||
loadInstancesFromLocalStorage,
|
||||
fetchAllInstances,
|
||||
fetchConnectionState
|
||||
} from '@/services/whatsAppService';
|
||||
|
||||
export const useWhatsAppInstances = () => {
|
||||
const { toast } = useToast();
|
||||
const [instances, setInstances] = useState<WhatsAppInstance[]>([]);
|
||||
const [currentUserId, setCurrentUserId] = useState<string>('');
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
// Get current user ID and load instances on component mount
|
||||
useEffect(() => {
|
||||
const userId = localStorage.getItem('userId') || '';
|
||||
console.log("Current userId from localStorage:", userId);
|
||||
setCurrentUserId(userId);
|
||||
|
||||
if (userId) {
|
||||
const userInstances = loadInstancesFromLocalStorage(userId);
|
||||
console.log('Loaded user instances:', userInstances);
|
||||
setInstances(userInstances);
|
||||
}
|
||||
}, []); // Only run once on mount
|
||||
|
||||
// Save instances to localStorage whenever they change
|
||||
useEffect(() => {
|
||||
if (currentUserId && instances.length > 0) {
|
||||
console.log('Saving instances to localStorage:', instances);
|
||||
saveInstancesToLocalStorage(instances, currentUserId);
|
||||
}
|
||||
}, [instances, currentUserId]);
|
||||
|
||||
// Function to check connection status for all instances
|
||||
const checkAllInstancesStatus = useCallback(async () => {
|
||||
if (instances.length === 0) return;
|
||||
|
||||
console.log(`Checking connection status for ${instances.length} instances`);
|
||||
|
||||
try {
|
||||
const updatedInstances = await Promise.all(
|
||||
instances.map(async (instance) => {
|
||||
try {
|
||||
console.log(`Checking status for ${instance.instanceName}`);
|
||||
const state = await fetchConnectionState(instance.instanceName);
|
||||
console.log(`Status for ${instance.instanceName}: ${state}`);
|
||||
|
||||
// Only update connectionState if it's different
|
||||
if (instance.connectionState !== state) {
|
||||
// Ensure the state is one of the valid values or treat as 'closed'
|
||||
let validState: 'open' | 'closed' | 'connecting';
|
||||
if (state === 'open' || state === 'connecting') {
|
||||
validState = state;
|
||||
} else {
|
||||
validState = 'closed';
|
||||
}
|
||||
|
||||
return {
|
||||
...instance,
|
||||
connectionState: validState,
|
||||
status: state === 'open' ? 'connected' :
|
||||
state === 'connecting' ? 'connecting' : 'disconnected'
|
||||
};
|
||||
}
|
||||
return instance;
|
||||
} catch (error) {
|
||||
console.error(`Error checking status for ${instance.instanceName}:`, error);
|
||||
|
||||
// If the instance doesn't exist on the server, mark it as closed
|
||||
if (error instanceof Error && error.message.includes("does not exist")) {
|
||||
return {
|
||||
...instance,
|
||||
connectionState: 'closed' as const,
|
||||
status: 'disconnected'
|
||||
};
|
||||
}
|
||||
|
||||
// Keep the current state in case of other errors
|
||||
return instance;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Only update state if there are actual changes
|
||||
const hasChanges = JSON.stringify(updatedInstances) !== JSON.stringify(instances);
|
||||
if (hasChanges) {
|
||||
console.log('Updated instances after status check:', updatedInstances);
|
||||
setInstances(updatedInstances);
|
||||
} else {
|
||||
console.log('No changes in instance status');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking instances status:", error);
|
||||
}
|
||||
}, [instances]);
|
||||
|
||||
// Handler para atualizar a lista de instâncias do servidor
|
||||
const refreshInstances = async () => {
|
||||
if (!currentUserId) {
|
||||
toast({
|
||||
title: "Erro de autenticação",
|
||||
description: "Você precisa estar logado para atualizar as instâncias",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
console.log("Fetching instances from server...");
|
||||
const response = await fetchAllInstances();
|
||||
console.log("Fetched instances from server:", response);
|
||||
|
||||
if (response.instances && Array.isArray(response.instances)) {
|
||||
// Mapeia as instâncias do servidor para o formato correto com userId
|
||||
const serverInstances: WhatsAppInstance[] = response.instances
|
||||
.map(serverInstance => {
|
||||
// Ensure we convert server state to valid connectionState
|
||||
let connectionState: 'open' | 'closed' | 'connecting';
|
||||
const state = serverInstance.state || 'closed';
|
||||
|
||||
if (state === 'open' || state === 'connecting') {
|
||||
connectionState = state;
|
||||
} else {
|
||||
connectionState = 'closed';
|
||||
}
|
||||
|
||||
return {
|
||||
instanceName: serverInstance.instanceName,
|
||||
instanceId: serverInstance.instanceName, // Using instanceName as the ID for consistency
|
||||
phoneNumber: serverInstance.number || 'Desconhecido',
|
||||
userId: currentUserId,
|
||||
connectionState: connectionState,
|
||||
status: connectionState === 'open' ? 'connected' :
|
||||
connectionState === 'connecting' ? 'connecting' : 'disconnected'
|
||||
};
|
||||
});
|
||||
|
||||
console.log("Mapped server instances:", serverInstances);
|
||||
|
||||
// Identifica instâncias novas que não existem localmente
|
||||
const localInstanceIds = new Set(instances.map(i => i.instanceId));
|
||||
const newServerInstances = serverInstances.filter(i => !localInstanceIds.has(i.instanceId));
|
||||
|
||||
// Atualiza instâncias existentes com dados do servidor
|
||||
const updatedExistingInstances = instances.map(localInstance => {
|
||||
const serverMatch = serverInstances.find(si => si.instanceId === localInstance.instanceId);
|
||||
if (serverMatch) {
|
||||
return {
|
||||
...localInstance,
|
||||
connectionState: serverMatch.connectionState,
|
||||
status: serverMatch.status
|
||||
};
|
||||
}
|
||||
return localInstance;
|
||||
});
|
||||
|
||||
// Combina tudo
|
||||
const allInstances = [...updatedExistingInstances, ...newServerInstances];
|
||||
|
||||
console.log("Combined instances after refresh:", allInstances);
|
||||
setInstances(allInstances);
|
||||
|
||||
toast({
|
||||
title: "Sucesso",
|
||||
description: `${serverInstances.length} instâncias encontradas no servidor`,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: "Aviso",
|
||||
description: "Nenhuma instância encontrada no servidor",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro ao buscar instâncias:", error);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: "Falha ao buscar instâncias do servidor",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
// Verificar status após atualizar a lista
|
||||
await checkAllInstancesStatus();
|
||||
}
|
||||
};
|
||||
|
||||
// Add a new instance
|
||||
const addInstance = (newInstance: WhatsAppInstance) => {
|
||||
console.log("New instance created, adding to instances list:", newInstance);
|
||||
|
||||
// Verifica se já existe uma instância com o mesmo ID
|
||||
const existingIndex = instances.findIndex(inst => inst.instanceId === newInstance.instanceId);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// Atualiza a instância existente
|
||||
console.log(`Instance with ID ${newInstance.instanceId} already exists, updating it`);
|
||||
setInstances(prevInstances =>
|
||||
prevInstances.map((instance, index) =>
|
||||
index === existingIndex ? newInstance : instance
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Adiciona nova instância
|
||||
setInstances(prevInstances => [...prevInstances, newInstance]);
|
||||
}
|
||||
};
|
||||
|
||||
// Remove an instance
|
||||
const removeInstance = (instanceId: string) => {
|
||||
console.log(`Removing instance with ID: ${instanceId}`);
|
||||
setInstances(prevInstances => {
|
||||
const filtered = prevInstances.filter(instance => instance.instanceId !== instanceId);
|
||||
console.log("Updated instances after deletion:", filtered);
|
||||
return filtered;
|
||||
});
|
||||
};
|
||||
|
||||
// Update an instance
|
||||
const updateInstance = (updatedInstance: WhatsAppInstance) => {
|
||||
console.log(`Updating instance: ${updatedInstance.instanceName}`, updatedInstance);
|
||||
setInstances(prevInstances => {
|
||||
const newInstances = prevInstances.map(instance =>
|
||||
instance.instanceId === updatedInstance.instanceId
|
||||
? updatedInstance
|
||||
: instance
|
||||
);
|
||||
|
||||
console.log("Updated instances:", newInstances);
|
||||
return newInstances;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
instances,
|
||||
isRefreshing,
|
||||
currentUserId,
|
||||
addInstance,
|
||||
removeInstance,
|
||||
updateInstance,
|
||||
refreshInstances,
|
||||
checkAllInstancesStatus
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,76 +0,0 @@
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
import { fetchConnectionState } from '@/services/whatsAppService';
|
||||
|
||||
/**
|
||||
* Checks the connection status for a single WhatsApp instance
|
||||
*/
|
||||
export const checkInstanceStatus = async (instance: WhatsAppInstance): Promise<WhatsAppInstance> => {
|
||||
try {
|
||||
console.log(`Checking status for ${instance.instanceName}`);
|
||||
const state = await fetchConnectionState(instance.instanceName);
|
||||
console.log(`Status for ${instance.instanceName}: ${state}`);
|
||||
|
||||
// Only update connectionState if it's different
|
||||
if (instance.connectionState !== state) {
|
||||
// Ensure the state is one of the valid values or treat as 'closed'
|
||||
let validState: 'open' | 'closed' | 'connecting';
|
||||
if (state === 'open' || state === 'connecting') {
|
||||
validState = state;
|
||||
} else {
|
||||
validState = 'closed';
|
||||
}
|
||||
|
||||
return {
|
||||
...instance,
|
||||
connectionState: validState,
|
||||
status: state === 'open' ? 'connected' :
|
||||
state === 'connecting' ? 'connecting' : 'disconnected'
|
||||
};
|
||||
}
|
||||
return instance;
|
||||
} catch (error) {
|
||||
console.error(`Error checking status for ${instance.instanceName}:`, error);
|
||||
|
||||
// If the instance doesn't exist on the server, mark it as closed
|
||||
if (error instanceof Error && error.message.includes("does not exist")) {
|
||||
return {
|
||||
...instance,
|
||||
connectionState: 'closed' as const,
|
||||
status: 'disconnected'
|
||||
};
|
||||
}
|
||||
|
||||
// Keep the current state in case of other errors
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps server instances to the local WhatsAppInstance format
|
||||
*/
|
||||
export const mapServerInstancesToLocal = (
|
||||
serverInstances: any[],
|
||||
currentUserId: string
|
||||
): WhatsAppInstance[] => {
|
||||
return serverInstances.map(serverInstance => {
|
||||
// Ensure we convert server state to valid connectionState
|
||||
let connectionState: 'open' | 'closed' | 'connecting';
|
||||
const state = serverInstance.state || 'closed';
|
||||
|
||||
if (state === 'open' || state === 'connecting') {
|
||||
connectionState = state;
|
||||
} else {
|
||||
connectionState = 'closed';
|
||||
}
|
||||
|
||||
return {
|
||||
instanceName: serverInstance.instanceName,
|
||||
instanceId: serverInstance.instanceName, // Using instanceName as the ID for consistency
|
||||
phoneNumber: serverInstance.number || 'Desconhecido',
|
||||
userId: currentUserId,
|
||||
connectionState: connectionState,
|
||||
status: connectionState === 'open' ? 'connected' :
|
||||
connectionState === 'connecting' ? 'connecting' : 'disconnected'
|
||||
};
|
||||
});
|
||||
};
|
||||
@ -1,31 +0,0 @@
|
||||
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
|
||||
/**
|
||||
* Load WhatsApp instances from localStorage for a specific user
|
||||
*/
|
||||
export const loadInstancesFromLocalStorage = (userId: string): WhatsAppInstance[] => {
|
||||
try {
|
||||
const storedData = localStorage.getItem(`whatsAppInstances_${userId}`);
|
||||
if (storedData) {
|
||||
const parsedData = JSON.parse(storedData);
|
||||
console.log(`Loaded ${parsedData.length} instances from localStorage for user ${userId}`);
|
||||
return parsedData;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading WhatsApp instances from localStorage:', error);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Save WhatsApp instances to localStorage for a specific user
|
||||
*/
|
||||
export const saveInstancesToLocalStorage = (instances: WhatsAppInstance[], userId: string): void => {
|
||||
try {
|
||||
console.log(`Saving ${instances.length} instances to localStorage for user ${userId}`);
|
||||
localStorage.setItem(`whatsAppInstances_${userId}`, JSON.stringify(instances));
|
||||
} catch (error) {
|
||||
console.error('Error saving WhatsApp instances to localStorage:', error);
|
||||
}
|
||||
};
|
||||
@ -1,18 +0,0 @@
|
||||
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
|
||||
export interface WhatsAppInstancesState {
|
||||
instances: WhatsAppInstance[];
|
||||
currentUserId: string;
|
||||
isRefreshing: boolean;
|
||||
}
|
||||
|
||||
export interface WhatsAppInstancesActions {
|
||||
addInstance: (newInstance: WhatsAppInstance) => void;
|
||||
removeInstance: (instanceId: string) => void;
|
||||
updateInstance: (updatedInstance: WhatsAppInstance) => void;
|
||||
refreshInstances: () => Promise<void>;
|
||||
checkAllInstancesStatus: () => Promise<void>;
|
||||
}
|
||||
|
||||
export type WhatsAppInstancesHookReturn = WhatsAppInstancesState & WhatsAppInstancesActions;
|
||||
@ -1,82 +0,0 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
import { restartInstance, logoutInstance } from '@/services/whatsAppService';
|
||||
|
||||
/**
|
||||
* Hook for handling WhatsApp instance connection actions like restart and logout
|
||||
*/
|
||||
export const useInstanceConnection = (
|
||||
updateInstance: (instance: WhatsAppInstance) => void,
|
||||
checkAllInstancesStatus: () => Promise<void>
|
||||
) => {
|
||||
const { toast } = useToast();
|
||||
|
||||
// Handler for when an instance is restarted
|
||||
const handleRestartInstance = async (instance: WhatsAppInstance) => {
|
||||
try {
|
||||
console.log(`Attempting to restart instance ${instance.instanceName}`);
|
||||
await restartInstance(instance.instanceName);
|
||||
|
||||
// Update the instance state to "connecting"
|
||||
const updatedInstance = {
|
||||
...instance,
|
||||
connectionState: 'connecting' as const
|
||||
};
|
||||
console.log(`Instance ${instance.instanceName} restart initiated, setting state to connecting`, updatedInstance);
|
||||
updateInstance(updatedInstance);
|
||||
|
||||
toast({
|
||||
title: "Sucesso",
|
||||
description: `Instância ${instance.instanceName} reiniciada com sucesso`
|
||||
});
|
||||
|
||||
// Check status after a brief delay to give time to update
|
||||
setTimeout(() => checkAllInstancesStatus(), 3000);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error restarting instance ${instance.instanceName}:`, error);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: `Falha ao reiniciar a instância ${instance.instanceName}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Handler for when an instance is disconnected
|
||||
const handleLogoutInstance = async (instance: WhatsAppInstance) => {
|
||||
try {
|
||||
console.log(`Attempting to logout instance ${instance.instanceName}`);
|
||||
await logoutInstance(instance.instanceName);
|
||||
|
||||
// Update the instance state to "closed"
|
||||
const updatedInstance = {
|
||||
...instance,
|
||||
connectionState: 'closed' as const,
|
||||
status: 'disconnected'
|
||||
};
|
||||
console.log(`Instance ${instance.instanceName} logout successful, updating instance state`, updatedInstance);
|
||||
updateInstance(updatedInstance);
|
||||
|
||||
toast({
|
||||
title: "Sucesso",
|
||||
description: `Instância ${instance.instanceName} desconectada com sucesso`
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error logging out instance ${instance.instanceName}:`, error);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: `Falha ao desconectar a instância ${instance.instanceName}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleRestartInstance,
|
||||
handleLogoutInstance
|
||||
};
|
||||
};
|
||||
@ -1,47 +0,0 @@
|
||||
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { deleteInstance } from '@/services/whatsAppService';
|
||||
|
||||
/**
|
||||
* Hook for handling WhatsApp instance deletion
|
||||
*/
|
||||
export const useInstanceDeletion = (
|
||||
removeInstance: (instanceId: string) => void
|
||||
) => {
|
||||
const { toast } = useToast();
|
||||
|
||||
// Handler for when an instance is deleted
|
||||
const handleDeleteInstance = async (instanceId: string, instanceName: string) => {
|
||||
console.log(`Deleting instance with ID: ${instanceId}, name: ${instanceName}`);
|
||||
|
||||
try {
|
||||
// Call API to delete instance
|
||||
const response = await deleteInstance(instanceName);
|
||||
console.log(`Deletion API response for instance ${instanceName}:`, response);
|
||||
|
||||
// Remove from local state
|
||||
removeInstance(instanceId);
|
||||
|
||||
toast({
|
||||
title: "Instância removida",
|
||||
description: "A instância do WhatsApp foi removida com sucesso.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error deleting instance with ID ${instanceId}:`, error);
|
||||
|
||||
// Even if the API call fails, we still want to remove the instance from local storage
|
||||
// This handles the case when instances are in inconsistent state with the server
|
||||
removeInstance(instanceId);
|
||||
|
||||
toast({
|
||||
title: "Aviso",
|
||||
description: "Instância removida localmente, mas pode haver falha na comunicação com o servidor.",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleDeleteInstance
|
||||
};
|
||||
};
|
||||
@ -1,45 +0,0 @@
|
||||
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
import { setInstancePresence } from '@/services/whatsAppService';
|
||||
|
||||
/**
|
||||
* Hook for handling WhatsApp instance presence (online/offline)
|
||||
*/
|
||||
export const useInstancePresence = (
|
||||
updateInstance: (instance: WhatsAppInstance) => void
|
||||
) => {
|
||||
const { toast } = useToast();
|
||||
|
||||
// Handler for when presence is changed
|
||||
const handleSetPresence = async (instance: WhatsAppInstance, presence: 'online' | 'offline') => {
|
||||
try {
|
||||
console.log(`Setting presence to ${presence} for instance ${instance.instanceName}`);
|
||||
await setInstancePresence(instance.instanceName, presence);
|
||||
|
||||
const updatedInstance = {
|
||||
...instance,
|
||||
presence: presence
|
||||
};
|
||||
console.log(`Updated instance with new presence status`, updatedInstance);
|
||||
updateInstance(updatedInstance);
|
||||
|
||||
toast({
|
||||
title: "Sucesso",
|
||||
description: `Instância ${instance.instanceName} agora está ${presence === 'online' ? 'Online' : 'Offline'}`
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error setting presence to ${presence} for instance ${instance.instanceName}:`, error);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: `Falha ao definir presença ${presence} para ${instance.instanceName}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleSetPresence
|
||||
};
|
||||
};
|
||||
@ -1,43 +0,0 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
import { fetchQrCode } from '@/services/whatsAppService';
|
||||
|
||||
/**
|
||||
* Hook for handling WhatsApp QR code dialog functionality
|
||||
*/
|
||||
export const useQrCodeDialog = () => {
|
||||
const { toast } = useToast();
|
||||
const [activeInstance, setActiveInstance] = useState<WhatsAppInstance | null>(null);
|
||||
const [qrDialogOpen, setQrDialogOpen] = useState(false);
|
||||
|
||||
// Handler for when QR code dialog is requested
|
||||
const handleViewQrCode = async (instance: WhatsAppInstance) => {
|
||||
console.log(`Opening QR code dialog for instance: ${instance.instanceName}`);
|
||||
setActiveInstance(instance);
|
||||
setQrDialogOpen(true);
|
||||
|
||||
try {
|
||||
const data = await fetchQrCode(instance.instanceName);
|
||||
console.log('QR Code API response:', data);
|
||||
|
||||
// If the fetchQrCode call was successful, QrCodeDialog will handle showing the QR code
|
||||
} catch (error) {
|
||||
console.error("Error initiating QR code fetch:", error);
|
||||
toast({
|
||||
title: "Erro ao obter QR Code",
|
||||
description: "Falha ao iniciar obtenção de QR Code. Tente novamente.",
|
||||
variant: "destructive",
|
||||
});
|
||||
setQrDialogOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
activeInstance,
|
||||
qrDialogOpen,
|
||||
setQrDialogOpen,
|
||||
handleViewQrCode
|
||||
};
|
||||
};
|
||||
@ -1,40 +0,0 @@
|
||||
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
import { useInstanceConnection } from './useInstanceConnection';
|
||||
import { useInstancePresence } from './useInstancePresence';
|
||||
import { useInstanceDeletion } from './useInstanceDeletion';
|
||||
import { useQrCodeDialog } from './useQrCodeDialog';
|
||||
|
||||
/**
|
||||
* Main hook that combines all WhatsApp instance actions
|
||||
*/
|
||||
export const useWhatsAppActions = (
|
||||
updateInstance: (instance: WhatsAppInstance) => void,
|
||||
removeInstance: (instanceId: string) => void,
|
||||
checkAllInstancesStatus: () => Promise<void>
|
||||
) => {
|
||||
// Combine all the action hooks
|
||||
const { handleRestartInstance, handleLogoutInstance } =
|
||||
useInstanceConnection(updateInstance, checkAllInstancesStatus);
|
||||
|
||||
const { handleSetPresence } =
|
||||
useInstancePresence(updateInstance);
|
||||
|
||||
const { handleDeleteInstance } =
|
||||
useInstanceDeletion(removeInstance);
|
||||
|
||||
const { activeInstance, qrDialogOpen, setQrDialogOpen, handleViewQrCode } =
|
||||
useQrCodeDialog();
|
||||
|
||||
return {
|
||||
// Re-export all actions and state from the specialized hooks
|
||||
activeInstance,
|
||||
qrDialogOpen,
|
||||
setQrDialogOpen,
|
||||
handleRestartInstance,
|
||||
handleLogoutInstance,
|
||||
handleSetPresence,
|
||||
handleDeleteInstance,
|
||||
handleViewQrCode
|
||||
};
|
||||
};
|
||||
@ -1,196 +0,0 @@
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
import { fetchAllInstances } from '@/services/whatsAppService';
|
||||
import {
|
||||
loadInstancesFromLocalStorage,
|
||||
saveInstancesToLocalStorage
|
||||
} from './instanceStorageUtils';
|
||||
import {
|
||||
checkInstanceStatus,
|
||||
mapServerInstancesToLocal
|
||||
} from './instanceStatusUtils';
|
||||
import { WhatsAppInstancesHookReturn } from './types';
|
||||
|
||||
/**
|
||||
* Hook for managing WhatsApp instances
|
||||
*/
|
||||
export const useWhatsAppInstances = (): WhatsAppInstancesHookReturn => {
|
||||
const { toast } = useToast();
|
||||
const [instances, setInstances] = useState<WhatsAppInstance[]>([]);
|
||||
const [currentUserId, setCurrentUserId] = useState<string>('');
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
// Get current user ID and load instances on component mount
|
||||
useEffect(() => {
|
||||
const userId = localStorage.getItem('userId') || '';
|
||||
console.log("Current userId from localStorage:", userId);
|
||||
setCurrentUserId(userId);
|
||||
|
||||
if (userId) {
|
||||
const userInstances = loadInstancesFromLocalStorage(userId);
|
||||
console.log('Loaded user instances:', userInstances);
|
||||
setInstances(userInstances);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Save instances to localStorage whenever they change
|
||||
useEffect(() => {
|
||||
if (currentUserId && instances.length > 0) {
|
||||
console.log('Saving instances to localStorage:', instances);
|
||||
saveInstancesToLocalStorage(instances, currentUserId);
|
||||
}
|
||||
}, [instances, currentUserId]);
|
||||
|
||||
// Check connection status for all instances
|
||||
const checkAllInstancesStatus = useCallback(async () => {
|
||||
if (instances.length === 0) return;
|
||||
|
||||
console.log(`Checking connection status for ${instances.length} instances`);
|
||||
|
||||
try {
|
||||
const updatedInstances = await Promise.all(
|
||||
instances.map(async (instance) => checkInstanceStatus(instance))
|
||||
);
|
||||
|
||||
// Only update state if there are actual changes
|
||||
const hasChanges = JSON.stringify(updatedInstances) !== JSON.stringify(instances);
|
||||
if (hasChanges) {
|
||||
console.log('Updated instances after status check:', updatedInstances);
|
||||
setInstances(updatedInstances);
|
||||
} else {
|
||||
console.log('No changes in instance status');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking instances status:", error);
|
||||
}
|
||||
}, [instances]);
|
||||
|
||||
// Refresh instances from the server
|
||||
const refreshInstances = async () => {
|
||||
if (!currentUserId) {
|
||||
toast({
|
||||
title: "Erro de autenticação",
|
||||
description: "Você precisa estar logado para atualizar as instâncias",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
console.log("Fetching instances from server...");
|
||||
const response = await fetchAllInstances();
|
||||
console.log("Fetched instances from server:", response);
|
||||
|
||||
if (response.instances && Array.isArray(response.instances)) {
|
||||
// Map server instances to local format
|
||||
const serverInstances = mapServerInstancesToLocal(response.instances, currentUserId);
|
||||
|
||||
console.log("Mapped server instances:", serverInstances);
|
||||
|
||||
// Find new instances that don't exist locally
|
||||
const localInstanceIds = new Set(instances.map(i => i.instanceId));
|
||||
const newServerInstances = serverInstances.filter(i => !localInstanceIds.has(i.instanceId));
|
||||
|
||||
// Update existing instances with server data
|
||||
const updatedExistingInstances = instances.map(localInstance => {
|
||||
const serverMatch = serverInstances.find(si => si.instanceId === localInstance.instanceId);
|
||||
if (serverMatch) {
|
||||
return {
|
||||
...localInstance,
|
||||
connectionState: serverMatch.connectionState,
|
||||
status: serverMatch.status
|
||||
};
|
||||
}
|
||||
return localInstance;
|
||||
});
|
||||
|
||||
// Combine everything
|
||||
const allInstances = [...updatedExistingInstances, ...newServerInstances];
|
||||
|
||||
console.log("Combined instances after refresh:", allInstances);
|
||||
setInstances(allInstances);
|
||||
|
||||
toast({
|
||||
title: "Sucesso",
|
||||
description: `${serverInstances.length} instâncias encontradas no servidor`,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: "Aviso",
|
||||
description: "Nenhuma instância encontrada no servidor",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro ao buscar instâncias:", error);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: "Falha ao buscar instâncias do servidor",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
// Check status after updating the list
|
||||
await checkAllInstancesStatus();
|
||||
}
|
||||
};
|
||||
|
||||
// Add a new instance
|
||||
const addInstance = (newInstance: WhatsAppInstance) => {
|
||||
console.log("New instance created, adding to instances list:", newInstance);
|
||||
|
||||
// Check if instance with same ID already exists
|
||||
const existingIndex = instances.findIndex(inst => inst.instanceId === newInstance.instanceId);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// Update existing instance
|
||||
console.log(`Instance with ID ${newInstance.instanceId} already exists, updating it`);
|
||||
setInstances(prevInstances =>
|
||||
prevInstances.map((instance, index) =>
|
||||
index === existingIndex ? newInstance : instance
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Add new instance
|
||||
setInstances(prevInstances => [...prevInstances, newInstance]);
|
||||
}
|
||||
};
|
||||
|
||||
// Remove an instance
|
||||
const removeInstance = (instanceId: string) => {
|
||||
console.log(`Removing instance with ID: ${instanceId}`);
|
||||
setInstances(prevInstances => {
|
||||
const filtered = prevInstances.filter(instance => instance.instanceId !== instanceId);
|
||||
console.log("Updated instances after deletion:", filtered);
|
||||
return filtered;
|
||||
});
|
||||
};
|
||||
|
||||
// Update an instance
|
||||
const updateInstance = (updatedInstance: WhatsAppInstance) => {
|
||||
console.log(`Updating instance: ${updatedInstance.instanceName}`, updatedInstance);
|
||||
setInstances(prevInstances => {
|
||||
const newInstances = prevInstances.map(instance =>
|
||||
instance.instanceId === updatedInstance.instanceId
|
||||
? updatedInstance
|
||||
: instance
|
||||
);
|
||||
|
||||
console.log("Updated instances:", newInstances);
|
||||
return newInstances;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
instances,
|
||||
isRefreshing,
|
||||
currentUserId,
|
||||
addInstance,
|
||||
removeInstance,
|
||||
updateInstance,
|
||||
refreshInstances,
|
||||
checkAllInstancesStatus
|
||||
};
|
||||
};
|
||||
@ -147,42 +147,6 @@ export type Database = {
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
rodrigo_audio_messages: {
|
||||
Row: {
|
||||
audio_url: string | null
|
||||
created_at: string
|
||||
duration: number | null
|
||||
id: string
|
||||
instance_id: string
|
||||
message_id: string | null
|
||||
metadata: Json | null
|
||||
sender_id: string
|
||||
sender_name: string | null
|
||||
}
|
||||
Insert: {
|
||||
audio_url?: string | null
|
||||
created_at?: string
|
||||
duration?: number | null
|
||||
id?: string
|
||||
instance_id: string
|
||||
message_id?: string | null
|
||||
metadata?: Json | null
|
||||
sender_id: string
|
||||
sender_name?: string | null
|
||||
}
|
||||
Update: {
|
||||
audio_url?: string | null
|
||||
created_at?: string
|
||||
duration?: number | null
|
||||
id?: string
|
||||
instance_id?: string
|
||||
message_id?: string | null
|
||||
metadata?: Json | null
|
||||
sender_id?: string
|
||||
sender_name?: string | null
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
transacoes: {
|
||||
Row: {
|
||||
categoria: string | null
|
||||
@ -190,7 +154,6 @@ export type Database = {
|
||||
detalhes: string | null
|
||||
estabelecimento: string | null
|
||||
id: number
|
||||
login: string | null
|
||||
quando: string | null
|
||||
tipo: string | null
|
||||
user: string | null
|
||||
@ -202,7 +165,6 @@ export type Database = {
|
||||
detalhes?: string | null
|
||||
estabelecimento?: string | null
|
||||
id?: number
|
||||
login?: string | null
|
||||
quando?: string | null
|
||||
tipo?: string | null
|
||||
user?: string | null
|
||||
@ -214,7 +176,6 @@ export type Database = {
|
||||
detalhes?: string | null
|
||||
estabelecimento?: string | null
|
||||
id?: number
|
||||
login?: string | null
|
||||
quando?: string | null
|
||||
tipo?: string | null
|
||||
user?: string | null
|
||||
|
||||
@ -1,84 +0,0 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Layout from '@/components/layout/Layout';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { AudioMessage, getRodrigoAudioMessages } from '@/services/audioMessageService';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
const RodrigoAudio = () => {
|
||||
const [audioMessages, setAudioMessages] = useState<AudioMessage[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const fetchAudioMessages = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const messages = await getRodrigoAudioMessages();
|
||||
setAudioMessages(messages);
|
||||
} catch (error) {
|
||||
console.error('Error fetching Rodrigo audio messages:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAudioMessages();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto p-4">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-2xl font-bold">Áudios do Rodrigo</h1>
|
||||
<Button
|
||||
onClick={fetchAudioMessages}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? 'Carregando...' : 'Atualizar'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{audioMessages.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="py-6">
|
||||
<div className="text-center text-muted-foreground">
|
||||
<p>Nenhuma mensagem de áudio encontrada para Rodrigo.</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{audioMessages.map((message) => (
|
||||
<Card key={message.id}>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">
|
||||
{message.sender_name || 'Usuário Desconhecido'}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Data: {message.created_at ? format(new Date(message.created_at), 'dd/MM/yyyy HH:mm') : 'N/A'}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Duração: {message.duration ? `${message.duration}s` : 'N/A'}
|
||||
</p>
|
||||
{message.audio_url && (
|
||||
<audio controls className="w-full">
|
||||
<source src={message.audio_url} type="audio/mpeg" />
|
||||
Seu navegador não suporta áudio
|
||||
</audio>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default RodrigoAudio;
|
||||
@ -1,27 +1,25 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import Layout from '@/components/layout/Layout';
|
||||
import {
|
||||
CreateInstanceForm,
|
||||
InstanceList,
|
||||
InstanceStats,
|
||||
QrCodeDialog
|
||||
} from '@/components/whatsapp';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import UsersDialog from '@/components/usuarios/UsersDialog';
|
||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||
import CreateInstanceForm from '@/components/whatsapp/CreateInstanceForm';
|
||||
import InstanceList from '@/components/whatsapp/InstanceList';
|
||||
import InstanceStats from '@/components/whatsapp/InstanceStats';
|
||||
import QrCodeDialog from '@/components/whatsapp/QrCodeDialog';
|
||||
import { useWhatsAppInstances } from '@/hooks/useWhatsAppInstances';
|
||||
import { useWhatsAppActions } from '@/hooks/useWhatsAppActions';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
const WhatsApp = () => {
|
||||
const { toast } = useToast();
|
||||
const {
|
||||
instances,
|
||||
isRefreshing,
|
||||
currentUserId,
|
||||
isRefreshing,
|
||||
addInstance,
|
||||
removeInstance,
|
||||
updateInstance,
|
||||
refreshInstances,
|
||||
checkAllInstancesStatus
|
||||
refreshInstances,
|
||||
checkAllInstancesStatus
|
||||
} = useWhatsAppInstances();
|
||||
|
||||
const {
|
||||
@ -35,85 +33,119 @@ const WhatsApp = () => {
|
||||
handleViewQrCode
|
||||
} = useWhatsAppActions(updateInstance, removeInstance, checkAllInstancesStatus);
|
||||
|
||||
// Poll for status updates every 30 seconds
|
||||
// Set up periodic status checks
|
||||
useEffect(() => {
|
||||
let interval: ReturnType<typeof setInterval> | null = null;
|
||||
console.log("Setting up periodic status checks, current instances:", instances.length);
|
||||
|
||||
const startPolling = () => {
|
||||
interval = setInterval(() => {
|
||||
console.log("Polling for WhatsApp instance status updates");
|
||||
checkAllInstancesStatus();
|
||||
}, 30000); // Check every 30 seconds
|
||||
};
|
||||
// Check status initially
|
||||
if (instances.length > 0) {
|
||||
checkAllInstancesStatus();
|
||||
}
|
||||
|
||||
// Start polling
|
||||
startPolling();
|
||||
|
||||
// Cleanup on unmount
|
||||
// Set up interval for periodic checks (every 30 seconds)
|
||||
const interval = setInterval(() => {
|
||||
if (instances.length > 0) {
|
||||
console.log("Running periodic status check");
|
||||
checkAllInstancesStatus();
|
||||
}
|
||||
}, 30000); // 30 seconds
|
||||
|
||||
// Clean up interval when component unmounts
|
||||
return () => {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
};
|
||||
}, [checkAllInstancesStatus]);
|
||||
}, [instances.length, checkAllInstancesStatus]);
|
||||
|
||||
// Create a wrapper function that matches the expected signature
|
||||
const handleDeleteInstanceWrapper = (instanceId: string) => {
|
||||
const instance = instances.find(inst => inst.instanceId === instanceId);
|
||||
if (instance) {
|
||||
handleDeleteInstance(instanceId, instance.instanceName);
|
||||
} else {
|
||||
console.error(`Instance with ID ${instanceId} not found`);
|
||||
// Run refresh instances on initial load to get server instances
|
||||
useEffect(() => {
|
||||
const initialLoad = async () => {
|
||||
if (instances.length === 0) {
|
||||
try {
|
||||
await refreshInstances();
|
||||
} catch (error) {
|
||||
console.error("Error on initial instance refresh:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
initialLoad();
|
||||
}, []);
|
||||
|
||||
// Handler for when a new instance is created
|
||||
const handleInstanceCreated = async (newInstance: WhatsAppInstance) => {
|
||||
console.log('New instance to be added:', newInstance);
|
||||
addInstance(newInstance);
|
||||
|
||||
// If there's a QR code in the response, show it
|
||||
if (newInstance.qrcode) {
|
||||
handleViewQrCode(newInstance);
|
||||
}
|
||||
|
||||
// Trigger a status check for all instances
|
||||
try {
|
||||
await checkAllInstancesStatus();
|
||||
} catch (error) {
|
||||
console.error("Error checking status after instance creation:", error);
|
||||
toast({
|
||||
title: "Aviso",
|
||||
description: "Instância criada, mas não foi possível verificar o status. Tente atualizar a lista manualmente.",
|
||||
variant: "default",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Handler for when an instance is deleted
|
||||
const handleDeleteInstanceWrapper = (instanceId: string) => {
|
||||
console.log(`Instance deletion requested for ID: ${instanceId}`);
|
||||
const instanceToDelete = instances.find(i => i.instanceId === instanceId);
|
||||
if (instanceToDelete) {
|
||||
handleDeleteInstance(instanceId, instanceToDelete.instanceName);
|
||||
} else {
|
||||
console.error(`Instance with ID ${instanceId} not found for deletion`);
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: "Instância não encontrada para exclusão",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
console.log('Current instances in WhatsApp component:', instances);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto p-4">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center mb-6">
|
||||
<h1 className="text-2xl font-bold mb-4 md:mb-0">WhatsApp Instances</h1>
|
||||
<div className="flex gap-2 items-center">
|
||||
<UsersDialog />
|
||||
<Button
|
||||
onClick={refreshInstances}
|
||||
disabled={isRefreshing}
|
||||
variant="outline"
|
||||
>
|
||||
{isRefreshing ? 'Atualizando...' : 'Atualizar Instâncias'}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold tracking-tight">Conectar WhatsApp</h1>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="md:col-span-2">
|
||||
<CreateInstanceForm onInstanceCreated={addInstance} />
|
||||
<div className="mt-8">
|
||||
<InstanceList
|
||||
instances={instances}
|
||||
onRestartInstance={handleRestartInstance}
|
||||
onLogoutInstance={handleLogoutInstance}
|
||||
onSetPresence={handleSetPresence}
|
||||
onDeleteInstance={handleDeleteInstanceWrapper}
|
||||
onViewQrCode={handleViewQrCode}
|
||||
onRefreshInstances={refreshInstances}
|
||||
isRefreshing={isRefreshing}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<InstanceStats instances={instances} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{activeInstance && (
|
||||
<QrCodeDialog
|
||||
open={qrDialogOpen}
|
||||
setOpen={setQrDialogOpen}
|
||||
instanceName={activeInstance.instanceName}
|
||||
phoneNumber={activeInstance.phoneNumber}
|
||||
onStatusCheck={checkAllInstancesStatus}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Form to create a new instance */}
|
||||
<CreateInstanceForm onInstanceCreated={handleInstanceCreated} />
|
||||
|
||||
{/* Stats component */}
|
||||
<InstanceStats instances={instances} />
|
||||
|
||||
{/* List of created instances */}
|
||||
<InstanceList
|
||||
instances={instances}
|
||||
onViewQrCode={handleViewQrCode}
|
||||
onDelete={handleDeleteInstanceWrapper}
|
||||
onRestart={handleRestartInstance}
|
||||
onLogout={handleLogoutInstance}
|
||||
onSetPresence={handleSetPresence}
|
||||
onRefreshInstances={refreshInstances}
|
||||
isRefreshing={isRefreshing}
|
||||
/>
|
||||
|
||||
{/* QR Code Dialog */}
|
||||
<QrCodeDialog
|
||||
open={qrDialogOpen}
|
||||
onOpenChange={setQrDialogOpen}
|
||||
activeInstance={activeInstance}
|
||||
onStatusCheck={checkAllInstancesStatus}
|
||||
/>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
@ -1,104 +0,0 @@
|
||||
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
// Define types for audio message data
|
||||
export interface AudioMessage {
|
||||
id?: string;
|
||||
created_at?: string;
|
||||
sender_id: string;
|
||||
sender_name?: string;
|
||||
message_id?: string;
|
||||
audio_url?: string;
|
||||
duration?: number;
|
||||
instance_id: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
// Constants for specific users
|
||||
const RODRIGO_GROUP_ID = '120363420212322973@g.us';
|
||||
const RODRIGO_EMAIL = 'rodrigobm10@gmail.com';
|
||||
|
||||
/**
|
||||
* Stores an audio message from a specific sender to Rodrigo's database
|
||||
*/
|
||||
export const storeAudioMessageForRodrigo = async (audioMessage: AudioMessage): Promise<boolean> => {
|
||||
try {
|
||||
console.log(`Storing audio message for Rodrigo from sender ${audioMessage.sender_id}`);
|
||||
|
||||
// Ensure this is a message for Rodrigo's group
|
||||
if (audioMessage.sender_id !== RODRIGO_GROUP_ID) {
|
||||
console.log('Message not for Rodrigo group, skipping storage');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert the audio message into Rodrigo's table
|
||||
const { data, error } = await supabase
|
||||
.from('rodrigo_audio_messages')
|
||||
.insert([audioMessage]);
|
||||
|
||||
if (error) {
|
||||
console.error('Error storing audio message for Rodrigo:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('Successfully stored audio message for Rodrigo', data);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Exception storing audio message for Rodrigo:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves all audio messages stored for Rodrigo
|
||||
*/
|
||||
export const getRodrigoAudioMessages = async (): Promise<AudioMessage[]> => {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('rodrigo_audio_messages')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching Rodrigo audio messages:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data as AudioMessage[];
|
||||
} catch (error) {
|
||||
console.error('Exception fetching Rodrigo audio messages:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for managing audio messages for Rodrigo
|
||||
*/
|
||||
export const useRodrigoAudioMessages = () => {
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleNewAudioMessage = async (audioMessage: AudioMessage): Promise<boolean> => {
|
||||
const success = await storeAudioMessageForRodrigo(audioMessage);
|
||||
|
||||
if (success) {
|
||||
toast({
|
||||
title: "Áudio gravado",
|
||||
description: `Áudio de ${audioMessage.sender_name || 'usuário'} foi gravado para o Rodrigo.`,
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
toast({
|
||||
title: "Erro",
|
||||
description: "Não foi possível gravar o áudio para o Rodrigo.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleNewAudioMessage,
|
||||
getRodrigoAudioMessages
|
||||
};
|
||||
};
|
||||
@ -1,62 +0,0 @@
|
||||
|
||||
import { supabase } from "@/integrations/supabase/client";
|
||||
import { format } from "date-fns";
|
||||
|
||||
export interface UserInfo {
|
||||
id: string;
|
||||
nome: string;
|
||||
email: string;
|
||||
empresa: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export const getUsersRegisteredToday = async (): Promise<UserInfo[]> => {
|
||||
try {
|
||||
console.log("Buscando usuários cadastrados hoje...");
|
||||
// Obtém a data atual no formato YYYY-MM-DD
|
||||
const today = format(new Date(), 'yyyy-MM-dd');
|
||||
|
||||
// Busca usuários cadastrados hoje
|
||||
const { data, error } = await supabase
|
||||
.from("usuarios")
|
||||
.select("*")
|
||||
.gte('created_at', `${today}T00:00:00`) // Data início (hoje 00:00:00)
|
||||
.lte('created_at', `${today}T23:59:59`) // Data fim (hoje 23:59:59)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error("Erro ao buscar usuários:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log(`Encontrados ${data?.length || 0} usuários cadastrados hoje:`, data);
|
||||
|
||||
return data as UserInfo[];
|
||||
} catch (error) {
|
||||
console.error("Erro ao buscar usuários cadastrados hoje:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllUsers = async (): Promise<UserInfo[]> => {
|
||||
try {
|
||||
console.log("Buscando todos os usuários...");
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from("usuarios")
|
||||
.select("*")
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error("Erro ao buscar usuários:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log(`Encontrados ${data?.length || 0} usuários no total`);
|
||||
|
||||
return data as UserInfo[];
|
||||
} catch (error) {
|
||||
console.error("Erro ao buscar todos os usuários:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@ -1,125 +0,0 @@
|
||||
|
||||
import { AudioMessage, storeAudioMessageForRodrigo } from './audioMessageService';
|
||||
|
||||
// Constants for specific users
|
||||
const RODRIGO_GROUP_ID = '120363420212322973@g.us';
|
||||
const RODRIGO_EMAIL = 'rodrigobm10@gmail.com';
|
||||
|
||||
interface WebhookMessageEvent {
|
||||
instance: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
message: {
|
||||
id: string;
|
||||
from: string;
|
||||
fromMe: boolean;
|
||||
to: string;
|
||||
body: string;
|
||||
type: string;
|
||||
caption?: string;
|
||||
timestamp: number;
|
||||
hasMedia: boolean;
|
||||
mediaUrl?: string;
|
||||
mediaMimeType?: string;
|
||||
mediaDuration?: number;
|
||||
sender?: {
|
||||
id: string;
|
||||
name?: string;
|
||||
pushname?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming webhook event from the Evolution API
|
||||
*/
|
||||
export const processWebhookEvent = async (event: any): Promise<boolean> => {
|
||||
try {
|
||||
console.log('Processing webhook event:', JSON.stringify(event));
|
||||
|
||||
// Ensure this is a message event
|
||||
if (!event.message) {
|
||||
console.log('Not a message event, skipping');
|
||||
return false;
|
||||
}
|
||||
|
||||
const messageEvent = event as WebhookMessageEvent;
|
||||
|
||||
// Check if this is an audio message
|
||||
if (messageEvent.message.type === 'audio' || messageEvent.message.type === 'voice' || messageEvent.message.type === 'ptt') {
|
||||
return await processAudioMessage(messageEvent);
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error processing webhook event:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Process an audio message from the webhook event
|
||||
*/
|
||||
const processAudioMessage = async (event: WebhookMessageEvent): Promise<boolean> => {
|
||||
// Check if the message is for Rodrigo's group
|
||||
if (event.message.to === RODRIGO_GROUP_ID || event.message.from === RODRIGO_GROUP_ID) {
|
||||
console.log(`Audio message for Rodrigo detected from ${event.message.from}`);
|
||||
|
||||
const audioMessage: AudioMessage = {
|
||||
sender_id: event.message.from,
|
||||
sender_name: event.message.sender?.name || event.message.sender?.pushname,
|
||||
message_id: event.message.id,
|
||||
audio_url: event.message.mediaUrl,
|
||||
duration: event.message.mediaDuration,
|
||||
instance_id: event.instance.id,
|
||||
metadata: {
|
||||
timestamp: event.message.timestamp,
|
||||
mediaType: event.message.mediaMimeType,
|
||||
caption: event.message.caption
|
||||
}
|
||||
};
|
||||
|
||||
return await storeAudioMessageForRodrigo(audioMessage);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configure a webhook for an instance in the Evolution API
|
||||
*/
|
||||
export const configureWebhook = async (
|
||||
instanceName: string,
|
||||
webhookUrl: string,
|
||||
apiKey: string
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const serverUrl = "evolutionapi2.innova1001.com.br";
|
||||
|
||||
const response = await fetch(`https://${serverUrl}/instance/webhook/${encodeURIComponent(instanceName)}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'apikey': apiKey
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url: webhookUrl,
|
||||
events: ['messages', 'messages.ack', 'messages.upsert']
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
console.error('Error configuring webhook:', errorData);
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Webhook configuration response:', data);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error configuring webhook:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -1,10 +0,0 @@
|
||||
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export interface MainNavItem {
|
||||
title: string;
|
||||
href?: string;
|
||||
icon?: ReactNode;
|
||||
disabled?: boolean;
|
||||
external?: boolean;
|
||||
}
|
||||
@ -1,6 +1 @@
|
||||
|
||||
project_id = "tnurlgbvfsxwqgwxamni"
|
||||
|
||||
# Enable webhook edge function without JWT verification
|
||||
[functions.whatsapp-webhook]
|
||||
verify_jwt = false
|
||||
project_id = "tnurlgbvfsxwqgwxamni"
|
||||
@ -1,132 +0,0 @@
|
||||
|
||||
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.4";
|
||||
|
||||
// Constants for specific users
|
||||
const RODRIGO_GROUP_ID = '120363420212322973@g.us';
|
||||
const RODRIGO_EMAIL = 'rodrigobm10@gmail.com';
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers":
|
||||
"authorization, x-client-info, apikey, content-type",
|
||||
};
|
||||
|
||||
interface AudioMessage {
|
||||
sender_id: string;
|
||||
sender_name?: string;
|
||||
message_id?: string;
|
||||
audio_url?: string;
|
||||
duration?: number;
|
||||
instance_id: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL') || '';
|
||||
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') || '';
|
||||
|
||||
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
||||
|
||||
async function storeAudioMessageForRodrigo(audioMessage: AudioMessage) {
|
||||
try {
|
||||
// Insert the audio message into Rodrigo's table
|
||||
const { data, error } = await supabase
|
||||
.from('rodrigo_audio_messages')
|
||||
.insert([audioMessage]);
|
||||
|
||||
if (error) {
|
||||
console.error('Error storing audio message for Rodrigo:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('Successfully stored audio message for Rodrigo', data);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Exception storing audio message for Rodrigo:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Process the webhook event and store audio messages for Rodrigo
|
||||
async function processWebhookEvent(event: any): Promise<boolean> {
|
||||
try {
|
||||
console.log('Processing webhook event:', JSON.stringify(event));
|
||||
|
||||
// Ensure this is a message event
|
||||
if (!event.message) {
|
||||
console.log('Not a message event, skipping');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if this is an audio message
|
||||
const messageType = event.message.type;
|
||||
if (messageType === 'audio' || messageType === 'voice' || messageType === 'ptt') {
|
||||
// Check if the message is for Rodrigo's group
|
||||
if (event.message.to === RODRIGO_GROUP_ID || event.message.from === RODRIGO_GROUP_ID) {
|
||||
console.log(`Audio message for Rodrigo detected from ${event.message.from}`);
|
||||
|
||||
const audioMessage: AudioMessage = {
|
||||
sender_id: event.message.from,
|
||||
sender_name: event.message.sender?.name || event.message.sender?.pushname,
|
||||
message_id: event.message.id,
|
||||
audio_url: event.message.mediaUrl,
|
||||
duration: event.message.mediaDuration,
|
||||
instance_id: event.instance.id,
|
||||
metadata: {
|
||||
timestamp: event.message.timestamp,
|
||||
mediaType: event.message.mediaMimeType,
|
||||
caption: event.message.caption
|
||||
}
|
||||
};
|
||||
|
||||
return await storeAudioMessageForRodrigo(audioMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error processing webhook event:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
serve(async (req) => {
|
||||
// Handle CORS preflight requests
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
if (req.method === "POST") {
|
||||
const event = await req.json();
|
||||
|
||||
// Process the webhook event
|
||||
const success = await processWebhookEvent(event);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ success }),
|
||||
{
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
status: 200,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Method not allowed" }),
|
||||
{
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
status: 405,
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error handling webhook:", error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Internal server error" }),
|
||||
{
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
status: 500,
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user