Files
gruperly/apps/web/src/routes/onboarding.tsx
Jose Selesan 6100c998be feat: add brand assets, design tokens and light/dark theme
- Use real logo/isotype SVGs across app and emails
- Token-based color system aligned with landing (brand scale + semantic tokens)
- ThemeProvider (light/dark/system) with header toggle and settings card
- Embed logo in transactional emails; centralize email colors
2026-09-16 17:35:24 -03:00

130 lines
4.0 KiB
TypeScript

import { useEffect, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { useNavigate } from '@tanstack/react-router'
import { Loader2 } from 'lucide-react'
import type { ConnectPaymentResult, OnboardingGroupDto } from '@gruperly/shared'
import { Logo, LogoIcon } from '../components/brand'
import {
ConfirmationStep,
FirstGroupStep,
PaymentStep,
Stepper,
WelcomeStep,
} from '../components/onboarding'
import { PAYMENT_PROVIDERS } from '../components/onboarding/constants'
import { Button } from '../components/ui'
import { useAuth } from '../context/AuthProvider'
import { getOnboardingStatus } from '../lib/api'
const STEPS = [
{ label: 'Bienvenida' },
{ label: 'Cobros' },
{ label: 'Tu grupo' },
{ label: 'Listo' },
]
export function OnboardingView() {
const { user, isPending: authPending } = useAuth()
const navigate = useNavigate()
const [step, setStep] = useState(0)
const [paymentResult, setPaymentResult] = useState<ConnectPaymentResult | null>(null)
const [createdGroup, setCreatedGroup] = useState<OnboardingGroupDto | null>(null)
const statusQuery = useQuery({
queryKey: ['onboarding', 'status'],
queryFn: getOnboardingStatus,
enabled: !!user,
})
useEffect(() => {
const status = statusQuery.data
if (!status) return
if (status.completed) {
void navigate({ to: '/' })
return
}
// Si interrumpió después de conectar el cobro, retomamos en el paso del grupo.
if (status.step === 'PAYMENT_CONNECTED') {
setStep((current) => (current === 0 ? 2 : current))
}
}, [statusQuery.data, navigate])
if (authPending) {
return (
<div className="flex min-h-dvh items-center justify-center">
<Loader2 className="size-6 animate-spin text-foreground/40" />
</div>
)
}
if (!user) {
return (
<div className="flex min-h-dvh items-center justify-center px-4">
<div className="w-full max-w-sm space-y-4 text-center">
<span className="mx-auto flex size-14 items-center justify-center rounded-2xl bg-accent-soft">
<LogoIcon className="size-8" />
</span>
<h1 className="text-2xl font-bold text-primary">Tu cuenta, lista</h1>
<p className="text-sm text-foreground/60">
Iniciá sesión para configurar tus cobros y crear tu primer grupo.
</p>
<Button variant="primary" className="w-full" onClick={() => void navigate({ to: '/login' })}>
Iniciar sesión
</Button>
</div>
</div>
)
}
if (statusQuery.isPending) {
return (
<div className="flex min-h-dvh items-center justify-center">
<Loader2 className="size-6 animate-spin text-foreground/40" />
</div>
)
}
const firstName = user.name?.trim().split(/\s+/)[0] ?? 'profesor/a'
const providerName = paymentResult
? PAYMENT_PROVIDERS.find((p) => p.value === paymentResult.provider)?.name
: undefined
return (
<main className="mx-auto flex min-h-dvh w-full max-w-md flex-col px-4 pb-12 pt-8 sm:pt-12">
<header className="mb-6 flex items-center justify-center">
<Logo className="h-6" />
</header>
<Stepper steps={STEPS} current={step} />
<div key={step} className="mt-8 animate-step-enter">
{step === 0 ? (
<WelcomeStep name={firstName} onNext={() => setStep(1)} />
) : null}
{step === 1 ? (
<PaymentStep
initialResult={paymentResult}
onConnected={(result) => {
setPaymentResult(result)
setStep(2)
}}
onBack={() => setStep(0)}
/>
) : null}
{step === 2 ? (
<FirstGroupStep
onBack={() => setStep(1)}
onCompleted={(group) => {
setCreatedGroup(group)
setStep(3)
}}
/>
) : null}
{step === 3 && createdGroup ? (
<ConfirmationStep group={createdGroup} providerName={providerName} />
) : null}
</div>
</main>
)
}