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:
parent
f16b060416
commit
b0bbce2637
@ -8,6 +8,7 @@ interface AccessControlResult {
|
|||||||
motivo: string | null; // mensagem para exibir ao usuário se bloqueado
|
motivo: string | null; // mensagem para exibir ao usuário se bloqueado
|
||||||
diasRestantesTrial: number;
|
diasRestantesTrial: number;
|
||||||
trialExpiraEm: Date | null;
|
trialExpiraEm: Date | null;
|
||||||
|
adminLiberou: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAccessControl(): AccessControlResult {
|
export function useAccessControl(): AccessControlResult {
|
||||||
@ -17,6 +18,7 @@ export function useAccessControl(): AccessControlResult {
|
|||||||
motivo: null,
|
motivo: null,
|
||||||
diasRestantesTrial: 0,
|
diasRestantesTrial: 0,
|
||||||
trialExpiraEm: null,
|
trialExpiraEm: null,
|
||||||
|
adminLiberou: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -29,14 +31,15 @@ export function useAccessControl(): AccessControlResult {
|
|||||||
motivo: "Erro ao identificar usuário. Faça login novamente.",
|
motivo: "Erro ao identificar usuário. Faça login novamente.",
|
||||||
diasRestantesTrial: 0,
|
diasRestantesTrial: 0,
|
||||||
trialExpiraEm: null,
|
trialExpiraEm: null,
|
||||||
|
adminLiberou: false,
|
||||||
});
|
});
|
||||||
return;
|
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
|
const { data: usuarios, error: userError } = await supabase
|
||||||
.from("usuarios")
|
.from("usuarios")
|
||||||
.select("created_at")
|
.select("created_at, liberado_permanente")
|
||||||
.eq("email", userEmail.trim().toLowerCase());
|
.eq("email", userEmail.trim().toLowerCase());
|
||||||
|
|
||||||
if (userError || !usuarios || usuarios.length === 0) {
|
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.",
|
motivo: "Erro ao buscar dados do usuário. Tente novamente mais tarde.",
|
||||||
diasRestantesTrial: 0,
|
diasRestantesTrial: 0,
|
||||||
trialExpiraEm: null,
|
trialExpiraEm: null,
|
||||||
|
adminLiberou: false,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -56,6 +60,19 @@ export function useAccessControl(): AccessControlResult {
|
|||||||
trialExpiraEm.setDate(trialExpiraEm.getDate() + 30);
|
trialExpiraEm.setDate(trialExpiraEm.getDate() + 30);
|
||||||
const dentroTrial = agora < trialExpiraEm;
|
const dentroTrial = agora < trialExpiraEm;
|
||||||
const diasRestantes = Math.ceil((trialExpiraEm.getTime() - agora.getTime()) / (1000 * 60 * 60 * 24));
|
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) {
|
if (dentroTrial) {
|
||||||
setResult({
|
setResult({
|
||||||
@ -64,6 +81,7 @@ export function useAccessControl(): AccessControlResult {
|
|||||||
motivo: null,
|
motivo: null,
|
||||||
diasRestantesTrial: diasRestantes,
|
diasRestantesTrial: diasRestantes,
|
||||||
trialExpiraEm,
|
trialExpiraEm,
|
||||||
|
adminLiberou: false,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -82,17 +100,24 @@ export function useAccessControl(): AccessControlResult {
|
|||||||
motivo: "Erro ao verificar status de pagamento.",
|
motivo: "Erro ao verificar status de pagamento.",
|
||||||
diasRestantesTrial: 0,
|
diasRestantesTrial: 0,
|
||||||
trialExpiraEm,
|
trialExpiraEm,
|
||||||
|
adminLiberou: false,
|
||||||
});
|
});
|
||||||
return;
|
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({
|
setResult({
|
||||||
loading: false,
|
loading: false,
|
||||||
podeAdicionarTransacao: true,
|
podeAdicionarTransacao: true,
|
||||||
motivo: null,
|
motivo: null,
|
||||||
diasRestantesTrial: 0,
|
diasRestantesTrial: 0,
|
||||||
trialExpiraEm,
|
trialExpiraEm,
|
||||||
|
adminLiberou: false,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -101,9 +126,10 @@ export function useAccessControl(): AccessControlResult {
|
|||||||
loading: false,
|
loading: false,
|
||||||
podeAdicionarTransacao: false,
|
podeAdicionarTransacao: false,
|
||||||
motivo:
|
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,
|
diasRestantesTrial: 0,
|
||||||
trialExpiraEm,
|
trialExpiraEm,
|
||||||
|
adminLiberou: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -506,6 +506,7 @@ export type Database = {
|
|||||||
empresa: string | null
|
empresa: string | null
|
||||||
id: string
|
id: string
|
||||||
instancia_zap: string | null
|
instancia_zap: string | null
|
||||||
|
liberado_permanente: boolean | null
|
||||||
nome: string | null
|
nome: string | null
|
||||||
remote_jid: string | null
|
remote_jid: string | null
|
||||||
status_instancia: string | null
|
status_instancia: string | null
|
||||||
@ -518,6 +519,7 @@ export type Database = {
|
|||||||
empresa?: string | null
|
empresa?: string | null
|
||||||
id?: string
|
id?: string
|
||||||
instancia_zap?: string | null
|
instancia_zap?: string | null
|
||||||
|
liberado_permanente?: boolean | null
|
||||||
nome?: string | null
|
nome?: string | null
|
||||||
remote_jid?: string | null
|
remote_jid?: string | null
|
||||||
status_instancia?: string | null
|
status_instancia?: string | null
|
||||||
@ -530,6 +532,7 @@ export type Database = {
|
|||||||
empresa?: string | null
|
empresa?: string | null
|
||||||
id?: string
|
id?: string
|
||||||
instancia_zap?: string | null
|
instancia_zap?: string | null
|
||||||
|
liberado_permanente?: boolean | null
|
||||||
nome?: string | null
|
nome?: string | null
|
||||||
remote_jid?: string | null
|
remote_jid?: string | null
|
||||||
status_instancia?: string | null
|
status_instancia?: string | null
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import React, { useState } 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';
|
||||||
@ -61,10 +62,17 @@ 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 && (
|
{/* Exibe mensagem para trial OU para admin-liberou */}
|
||||||
<div className="mt-1 text-xs text-blue-600 font-medium">
|
{!access.loading && access.podeAdicionarTransacao && (
|
||||||
{`Você está em período gratuito. ${access.diasRestantesTrial} dia(s) restante(s) de teste.`}
|
access.adminLiberou ? (
|
||||||
</div>
|
<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>
|
</div>
|
||||||
<MonthFilter
|
<MonthFilter
|
||||||
@ -73,6 +81,7 @@ const TransacoesPage = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Mensagem de bloqueio */}
|
||||||
{!access.loading && !access.podeAdicionarTransacao && (
|
{!access.loading && !access.podeAdicionarTransacao && (
|
||||||
<div className="bg-yellow-100 border border-yellow-300 text-yellow-800 px-4 py-3 rounded relative mb-4">
|
<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}
|
<span className="font-medium">Atenção:</span> {access.motivo}
|
||||||
|
|||||||
@ -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;
|
||||||
Loading…
Reference in New Issue
Block a user