--- phase: 01-foundation-auth plan: 05 type: execute wave: 3 depends_on: ["01-03", "01-04"] files_modified: - middleware.ts - src/lib/supabase/middleware.ts - src/app/(dashboard)/layout.tsx - src/app/(dashboard)/dashboard/page.tsx - src/components/layout/user-nav.tsx autonomous: true must_haves: truths: - "Unauthenticated users are redirected to /login when accessing /dashboard" - "Authenticated users stay logged in across page refreshes" - "User can log out and is redirected to login" - "Session refreshes automatically (no random logouts)" artifacts: - path: "middleware.ts" provides: "Route protection and session refresh" min_lines: 15 - path: "src/lib/supabase/middleware.ts" provides: "Supabase session update helper" exports: ["updateSession"] - path: "src/app/(dashboard)/dashboard/page.tsx" provides: "Protected dashboard page" min_lines: 10 key_links: - from: "middleware.ts" to: "src/lib/supabase/middleware.ts" via: "updateSession import" pattern: "updateSession" - from: "middleware.ts" to: "Next.js request handling" via: "matcher config" pattern: "matcher.*dashboard" --- Implement middleware for session management and route protection, plus a basic protected dashboard. Purpose: Ensure authenticated state persists across requests and protect private routes. This is MANDATORY per research - without middleware, sessions expire randomly. Output: Working route protection where /dashboard requires authentication and sessions auto-refresh. @C:\Users\miche\.claude/get-shit-done/workflows/execute-plan.md @C:\Users\miche\.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/01-foundation-auth/01-RESEARCH.md @.planning/phases/01-foundation-auth/01-03-SUMMARY.md @.planning/phases/01-foundation-auth/01-04-SUMMARY.md Task 1: Create middleware helper and main middleware src/lib/supabase/middleware.ts middleware.ts Create the middleware helper at src/lib/supabase/middleware.ts: ```typescript import { createServerClient } from '@supabase/ssr' import { NextResponse, type NextRequest } from 'next/server' export async function updateSession(request: NextRequest) { let supabaseResponse = NextResponse.next({ request, }) const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { getAll() { return request.cookies.getAll() }, setAll(cookiesToSet) { cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value) ) supabaseResponse = NextResponse.next({ request, }) cookiesToSet.forEach(({ name, value, options }) => supabaseResponse.cookies.set(name, value, options) ) }, }, } ) // IMPORTANT: Do not remove this line // Refreshing the auth token is crucial for keeping the session alive const { data: { user } } = await supabase.auth.getUser() return { supabaseResponse, user } } ``` Create the main middleware at middleware.ts (project root, NOT in src): ```typescript import { type NextRequest, NextResponse } from 'next/server' import { updateSession } from '@/lib/supabase/middleware' // Routes that require authentication const protectedRoutes = ['/dashboard', '/settings', '/subscription'] // Routes that should redirect to dashboard if already authenticated const authRoutes = ['/login', '/register'] export async function middleware(request: NextRequest) { const { supabaseResponse, user } = await updateSession(request) const { pathname } = request.nextUrl // Check if trying to access protected route without auth const isProtectedRoute = protectedRoutes.some(route => pathname.startsWith(route) ) if (isProtectedRoute && !user) { const redirectUrl = new URL('/login', request.url) // Save the original URL to redirect back after login redirectUrl.searchParams.set('redirectTo', pathname) return NextResponse.redirect(redirectUrl) } // Check if trying to access auth routes while already authenticated const isAuthRoute = authRoutes.some(route => pathname.startsWith(route) ) if (isAuthRoute && user) { return NextResponse.redirect(new URL('/dashboard', request.url)) } return supabaseResponse } export const config = { matcher: [ /* * Match all request paths except for the ones starting with: * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) * - public folder files */ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', ], } ``` CRITICAL from RESEARCH.md: - Middleware MUST call supabase.auth.getUser() to refresh session - Without this, sessions expire and users get randomly logged out - The matcher excludes static files for performance - Check path BEFORE redirecting to avoid infinite loops - middleware.ts exists in project root (not src/) - src/lib/supabase/middleware.ts exists - No TypeScript errors - Matcher pattern is correct Middleware configured for session refresh and route protection. Task 2: Create protected dashboard layout and page src/app/(dashboard)/layout.tsx src/app/(dashboard)/dashboard/page.tsx src/components/layout/user-nav.tsx Create user navigation component at src/components/layout/user-nav.tsx: ```typescript '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 (

{email}

{planName && (

Piano {planName}

)}
) } ``` Create dashboard layout at src/app/(dashboard)/layout.tsx: ```typescript 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 (
{/* Header */}
Leopost
{/* Main content */}
{children}
) } ``` Create dashboard page at src/app/(dashboard)/dashboard/page.tsx: ```typescript 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 (

Dashboard

Benvenuto in Leopost

Il tuo piano {profile?.plans?.display_name_it || 'Gratuito'}
  • {features?.posts_per_month || 10} post/mese
  • {features?.social_accounts || 1} account social
  • {features?.ai_models?.length || 1} modelli AI
Prossimi passi Completa la configurazione
  • Account creato
  • 2 Collega social (Phase 2)
  • 3 Configura brand (Phase 3)
Attivita Le tue statistiche
Nessuna attivita ancora.
Inizia collegando un account social!
) } ``` Key points: - Layout fetches user and profile data server-side - Dashboard shows plan info from database - "Prossimi passi" teases future phases - All text in Italian - Uses RLS-protected queries (profile data auto-filtered)
- Dashboard layout shows header with navigation - UserNav shows email and plan name - Logout button works - Dashboard page shows plan info - Page redirects to login if not authenticated Protected dashboard with user navigation and plan info display.
Task 3: Update home page to redirect appropriately src/app/page.tsx Update the home page to redirect based on auth state. Modify src/app/page.tsx: ```typescript import { createClient } from '@/lib/supabase/server' import { redirect } from 'next/navigation' import Link from 'next/link' import { Button } from '@/components/ui/button' export default async function Home() { const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser() // If logged in, redirect to dashboard if (user) { redirect('/dashboard') } // Landing page for non-authenticated users return (

Leopost

Il tuo social media manager potenziato dall'AI.
Minimo sforzo, massima resa.

Nessuna carta richiesta. Piano gratuito disponibile.

) } ``` This creates a simple landing page that: - Redirects authenticated users to dashboard - Shows value proposition to visitors - Provides clear CTAs (register/login) - Italian copy reflecting the core value
- Visiting / when logged in redirects to /dashboard - Visiting / when logged out shows landing page - Register and Login buttons work Home page with auth-aware redirect and landing content.
After all tasks complete: 1. Visit /dashboard when logged out -> redirects to /login 2. Login successfully -> redirects to /dashboard 3. Refresh /dashboard -> stays authenticated (session persists) 4. Click "Esci" -> logs out and redirects to /login 5. Visit / when logged in -> redirects to /dashboard 6. Visit /login when logged in -> redirects to /dashboard - Middleware refreshes session on every request - Protected routes redirect unauthenticated users - Auth routes redirect authenticated users - Logout works and clears session - No infinite redirect loops - Dashboard displays user's plan info After completion, create `.planning/phases/01-foundation-auth/01-05-SUMMARY.md`