feat(01-05): add protected dashboard layout and page

- Create UserNav component with logout functionality
- Add dashboard layout with header, navigation, and user info
- Create dashboard page displaying plan info and onboarding steps
- All text in Italian for target audience

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michele
2026-01-31 13:37:29 +01:00
parent 6cfe58e96d
commit af17f90d44
3 changed files with 228 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
export default async function DashboardPage() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
redirect('/login')
}
// Get profile with plan details
const { data: profile } = await supabase
.from('profiles')
.select(`
*,
plans (
name,
display_name_it,
features
)
`)
.eq('id', user.id)
.single()
const features = profile?.plans?.features as {
posts_per_month?: number
ai_models?: string[]
social_accounts?: number
} | null
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold text-gray-900">Dashboard</h1>
<p className="text-gray-500">Benvenuto in Leopost</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card>
<CardHeader>
<CardTitle className="text-lg">Il tuo piano</CardTitle>
<CardDescription>
{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>
<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>
<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!
</div>
</CardContent>
</Card>
</div>
</div>
)
}

View File

@@ -0,0 +1,72 @@
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'
import { UserNav } from '@/components/layout/user-nav'
import Link from 'next/link'
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const supabase = await createClient()
// Get user (should always exist due to middleware)
const { data: { user }, error: authError } = await supabase.auth.getUser()
if (authError || !user) {
redirect('/login')
}
// Get profile with plan info
const { data: profile } = await supabase
.from('profiles')
.select(`
*,
plans (
name,
display_name_it
)
`)
.eq('id', user.id)
.single()
return (
<div className="min-h-screen bg-gray-50">
{/* 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">
<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">
Leopost
</Link>
<nav className="hidden md:flex items-center gap-4">
<Link
href="/dashboard"
className="text-gray-600 hover:text-gray-900 text-sm font-medium"
>
Dashboard
</Link>
<Link
href="/subscription"
className="text-gray-600 hover:text-gray-900 text-sm font-medium"
>
Piano
</Link>
</nav>
</div>
<UserNav
email={user.email || ''}
planName={profile?.plans?.display_name_it}
/>
</div>
</div>
</header>
{/* Main content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{children}
</main>
</div>
)
}

View File

@@ -0,0 +1,43 @@
'use client'
import { createClient } from '@/lib/supabase/client'
import { useRouter } from 'next/navigation'
import { Button } from '@/components/ui/button'
import { useState } from 'react'
interface UserNavProps {
email: string
planName?: string
}
export function UserNav({ email, planName }: UserNavProps) {
const [loading, setLoading] = useState(false)
const router = useRouter()
const supabase = createClient()
async function handleSignOut() {
setLoading(true)
await supabase.auth.signOut()
router.push('/login')
router.refresh()
}
return (
<div className="flex items-center gap-4">
<div className="text-sm text-right">
<p className="font-medium">{email}</p>
{planName && (
<p className="text-gray-500 text-xs capitalize">Piano {planName}</p>
)}
</div>
<Button
variant="outline"
size="sm"
onClick={handleSignOut}
disabled={loading}
>
{loading ? 'Uscita...' : 'Esci'}
</Button>
</div>
)
}