Implement trial period and access restrictions

Implement a 30-day trial period for new users. After the trial, restrict the ability to add new transactions if no payment is detected. Use `usuarios.created_at` for the registration date and consider a payment active if a record exists in `pagamentos_mercadopago` with status "approved".
This commit is contained in:
gpt-engineer-app[bot] 2025-06-15 21:43:16 +00:00
parent d16695dfa4
commit 528714622a
3 changed files with 165 additions and 37 deletions

View File

@ -1,43 +1,42 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { PlusCircle, CreditCard } from 'lucide-react';
interface TransactionHeaderProps {
onOpenDialog: (tipo: 'receita' | 'despesa') => void;
onOpenCartaoCreditoDialog: () => void;
disableAdicionar?: boolean;
}
export const TransactionHeader = ({
onOpenDialog,
onOpenCartaoCreditoDialog
}: TransactionHeaderProps) => {
export function TransactionHeader({
onOpenDialog,
onOpenCartaoCreditoDialog,
disableAdicionar = false,
}: TransactionHeaderProps) {
return (
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Todas as Transações</h1>
<div className="flex gap-2">
<Button
className="flex items-center gap-2 bg-finance-green hover:bg-finance-green/90"
onClick={() => onOpenDialog('receita')}
>
<PlusCircle className="h-4 w-4" />
Nova Receita
</Button>
<Button
className="flex items-center gap-2 bg-finance-red hover:bg-finance-red/90"
onClick={() => onOpenDialog('despesa')}
>
<PlusCircle className="h-4 w-4" />
Nova Despesa
</Button>
<Button
className="flex items-center gap-2"
onClick={onOpenCartaoCreditoDialog}
>
<CreditCard className="h-4 w-4" />
Despesa Cartão
</Button>
</div>
<div className="flex gap-2">
<button
className={`bg-finance-green hover:bg-green-600 text-white text-sm px-4 py-2 rounded transition-colors ${disableAdicionar ? 'opacity-50 cursor-not-allowed' : ''}`}
disabled={disableAdicionar}
onClick={() => onOpenDialog('receita')}
type="button"
>
Nova Receita
</button>
<button
className={`bg-finance-red hover:bg-red-600 text-white text-sm px-4 py-2 rounded transition-colors ${disableAdicionar ? 'opacity-50 cursor-not-allowed' : ''}`}
disabled={disableAdicionar}
onClick={() => onOpenDialog('despesa')}
type="button"
>
Nova Despesa
</button>
<button
className={`bg-blue-500 hover:bg-blue-600 text-white text-sm px-4 py-2 rounded transition-colors ${disableAdicionar ? 'opacity-50 cursor-not-allowed' : ''}`}
disabled={disableAdicionar}
onClick={onOpenCartaoCreditoDialog}
type="button"
>
Despesa de Cartão
</button>
</div>
);
};
}

View File

@ -0,0 +1,114 @@
import { useState, useEffect } from "react";
import { supabase } from "@/integrations/supabase/client";
interface AccessControlResult {
loading: boolean;
podeAdicionarTransacao: boolean;
motivo: string | null; // mensagem para exibir ao usuário se bloqueado
diasRestantesTrial: number;
trialExpiraEm: Date | null;
}
export function useAccessControl(): AccessControlResult {
const [result, setResult] = useState<AccessControlResult>({
loading: true,
podeAdicionarTransacao: false,
motivo: null,
diasRestantesTrial: 0,
trialExpiraEm: null,
});
useEffect(() => {
async function checkAccess() {
const userEmail = localStorage.getItem("userEmail");
if (!userEmail) {
setResult({
loading: false,
podeAdicionarTransacao: false,
motivo: "Erro ao identificar usuário. Faça login novamente.",
diasRestantesTrial: 0,
trialExpiraEm: null,
});
return;
}
// Buscar data de cadastro do usuário
const { data: usuarios, error: userError } = await supabase
.from("usuarios")
.select("created_at")
.eq("email", userEmail.trim().toLowerCase());
if (userError || !usuarios || usuarios.length === 0) {
setResult({
loading: false,
podeAdicionarTransacao: false,
motivo: "Erro ao buscar dados do usuário. Tente novamente mais tarde.",
diasRestantesTrial: 0,
trialExpiraEm: null,
});
return;
}
const cadastro = new Date(usuarios[0].created_at);
const agora = new Date();
const trialExpiraEm = new Date(cadastro.getTime());
trialExpiraEm.setDate(trialExpiraEm.getDate() + 30);
const dentroTrial = agora < trialExpiraEm;
const diasRestantes = Math.ceil((trialExpiraEm.getTime() - agora.getTime()) / (1000 * 60 * 60 * 24));
if (dentroTrial) {
setResult({
loading: false,
podeAdicionarTransacao: true,
motivo: null,
diasRestantesTrial: diasRestantes,
trialExpiraEm,
});
return;
}
// Busca pagamento aprovado na tabela de pagamentos_mercadopago
const { data: pagamentos, error: pagamentoError } = await supabase
.from("pagamentos_mercadopago")
.select("id, status, created_at, payer_email")
.eq("payer_email", userEmail.trim().toLowerCase())
.eq("status", "approved");
if (pagamentoError) {
setResult({
loading: false,
podeAdicionarTransacao: false,
motivo: "Erro ao verificar status de pagamento.",
diasRestantesTrial: 0,
trialExpiraEm,
});
return;
}
if (pagamentos && pagamentos.length > 0) {
setResult({
loading: false,
podeAdicionarTransacao: true,
motivo: null,
diasRestantesTrial: 0,
trialExpiraEm,
});
return;
}
setResult({
loading: false,
podeAdicionarTransacao: false,
motivo:
"Seu período de teste acabou. Para continuar adicionando transações realize o pagamento da assinatura.",
diasRestantesTrial: 0,
trialExpiraEm,
});
}
checkAccess();
}, []);
return result;
}

View File

@ -1,5 +1,4 @@
import React, { useState } from 'react';
import React from 'react';
import Layout from '@/components/layout/Layout';
import TransactionsTable from '@/components/dashboard/TransactionsTable';
import { useTransactions } from '@/hooks/useTransactions';
@ -7,6 +6,7 @@ import { TransactionHeader } from '@/components/transacoes/TransactionHeader';
import { TransactionSummaryCards } from '@/components/transacoes/TransactionSummaryCards';
import { TransactionDialogs } from '@/components/transacoes/TransactionDialogs';
import { MonthFilter } from '@/components/filters/MonthFilter';
import { useAccessControl } from '@/hooks/useAccessControl';
const TransacoesPage = () => {
// Função para obter o mês atual no formato YYYY-MM
@ -17,6 +17,8 @@ const TransacoesPage = () => {
const [selectedMonth, setSelectedMonth] = useState<string>(getCurrentMonth());
const access = useAccessControl();
const {
transactions,
isLoading,
@ -59,6 +61,11 @@ const TransacoesPage = () => {
<p className="text-sm text-muted-foreground">
Dados de: {formatMonthDisplay(selectedMonth)}
</p>
{!access.loading && access.podeAdicionarTransacao && access.diasRestantesTrial > 0 && (
<div className="mt-1 text-xs text-blue-600 font-medium">
{`Você está em período gratuito. ${access.diasRestantesTrial} dia(s) restante(s) de teste.`}
</div>
)}
</div>
<MonthFilter
selectedMonth={selectedMonth}
@ -66,9 +73,17 @@ const TransacoesPage = () => {
/>
</div>
{!access.loading && !access.podeAdicionarTransacao && (
<div className="bg-yellow-100 border border-yellow-300 text-yellow-800 px-4 py-3 rounded relative mb-4">
<span className="font-medium">Atenção:</span> {access.motivo}
</div>
)}
{/* Só passa as funções de adicionar se permitido */}
<TransactionHeader
onOpenDialog={handleOpenDialog}
onOpenCartaoCreditoDialog={handleOpenCartaoCreditoDialog}
onOpenDialog={access.podeAdicionarTransacao ? handleOpenDialog : () => {}}
onOpenCartaoCreditoDialog={access.podeAdicionarTransacao ? handleOpenCartaoCreditoDialog : () => {}}
disableAdicionar={!access.podeAdicionarTransacao}
/>
{/* Resumo em Cards */}