Redesign: Editorial Fresh design system
Complete visual overhaul with distinctive editorial aesthetic: Design System: - New fonts: Fraunces (display) + DM Sans (body) - Color palette: Cream background, ink text, coral accents - Custom CSS variables and utility classes - Sharp edges (no rounded corners) for editorial feel Components: - Updated Button with new variants and colors - Updated Input with editorial styling - New card-editorial class with accent bar Pages: - Homepage: Full redesign with hero, features, CTA - Auth layout: Split screen with branding side - Login/Register: Editorial styling with tags - Dashboard: Cards with accent bars - Subscription: Clean feature comparison table Typography: - Fraunces for all headings (serif, characterful) - DM Sans for body text (clean, readable) - Editorial tags (uppercase, accent color) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,61 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function AuthLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
{children}
|
||||
<div className="min-h-screen bg-cream flex">
|
||||
{/* Left side - Branding */}
|
||||
<div className="hidden lg:flex lg:w-1/2 bg-ink p-12 flex-col justify-between relative overflow-hidden">
|
||||
{/* Decorative elements */}
|
||||
<div className="absolute top-0 right-0 w-64 h-64 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2"></div>
|
||||
<div className="absolute bottom-0 left-0 w-96 h-96 bg-accent/5 rounded-full translate-y-1/2 -translate-x-1/2"></div>
|
||||
|
||||
{/* Logo */}
|
||||
<Link href="/" className="relative z-10">
|
||||
<span className="font-display text-3xl font-semibold text-cream">Leopost</span>
|
||||
</Link>
|
||||
|
||||
{/* Quote */}
|
||||
<div className="relative z-10 max-w-md">
|
||||
<div className="w-12 h-1 bg-accent mb-8"></div>
|
||||
<blockquote className="font-display text-3xl text-cream/90 leading-snug font-medium italic">
|
||||
"Finalmente un assistente che capisce il mio brand e mi fa risparmiare ore ogni settimana."
|
||||
</blockquote>
|
||||
<div className="mt-8 flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-cream/20 flex items-center justify-center">
|
||||
<span className="font-display text-cream font-semibold">MR</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-cream font-medium">Marco Rossi</p>
|
||||
<p className="text-cream/60 text-sm">Social Media Manager</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<p className="text-cream/40 text-sm relative z-10">
|
||||
© 2026 Leopost
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right side - Form */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{/* Mobile header */}
|
||||
<div className="lg:hidden p-6 border-b border-editorial">
|
||||
<Link href="/" className="font-display text-2xl font-semibold">
|
||||
Leopost
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Form container */}
|
||||
<div className="flex-1 flex items-center justify-center p-6 sm:p-12">
|
||||
<div className="w-full max-w-md opacity-0 animate-fade-up">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,30 +1,48 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { LoginForm } from '@/components/auth/login-form'
|
||||
import { GoogleSignInButton } from '@/components/auth/google-button'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle>Accedi a Leopost</CardTitle>
|
||||
<CardDescription>
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<span className="editorial-tag">Bentornato</span>
|
||||
<h1 className="mt-4 text-3xl font-display font-semibold">
|
||||
Accedi a Leopost
|
||||
</h1>
|
||||
<p className="mt-2 text-ink-light">
|
||||
Inserisci le tue credenziali per continuare
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<GoogleSignInButton />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t border-gray-300" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-white px-2 text-gray-500">oppure</span>
|
||||
</div>
|
||||
{/* Google Sign In */}
|
||||
<GoogleSignInButton />
|
||||
|
||||
{/* Divider */}
|
||||
<div className="relative my-8">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-editorial"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center">
|
||||
<span className="bg-cream px-4 text-sm text-muted uppercase tracking-wide">
|
||||
oppure
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LoginForm />
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Email/Password Form */}
|
||||
<LoginForm />
|
||||
|
||||
{/* Footer links */}
|
||||
<div className="mt-8 pt-6 border-t border-editorial text-center">
|
||||
<p className="text-sm text-ink-light">
|
||||
Non hai un account?{' '}
|
||||
<Link href="/register/" className="text-accent hover:underline font-medium">
|
||||
Registrati gratis
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,30 +1,48 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { RegisterForm } from '@/components/auth/register-form'
|
||||
import { GoogleSignInButton } from '@/components/auth/google-button'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function RegisterPage() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle>Crea il tuo account</CardTitle>
|
||||
<CardDescription>
|
||||
Inizia a usare Leopost gratuitamente
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<GoogleSignInButton />
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<span className="editorial-tag">Inizia gratis</span>
|
||||
<h1 className="mt-4 text-3xl font-display font-semibold">
|
||||
Crea il tuo account
|
||||
</h1>
|
||||
<p className="mt-2 text-ink-light">
|
||||
Inizia a gestire i tuoi social in modo intelligente
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t border-gray-300" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-white px-2 text-gray-500">oppure</span>
|
||||
</div>
|
||||
{/* Google Sign In */}
|
||||
<GoogleSignInButton />
|
||||
|
||||
{/* Divider */}
|
||||
<div className="relative my-8">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-editorial"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center">
|
||||
<span className="bg-cream px-4 text-sm text-muted uppercase tracking-wide">
|
||||
oppure
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RegisterForm />
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Email/Password Form */}
|
||||
<RegisterForm />
|
||||
|
||||
{/* Footer links */}
|
||||
<div className="mt-8 pt-6 border-t border-editorial text-center">
|
||||
<p className="text-sm text-ink-light">
|
||||
Hai già un account?{' '}
|
||||
<Link href="/login/" className="text-accent hover:underline font-medium">
|
||||
Accedi
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const supabase = await createClient()
|
||||
@@ -32,81 +32,103 @@ export default async function DashboardPage() {
|
||||
} | null
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-10 opacity-0 animate-fade-up">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Dashboard</h1>
|
||||
<p className="text-gray-500">Benvenuto in Leopost</p>
|
||||
<span className="editorial-tag">Dashboard</span>
|
||||
<h1 className="mt-4 text-4xl font-display font-semibold">
|
||||
Bentornato in Leopost
|
||||
</h1>
|
||||
<p className="mt-2 text-ink-light text-lg">
|
||||
Gestisci i tuoi contenuti social da qui.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Cards Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Il tuo piano</CardTitle>
|
||||
<CardDescription>
|
||||
{/* Plan Card */}
|
||||
<div className="card-editorial">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<h2 className="font-display text-xl font-semibold">Il tuo piano</h2>
|
||||
<span className="editorial-tag">
|
||||
{profile?.plans?.display_name_it || 'Gratuito'}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2 text-sm text-gray-600">
|
||||
<li>
|
||||
<span className="font-medium">{features?.posts_per_month || 10}</span> post/mese
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-medium">{features?.social_accounts || 1}</span> account social
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-medium">{features?.ai_models?.length || 1}</span> modelli AI
|
||||
</li>
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</span>
|
||||
</div>
|
||||
<ul className="space-y-3 text-ink-light">
|
||||
<li className="flex justify-between">
|
||||
<span>Post al mese</span>
|
||||
<span className="font-medium text-ink">{features?.posts_per_month || 10}</span>
|
||||
</li>
|
||||
<li className="flex justify-between">
|
||||
<span>Account social</span>
|
||||
<span className="font-medium text-ink">{features?.social_accounts || 1}</span>
|
||||
</li>
|
||||
<li className="flex justify-between">
|
||||
<span>Modelli AI</span>
|
||||
<span className="font-medium text-ink">{features?.ai_models?.length || 1}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="mt-6 pt-4 border-t border-editorial">
|
||||
<Link
|
||||
href="/subscription/"
|
||||
className="text-sm text-accent hover:underline font-medium"
|
||||
>
|
||||
Cambia piano →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Prossimi passi</CardTitle>
|
||||
<CardDescription>
|
||||
Completa la configurazione
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="w-5 h-5 rounded-full bg-green-100 text-green-600 flex items-center justify-center text-xs">
|
||||
✓
|
||||
</span>
|
||||
Account creato
|
||||
</li>
|
||||
<li className="flex items-center gap-2 text-gray-400">
|
||||
<span className="w-5 h-5 rounded-full bg-gray-100 flex items-center justify-center text-xs">
|
||||
2
|
||||
</span>
|
||||
Collega social (Phase 2)
|
||||
</li>
|
||||
<li className="flex items-center gap-2 text-gray-400">
|
||||
<span className="w-5 h-5 rounded-full bg-gray-100 flex items-center justify-center text-xs">
|
||||
3
|
||||
</span>
|
||||
Configura brand (Phase 3)
|
||||
</li>
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Onboarding Card */}
|
||||
<div className="card-editorial">
|
||||
<h2 className="font-display text-xl font-semibold mb-4">Prossimi passi</h2>
|
||||
<ul className="space-y-4">
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="w-6 h-6 flex items-center justify-center bg-success text-white text-xs font-medium">
|
||||
✓
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-medium text-ink">Account creato</p>
|
||||
<p className="text-sm text-ink-light">Completo</p>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="w-6 h-6 flex items-center justify-center bg-cream-dark text-ink-muted text-xs font-medium">
|
||||
2
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-medium text-ink-muted">Collega social</p>
|
||||
<p className="text-sm text-ink-muted">Prossimamente</p>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="w-6 h-6 flex items-center justify-center bg-cream-dark text-ink-muted text-xs font-medium">
|
||||
3
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-medium text-ink-muted">Configura brand</p>
|
||||
<p className="text-sm text-ink-muted">Prossimamente</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Attivita</CardTitle>
|
||||
<CardDescription>
|
||||
Le tue statistiche
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-center py-4 text-gray-400 text-sm">
|
||||
Nessuna attivita ancora.
|
||||
<br />
|
||||
Inizia collegando un account social!
|
||||
{/* Activity Card */}
|
||||
<div className="card-editorial">
|
||||
<h2 className="font-display text-xl font-semibold mb-4">Attività</h2>
|
||||
<div className="py-8 text-center">
|
||||
<div className="w-16 h-16 mx-auto mb-4 bg-cream-dark flex items-center justify-center">
|
||||
<svg className="w-8 h-8 text-ink-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<p className="text-ink-muted text-sm">
|
||||
Nessuna attività ancora.
|
||||
</p>
|
||||
<p className="text-ink-muted text-sm mt-1">
|
||||
Collega un account social per iniziare!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -31,25 +31,25 @@ export default async function DashboardLayout({
|
||||
.single()
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="min-h-screen bg-cream">
|
||||
{/* Header */}
|
||||
<header className="bg-white border-b border-gray-200">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<header className="bg-white border-b border-editorial sticky top-0 z-50">
|
||||
<div className="max-w-7xl mx-auto px-6">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
<div className="flex items-center gap-8">
|
||||
<Link href="/dashboard" className="text-xl font-bold text-blue-600">
|
||||
<div className="flex items-center gap-10">
|
||||
<Link href="/dashboard/" className="font-display text-xl font-semibold text-ink">
|
||||
Leopost
|
||||
</Link>
|
||||
<nav className="hidden md:flex items-center gap-4">
|
||||
<nav className="hidden md:flex items-center gap-6">
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="text-gray-600 hover:text-gray-900 text-sm font-medium"
|
||||
href="/dashboard/"
|
||||
className="text-ink-light hover:text-ink transition-colors text-sm font-medium"
|
||||
>
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
href="/subscription"
|
||||
className="text-gray-600 hover:text-gray-900 text-sm font-medium"
|
||||
href="/subscription/"
|
||||
className="text-ink-light hover:text-ink transition-colors text-sm font-medium"
|
||||
>
|
||||
Piano
|
||||
</Link>
|
||||
@@ -64,7 +64,7 @@ export default async function DashboardLayout({
|
||||
</header>
|
||||
|
||||
{/* Main content */}
|
||||
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<main className="max-w-7xl mx-auto px-6 py-10">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@ export default async function SubscriptionPage() {
|
||||
if (plansError || !plans) {
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-red-600">Errore nel caricamento dei piani</p>
|
||||
<p className="text-error">Errore nel caricamento dei piani</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -41,19 +41,23 @@ export default async function SubscriptionPage() {
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-12 opacity-0 animate-fade-up">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Il tuo abbonamento</h1>
|
||||
<p className="text-gray-500 mt-1">
|
||||
Scegli il piano piu adatto alle tue esigenze
|
||||
<span className="editorial-tag">Abbonamento</span>
|
||||
<h1 className="mt-4 text-4xl font-display font-semibold">
|
||||
Scegli il tuo piano
|
||||
</h1>
|
||||
<p className="mt-2 text-ink-light text-lg">
|
||||
Trova il piano più adatto alle tue esigenze
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Info banner */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<p className="text-sm text-blue-800">
|
||||
<strong>Nota:</strong> Il pagamento verra implementato nelle prossime versioni.
|
||||
Per ora puoi passare liberamente tra i piani per testare le funzionalita.
|
||||
<div className="p-5 bg-accent-light border-l-4 border-accent">
|
||||
<p className="text-sm text-ink">
|
||||
<strong>Nota:</strong> Il pagamento verrà implementato nelle prossime versioni.
|
||||
Per ora puoi passare liberamente tra i piani per testare le funzionalità.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -69,19 +73,19 @@ export default async function SubscriptionPage() {
|
||||
</div>
|
||||
|
||||
{/* Feature comparison */}
|
||||
<div className="mt-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">
|
||||
Confronto funzionalita
|
||||
<div>
|
||||
<h2 className="text-2xl font-display font-semibold mb-6">
|
||||
Confronto funzionalità
|
||||
</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b">
|
||||
<th className="text-left py-3 px-4 font-medium text-gray-600">
|
||||
Funzionalita
|
||||
<tr className="border-b-2 border-ink">
|
||||
<th className="text-left py-4 px-4 font-display font-semibold text-ink">
|
||||
Funzionalità
|
||||
</th>
|
||||
{sortedPlans.map((plan) => (
|
||||
<th key={plan.id} className="text-center py-3 px-4 font-medium text-gray-900">
|
||||
<th key={plan.id} className="text-center py-4 px-4 font-display font-semibold text-ink">
|
||||
{plan.display_name_it}
|
||||
</th>
|
||||
))}
|
||||
@@ -106,17 +110,17 @@ export default async function SubscriptionPage() {
|
||||
<FeatureRow
|
||||
feature="Generazione immagini"
|
||||
plans={sortedPlans as Plan[]}
|
||||
getValue={(p) => (p.features as PlanFeatures).image_generation ? '\u2713' : '\u2014'}
|
||||
getValue={(p) => (p.features as PlanFeatures).image_generation ? '✓' : '—'}
|
||||
/>
|
||||
<FeatureRow
|
||||
feature="Automazione"
|
||||
plans={sortedPlans as Plan[]}
|
||||
getValue={(p) => {
|
||||
const auto = (p.features as PlanFeatures).automation
|
||||
if (auto === false) return '\u2014'
|
||||
if (auto === false) return '—'
|
||||
if (auto === 'manual') return 'Manuale'
|
||||
if (auto === 'full') return 'Completa'
|
||||
return '\u2014'
|
||||
return '—'
|
||||
}}
|
||||
/>
|
||||
</tbody>
|
||||
@@ -125,14 +129,14 @@ export default async function SubscriptionPage() {
|
||||
</div>
|
||||
|
||||
{/* FAQ */}
|
||||
<div className="mt-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">
|
||||
<div>
|
||||
<h2 className="text-2xl font-display font-semibold mb-6">
|
||||
Domande frequenti
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Posso cambiare piano in qualsiasi momento?"
|
||||
answer="Si, puoi passare a un piano superiore o inferiore quando vuoi. Le modifiche sono immediate."
|
||||
answer="Sì, puoi passare a un piano superiore o inferiore quando vuoi. Le modifiche sono immediate."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Cosa succede se supero i limiti del mio piano?"
|
||||
@@ -140,7 +144,7 @@ export default async function SubscriptionPage() {
|
||||
/>
|
||||
<FaqItem
|
||||
question="Come funziona il pagamento?"
|
||||
answer="Il sistema di pagamento verra implementato a breve. Per ora tutti i piani sono disponibili gratuitamente per i test."
|
||||
answer="Il sistema di pagamento verrà implementato a breve. Per ora tutti i piani sono disponibili gratuitamente per i test."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,10 +162,10 @@ function FeatureRow({
|
||||
getValue: (plan: Plan) => string
|
||||
}) {
|
||||
return (
|
||||
<tr className="border-b">
|
||||
<td className="py-3 px-4 text-gray-600">{feature}</td>
|
||||
<tr className="border-b border-editorial">
|
||||
<td className="py-4 px-4 text-ink-light">{feature}</td>
|
||||
{plans.map((plan) => (
|
||||
<td key={plan.id} className="text-center py-3 px-4">
|
||||
<td key={plan.id} className="text-center py-4 px-4 font-medium">
|
||||
{getValue(plan)}
|
||||
</td>
|
||||
))}
|
||||
@@ -171,9 +175,9 @@ function FeatureRow({
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<h3 className="font-medium text-gray-900 mb-2">{question}</h3>
|
||||
<p className="text-sm text-gray-600">{answer}</p>
|
||||
<div className="card-editorial">
|
||||
<h3 className="font-display font-semibold text-ink mb-2">{question}</h3>
|
||||
<p className="text-ink-light">{answer}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,26 +1,238 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
/* ==========================================================================
|
||||
LEOPOST DESIGN SYSTEM - "Editorial Fresh"
|
||||
========================================================================== */
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
/* Typography */
|
||||
--font-body: var(--font-body), system-ui, sans-serif;
|
||||
--font-display: var(--font-display), Georgia, serif;
|
||||
|
||||
/* Color Palette */
|
||||
--color-cream: #FFFBF5;
|
||||
--color-cream-dark: #F5F0E8;
|
||||
--color-ink: #1A1A1A;
|
||||
--color-ink-light: #4A4A4A;
|
||||
--color-ink-muted: #7A7A7A;
|
||||
|
||||
/* Accent - Coral Red */
|
||||
--color-accent: #E85A4F;
|
||||
--color-accent-hover: #D14940;
|
||||
--color-accent-light: #FFF0EE;
|
||||
|
||||
/* Semantic */
|
||||
--color-background: var(--color-cream);
|
||||
--color-foreground: var(--color-ink);
|
||||
--color-muted: var(--color-ink-muted);
|
||||
--color-border: #E5E0D8;
|
||||
--color-border-strong: #D0C9BD;
|
||||
|
||||
/* Success/Error */
|
||||
--color-success: #2D7A4F;
|
||||
--color-success-light: #E8F5ED;
|
||||
--color-error: #C53030;
|
||||
--color-error-light: #FEE2E2;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
/* ==========================================================================
|
||||
BASE STYLES
|
||||
========================================================================== */
|
||||
|
||||
body {
|
||||
background-color: var(--color-cream);
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Display Typography */
|
||||
.font-display {
|
||||
font-family: var(--font-display);
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
.font-body {
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
/* Heading Styles */
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
.heading {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
color: var(--color-ink);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
EDITORIAL ELEMENTS
|
||||
========================================================================== */
|
||||
|
||||
/* Decorative line */
|
||||
.editorial-line {
|
||||
width: 60px;
|
||||
height: 3px;
|
||||
background-color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Pull quote style */
|
||||
.pull-quote {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.5rem;
|
||||
font-style: italic;
|
||||
color: var(--color-ink-light);
|
||||
border-left: 3px solid var(--color-accent);
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
/* Label/Tag style */
|
||||
.editorial-tag {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
COMPONENT OVERRIDES
|
||||
========================================================================== */
|
||||
|
||||
/* Buttons - Primary */
|
||||
.btn-primary {
|
||||
background-color: var(--color-ink);
|
||||
color: var(--color-cream);
|
||||
font-weight: 500;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--color-accent);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Buttons - Secondary */
|
||||
.btn-secondary {
|
||||
background-color: transparent;
|
||||
color: var(--color-ink);
|
||||
font-weight: 500;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: 2px solid var(--color-ink);
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--color-ink);
|
||||
color: var(--color-cream);
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card-editorial {
|
||||
background-color: white;
|
||||
border: 1px solid var(--color-border);
|
||||
padding: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card-editorial::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Input fields */
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
textarea {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0;
|
||||
background-color: white;
|
||||
padding: 0.75rem 1rem;
|
||||
font-family: var(--font-body);
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-ink);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
UTILITIES
|
||||
========================================================================== */
|
||||
|
||||
/* Text colors */
|
||||
.text-accent { color: var(--color-accent); }
|
||||
.text-muted { color: var(--color-ink-muted); }
|
||||
.text-ink { color: var(--color-ink); }
|
||||
.text-ink-light { color: var(--color-ink-light); }
|
||||
|
||||
/* Background colors */
|
||||
.bg-cream { background-color: var(--color-cream); }
|
||||
.bg-cream-dark { background-color: var(--color-cream-dark); }
|
||||
.bg-accent-light { background-color: var(--color-accent-light); }
|
||||
|
||||
/* Border colors */
|
||||
.border-editorial { border-color: var(--color-border); }
|
||||
|
||||
/* ==========================================================================
|
||||
ANIMATIONS
|
||||
========================================================================== */
|
||||
|
||||
@keyframes fade-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.animate-fade-up {
|
||||
animation: fade-up 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.4s ease-out forwards;
|
||||
}
|
||||
|
||||
/* Staggered animations */
|
||||
.stagger-1 { animation-delay: 0.1s; }
|
||||
.stagger-2 { animation-delay: 0.2s; }
|
||||
.stagger-3 { animation-delay: 0.3s; }
|
||||
.stagger-4 { animation-delay: 0.4s; }
|
||||
|
||||
/* ==========================================================================
|
||||
SELECTION & FOCUS
|
||||
========================================================================== */
|
||||
|
||||
::selection {
|
||||
background-color: var(--color-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { DM_Sans, Fraunces } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
const dmSans = DM_Sans({
|
||||
variable: "--font-body",
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
const fraunces = Fraunces({
|
||||
variable: "--font-display",
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -27,9 +29,7 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="it">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<body className={`${dmSans.variable} ${fraunces.variable} font-body antialiased`}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
209
src/app/page.tsx
209
src/app/page.tsx
@@ -1,37 +1,194 @@
|
||||
import Link from 'next/link'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
export default function Home() {
|
||||
// Simple static landing page - no Supabase for now
|
||||
return (
|
||||
<main className="min-h-screen flex flex-col items-center justify-center p-8 bg-gradient-to-b from-blue-50 to-white">
|
||||
<div className="text-center max-w-2xl">
|
||||
<h1 className="text-5xl font-bold text-gray-900 mb-4">
|
||||
Leopost
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mb-8">
|
||||
Il tuo social media manager potenziato dall'AI.
|
||||
<br />
|
||||
Minimo sforzo, massima resa.
|
||||
</p>
|
||||
|
||||
<div className="flex gap-4 justify-center">
|
||||
<Link href="/register/">
|
||||
<Button size="lg">
|
||||
Inizia gratis
|
||||
</Button>
|
||||
<main className="min-h-screen bg-cream">
|
||||
{/* Header */}
|
||||
<header className="fixed top-0 left-0 right-0 z-50 bg-cream/90 backdrop-blur-sm border-b border-editorial">
|
||||
<div className="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||
<Link href="/" className="font-display text-2xl font-semibold tracking-tight">
|
||||
Leopost
|
||||
</Link>
|
||||
<Link href="/login/">
|
||||
<Button variant="outline" size="lg">
|
||||
<nav className="flex items-center gap-6">
|
||||
<Link
|
||||
href="/login/"
|
||||
className="text-ink-light hover:text-ink transition-colors text-sm font-medium"
|
||||
>
|
||||
Accedi
|
||||
</Button>
|
||||
</Link>
|
||||
</Link>
|
||||
<Link
|
||||
href="/register/"
|
||||
className="btn-primary text-sm"
|
||||
>
|
||||
Inizia gratis
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<p className="mt-8 text-sm text-gray-500">
|
||||
Nessuna carta richiesta. Piano gratuito disponibile.
|
||||
</p>
|
||||
</div>
|
||||
{/* Hero Section */}
|
||||
<section className="pt-32 pb-20 px-6">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
||||
{/* Left Column - Text */}
|
||||
<div className="opacity-0 animate-fade-up">
|
||||
<span className="editorial-tag">Social Media Manager</span>
|
||||
|
||||
<h1 className="mt-6 text-5xl md:text-6xl lg:text-7xl font-display font-semibold leading-[1.1] tracking-tight">
|
||||
Il tuo assistente
|
||||
<br />
|
||||
<span className="text-accent">editoriale</span>
|
||||
<br />
|
||||
potenziato dall'AI
|
||||
</h1>
|
||||
|
||||
<div className="editorial-line mt-8"></div>
|
||||
|
||||
<p className="mt-8 text-xl text-ink-light leading-relaxed max-w-lg">
|
||||
Crea, programma e pubblica contenuti su tutti i tuoi canali social.
|
||||
Minimo sforzo, massima resa.
|
||||
</p>
|
||||
|
||||
<div className="mt-10 flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/register/" className="btn-primary text-center">
|
||||
Inizia gratis
|
||||
</Link>
|
||||
<Link href="/login/" className="btn-secondary text-center">
|
||||
Ho già un account
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<p className="mt-6 text-sm text-muted">
|
||||
Nessuna carta richiesta · Piano gratuito per sempre
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Visual */}
|
||||
<div className="opacity-0 animate-fade-up stagger-2 hidden lg:block">
|
||||
<div className="relative">
|
||||
{/* Decorative background */}
|
||||
<div className="absolute -inset-4 bg-cream-dark rounded-sm -rotate-2"></div>
|
||||
|
||||
{/* Main card */}
|
||||
<div className="card-editorial relative">
|
||||
<div className="space-y-6">
|
||||
{/* Fake chat message */}
|
||||
<div className="flex gap-4">
|
||||
<div className="w-10 h-10 rounded-full bg-accent/20 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-accent font-display font-semibold">L</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-sm">Leopost AI</p>
|
||||
<p className="mt-1 text-ink-light">
|
||||
Ho preparato 3 post per la tua pagina Instagram.
|
||||
Vuoi che li programmi per la prossima settimana?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="border-t border-editorial"></div>
|
||||
|
||||
{/* Post preview cards */}
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="aspect-square bg-cream-dark rounded-sm flex items-center justify-center">
|
||||
<span className="text-2xl">📸</span>
|
||||
</div>
|
||||
<div className="aspect-square bg-cream-dark rounded-sm flex items-center justify-center">
|
||||
<span className="text-2xl">✨</span>
|
||||
</div>
|
||||
<div className="aspect-square bg-cream-dark rounded-sm flex items-center justify-center">
|
||||
<span className="text-2xl">🎯</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className="flex gap-3">
|
||||
<button className="flex-1 py-2 text-sm font-medium bg-ink text-cream">
|
||||
Programma tutti
|
||||
</button>
|
||||
<button className="flex-1 py-2 text-sm font-medium border border-editorial">
|
||||
Modifica
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Section */}
|
||||
<section className="py-20 px-6 bg-cream-dark">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="text-center mb-16 opacity-0 animate-fade-up">
|
||||
<span className="editorial-tag">Come funziona</span>
|
||||
<h2 className="mt-4 text-4xl font-display font-semibold">
|
||||
Tre passi verso la libertà editoriale
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{/* Feature 1 */}
|
||||
<div className="opacity-0 animate-fade-up stagger-1">
|
||||
<div className="text-5xl font-display font-semibold text-accent/30">01</div>
|
||||
<h3 className="mt-4 text-xl font-display font-semibold">Connetti i tuoi canali</h3>
|
||||
<p className="mt-2 text-ink-light">
|
||||
Collega Facebook, Instagram e gli altri social in pochi click.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Feature 2 */}
|
||||
<div className="opacity-0 animate-fade-up stagger-2">
|
||||
<div className="text-5xl font-display font-semibold text-accent/30">02</div>
|
||||
<h3 className="mt-4 text-xl font-display font-semibold">Chatta con l'AI</h3>
|
||||
<p className="mt-2 text-ink-light">
|
||||
Descrivi cosa vuoi comunicare. L'AI crea contenuti su misura per te.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Feature 3 */}
|
||||
<div className="opacity-0 animate-fade-up stagger-3">
|
||||
<div className="text-5xl font-display font-semibold text-accent/30">03</div>
|
||||
<h3 className="mt-4 text-xl font-display font-semibold">Rilassati</h3>
|
||||
<p className="mt-2 text-ink-light">
|
||||
Leopost pubblica automaticamente. Tu pensa al tuo business.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 px-6">
|
||||
<div className="max-w-3xl mx-auto text-center opacity-0 animate-fade-up">
|
||||
<h2 className="text-4xl md:text-5xl font-display font-semibold">
|
||||
Pronto a trasformare
|
||||
<br />
|
||||
la tua presenza social?
|
||||
</h2>
|
||||
<div className="editorial-line mx-auto mt-8"></div>
|
||||
<p className="mt-8 text-xl text-ink-light">
|
||||
Inizia oggi con il piano gratuito. Nessun vincolo.
|
||||
</p>
|
||||
<div className="mt-10">
|
||||
<Link href="/register/" className="btn-primary inline-block">
|
||||
Crea il tuo account
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="py-8 px-6 border-t border-editorial">
|
||||
<div className="max-w-6xl mx-auto flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
<div className="font-display text-lg font-semibold">Leopost</div>
|
||||
<p className="text-sm text-muted">
|
||||
© 2026 Leopost. Tutti i diritti riservati.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -62,10 +62,10 @@ export function GoogleSignInButton() {
|
||||
variant="outline"
|
||||
onClick={handleGoogleSignIn}
|
||||
disabled={loading}
|
||||
className="w-full flex items-center justify-center gap-2"
|
||||
className="w-full flex items-center justify-center gap-3 h-12 text-base"
|
||||
>
|
||||
<GoogleIcon className="w-5 h-5" />
|
||||
{loading ? 'Reindirizzamento...' : 'Accedi con Google'}
|
||||
{loading ? 'Reindirizzamento...' : 'Continua con Google'}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,15 +43,15 @@ export function LoginForm() {
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
{error && (
|
||||
<div className="p-3 bg-red-50 border border-red-200 rounded-md">
|
||||
<p className="text-red-800 text-sm">{error}</p>
|
||||
<div className="p-4 bg-error-light border-l-4 border-error">
|
||||
<p className="text-error text-sm">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-ink mb-2">
|
||||
Email
|
||||
</label>
|
||||
<Input
|
||||
@@ -67,7 +67,7 @@ export function LoginForm() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-ink mb-2">
|
||||
Password
|
||||
</label>
|
||||
<Input
|
||||
@@ -83,21 +83,14 @@ export function LoginForm() {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end">
|
||||
<Link href="/reset-password" className="text-sm text-blue-600 hover:underline">
|
||||
<Link href="/reset-password/" className="text-sm text-accent hover:underline">
|
||||
Password dimenticata?
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{loading ? 'Accesso...' : 'Accedi'}
|
||||
<Button type="submit" className="w-full h-12 text-base" disabled={loading}>
|
||||
{loading ? 'Accesso in corso...' : 'Accedi'}
|
||||
</Button>
|
||||
|
||||
<p className="text-center text-sm text-gray-600">
|
||||
Non hai un account?{' '}
|
||||
<Link href="/register" className="text-blue-600 hover:underline">
|
||||
Registrati
|
||||
</Link>
|
||||
</p>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -81,12 +81,15 @@ export function RegisterForm() {
|
||||
if (success) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<div className="mb-4 p-4 bg-green-50 border border-green-200 rounded-md">
|
||||
<p className="text-green-800">
|
||||
Registrazione completata! Controlla la tua email per confermare l'account.
|
||||
<div className="mb-6 p-6 bg-success-light border-l-4 border-success">
|
||||
<p className="text-success font-medium">
|
||||
Registrazione completata!
|
||||
</p>
|
||||
<p className="mt-2 text-ink-light text-sm">
|
||||
Controlla la tua email per confermare l'account.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/login" className="text-blue-600 hover:underline">
|
||||
<Link href="/login/" className="text-accent hover:underline font-medium">
|
||||
Torna al login
|
||||
</Link>
|
||||
</div>
|
||||
@@ -94,15 +97,15 @@ export function RegisterForm() {
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
{error && (
|
||||
<div className="p-3 bg-red-50 border border-red-200 rounded-md">
|
||||
<p className="text-red-800 text-sm">{error}</p>
|
||||
<div className="p-4 bg-error-light border-l-4 border-error">
|
||||
<p className="text-error text-sm">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-ink mb-2">
|
||||
Email
|
||||
</label>
|
||||
<Input
|
||||
@@ -118,7 +121,7 @@ export function RegisterForm() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-ink mb-2">
|
||||
Password
|
||||
</label>
|
||||
<Input
|
||||
@@ -133,15 +136,15 @@ export function RegisterForm() {
|
||||
error={!!fieldErrors.password}
|
||||
/>
|
||||
{fieldErrors.password && (
|
||||
<p className="mt-1 text-sm text-red-600">{fieldErrors.password}</p>
|
||||
<p className="mt-2 text-sm text-error">{fieldErrors.password}</p>
|
||||
)}
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
<p className="mt-2 text-xs text-muted">
|
||||
Almeno 8 caratteri, 1 numero, 1 maiuscola
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label htmlFor="confirmPassword" className="block text-sm font-medium text-ink mb-2">
|
||||
Conferma Password
|
||||
</label>
|
||||
<Input
|
||||
@@ -156,20 +159,13 @@ export function RegisterForm() {
|
||||
error={!!fieldErrors.confirmPassword}
|
||||
/>
|
||||
{fieldErrors.confirmPassword && (
|
||||
<p className="mt-1 text-sm text-red-600">{fieldErrors.confirmPassword}</p>
|
||||
<p className="mt-2 text-sm text-error">{fieldErrors.confirmPassword}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{loading ? 'Registrazione...' : 'Registrati'}
|
||||
<Button type="submit" className="w-full h-12 text-base" disabled={loading}>
|
||||
{loading ? 'Registrazione...' : 'Crea account'}
|
||||
</Button>
|
||||
|
||||
<p className="text-center text-sm text-gray-600">
|
||||
Hai gia un account?{' '}
|
||||
<Link href="/login" className="text-blue-600 hover:underline">
|
||||
Accedi
|
||||
</Link>
|
||||
</p>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,19 +22,36 @@ export function UserNav({ email, planName }: UserNavProps) {
|
||||
router.refresh()
|
||||
}
|
||||
|
||||
// Get initials from email
|
||||
const initials = email
|
||||
.split('@')[0]
|
||||
.split(/[._-]/)
|
||||
.slice(0, 2)
|
||||
.map(part => part.charAt(0).toUpperCase())
|
||||
.join('')
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-sm text-right">
|
||||
<p className="font-medium">{email}</p>
|
||||
{/* Avatar */}
|
||||
<div className="w-9 h-9 bg-ink flex items-center justify-center">
|
||||
<span className="text-cream text-sm font-medium">{initials}</span>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<div className="hidden sm:block text-sm text-right">
|
||||
<p className="font-medium text-ink">{email}</p>
|
||||
{planName && (
|
||||
<p className="text-gray-500 text-xs capitalize">Piano {planName}</p>
|
||||
<p className="text-ink-muted text-xs">Piano {planName}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Logout button */}
|
||||
<Button
|
||||
variant="outline"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleSignOut}
|
||||
disabled={loading}
|
||||
className="text-ink-light hover:text-ink"
|
||||
>
|
||||
{loading ? 'Uscita...' : 'Esci'}
|
||||
</Button>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { Plan, PlanFeatures } from '@/types/database'
|
||||
import { formatFeatureValue, formatPrice, FEATURE_LABELS, getPlanBadgeColor } from '@/lib/plans'
|
||||
import { formatFeatureValue, formatPrice, FEATURE_LABELS } from '@/lib/plans'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { switchPlan } from '@/app/actions/subscription'
|
||||
import { useTransition } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
@@ -41,51 +40,57 @@ export function PlanCard({ plan, isCurrentPlan, onPlanChange }: PlanCardProps) {
|
||||
'automation',
|
||||
]
|
||||
|
||||
const isPro = plan.name === 'pro'
|
||||
|
||||
return (
|
||||
<Card className={`relative ${isCurrentPlan ? 'ring-2 ring-blue-500' : ''}`}>
|
||||
<div className={`relative bg-white border ${isCurrentPlan ? 'border-accent' : 'border-editorial'} ${isPro ? 'ring-2 ring-ink' : ''}`}>
|
||||
{/* Top accent bar */}
|
||||
<div className={`h-1 ${isPro ? 'bg-ink' : isCurrentPlan ? 'bg-accent' : 'bg-cream-dark'}`}></div>
|
||||
|
||||
{/* Current plan badge */}
|
||||
{isCurrentPlan && (
|
||||
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
|
||||
<span className="bg-blue-500 text-white text-xs font-medium px-3 py-1 rounded-full">
|
||||
<span className="bg-accent text-white text-xs font-medium px-3 py-1">
|
||||
Piano attuale
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CardHeader className="text-center pt-8">
|
||||
<div className="mb-2">
|
||||
<span className={`inline-block px-3 py-1 text-xs font-medium rounded-full border ${getPlanBadgeColor(plan.name)}`}>
|
||||
{plan.display_name_it}
|
||||
</span>
|
||||
</div>
|
||||
<CardTitle className="text-3xl">
|
||||
{/* Header */}
|
||||
<div className="p-6 text-center border-b border-editorial">
|
||||
<span className="editorial-tag">
|
||||
{plan.display_name_it}
|
||||
</span>
|
||||
<div className="mt-4 font-display text-4xl font-semibold text-ink">
|
||||
{formatPrice(plan.price_monthly)}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-ink-light">
|
||||
{plan.name === 'free' && 'Perfetto per iniziare'}
|
||||
{plan.name === 'creator' && 'Per creator seri'}
|
||||
{plan.name === 'pro' && 'Per professionisti'}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<CardContent>
|
||||
<ul className="space-y-3">
|
||||
{/* Features */}
|
||||
<div className="p-6">
|
||||
<ul className="space-y-4">
|
||||
{displayFeatures.map((featureKey) => {
|
||||
const value = features[featureKey]
|
||||
const isIncluded = value !== false
|
||||
|
||||
return (
|
||||
<li key={featureKey} className="flex items-center gap-2">
|
||||
<span className={`w-5 h-5 rounded-full flex items-center justify-center text-xs ${
|
||||
isIncluded ? 'bg-green-100 text-green-600' : 'bg-gray-100 text-gray-400'
|
||||
<li key={featureKey} className="flex items-center gap-3">
|
||||
<span className={`w-5 h-5 flex items-center justify-center text-xs font-medium ${
|
||||
isIncluded ? 'bg-success text-white' : 'bg-cream-dark text-ink-muted'
|
||||
}`}>
|
||||
{isIncluded ? '\u2713' : '\u2014'}
|
||||
{isIncluded ? '✓' : '—'}
|
||||
</span>
|
||||
<span className="text-sm">
|
||||
<span className="font-medium">
|
||||
<span className="font-medium text-ink">
|
||||
{formatFeatureValue(featureKey, value)}
|
||||
</span>
|
||||
{' '}
|
||||
<span className="text-gray-500">
|
||||
<span className="text-ink-light">
|
||||
{FEATURE_LABELS[featureKey].toLowerCase()}
|
||||
</span>
|
||||
</span>
|
||||
@@ -93,9 +98,10 @@ export function PlanCard({ plan, isCurrentPlan, onPlanChange }: PlanCardProps) {
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</div>
|
||||
|
||||
<CardFooter>
|
||||
{/* Footer */}
|
||||
<div className="p-6 pt-0">
|
||||
{isCurrentPlan ? (
|
||||
<Button variant="outline" className="w-full" disabled>
|
||||
Piano attuale
|
||||
@@ -105,14 +111,14 @@ export function PlanCard({ plan, isCurrentPlan, onPlanChange }: PlanCardProps) {
|
||||
className="w-full"
|
||||
onClick={handleSwitchPlan}
|
||||
disabled={isPending}
|
||||
variant={plan.name === 'pro' ? 'default' : 'outline'}
|
||||
variant={isPro ? 'default' : 'outline'}
|
||||
>
|
||||
{isPending ? 'Cambio in corso...' : (
|
||||
plan.price_monthly === 0 ? 'Passa a Gratuito' : `Passa a ${plan.display_name_it}`
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { forwardRef, ButtonHTMLAttributes } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'default' | 'outline' | 'ghost'
|
||||
variant?: 'default' | 'outline' | 'ghost' | 'accent'
|
||||
size?: 'default' | 'sm' | 'lg'
|
||||
}
|
||||
|
||||
@@ -11,18 +11,25 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
|
||||
'inline-flex items-center justify-center font-medium transition-all duration-200',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2',
|
||||
'disabled:pointer-events-none disabled:opacity-50',
|
||||
// Variants
|
||||
{
|
||||
'bg-blue-600 text-white hover:bg-blue-700': variant === 'default',
|
||||
'border border-gray-300 bg-white hover:bg-gray-50': variant === 'outline',
|
||||
'hover:bg-gray-100': variant === 'ghost',
|
||||
// Primary - Black button
|
||||
'bg-ink text-cream hover:bg-accent': variant === 'default',
|
||||
// Outline - Border button
|
||||
'border-2 border-ink bg-transparent text-ink hover:bg-ink hover:text-cream': variant === 'outline',
|
||||
// Ghost - No background
|
||||
'text-ink hover:bg-cream-dark': variant === 'ghost',
|
||||
// Accent - Coral button
|
||||
'bg-accent text-white hover:bg-accent-hover': variant === 'accent',
|
||||
},
|
||||
// Sizes - No border radius for editorial style
|
||||
{
|
||||
'h-10 px-4 py-2': size === 'default',
|
||||
'h-8 px-3 text-sm': size === 'sm',
|
||||
'h-12 px-6 text-lg': size === 'lg',
|
||||
'h-11 px-5 py-2': size === 'default',
|
||||
'h-9 px-4 text-sm': size === 'sm',
|
||||
'h-13 px-8 text-lg': size === 'lg',
|
||||
},
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -10,11 +10,12 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
return (
|
||||
<input
|
||||
className={cn(
|
||||
'flex h-10 w-full rounded-md border bg-white px-3 py-2 text-sm',
|
||||
'placeholder:text-gray-400',
|
||||
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1',
|
||||
'flex h-11 w-full border bg-white px-4 py-2 text-base',
|
||||
'placeholder:text-ink-muted',
|
||||
'focus:outline-none focus:border-ink',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
error ? 'border-red-500' : 'border-gray-300',
|
||||
'transition-colors duration-200',
|
||||
error ? 'border-error' : 'border-editorial',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
Reference in New Issue
Block a user