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:
parent
9f634a7a6e
commit
33f2d389cd
@ -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>
|
||||||
|
|||||||
42
src/components/admin/AdminPanel.tsx
Normal file
42
src/components/admin/AdminPanel.tsx
Normal 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;
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
60
src/hooks/useAdminSettings.ts
Normal file
60
src/hooks/useAdminSettings.ts
Normal 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
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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
35
src/pages/Admin.tsx
Normal 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;
|
||||||
Loading…
Reference in New Issue
Block a user