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:
parent
6f6292f857
commit
1f3197caf5
97
src/components/dashboard/UserProfileButton.tsx
Normal file
97
src/components/dashboard/UserProfileButton.tsx
Normal 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;
|
||||||
@ -4,6 +4,7 @@ import { ArrowLeft } from 'lucide-react';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { enviarFaleConoscoParaWebhook } from '@/utils/webhookService';
|
||||||
import ContactFormFields from './ContactFormFields';
|
import ContactFormFields from './ContactFormFields';
|
||||||
import ContactFormHeader from './ContactFormHeader';
|
import ContactFormHeader from './ContactFormHeader';
|
||||||
|
|
||||||
@ -37,6 +38,15 @@ const ContactForm = ({ onBack }: ContactFormProps) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
let anexoUrl = null;
|
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
|
// Upload do anexo se existe
|
||||||
if (formData.anexo) {
|
if (formData.anexo) {
|
||||||
@ -64,12 +74,31 @@ const ContactForm = ({ onBack }: ContactFormProps) => {
|
|||||||
motivo: formData.motivo,
|
motivo: formData.motivo,
|
||||||
mensagem: formData.mensagem,
|
mensagem: formData.mensagem,
|
||||||
anexo_url: anexoUrl,
|
anexo_url: anexoUrl,
|
||||||
user_id: (await supabase.auth.getUser()).data.user?.id,
|
user_id: userId,
|
||||||
status: 'pendente'
|
status: 'pendente'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) throw error;
|
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({
|
toast({
|
||||||
title: "Mensagem enviada!",
|
title: "Mensagem enviada!",
|
||||||
description: "Você vai receber a resposta no e-mail que cadastrou aqui. Obrigado!",
|
description: "Você vai receber a resposta no e-mail que cadastrou aqui. Obrigado!",
|
||||||
|
|||||||
@ -78,10 +78,6 @@ export default function ModernLayout({ children }: ModernLayoutProps) {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const { toast } = useToast();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [userEmail, setUserEmail] = useState<string>('');
|
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: tourOpen,
|
isOpen: tourOpen,
|
||||||
@ -91,42 +87,6 @@ export default function ModernLayout({ children }: ModernLayoutProps) {
|
|||||||
closeTour
|
closeTour
|
||||||
} = useOnboardingTour();
|
} = 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 = [
|
const mainLinks = [
|
||||||
{
|
{
|
||||||
id: "dashboard",
|
id: "dashboard",
|
||||||
@ -260,52 +220,6 @@ export default function ModernLayout({ children }: ModernLayoutProps) {
|
|||||||
className="space-y-1"
|
className="space-y-1"
|
||||||
iconContainerClassName={!open ? "justify-center" : ""}
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import Layout from '@/components/layout/Layout';
|
import Layout from '@/components/layout/Layout';
|
||||||
import SummaryCard from '@/components/dashboard/SummaryCard';
|
import SummaryCard from '@/components/dashboard/SummaryCard';
|
||||||
import TransactionsTable from '@/components/dashboard/TransactionsTable';
|
import TransactionsTable from '@/components/dashboard/TransactionsTable';
|
||||||
import CategoryChart from '@/components/dashboard/CategoryChart';
|
import CategoryChart from '@/components/dashboard/CategoryChart';
|
||||||
import MonthlyChart from '@/components/dashboard/MonthlyChart';
|
import MonthlyChart from '@/components/dashboard/MonthlyChart';
|
||||||
|
import UserProfileButton from '@/components/dashboard/UserProfileButton';
|
||||||
import { MonthFilter } from '@/components/filters/MonthFilter';
|
import { MonthFilter } from '@/components/filters/MonthFilter';
|
||||||
import { OnboardingTour, useOnboardingTour } from '@/components/onboarding';
|
import { OnboardingTour, useOnboardingTour } from '@/components/onboarding';
|
||||||
import { Wallet, ArrowUp, ArrowDown, PiggyBank } from 'lucide-react';
|
import { Wallet, ArrowUp, ArrowDown, PiggyBank } from 'lucide-react';
|
||||||
@ -154,11 +154,14 @@ const Dashboard = () => {
|
|||||||
Dados de: {formatMonthDisplay(selectedMonth)}
|
Dados de: {formatMonthDisplay(selectedMonth)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<UserProfileButton />
|
||||||
<MonthFilter
|
<MonthFilter
|
||||||
selectedMonth={selectedMonth}
|
selectedMonth={selectedMonth}
|
||||||
onMonthChange={setSelectedMonth}
|
onMonthChange={setSelectedMonth}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Resumo em cards */}
|
{/* Resumo em cards */}
|
||||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||||
|
|||||||
70
src/services/webhookService.ts
Normal file
70
src/services/webhookService.ts
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user