feat: Implement onboarding tour and admin features

- Activate onboarding tour for new users without groups.
- Remove WhatsApp connection steps from the tour.
- Add admin menu and button to hide WhatsApp connect button for all users.
- Disable Finance Home icon redirect to landing page.
- Adjust tour to teach menu open/close.
- Edited: src/hooks/useOnboardingTour.ts, src/components/onboarding/OnboardingTour.tsx, src/components/layout/NewModernLayout.tsx, src/components/layout/Header.tsx, src/App.tsx
This commit is contained in:
gpt-engineer-app[bot] 2025-07-02 04:56:21 +00:00
parent 9f634a7a6e
commit 33f2d389cd
7 changed files with 196 additions and 24 deletions

View File

@ -27,6 +27,7 @@ import Categorias from "./pages/Categorias";
import AdminFAQ from "./pages/AdminFAQ"; import AdminFAQ from "./pages/AdminFAQ";
import Assinatura from "./pages/Assinatura"; import Assinatura from "./pages/Assinatura";
import AvisosContas from "./pages/AvisosContas"; import AvisosContas from "./pages/AvisosContas";
import Admin from "./pages/Admin";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@ -75,6 +76,7 @@ function App() {
<Route path="/complete-profile" element={<CompleteProfile />} /> <Route path="/complete-profile" element={<CompleteProfile />} />
<Route path="/categorias" element={<Categorias />} /> <Route path="/categorias" element={<Categorias />} />
<Route path="/admin/faq" element={<AdminFAQ />} /> <Route path="/admin/faq" element={<AdminFAQ />} />
<Route path="/admin" element={<Admin />} />
<Route path="/assinatura" element={<Assinatura />} /> <Route path="/assinatura" element={<Assinatura />} />
<Route path="/avisos-contas" element={<AvisosContas />} /> <Route path="/avisos-contas" element={<AvisosContas />} />
</Route> </Route>

View File

@ -0,0 +1,42 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Shield } from 'lucide-react';
import { useAdminSettings } from '@/hooks/useAdminSettings';
const AdminPanel = () => {
const { isAdmin, hideWhatsAppButton, loading, toggleWhatsAppButton } = useAdminSettings();
if (!isAdmin || loading) {
return null;
}
return (
<Card className="w-full max-w-2xl mx-auto">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Shield className="h-5 w-5 text-red-600" />
Painel Administrativo
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<Label htmlFor="hide-whatsapp" className="text-sm font-medium">
Esconder botão "Conectar WhatsApp" para todos os usuários
</Label>
<Switch
id="hide-whatsapp"
checked={hideWhatsAppButton}
onCheckedChange={toggleWhatsAppButton}
/>
</div>
<p className="text-xs text-gray-500">
Quando ativado, o botão "Conectar WhatsApp" ficará oculto para todos os usuários do sistema.
</p>
</CardContent>
</Card>
);
};
export default AdminPanel;

View File

@ -27,12 +27,14 @@ import {
MessageSquareText, MessageSquareText,
Users, Users,
Settings, Settings,
Bell Bell,
Shield
} from 'lucide-react'; } from 'lucide-react';
import OnboardingTour from '@/components/onboarding/OnboardingTour'; import OnboardingTour from '@/components/onboarding/OnboardingTour';
import HelpIcon from '@/components/help/HelpIcon'; import HelpIcon from '@/components/help/HelpIcon';
import UserProfileButton from '@/components/dashboard/UserProfileButton'; import UserProfileButton from '@/components/dashboard/UserProfileButton';
import { useOnboardingTour } from '@/hooks/useOnboardingTour'; import { useOnboardingTour } from '@/hooks/useOnboardingTour';
import { useAdminSettings } from '@/hooks/useAdminSettings';
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
@ -46,10 +48,7 @@ interface NewModernLayoutProps {
const Logo = () => { const Logo = () => {
return ( return (
<Link <div className="relative z-20 flex items-center space-x-2 py-1 text-sm font-normal text-black cursor-default">
to="/"
className="relative z-20 flex items-center space-x-2 py-1 text-sm font-normal text-black"
>
<img <img
src="/lovable-uploads/7149adf3-440a-491e-83c2-d964a3348cc9.png" src="/lovable-uploads/7149adf3-440a-491e-83c2-d964a3348cc9.png"
alt="Finance Home Logo" alt="Finance Home Logo"
@ -58,12 +57,13 @@ const Logo = () => {
<span className="font-medium whitespace-pre text-blue-700 dark:text-white"> <span className="font-medium whitespace-pre text-blue-700 dark:text-white">
Finance Home Finance Home
</span> </span>
</Link> </div>
); );
}; };
export default function NewModernLayout({ children }: NewModernLayoutProps) { export default function NewModernLayout({ children }: NewModernLayoutProps) {
const location = useLocation(); const location = useLocation();
const { isAdmin, hideWhatsAppButton } = useAdminSettings();
const { const {
isOpen: tourOpen, isOpen: tourOpen,
@ -117,15 +117,16 @@ export default function NewModernLayout({ children }: NewModernLayoutProps) {
]; ];
const whatsappItems = [ const whatsappItems = [
{ ...(hideWhatsAppButton ? [] : [{
title: "Conectar WhatsApp", title: "Conectar WhatsApp",
url: "/whatsapp", url: "/whatsapp",
icon: MessageSquareText, icon: MessageSquareText,
}, }]),
{ {
title: "Grupos", title: "Grupos",
url: "/grupos-whatsapp", url: "/grupos-whatsapp",
icon: Users, icon: Users,
'data-tour': 'grupos-menu'
} }
]; ];
@ -137,6 +138,14 @@ export default function NewModernLayout({ children }: NewModernLayoutProps) {
} }
]; ];
const adminItems = isAdmin ? [
{
title: "Admin",
url: "/admin",
icon: Shield,
}
] : [];
return ( return (
<div className="min-h-screen bg-background flex w-full"> <div className="min-h-screen bg-background flex w-full">
<SidebarProvider defaultOpen={false}> <SidebarProvider defaultOpen={false}>
@ -173,6 +182,29 @@ export default function NewModernLayout({ children }: NewModernLayoutProps) {
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu> <SidebarMenu>
{whatsappItems.map((item) => ( {whatsappItems.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
asChild
tooltip={item.title}
isActive={location.pathname === item.url}
>
<Link to={item.url} data-tour={item['data-tour']}>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
{adminItems.length > 0 && (
<SidebarGroup>
<SidebarGroupLabel>Administração</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{adminItems.map((item) => (
<SidebarMenuItem key={item.title}> <SidebarMenuItem key={item.title}>
<SidebarMenuButton <SidebarMenuButton
asChild asChild
@ -189,6 +221,7 @@ export default function NewModernLayout({ children }: NewModernLayoutProps) {
</SidebarMenu> </SidebarMenu>
</SidebarGroupContent> </SidebarGroupContent>
</SidebarGroup> </SidebarGroup>
)}
</SidebarContent> </SidebarContent>
<SidebarFooter> <SidebarFooter>

View File

@ -23,34 +23,34 @@ const OnboardingTour: React.FC<OnboardingTourProps> = ({
const steps = [ const steps = [
{ {
title: "Passo 1 - Conecte seu WhatsApp", title: "Bem-vindo ao Finance Home! 🎉",
content: ( content: (
<> <>
<p className="mb-4"> <p className="mb-4">
Para que o app funcione, você precisa integrar seu WhatsApp com nossa plataforma. Olá! Este é o Finance Home, sua ferramenta para controle financeiro automático.
</p> </p>
<p className="mb-4"> <p className="mb-4">
Isso permitirá que suas transações feitas no WhatsApp apareçam automaticamente aqui no Finance Home. Vamos te ensinar como navegar pelo sistema e usar suas principais funcionalidades.
</p> </p>
<p className="font-semibold text-blue-600"> <p className="font-semibold text-blue-600">
👉 Clique em "Conectar WhatsApp", digite o código da cidade + seu número de WhatsApp e depois clique em "Criar Instância". 👉 Use o botão do menu () no canto superior esquerdo para abrir e retrair o menu lateral.
</p> </p>
</> </>
), ),
spotlight: 'whatsapp-menu' spotlight: null
}, },
{ {
title: "Passo 2 - Crie seu Grupo com a IA", title: "Passo 1 - Crie seu Grupo com a IA",
content: ( content: (
<> <>
<p className="mb-4"> <p className="mb-4">
Agora você precisa criar um grupo no WhatsApp com o nosso bot de IA chamado Angelina. Para começar, você precisa criar um grupo no WhatsApp com o nosso bot de IA chamado Angelina.
</p> </p>
<p className="mb-4"> <p className="mb-4">
Nele, você vai mandar mensagens, áudios ou comprovantes, e o sistema vai registrar automaticamente suas despesas e receitas. Nele, você vai mandar mensagens, áudios ou comprovantes, e o sistema vai registrar automaticamente suas despesas e receitas.
</p> </p>
<p className="font-semibold text-blue-600"> <p className="font-semibold text-blue-600">
👉 Clique em "Grupos", escolha o nome do grupo e clique em "Cadastrar Grupo". 👉 Clique em "Grupos" no menu lateral, escolha o nome do grupo e clique em "Cadastrar Grupo".
</p> </p>
</> </>
), ),
@ -63,9 +63,12 @@ const OnboardingTour: React.FC<OnboardingTourProps> = ({
<p className="text-lg mb-4"> <p className="text-lg mb-4">
Agora você pode começar a usar o Finance Home de forma automática. Agora você pode começar a usar o Finance Home de forma automática.
</p> </p>
<p className="text-base"> <p className="text-base mb-4">
Tudo que você enviar para o grupo vai ser registrado no app. Tudo que você enviar para o grupo vai ser registrado no app.
</p> </p>
<p className="text-sm text-gray-600">
💡 Dica: Use o menu lateral para navegar entre as diferentes seções do sistema.
</p>
</> </>
), ),
spotlight: null spotlight: null
@ -87,7 +90,6 @@ const OnboardingTour: React.FC<OnboardingTourProps> = ({
}); });
}; };
// Aplicar highlight no elemento
const highlightElementStyle = () => { const highlightElementStyle = () => {
const currentStepData = steps[currentStep]; const currentStepData = steps[currentStep];
if (!currentStepData?.spotlight) return; if (!currentStepData?.spotlight) return;
@ -112,7 +114,6 @@ const OnboardingTour: React.FC<OnboardingTourProps> = ({
return; return;
} }
// Limpar estilos anteriores
removeHighlightStyle(); removeHighlightStyle();
const currentStepData = steps[currentStep]; const currentStepData = steps[currentStep];
@ -134,7 +135,6 @@ const OnboardingTour: React.FC<OnboardingTourProps> = ({
}; };
}, [isOpen, currentStep]); }, [isOpen, currentStep]);
// Cleanup quando o componente for desmontado
useEffect(() => { useEffect(() => {
return () => { return () => {
removeHighlightStyle(); removeHighlightStyle();

View File

@ -0,0 +1,60 @@
import { useState, useEffect } from 'react';
import { supabase } from '@/integrations/supabase/client';
const ADMIN_EMAIL = 'rodrigobm10@gmail.com';
const SETTINGS_KEY = 'admin_hide_whatsapp_button';
export const useAdminSettings = () => {
const [isAdmin, setIsAdmin] = useState(false);
const [hideWhatsAppButton, setHideWhatsAppButton] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
checkAdminStatus();
loadSettings();
}, []);
const checkAdminStatus = async () => {
try {
const { data: { user } } = await supabase.auth.getUser();
if (user?.email === ADMIN_EMAIL) {
setIsAdmin(true);
}
} catch (error) {
console.error('Erro ao verificar status admin:', error);
}
};
const loadSettings = async () => {
try {
const savedSetting = localStorage.getItem(SETTINGS_KEY);
if (savedSetting) {
setHideWhatsAppButton(JSON.parse(savedSetting));
}
} catch (error) {
console.error('Erro ao carregar configurações:', error);
} finally {
setLoading(false);
}
};
const toggleWhatsAppButton = async () => {
if (!isAdmin) return;
try {
const newValue = !hideWhatsAppButton;
setHideWhatsAppButton(newValue);
localStorage.setItem(SETTINGS_KEY, JSON.stringify(newValue));
} catch (error) {
console.error('Erro ao salvar configuração:', error);
}
};
return {
isAdmin,
hideWhatsAppButton,
loading,
toggleWhatsAppButton
};
};

View File

@ -27,7 +27,7 @@ export const useOnboardingTour = () => {
console.log('🔍 [TOUR] Iniciando verificação de condições do tour...'); console.log('🔍 [TOUR] Iniciando verificação de condições do tour...');
// Só mostrar o tour na página inicial (dashboard) // Só mostrar o tour na página inicial (dashboard)
if (location.pathname !== '/') { if (location.pathname !== '/dashboard') {
console.log('❌ [TOUR] Tour só aparece no dashboard, página atual:', location.pathname); console.log('❌ [TOUR] Tour só aparece no dashboard, página atual:', location.pathname);
setShouldShowTour(false); setShouldShowTour(false);
return; return;
@ -108,7 +108,7 @@ export const useOnboardingTour = () => {
// Verificar condições quando a localização mudar ou na inicialização // Verificar condições quando a localização mudar ou na inicialização
useEffect(() => { useEffect(() => {
if (!tourCheckedRef.current && location.pathname === '/') { if (!tourCheckedRef.current && location.pathname === '/dashboard') {
console.log('🔄 [TOUR] useEffect disparado - verificando condições do tour'); console.log('🔄 [TOUR] useEffect disparado - verificando condições do tour');
tourCheckedRef.current = true; tourCheckedRef.current = true;
const timer = setTimeout(() => { const timer = setTimeout(() => {
@ -123,7 +123,7 @@ export const useOnboardingTour = () => {
useEffect(() => { useEffect(() => {
const handleUserLoggedIn = (event: CustomEvent) => { const handleUserLoggedIn = (event: CustomEvent) => {
console.log('🎉 [TOUR] Evento de login recebido:', event.detail); console.log('🎉 [TOUR] Evento de login recebido:', event.detail);
if (location.pathname === '/') { if (location.pathname === '/dashboard') {
console.log('📧 [TOUR] Login detectado no dashboard, re-verificando condições do tour'); console.log('📧 [TOUR] Login detectado no dashboard, re-verificando condições do tour');
tourCheckedRef.current = false; tourCheckedRef.current = false;
sessionStorage.removeItem(TOUR_SESSION_KEY); sessionStorage.removeItem(TOUR_SESSION_KEY);

35
src/pages/Admin.tsx Normal file
View File

@ -0,0 +1,35 @@
import AdminPanel from '@/components/admin/AdminPanel';
import { useAdminSettings } from '@/hooks/useAdminSettings';
import { Navigate } from 'react-router-dom';
const Admin = () => {
const { isAdmin, loading } = useAdminSettings();
if (loading) {
return (
<div className="flex items-center justify-center min-h-[200px]">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-2 text-gray-600">Carregando...</p>
</div>
</div>
);
}
if (!isAdmin) {
return <Navigate to="/dashboard" replace />;
}
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Administração</h1>
</div>
<AdminPanel />
</div>
);
};
export default Admin;