From c05ba05353d2d0f669dffdb6ef2f9f79da6d90f5 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 18 May 2025 13:06:18 +0000 Subject: [PATCH] Run SQL to create user and transaction tables. --- src/App.tsx | 33 ++- src/components/auth/ProtectedRoute.tsx | 42 +++ src/components/dashboard/TransactionForm.tsx | 19 +- src/components/layout/Header.tsx | 60 +++++ src/components/layout/Layout.tsx | 10 +- src/components/ui/dropdown-menu.tsx | 3 + src/integrations/supabase/types.ts | 35 +++ src/pages/Auth.tsx | 255 +++++++++++++++++++ src/services/transacaoService.ts | 43 +++- 9 files changed, 486 insertions(+), 14 deletions(-) create mode 100644 src/components/auth/ProtectedRoute.tsx create mode 100644 src/components/layout/Header.tsx create mode 100644 src/pages/Auth.tsx diff --git a/src/App.tsx b/src/App.tsx index 4f63a60..5db6ef4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,12 +4,14 @@ import { Toaster } from "@/components/ui/toaster"; import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import Index from "./pages/Index"; import Transacoes from "./pages/Transacoes"; import Categorias from "./pages/Categorias"; import Calendario from "./pages/Calendario"; +import Auth from "./pages/Auth"; import NotFound from "./pages/NotFound"; +import ProtectedRoute from "./components/auth/ProtectedRoute"; // Create a client const queryClient = new QueryClient({ @@ -30,11 +32,30 @@ const App = () => { - } /> - } /> - } /> - } /> - {/* Adicione novas rotas acima desta linha */} + {/* Rota inicial redireciona para autenticação ou dashboard */} + } /> + + {/* Rota de autenticação */} + } /> + + {/* Rotas protegidas que exigem autenticação */} + + + + } /> + + + + } /> + + + + } /> + + {/* Rota 404 */} } /> diff --git a/src/components/auth/ProtectedRoute.tsx b/src/components/auth/ProtectedRoute.tsx new file mode 100644 index 0000000..f3a3003 --- /dev/null +++ b/src/components/auth/ProtectedRoute.tsx @@ -0,0 +1,42 @@ + +import { ReactNode, useEffect, useState } from 'react'; +import { Navigate } from 'react-router-dom'; +import { useToast } from "@/components/ui/use-toast"; + +interface ProtectedRouteProps { + children: ReactNode; +} + +const ProtectedRoute = ({ children }: ProtectedRouteProps) => { + const [isAutenticado, setIsAutenticado] = useState(null); + const { toast } = useToast(); + + useEffect(() => { + // Verifica se o usuário está autenticado + const autenticado = localStorage.getItem('autenticado') === 'true'; + setIsAutenticado(autenticado); + + if (!autenticado) { + toast({ + title: "Acesso restrito", + description: "Faça login para acessar esta página", + variant: "destructive" + }); + } + }, [toast]); + + // Mostra um carregamento enquanto verifica a autenticação + if (isAutenticado === null) { + return Carregando...; + } + + // Redireciona para a tela de login se não estiver autenticado + if (!isAutenticado) { + return ; + } + + // Renderiza o conteúdo protegido se estiver autenticado + return <>{children}>; +}; + +export default ProtectedRoute; diff --git a/src/components/dashboard/TransactionForm.tsx b/src/components/dashboard/TransactionForm.tsx index 3a9689d..59b30a9 100644 --- a/src/components/dashboard/TransactionForm.tsx +++ b/src/components/dashboard/TransactionForm.tsx @@ -71,15 +71,32 @@ export function TransactionForm({ onSuccess, onCancel }: TransactionFormProps) { return; } + // Obtém o ID do usuário logado + const userId = localStorage.getItem('userId'); + if (!userId) { + toast({ + title: "Erro de autenticação", + description: "Você precisa estar logado para adicionar transações", + variant: "destructive" + }); + setIsSubmitting(false); + return; + } + // Ajusta o valor para ser positivo ou negativo com base no tipo const valorFinal = data.tipo.toLowerCase() === 'receita' ? Math.abs(valorNumerico) : Math.abs(valorNumerico); + // Cria o nome da tabela dinâmica para este usuário + const tabelaTransacoes = `transacoes_${userId}`; + + // Insere a transação na tabela específica do usuário const { error } = await supabase - .from('transacoes') + .from(tabelaTransacoes) .insert([ { + user: userId, estabelecimento: data.estabelecimento, valor: valorFinal, detalhes: data.detalhes, diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx new file mode 100644 index 0000000..de10bbf --- /dev/null +++ b/src/components/layout/Header.tsx @@ -0,0 +1,60 @@ + +import { useState } 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"; + +const Header = () => { + const navigate = useNavigate(); + const { toast } = useToast(); + const [userName, setUserName] = useState(() => { + return localStorage.getItem('userName') || 'Usuário'; + }); + + const handleLogout = () => { + // Limpar informações de sessão + localStorage.removeItem('autenticado'); + localStorage.removeItem('userId'); + localStorage.removeItem('userName'); + + toast({ + title: "Logout realizado", + description: "Você foi desconectado com sucesso" + }); + + // Redirecionar para a página de login + navigate('/auth'); + }; + + return ( + + + + + + {userName} + + + + Minha conta + + + + Sair + + + + + ); +}; + +export default Header; diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index 1dcaaa1..3d71cf9 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import Sidebar from '@/components/layout/Sidebar'; +import Header from '@/components/layout/Header'; import { useToast } from "@/components/ui/use-toast"; import { Menu } from 'lucide-react'; import { Button } from "@/components/ui/button"; @@ -44,9 +45,12 @@ const Layout: React.FC = ({ children }) => { ) : ( )} - - {children} - + + + + {children} + + ); }; diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index 769ff7a..e597549 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -1,3 +1,6 @@ + +"use client" + import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import { Check, ChevronRight, Circle } from "lucide-react" diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index b91d300..b26767a 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -156,11 +156,42 @@ export type Database = { } Relationships: [] } + usuarios: { + Row: { + created_at: string + email: string + empresa: string | null + id: string + nome: string + senha: string + } + Insert: { + created_at?: string + email: string + empresa?: string | null + id?: string + nome: string + senha: string + } + Update: { + created_at?: string + email?: string + empresa?: string | null + id?: string + nome?: string + senha?: string + } + Relationships: [] + } } Views: { [_ in never]: never } Functions: { + autenticar_usuario: { + Args: { email_login: string; senha_login: string } + Returns: string + } binary_quantize: { Args: { "": string } | { "": unknown } Returns: unknown @@ -226,6 +257,10 @@ export type Database = { Args: { "": string } | { "": unknown } | { "": unknown } Returns: string } + registrar_usuario: { + Args: { nome: string; empresa: string; email: string; senha: string } + Returns: string + } sparsevec_out: { Args: { "": unknown } Returns: unknown diff --git a/src/pages/Auth.tsx b/src/pages/Auth.tsx new file mode 100644 index 0000000..44eaa85 --- /dev/null +++ b/src/pages/Auth.tsx @@ -0,0 +1,255 @@ + +import { useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { useToast } from "@/components/ui/use-toast"; +import { supabase } from "@/integrations/supabase/client"; + +const Auth = () => { + const navigate = useNavigate(); + const { toast } = useToast(); + const [isLoading, setIsLoading] = useState(false); + + // Estado do formulário de login + const [loginEmail, setLoginEmail] = useState(''); + const [loginSenha, setLoginSenha] = useState(''); + + // Estado do formulário de cadastro + const [nome, setNome] = useState(''); + const [empresa, setEmpresa] = useState(''); + const [email, setEmail] = useState(''); + const [senha, setSenha] = useState(''); + const [confirmaSenha, setConfirmaSenha] = useState(''); + + // Função para fazer login + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + + try { + // Tenta autenticar o usuário através da função RPC do Supabase + const { data, error } = await supabase.rpc('autenticar_usuario', { + email_login: loginEmail, + senha_login: loginSenha + }); + + if (error || !data) { + throw new Error(error?.message || "Falha ao autenticar. Verifique seu email e senha."); + } + + // Se encontrou um usuário válido, redireciona para a página de transações + if (data) { + // Armazenar informações de sessão (simplificado para este exemplo) + localStorage.setItem('autenticado', 'true'); + localStorage.setItem('userId', data); + toast({ + title: "Login realizado com sucesso", + description: "Bem-vindo de volta!" + }); + navigate('/transacoes'); + } else { + toast({ + title: "Falha no login", + description: "Email ou senha incorretos", + variant: "destructive" + }); + } + } catch (error) { + console.error("Erro ao fazer login:", error); + toast({ + title: "Erro ao fazer login", + description: "Verifique suas credenciais e tente novamente", + variant: "destructive" + }); + } finally { + setIsLoading(false); + } + }; + + // Função para cadastrar usuário + const handleCadastro = async (e: React.FormEvent) => { + e.preventDefault(); + + // Validações básicas + if (!nome || !email || !senha) { + toast({ + title: "Campos obrigatórios", + description: "Por favor preencha todos os campos obrigatórios", + variant: "destructive" + }); + return; + } + + if (senha !== confirmaSenha) { + toast({ + title: "Senhas não conferem", + description: "A senha e a confirmação de senha devem ser iguais", + variant: "destructive" + }); + return; + } + + setIsLoading(true); + + try { + // Registrar o usuário usando a função RPC do Supabase + const { data, error } = await supabase.rpc('registrar_usuario', { + nome, + empresa, + email, + senha + }); + + if (error) throw error; + + if (data) { + toast({ + title: "Cadastro realizado", + description: "Sua conta foi criada com sucesso!" + }); + + // Fazer login automaticamente após o cadastro + const { data: loginData, error: loginError } = await supabase.rpc('autenticar_usuario', { + email_login: email, + senha_login: senha + }); + + if (loginError) throw loginError; + + if (loginData) { + localStorage.setItem('autenticado', 'true'); + localStorage.setItem('userId', loginData); + navigate('/transacoes'); + } + } + } catch (error: any) { + console.error("Erro ao cadastrar:", error); + toast({ + title: "Erro no cadastro", + description: error.message || "Não foi possível completar o cadastro", + variant: "destructive" + }); + } finally { + setIsLoading(false); + } + }; + + return ( + + + + Finanças Pessoais + Gerencie suas finanças de forma simples e eficiente + + + + + Login + Cadastro + + + + + + setLoginEmail(e.target.value)} + disabled={isLoading} + required + /> + + + setLoginSenha(e.target.value)} + disabled={isLoading} + required + /> + + + {isLoading ? "Entrando..." : "Entrar"} + + + + + + + + setNome(e.target.value)} + disabled={isLoading} + required + /> + + + setEmpresa(e.target.value)} + disabled={isLoading} + /> + + + setEmail(e.target.value)} + disabled={isLoading} + required + /> + + + setSenha(e.target.value)} + disabled={isLoading} + required + /> + + + setConfirmaSenha(e.target.value)} + disabled={isLoading} + required + /> + + + {isLoading ? "Cadastrando..." : "Cadastrar"} + + + + + + + + Ao continuar, você concorda com nossos termos de serviço e políticas de privacidade. + + + + + ); +}; + +export default Auth; diff --git a/src/services/transacaoService.ts b/src/services/transacaoService.ts index 4769b8b..8cb3bc9 100644 --- a/src/services/transacaoService.ts +++ b/src/services/transacaoService.ts @@ -4,8 +4,17 @@ import { Transaction } from "@/types/financialTypes"; export async function getTransacoes(): Promise { console.log("Buscando transações do Supabase..."); + + const userId = localStorage.getItem('userId'); + if (!userId) { + console.error('Usuário não autenticado'); + return []; + } + + const tabelaTransacoes = `transacoes_${userId}`; + const { data, error } = await supabase - .from('transacoes') + .from(tabelaTransacoes) .select('*') .order('quando', { ascending: false }); @@ -32,8 +41,17 @@ export async function getTransacoes(): Promise { export async function getTransactionSummary() { console.log("Buscando resumo das transações..."); + + const userId = localStorage.getItem('userId'); + if (!userId) { + console.error('Usuário não autenticado'); + return { receitas: 0, despesas: 0, saldo: 0 }; + } + + const tabelaTransacoes = `transacoes_${userId}`; + const { data, error } = await supabase - .from('transacoes') + .from(tabelaTransacoes) .select('tipo, valor'); if (error) { @@ -64,9 +82,17 @@ export async function getTransactionSummary() { export async function getCategorySummary() { console.log("Buscando resumo de categorias..."); + const userId = localStorage.getItem('userId'); + if (!userId) { + console.error('Usuário não autenticado'); + return []; + } + + const tabelaTransacoes = `transacoes_${userId}`; + // Buscar todas as transações que são despesas, independente da capitalização const { data, error } = await supabase - .from('transacoes') + .from(tabelaTransacoes) .select('categoria, valor, tipo'); if (error) { @@ -115,8 +141,17 @@ export async function getCategorySummary() { export async function getMonthlyData() { console.log("Buscando dados mensais..."); + + const userId = localStorage.getItem('userId'); + if (!userId) { + console.error('Usuário não autenticado'); + return []; + } + + const tabelaTransacoes = `transacoes_${userId}`; + const { data, error } = await supabase - .from('transacoes') + .from(tabelaTransacoes) .select('quando, valor, tipo'); if (error) {
+ Ao continuar, você concorda com nossos termos de serviço e políticas de privacidade. +