From b0bbce26375c2f57bb3bcdb64ddb6347a5665f00 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 15 Jun 2025 21:54:18 +0000 Subject: [PATCH] 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. --- src/hooks/useAccessControl.ts | 34 ++++++++++++++++--- src/integrations/supabase/types.ts | 3 ++ src/pages/Transacoes.tsx | 17 +++++++--- ...9-c97a32c2-8807-4aa2-b8c3-bcd1b25efa96.sql | 29 ++++++++++++++++ 4 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 supabase/migrations/20250615215349-c97a32c2-8807-4aa2-b8c3-bcd1b25efa96.sql diff --git a/src/hooks/useAccessControl.ts b/src/hooks/useAccessControl.ts index ab06e57..f8d0a8d 100644 --- a/src/hooks/useAccessControl.ts +++ b/src/hooks/useAccessControl.ts @@ -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, }); } diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 771e22c..4bd755e 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -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 diff --git a/src/pages/Transacoes.tsx b/src/pages/Transacoes.tsx index 8799aed..f092c77 100644 --- a/src/pages/Transacoes.tsx +++ b/src/pages/Transacoes.tsx @@ -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 = () => {

Dados de: {formatMonthDisplay(selectedMonth)}

- {!access.loading && access.podeAdicionarTransacao && access.diasRestantesTrial > 0 && ( -
- {`Você está em período gratuito. ${access.diasRestantesTrial} dia(s) restante(s) de teste.`} -
+ {/* Exibe mensagem para trial OU para admin-liberou */} + {!access.loading && access.podeAdicionarTransacao && ( + access.adminLiberou ? ( +
+ {access.motivo} +
+ ) : (access.diasRestantesTrial > 0 && ( +
+ {`Você está em período gratuito. ${access.diasRestantesTrial} dia(s) restante(s) de teste.`} +
+ )) )} { /> + {/* Mensagem de bloqueio */} {!access.loading && !access.podeAdicionarTransacao && (
Atenção: {access.motivo} diff --git a/supabase/migrations/20250615215349-c97a32c2-8807-4aa2-b8c3-bcd1b25efa96.sql b/supabase/migrations/20250615215349-c97a32c2-8807-4aa2-b8c3-bcd1b25efa96.sql new file mode 100644 index 0000000..697c7e7 --- /dev/null +++ b/supabase/migrations/20250615215349-c97a32c2-8807-4aa2-b8c3-bcd1b25efa96.sql @@ -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;