freemium F2 polish: welcome email + plano gratuito na vitrine

- edge function send-welcome-email: e-mail de boas-vindas ao DONO do tenant
  recem-provisionado (destinatario do JWT, SMTP global/sistema, defaults Mailpit).
  Best-effort, disparada fire-and-forget no OnboardingPage so no provisionamento novo.
- vitrine: seed plan_public + bullets dos planos free (cartao "Gratis"); Landingpage
  passa a mostrar "Gratis para sempre" (isFreePlan) em vez de "—".
- build OK

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-06-13 20:36:33 -03:00
parent f6470718b7
commit 52c34cf63a
4 changed files with 212 additions and 3 deletions
+5
View File
@@ -92,6 +92,11 @@ async function provision(slugOverride = null) {
// caminho pago (intent) — best-effort, não bloqueia
try { await supabase.rpc('processar_pos_signup'); } catch (e) { console.warn('[onboarding] processar_pos_signup:', e?.message || e); }
// welcome email — só no provisionamento NOVO, fire-and-forget (não bloqueia)
if (data?.status === 'provisioned') {
supabase.functions.invoke('send-welcome-email').catch(() => { /* best-effort */ });
}
await finishAndRedirect(data?.kind || 'therapist');
} catch (err) {
const msg = String(err?.message || '');
+16 -3
View File
@@ -80,6 +80,14 @@ function priceFor(p) {
return cents;
}
// plano gratuito: por chave (_free) ou preço zero/ausente
function isFreePlan(p) {
const k = String(p?.plan_key || '').toLowerCase();
if (k.endsWith('_free') || k === 'free') return true;
const cents = priceFor(p);
return cents == null || Number(cents) === 0;
}
async function fetchPricing() {
loadingPricing.value = true;
try {
@@ -481,11 +489,16 @@ onMounted(fetchPricing);
</div>
<div class="mt-4 text-3xl font-semibold leading-none">
{{ formatBRLFromCents(priceFor(p)) }}
<span class="text-sm font-normal text-[var(--text-color-secondary)]"> /{{ billingInterval === 'month' ? 'mês' : 'ano' }} </span>
<template v-if="isFreePlan(p)">
Grátis<span class="text-sm font-normal text-[var(--text-color-secondary)]"> para sempre </span>
</template>
<template v-else>
{{ formatBRLFromCents(priceFor(p)) }}
<span class="text-sm font-normal text-[var(--text-color-secondary)]"> /{{ billingInterval === 'month' ? 'mês' : 'ano' }} </span>
</template>
</div>
<div v-if="billingInterval === 'year'" class="text-xs text-emerald-500 mt-1 font-medium">Melhor custo-benefício</div>
<div v-if="!isFreePlan(p) && billingInterval === 'year'" class="text-xs text-emerald-500 mt-1 font-medium">Melhor custo-benefício</div>
<div class="mt-2 text-sm text-[var(--text-color-secondary)] min-h-[44px] leading-relaxed">
{{ p.public_description || '—' }}