ZERADO
This commit is contained in:
@@ -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ê já 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>
|
||||
Reference in New Issue
Block a user