Fix: Onboarding tour behavior and button style
Fixes the onboarding tour looping issue and adjusts the button style for better visibility. The tour now correctly opens only once per session until the required conditions are met (connected instance and active group).
This commit is contained in:
parent
6a9e57eb9a
commit
71026e9b63
@ -1,70 +1,31 @@
|
|||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { LogOut, User } from 'lucide-react';
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
|
||||||
|
|
||||||
const Header = () => {
|
import { Menu } from 'lucide-react';
|
||||||
const navigate = useNavigate();
|
import { Button } from '@/components/ui/button';
|
||||||
const { toast } = useToast();
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
const [userName, setUserName] = useState('Usuário');
|
|
||||||
|
|
||||||
useEffect(() => {
|
interface HeaderProps {
|
||||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
onMenuToggle: () => void;
|
||||||
setUserName(session?.user?.email || 'Usuário');
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => subscription.unsubscribe();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleLogout = async () => {
|
|
||||||
const { error } = await supabase.auth.signOut();
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error('Error logging out:', error);
|
|
||||||
toast({
|
|
||||||
title: "Erro no logout",
|
|
||||||
description: "Não foi possível desconectar. Tente novamente.",
|
|
||||||
variant: 'destructive'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: "Logout realizado",
|
|
||||||
description: "Você foi desconectado com sucesso"
|
|
||||||
});
|
|
||||||
navigate('/auth', { replace: true });
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
export default function Header({ onMenuToggle }: HeaderProps) {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-end items-center p-4 border-b">
|
<div className="flex h-14 items-center gap-4 border-b px-4 lg:h-[60px] lg:px-6">
|
||||||
<DropdownMenu>
|
{isMobile && (
|
||||||
<DropdownMenuTrigger asChild>
|
<Button
|
||||||
<Button variant="ghost" className="flex items-center gap-2">
|
variant="outline"
|
||||||
<User size={18} />
|
size="icon"
|
||||||
<span>{userName}</span>
|
className="shrink-0 md:hidden"
|
||||||
|
onClick={onMenuToggle}
|
||||||
|
>
|
||||||
|
<Menu className="h-5 w-5" />
|
||||||
|
<span className="sr-only">Toggle navigation menu</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
)}
|
||||||
<DropdownMenuContent align="end">
|
<div className="w-full flex-1">
|
||||||
<DropdownMenuLabel>Minha conta</DropdownMenuLabel>
|
<h1 className="text-lg font-semibold md:text-2xl">Finance Home</h1>
|
||||||
<DropdownMenuSeparator />
|
</div>
|
||||||
<DropdownMenuItem onClick={handleLogout} className="text-red-500 cursor-pointer">
|
|
||||||
<LogOut className="mr-2 h-4 w-4" />
|
|
||||||
<span>Sair</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Header;
|
|
||||||
|
|||||||
@ -88,9 +88,10 @@ const OnboardingTour: React.FC<OnboardingTourProps> = ({
|
|||||||
width: rect.width + 20,
|
width: rect.width + 20,
|
||||||
height: rect.height + 20,
|
height: rect.height + 20,
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
boxShadow: '0 0 0 9999px rgba(0, 0, 0, 0.7), 0 0 20px rgba(59, 130, 246, 0.8)',
|
boxShadow: '0 0 0 9999px rgba(0, 0, 0, 0.8), 0 0 30px rgba(59, 130, 246, 1), inset 0 0 0 3px rgba(59, 130, 246, 0.8)',
|
||||||
pointerEvents: 'none' as const,
|
pointerEvents: 'none' as const,
|
||||||
zIndex: 9998
|
zIndex: 9998,
|
||||||
|
border: '2px solid rgba(59, 130, 246, 0.9)'
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -98,7 +99,7 @@ const OnboardingTour: React.FC<OnboardingTourProps> = ({
|
|||||||
<>
|
<>
|
||||||
{/* Overlay escuro */}
|
{/* Overlay escuro */}
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black bg-opacity-70 z-[9997]"
|
className="fixed inset-0 bg-black bg-opacity-80 z-[9997]"
|
||||||
style={{ zIndex: 9997 }}
|
style={{ zIndex: 9997 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -109,7 +110,7 @@ const OnboardingTour: React.FC<OnboardingTourProps> = ({
|
|||||||
|
|
||||||
{/* Card do tour */}
|
{/* Card do tour */}
|
||||||
<div className="fixed inset-0 flex items-center justify-center z-[9999] p-4">
|
<div className="fixed inset-0 flex items-center justify-center z-[9999] p-4">
|
||||||
<Card className="w-full max-w-md bg-white shadow-2xl">
|
<Card className="w-full max-w-md bg-white shadow-2xl border-2 border-blue-200">
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="flex justify-between items-start mb-4">
|
<div className="flex justify-between items-start mb-4">
|
||||||
<h3 className="text-xl font-bold text-gray-800">
|
<h3 className="text-xl font-bold text-gray-800">
|
||||||
@ -142,10 +143,17 @@ const OnboardingTour: React.FC<OnboardingTourProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Button variant="outline" onClick={onSkip}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={onSkip}
|
||||||
|
className="hover:bg-gray-100 transition-colors"
|
||||||
|
>
|
||||||
Pular Tour
|
Pular Tour
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onNext}>
|
<Button
|
||||||
|
onClick={onNext}
|
||||||
|
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold px-6 transition-colors shadow-lg"
|
||||||
|
>
|
||||||
{currentStep === steps.length - 1 ? 'Finalizar' : 'Próximo'}
|
{currentStep === steps.length - 1 ? 'Finalizar' : 'Próximo'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,16 +4,29 @@ import { useLocation } from 'react-router-dom';
|
|||||||
import { useWhatsAppInstances } from '@/hooks/useWhatsAppInstances';
|
import { useWhatsAppInstances } from '@/hooks/useWhatsAppInstances';
|
||||||
import { listWhatsAppGroups } from '@/services/whatsAppGroupsService';
|
import { listWhatsAppGroups } from '@/services/whatsAppGroupsService';
|
||||||
|
|
||||||
|
const TOUR_SESSION_KEY = 'onboarding_tour_shown';
|
||||||
|
|
||||||
export const useOnboardingTour = () => {
|
export const useOnboardingTour = () => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [currentStep, setCurrentStep] = useState(0);
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
const [shouldShowTour, setShouldShowTour] = useState(false);
|
const [shouldShowTour, setShouldShowTour] = useState(false);
|
||||||
|
const [tourShownThisSession, setTourShownThisSession] = useState(false);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { instances } = useWhatsAppInstances();
|
const { instances } = useWhatsAppInstances();
|
||||||
|
|
||||||
// Verificar se o tour deve ser exibido
|
// Verificar se o tour deve ser exibido
|
||||||
const checkTourConditions = async () => {
|
const checkTourConditions = async () => {
|
||||||
try {
|
try {
|
||||||
|
// Verificar se já foi mostrado nesta sessão
|
||||||
|
const shownThisSession = sessionStorage.getItem(TOUR_SESSION_KEY) === 'true';
|
||||||
|
|
||||||
|
if (shownThisSession) {
|
||||||
|
console.log('Tour já foi exibido nesta sessão');
|
||||||
|
setShouldShowTour(false);
|
||||||
|
setTourShownThisSession(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Verificar se há instâncias conectadas
|
// Verificar se há instâncias conectadas
|
||||||
const hasConnectedInstance = instances.some(instance =>
|
const hasConnectedInstance = instances.some(instance =>
|
||||||
instance.status === 'connected' || instance.connectionState === 'open'
|
instance.status === 'connected' || instance.connectionState === 'open'
|
||||||
@ -30,16 +43,19 @@ export const useOnboardingTour = () => {
|
|||||||
hasConnectedInstance,
|
hasConnectedInstance,
|
||||||
hasGroups,
|
hasGroups,
|
||||||
shouldShow,
|
shouldShow,
|
||||||
instances: instances.length
|
instances: instances.length,
|
||||||
|
shownThisSession
|
||||||
});
|
});
|
||||||
|
|
||||||
setShouldShowTour(shouldShow);
|
setShouldShowTour(shouldShow);
|
||||||
|
|
||||||
// Se deve mostrar o tour e não está aberto, abrir automaticamente
|
// Se deve mostrar o tour e não foi mostrado ainda, abrir automaticamente
|
||||||
if (shouldShow && !isOpen) {
|
if (shouldShow && !shownThisSession && !isOpen) {
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
setCurrentStep(0);
|
setCurrentStep(0);
|
||||||
} else if (!shouldShow && isOpen) {
|
setTourShownThisSession(true);
|
||||||
|
sessionStorage.setItem(TOUR_SESSION_KEY, 'true');
|
||||||
|
} else if (!shouldShow) {
|
||||||
// Se as condições foram atendidas, fechar o tour
|
// Se as condições foram atendidas, fechar o tour
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
@ -56,11 +72,10 @@ export const useOnboardingTour = () => {
|
|||||||
}
|
}
|
||||||
}, [instances]);
|
}, [instances]);
|
||||||
|
|
||||||
// Verificar condições periodicamente
|
// Verificar condições na inicialização
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(checkTourConditions, 5000); // A cada 5 segundos
|
checkTourConditions();
|
||||||
return () => clearInterval(interval);
|
}, []);
|
||||||
}, [instances]);
|
|
||||||
|
|
||||||
const nextStep = () => {
|
const nextStep = () => {
|
||||||
if (currentStep < 2) {
|
if (currentStep < 2) {
|
||||||
@ -77,6 +92,9 @@ export const useOnboardingTour = () => {
|
|||||||
const closeTour = () => {
|
const closeTour = () => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
setCurrentStep(0);
|
setCurrentStep(0);
|
||||||
|
// Marcar como exibido nesta sessão
|
||||||
|
sessionStorage.setItem(TOUR_SESSION_KEY, 'true');
|
||||||
|
setTourShownThisSession(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user