This commit is contained in:
Leonardo
2026-03-06 06:37:13 -03:00
parent d58dc21297
commit f733db8436
146 changed files with 43436 additions and 12779 deletions
+114 -45
View File
@@ -1,13 +1,10 @@
<!-- src/views/pages/auth/WelcomePage.vue -->
<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { supabase } from '@/lib/supabase/client'
import Card from 'primevue/card'
import Message from 'primevue/message'
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import Tag from 'primevue/tag'
import Chip from 'primevue/chip'
import ProgressSpinner from 'primevue/progressspinner'
@@ -20,7 +17,7 @@ const router = useRouter()
const planFromQuery = computed(() => String(route.query.plan || '').trim().toLowerCase())
const intervalFromQuery = computed(() => String(route.query.interval || '').trim().toLowerCase())
function normalizeInterval(v) {
function normalizeInterval (v) {
if (v === 'monthly') return 'month'
if (v === 'annual' || v === 'yearly') return 'year'
return v
@@ -34,6 +31,22 @@ const intervalLabel = computed(() => {
return ''
})
const hasPlanQuery = computed(() => !!planFromQuery.value)
// ============================
// Session (opcional para CTA melhor)
// ============================
const hasSession = ref(false)
async function checkSession () {
try {
const { data } = await supabase.auth.getSession()
hasSession.value = !!data?.session
} catch {
hasSession.value = false
}
}
// ============================
// Pricing
// ============================
@@ -43,29 +56,36 @@ const planRow = ref(null)
const planName = computed(() => planRow.value?.public_name || planRow.value?.plan_name || null)
const planDescription = computed(() => planRow.value?.public_description || null)
const amountCents = computed(() => {
if (!planRow.value) return null
return intervalNormalized.value === 'year'
? planRow.value.yearly_cents
: planRow.value.monthly_cents
})
function amountForInterval (row, interval) {
if (!row) return null
const cents = interval === 'year' ? row.yearly_cents : row.monthly_cents
// fallback inteligente: se não houver preço nesse intervalo, tenta o outro
if (cents == null) return interval === 'year' ? row.monthly_cents : row.yearly_cents
return cents
}
const currency = computed(() => {
if (!planRow.value) return 'BRL'
return intervalNormalized.value === 'year'
? (planRow.value.yearly_currency || 'BRL')
: (planRow.value.monthly_currency || 'BRL')
})
function currencyForInterval (row, interval) {
if (!row) return 'BRL'
const cur = interval === 'year' ? (row.yearly_currency || 'BRL') : (row.monthly_currency || 'BRL')
return cur || 'BRL'
}
const amountCents = computed(() => amountForInterval(planRow.value, intervalNormalized.value))
const currency = computed(() => currencyForInterval(planRow.value, intervalNormalized.value))
const formattedPrice = computed(() => {
if (amountCents.value == null) return null
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: currency.value || 'BRL'
}).format(amountCents.value / 100)
try {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: currency.value || 'BRL'
}).format(Number(amountCents.value) / 100)
} catch {
return null
}
})
async function loadPlan() {
async function loadPlan () {
planRow.value = null
if (!planFromQuery.value) return
@@ -99,16 +119,31 @@ async function loadPlan() {
}
}
onMounted(loadPlan)
watch(() => planFromQuery.value, () => loadPlan())
function goLogin() {
function goLogin () {
router.push('/auth/login')
}
function goBackLanding() {
function goBackLanding () {
router.push('/lp')
}
function goDashboard () {
router.push('/admin')
}
function goPricing () {
router.push('/lp#pricing')
}
onMounted(async () => {
await checkSession()
await loadPlan()
})
watch(
() => planFromQuery.value,
() => loadPlan()
)
</script>
<template>
@@ -123,7 +158,7 @@ function goBackLanding() {
<div class="relative w-full max-w-6xl">
<div class="rounded-3xl border border-[var(--surface-border)] bg-[var(--surface-card)] shadow-sm overflow-hidden">
<div class="grid grid-cols-12">
<!-- LEFT: boas-vindas (PrimeBlocks-like) -->
<!-- LEFT -->
<div
class="col-span-12 lg:col-span-6 p-6 md:p-10 bg-[color-mix(in_srgb,var(--surface-card),transparent_6%)] border-b lg:border-b-0 lg:border-r border-[var(--surface-border)]"
>
@@ -145,8 +180,8 @@ function goBackLanding() {
Bem-vindo(a).
</div>
<div class="mt-3 text-sm md:text-base text-[var(--text-color-secondary)] max-w-lg">
Sua conta foi criada e a sua intenção de assinatura foi registrada.
<div class="mt-3 text-sm md:text-base text-[var(--text-color-secondary)] max-w-lg leading-relaxed">
Sua conta foi criada e sua intenção de assinatura foi registrada.
Agora o caminho é simples: instruções de pagamento confirmação ativação do plano.
</div>
@@ -171,7 +206,9 @@ function goBackLanding() {
<div>
<div class="text-xs text-[var(--text-color-secondary)]">3) Plano ativo</div>
<div class="font-semibold mt-1">Recursos liberados</div>
<div class="text-xs text-[var(--text-color-secondary)] mt-1">entitlements PRO quando pago</div>
<div class="text-xs text-[var(--text-color-secondary)] mt-1">
entitlements PRO quando confirmado
</div>
</div>
<i class="pi pi-verified opacity-60" />
</div>
@@ -182,25 +219,25 @@ function goBackLanding() {
<div class="mt-6 flex flex-wrap gap-2">
<Tag severity="secondary" value="Sem cobrança automática" />
<Tag severity="secondary" value="Ativação após confirmação" />
<Tag severity="secondary" value="Fluxo pronto para gateway depois" />
<Tag severity="secondary" value="Gateway depois, sem retrabalho" />
</div>
<div class="mt-6 text-xs text-[var(--text-color-secondary)]">
* Página de boas-vindas inspirada em layouts PrimeBlocks.
* Boas-vindas inspirada em layouts PrimeBlocks.
</div>
</div>
<!-- RIGHT: resumo + botões -->
<!-- RIGHT -->
<div class="col-span-12 lg:col-span-6 p-6 md:p-10">
<div class="max-w-md mx-auto">
<div class="text-2xl font-semibold">Conta criada 🎉</div>
<div class="text-sm text-[var(--text-color-secondary)] mt-1">
<div class="text-sm text-[var(--text-color-secondary)] mt-1 leading-relaxed">
Você pode entrar. Se o seu plano for PRO, ele será ativado após confirmação do pagamento.
</div>
<div class="mt-5">
<Message severity="success" class="mb-3">
Sua intenção de assinatura foi registrada.
Intenção de assinatura registrada.
</Message>
<div v-if="loading" class="flex items-center gap-2 text-sm text-[var(--text-color-secondary)]">
@@ -208,13 +245,13 @@ function goBackLanding() {
Carregando detalhes do plano
</div>
<Card v-else class="overflow-hidden">
<Card v-else class="overflow-hidden rounded-[2rem]">
<template #content>
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<div class="text-xs text-[var(--text-color-secondary)]">Resumo do plano</div>
<div class="text-xs text-[var(--text-color-secondary)]">Resumo</div>
<div class="mt-1 flex items-center gap-2 flex-wrap">
<div v-if="hasPlanQuery" class="mt-1 flex items-center gap-2 flex-wrap">
<div class="text-lg font-semibold truncate">
{{ planName || 'Plano' }}
</div>
@@ -223,18 +260,25 @@ function goBackLanding() {
<Chip v-if="intervalLabel" :label="intervalLabel" />
</div>
<div class="mt-2 text-2xl font-semibold leading-none">
<div v-else class="mt-1">
<div class="text-lg font-semibold">Sem plano selecionado</div>
<div class="text-sm text-[var(--text-color-secondary)] mt-1">
Você pode escolher um plano agora ou seguir no FREE.
</div>
</div>
<div v-if="hasPlanQuery" class="mt-2 text-2xl font-semibold leading-none">
{{ formattedPrice || '' }}
<span v-if="intervalLabel" class="text-sm font-normal text-[var(--text-color-secondary)]">
/{{ intervalNormalized === 'month' ? 'mês' : 'ano' }}
</span>
</div>
<div v-if="planDescription" class="mt-2 text-sm text-[var(--text-color-secondary)]">
<div v-if="planDescription" class="mt-2 text-sm text-[var(--text-color-secondary)] leading-relaxed">
{{ planDescription }}
</div>
<Message v-if="planFromQuery && !planRow" severity="warn" class="mt-3">
<Message v-if="hasPlanQuery && !planRow" severity="warn" class="mt-3">
Não encontrei esse plano na vitrine pública. Você pode continuar normalmente.
</Message>
</div>
@@ -243,15 +287,40 @@ function goBackLanding() {
<Divider class="my-4" />
<Message severity="info" class="mb-0">
Próximo passo: você receberá instruções de pagamento (PIX ou boleto).
Próximo passo: você receberá instruções de pagamento (PIX/boleto).
Assim que confirmado, sua assinatura será ativada.
</Message>
<div v-if="!hasPlanQuery" class="mt-3">
<Button
label="Escolher um plano"
icon="pi pi-credit-card"
severity="secondary"
outlined
class="w-full"
@click="goPricing"
/>
</div>
</template>
</Card>
</div>
<div class="mt-5 gap-2">
<Button label="Ir para login" class="w-full mb-2" icon="pi pi-sign-in" @click="goLogin" />
<Button
v-if="hasSession"
label="Ir para o painel"
class="w-full mb-2"
icon="pi pi-arrow-right"
@click="goDashboard"
/>
<Button
v-else
label="Ir para login"
class="w-full mb-2"
icon="pi pi-sign-in"
@click="goLogin"
/>
<Button
label="Voltar para a página inicial"
severity="secondary"
@@ -271,4 +340,4 @@ function goBackLanding() {
</div>
</div>
</div>
</template>
</template>