freemium F1: frontend do enforcement (toast + botao Upgrade PRO)

- utils/planLimit.js: parsePlanLimitError + maybeShowPlanLimitToast (traduz
  PLAN_LIMIT_REACHED em toast amigavel com CTA via grupo system-alerts)
- AppTopbar: botao "Upgrade PRO" quando plano ativo e gratuito (reusa
  resolveActiveSubscriptionContext; plan_key nos selects)
- ligado nos 3 pontos de criacao de paciente: PatientsCadastroPage,
  CadastrosRecebidos (intake), ComponentCadastroRapido (quick-create)
- build OK

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-06-13 18:05:28 -03:00
parent a73b82fa86
commit a979bdf1de
5 changed files with 126 additions and 10 deletions
@@ -68,6 +68,7 @@ import { tenantDb } from '@/lib/supabase/tenantClient';
import { useTenantStore } from '@/stores/tenantStore'
import { logError } from '@/support/supportLogger'
import { digitsOnly, fmtCPF, fmtRG, fmtPhone, toISODate, generateCPF } from '@/utils/validators'
import { maybeShowPlanLimitToast } from '@/utils/planLimit'
import CadastroRapidoConvenio from '@/components/CadastroRapidoConvenio.vue'
import CadastroRapidoMedico from '@/components/CadastroRapidoMedico.vue'
import ContactPhonesEditor from '@/components/ui/ContactPhonesEditor.vue'
@@ -680,7 +681,9 @@ async function onSubmit () {
await openPanel(0)
} catch (e) {
logError('patients.cadastro', 'save falhou', e)
toast.add({ severity:'error', summary:'Erro', detail:e?.message||'Falha ao salvar.', life:4000 })
if (!maybeShowPlanLimitToast(toast, e, route.fullPath)) {
toast.add({ severity:'error', summary:'Erro', detail:e?.message||'Falha ao salvar.', life:4000 })
}
} finally { saving.value=false }
}
@@ -21,6 +21,7 @@ import { useTenantStore } from '@/stores/tenantStore';
// extraídos pro repository pra remover duplicação.
import { createPatient, markIntakeConverted } from '@/features/patients/services/patientsRepository';
import { logError } from '@/support/supportLogger';
import { maybeShowPlanLimitToast } from '@/utils/planLimit';
import { useConfirm } from 'primevue/useconfirm';
import { useToast } from 'primevue/usetoast';
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
@@ -417,7 +418,9 @@ async function convertToPatient() {
await fetchIntakes();
} catch (err) {
logError('patients.recebidos', 'converter paciente falhou', err);
toast.add({ severity: 'error', summary: 'Falha ao converter', detail: err?.message || 'Não foi possível converter o cadastro.', life: 4500 });
if (!maybeShowPlanLimitToast(toast, err, '/admin/pacientes/recebidos')) {
toast.add({ severity: 'error', summary: 'Falha ao converter', detail: err?.message || 'Não foi possível converter o cadastro.', life: 4500 });
}
} finally {
converting.value = false;
}