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 React from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { PlusCircle, CreditCard } from 'lucide-react';
|
|
||||||
|
|
||||||
interface TransactionHeaderProps {
|
interface TransactionHeaderProps {
|
||||||
onOpenDialog: (tipo: 'receita' | 'despesa') => void;
|
onOpenDialog: (tipo: 'receita' | 'despesa') => void;
|
||||||
onOpenCartaoCreditoDialog: () => void;
|
onOpenCartaoCreditoDialog: () => void;
|
||||||
|
disableAdicionar?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TransactionHeader = ({
|
export function TransactionHeader({
|
||||||
onOpenDialog,
|
onOpenDialog,
|
||||||
onOpenCartaoCreditoDialog
|
onOpenCartaoCreditoDialog,
|
||||||
}: TransactionHeaderProps) => {
|
disableAdicionar = false,
|
||||||
|
}: TransactionHeaderProps) {
|
||||||
return (
|
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">
|
<div className="flex gap-2">
|
||||||
<Button
|
<button
|
||||||
className="flex items-center gap-2 bg-finance-green hover:bg-finance-green/90"
|
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')}
|
onClick={() => onOpenDialog('receita')}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<PlusCircle className="h-4 w-4" />
|
|
||||||
Nova Receita
|
Nova Receita
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
className="flex items-center gap-2 bg-finance-red hover:bg-finance-red/90"
|
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')}
|
onClick={() => onOpenDialog('despesa')}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<PlusCircle className="h-4 w-4" />
|
|
||||||
Nova Despesa
|
Nova Despesa
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
className="flex items-center gap-2"
|
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}
|
onClick={onOpenCartaoCreditoDialog}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<CreditCard className="h-4 w-4" />
|
Despesa de Cartão
|
||||||
Despesa Cartão
|
</button>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</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 from 'react';
|
||||||
import React, { useState } from 'react';
|
|
||||||
import Layout from '@/components/layout/Layout';
|
import Layout from '@/components/layout/Layout';
|
||||||
import TransactionsTable from '@/components/dashboard/TransactionsTable';
|
import TransactionsTable from '@/components/dashboard/TransactionsTable';
|
||||||
import { useTransactions } from '@/hooks/useTransactions';
|
import { useTransactions } from '@/hooks/useTransactions';
|
||||||
@ -7,6 +6,7 @@ import { TransactionHeader } from '@/components/transacoes/TransactionHeader';
|
|||||||
import { TransactionSummaryCards } from '@/components/transacoes/TransactionSummaryCards';
|
import { TransactionSummaryCards } from '@/components/transacoes/TransactionSummaryCards';
|
||||||
import { TransactionDialogs } from '@/components/transacoes/TransactionDialogs';
|
import { TransactionDialogs } from '@/components/transacoes/TransactionDialogs';
|
||||||
import { MonthFilter } from '@/components/filters/MonthFilter';
|
import { MonthFilter } from '@/components/filters/MonthFilter';
|
||||||
|
import { useAccessControl } from '@/hooks/useAccessControl';
|
||||||
|
|
||||||
const TransacoesPage = () => {
|
const TransacoesPage = () => {
|
||||||
// Função para obter o mês atual no formato YYYY-MM
|
// 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 [selectedMonth, setSelectedMonth] = useState<string>(getCurrentMonth());
|
||||||
|
|
||||||
|
const access = useAccessControl();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
transactions,
|
transactions,
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -59,6 +61,11 @@ const TransacoesPage = () => {
|
|||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Dados de: {formatMonthDisplay(selectedMonth)}
|
Dados de: {formatMonthDisplay(selectedMonth)}
|
||||||
</p>
|
</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>
|
</div>
|
||||||
<MonthFilter
|
<MonthFilter
|
||||||
selectedMonth={selectedMonth}
|
selectedMonth={selectedMonth}
|
||||||
@ -66,9 +73,17 @@ const TransacoesPage = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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
|
<TransactionHeader
|
||||||
onOpenDialog={handleOpenDialog}
|
onOpenDialog={access.podeAdicionarTransacao ? handleOpenDialog : () => {}}
|
||||||
onOpenCartaoCreditoDialog={handleOpenCartaoCreditoDialog}
|
onOpenCartaoCreditoDialog={access.podeAdicionarTransacao ? handleOpenCartaoCreditoDialog : () => {}}
|
||||||
|
disableAdicionar={!access.podeAdicionarTransacao}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Resumo em Cards */}
|
{/* Resumo em Cards */}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user