feat: Move user button and integrate webhook

Move the logged-in user button above the month filter on the dashboard. Integrate a webhook call to send contact form data to a specified URL.
This commit is contained in:
gpt-engineer-app[bot] 2025-06-23 02:25:49 +00:00
parent 6f6292f857
commit 1f3197caf5
5 changed files with 205 additions and 92 deletions

View File

@ -0,0 +1,97 @@
import React, { useState, useEffect } from 'react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from '@/components/ui/button';
import { LogOut, User } from 'lucide-react';
import { supabase } from '@/integrations/supabase/client';
import { useToast } from '@/hooks/use-toast';
import { useNavigate } from 'react-router-dom';
const UserProfileButton = () => {
const [userEmail, setUserEmail] = useState<string>('');
const [dropdownOpen, setDropdownOpen] = useState(false);
const { toast } = useToast();
const navigate = useNavigate();
useEffect(() => {
// Buscar dados do usuário logado
const getUserData = async () => {
const { data: { user } } = await supabase.auth.getUser();
if (user?.email) {
setUserEmail(user.email);
}
};
getUserData();
}, []);
const handleLogout = async () => {
try {
const { error } = await supabase.auth.signOut();
if (error) throw error;
// Limpar localStorage
localStorage.removeItem('userEmail');
toast({
title: "Logout realizado",
description: "Você foi desconectado com sucesso"
});
navigate('/auth');
} catch (error) {
console.error('Erro no logout:', error);
toast({
title: "Erro no logout",
description: "Ocorreu um erro ao sair da conta",
variant: "destructive"
});
}
};
return (
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
<DropdownMenuTrigger asChild>
<Button
className="rounded-full py-0 ps-0 h-10 justify-start"
variant="outline"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setDropdownOpen(!dropdownOpen);
}}
>
<div className="me-2 flex aspect-square h-full p-1.5">
<div className="h-full w-full rounded-full bg-blue-600 flex items-center justify-center">
<User className="h-4 w-4 text-white" />
</div>
</div>
<span className="text-sm truncate">
{userEmail || 'Usuário'}
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56 z-50">
<DropdownMenuItem
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setDropdownOpen(false);
handleLogout();
}}
className="flex items-center gap-2 text-red-600 hover:text-red-700 hover:bg-red-50 cursor-pointer"
>
<LogOut className="h-4 w-4" />
Sair
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};
export default UserProfileButton;

View File

@ -4,6 +4,7 @@ import { ArrowLeft } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useToast } from '@/hooks/use-toast';
import { supabase } from '@/integrations/supabase/client';
import { enviarFaleConoscoParaWebhook } from '@/utils/webhookService';
import ContactFormFields from './ContactFormFields';
import ContactFormHeader from './ContactFormHeader';
@ -37,6 +38,15 @@ const ContactForm = ({ onBack }: ContactFormProps) => {
try {
let anexoUrl = null;
let userId = null;
let userEmail = null;
// Obter dados do usuário
const { data: { user } } = await supabase.auth.getUser();
if (user) {
userId = user.id;
userEmail = user.email;
}
// Upload do anexo se existe
if (formData.anexo) {
@ -64,12 +74,31 @@ const ContactForm = ({ onBack }: ContactFormProps) => {
motivo: formData.motivo,
mensagem: formData.mensagem,
anexo_url: anexoUrl,
user_id: (await supabase.auth.getUser()).data.user?.id,
user_id: userId,
status: 'pendente'
});
if (error) throw error;
// Enviar dados para o webhook do N8N
const webhookData = {
assunto: formData.assunto,
motivo: formData.motivo,
mensagem: formData.mensagem,
anexo_url: anexoUrl,
user_id: userId,
user_email: userEmail,
data_envio: new Date().toISOString()
};
try {
await enviarFaleConoscoParaWebhook(webhookData);
console.log("Dados enviados para webhook N8N com sucesso");
} catch (webhookError) {
console.error("Erro ao enviar para webhook N8N:", webhookError);
// Não interrompe o fluxo se o webhook falhar
}
toast({
title: "Mensagem enviada!",
description: "Você vai receber a resposta no e-mail que cadastrou aqui. Obrigado!",

View File

@ -78,10 +78,6 @@ export default function ModernLayout({ children }: ModernLayoutProps) {
const [open, setOpen] = useState(false);
const location = useLocation();
const isMobile = useIsMobile();
const { toast } = useToast();
const navigate = useNavigate();
const [userEmail, setUserEmail] = useState<string>('');
const [dropdownOpen, setDropdownOpen] = useState(false);
const {
isOpen: tourOpen,
@ -91,42 +87,6 @@ export default function ModernLayout({ children }: ModernLayoutProps) {
closeTour
} = useOnboardingTour();
useEffect(() => {
// Buscar dados do usuário logado
const getUserData = async () => {
const { data: { user } } = await supabase.auth.getUser();
if (user?.email) {
setUserEmail(user.email);
}
};
getUserData();
}, []);
const handleLogout = async () => {
try {
const { error } = await supabase.auth.signOut();
if (error) throw error;
// Limpar localStorage
localStorage.removeItem('userEmail');
toast({
title: "Logout realizado",
description: "Você foi desconectado com sucesso"
});
navigate('/auth');
} catch (error) {
console.error('Erro no logout:', error);
toast({
title: "Erro no logout",
description: "Ocorreu um erro ao sair da conta",
variant: "destructive"
});
}
};
const mainLinks = [
{
id: "dashboard",
@ -260,52 +220,6 @@ export default function ModernLayout({ children }: ModernLayoutProps) {
className="space-y-1"
iconContainerClassName={!open ? "justify-center" : ""}
/>
{/* User Profile */}
<div className="mt-4 px-3">
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
<DropdownMenuTrigger asChild>
<Button
className={`w-full rounded-full py-0 ps-0 h-10 ${open ? 'justify-start' : 'justify-center px-0'}`}
variant="outline"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setDropdownOpen(!dropdownOpen);
}}
>
<div className={`${open ? 'me-2' : ''} flex aspect-square h-full p-1.5`}>
<div className="h-full w-full rounded-full bg-blue-600 flex items-center justify-center">
<User className="h-4 w-4 text-white" />
</div>
</div>
{open && (
<span className="text-sm truncate">
{userEmail || 'Usuário'}
</span>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align={open ? "end" : "center"}
side={open ? "top" : "right"}
className="w-56 z-50"
>
<DropdownMenuItem
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setDropdownOpen(false);
handleLogout();
}}
className="flex items-center gap-2 text-red-600 hover:text-red-700 hover:bg-red-50 cursor-pointer"
>
<LogOut className="h-4 w-4" />
Sair
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
</div>

View File

@ -1,10 +1,10 @@
import { useState, useEffect } from 'react';
import Layout from '@/components/layout/Layout';
import SummaryCard from '@/components/dashboard/SummaryCard';
import TransactionsTable from '@/components/dashboard/TransactionsTable';
import CategoryChart from '@/components/dashboard/CategoryChart';
import MonthlyChart from '@/components/dashboard/MonthlyChart';
import UserProfileButton from '@/components/dashboard/UserProfileButton';
import { MonthFilter } from '@/components/filters/MonthFilter';
import { OnboardingTour, useOnboardingTour } from '@/components/onboarding';
import { Wallet, ArrowUp, ArrowDown, PiggyBank } from 'lucide-react';
@ -154,10 +154,13 @@ const Dashboard = () => {
Dados de: {formatMonthDisplay(selectedMonth)}
</p>
</div>
<MonthFilter
selectedMonth={selectedMonth}
onMonthChange={setSelectedMonth}
/>
<div className="flex items-center gap-4">
<UserProfileButton />
<MonthFilter
selectedMonth={selectedMonth}
onMonthChange={setSelectedMonth}
/>
</div>
</div>
{/* Resumo em cards */}

View File

@ -0,0 +1,70 @@
// URL do webhook para notificação de novo cadastro
const WEBHOOK_URL = "https://hook.us1.make.com/j6y1odto82tyo38qv3jh4579kkluhsx7";
// URL do webhook para fale conosco
const FALE_CONOSCO_WEBHOOK_URL = "https://webhookn8n.innova1001.com.br/webhook/faleconosco";
interface UserData {
nome: string;
empresa?: string;
email: string;
whatsapp?: string;
data_cadastro: string;
origem: string;
}
interface FaleConoscoData {
assunto: string;
motivo: string;
mensagem: string;
anexo_url?: string;
user_id?: string;
user_email?: string;
data_envio: string;
}
// Função para enviar dados do usuário para o webhook
export const enviarDadosParaWebhook = async (dadosUsuario: UserData) => {
try {
console.log("Enviando dados do usuário para webhook:", dadosUsuario);
await fetch(WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
mode: 'no-cors', // Necessário para webhooks externos
body: JSON.stringify(dadosUsuario),
});
console.log("Dados enviados com sucesso para o webhook");
} catch (error) {
console.error("Erro ao enviar dados para o webhook:", error);
}
};
// Função para enviar dados do Fale Conosco para o webhook do N8N
export const enviarFaleConoscoParaWebhook = async (dadosFaleConosco: FaleConoscoData) => {
try {
console.log("Enviando dados do Fale Conosco para webhook N8N:", dadosFaleConosco);
const response = await fetch(FALE_CONOSCO_WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(dadosFaleConosco),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log("Dados do Fale Conosco enviados com sucesso para o webhook N8N");
return true;
} catch (error) {
console.error("Erro ao enviar dados do Fale Conosco para o webhook N8N:", error);
throw error;
}
};