Add permanent access feature

- Added a boolean field `liberado_permanente` to the `usuarios` table.
- Updated the RLS policy to allow inserts if `liberado_permanente` is true.
- Updated the frontend to show a custom message if the insert is blocked.
This commit is contained in:
gpt-engineer-app[bot] 2025-06-15 21:54:18 +00:00
parent f16b060416
commit b0bbce2637
4 changed files with 75 additions and 8 deletions

View File

@ -8,6 +8,7 @@ interface AccessControlResult {
motivo: string | null; // mensagem para exibir ao usuário se bloqueado
diasRestantesTrial: number;
trialExpiraEm: Date | null;
adminLiberou: boolean;
}
export function useAccessControl(): AccessControlResult {
@ -17,6 +18,7 @@ export function useAccessControl(): AccessControlResult {
motivo: null,
diasRestantesTrial: 0,
trialExpiraEm: null,
adminLiberou: false,
});
useEffect(() => {
@ -29,14 +31,15 @@ export function useAccessControl(): AccessControlResult {
motivo: "Erro ao identificar usuário. Faça login novamente.",
diasRestantesTrial: 0,
trialExpiraEm: null,
adminLiberou: false,
});
return;
}
// Buscar data de cadastro do usuário
// Buscar data de cadastro e liberado_permanente do usuário
const { data: usuarios, error: userError } = await supabase
.from("usuarios")
.select("created_at")
.select("created_at, liberado_permanente")
.eq("email", userEmail.trim().toLowerCase());
if (userError || !usuarios || usuarios.length === 0) {
@ -46,6 +49,7 @@ export function useAccessControl(): AccessControlResult {
motivo: "Erro ao buscar dados do usuário. Tente novamente mais tarde.",
diasRestantesTrial: 0,
trialExpiraEm: null,
adminLiberou: false,
});
return;
}
@ -56,6 +60,19 @@ export function useAccessControl(): AccessControlResult {
trialExpiraEm.setDate(trialExpiraEm.getDate() + 30);
const dentroTrial = agora < trialExpiraEm;
const diasRestantes = Math.ceil((trialExpiraEm.getTime() - agora.getTime()) / (1000 * 60 * 60 * 24));
const liberadoPermanente = usuarios[0].liberado_permanente === true;
if (liberadoPermanente) {
setResult({
loading: false,
podeAdicionarTransacao: true,
motivo: "Seu acesso foi liberado permanentemente pelo administrador.",
diasRestantesTrial: 0,
trialExpiraEm,
adminLiberou: true,
});
return;
}
if (dentroTrial) {
setResult({
@ -64,6 +81,7 @@ export function useAccessControl(): AccessControlResult {
motivo: null,
diasRestantesTrial: diasRestantes,
trialExpiraEm,
adminLiberou: false,
});
return;
}
@ -82,17 +100,24 @@ export function useAccessControl(): AccessControlResult {
motivo: "Erro ao verificar status de pagamento.",
diasRestantesTrial: 0,
trialExpiraEm,
adminLiberou: false,
});
return;
}
if (pagamentos && pagamentos.length > 0) {
const pagamentoMesVigente = pagamentos && pagamentos.some((p: any) => {
const pgDate = new Date(p.created_at);
return pgDate.getFullYear() === agora.getFullYear() && pgDate.getMonth() === agora.getMonth();
});
if (pagamentoMesVigente) {
setResult({
loading: false,
podeAdicionarTransacao: true,
motivo: null,
diasRestantesTrial: 0,
trialExpiraEm,
adminLiberou: false,
});
return;
}
@ -101,9 +126,10 @@ export function useAccessControl(): AccessControlResult {
loading: false,
podeAdicionarTransacao: false,
motivo:
"Seu período de teste acabou. Para continuar adicionando transações realize o pagamento da assinatura.",
"Seu período de teste acabou. Para continuar adicionando transações, realize o pagamento da assinatura.",
diasRestantesTrial: 0,
trialExpiraEm,
adminLiberou: false,
});
}

View File

@ -506,6 +506,7 @@ export type Database = {
empresa: string | null
id: string
instancia_zap: string | null
liberado_permanente: boolean | null
nome: string | null
remote_jid: string | null
status_instancia: string | null
@ -518,6 +519,7 @@ export type Database = {
empresa?: string | null
id?: string
instancia_zap?: string | null
liberado_permanente?: boolean | null
nome?: string | null
remote_jid?: string | null
status_instancia?: string | null
@ -530,6 +532,7 @@ export type Database = {
empresa?: string | null
id?: string
instancia_zap?: string | null
liberado_permanente?: boolean | null
nome?: string | null
remote_jid?: string | null
status_instancia?: string | null

View File

@ -1,3 +1,4 @@
import React, { useState } from 'react';
import Layout from '@/components/layout/Layout';
import TransactionsTable from '@/components/dashboard/TransactionsTable';
@ -61,10 +62,17 @@ 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>
{/* Exibe mensagem para trial OU para admin-liberou */}
{!access.loading && access.podeAdicionarTransacao && (
access.adminLiberou ? (
<div className="mt-1 text-xs text-green-600 font-bold">
{access.motivo}
</div>
) : (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
@ -73,6 +81,7 @@ const TransacoesPage = () => {
/>
</div>
{/* Mensagem de bloqueio */}
{!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}

View File

@ -0,0 +1,29 @@
-- Adiciona campo para liberar acesso vitalício/manual pelo admin
ALTER TABLE public.usuarios ADD COLUMN IF NOT EXISTS liberado_permanente boolean DEFAULT false;
-- Atualiza a policy de insert em transacoes para aceitar "liberado_permanente"
DROP POLICY IF EXISTS "Permitir inserts apenas com trial ativo ou pagamento aprovado do mês" ON public.transacoes;
CREATE POLICY "Permitir inserts apenas com trial/pagamento ou liberar permanente"
ON public.transacoes
FOR INSERT
WITH CHECK (
EXISTS (SELECT 1 FROM public.usuarios u WHERE u.email = login)
AND (
-- Caso o admin tenha liberado o usuário permanentemente
(SELECT u.liberado_permanente FROM public.usuarios u WHERE u.email = login) = true
-- Ou está dentro do período trial (primeiros 30 dias)
OR (SELECT NOW() < u.created_at + INTERVAL '30 days' FROM public.usuarios u WHERE u.email = login)
-- Ou possui pagamento aprovado para o mês vigente
OR EXISTS (
SELECT 1 FROM public.pagamentos_mercadopago p
WHERE p.payer_email = login
AND p.status = 'approved'
AND date_trunc('month', p.created_at) = date_trunc('month', NOW())
)
)
);
-- Mantém permissões normais nas outras operações
GRANT SELECT, INSERT, UPDATE, DELETE ON public.transacoes TO authenticated;