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:
parent
d16695dfa4
commit
528714622a
@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
114
src/hooks/useAccessControl.ts
Normal file
114
src/hooks/useAccessControl.ts
Normal 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;
|
||||
}
|
||||
@ -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 */}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user