Files
Michele bd3e1074a8 docs(01): create phase 1 plans - Foundation & Auth
Phase 01: Foundation & Auth
- 6 plans in 4 execution waves
- Wave 1: Project setup (01) + Database schema (02) [parallel]
- Wave 2: Email/password auth (03) + Google OAuth (04) [parallel]
- Wave 3: Middleware & route protection (05)
- Wave 4: Subscription management UI (06)

Requirements covered: AUTH-01, AUTH-02, AUTH-03
Ready for execution

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 03:12:38 +01:00

32 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
phase plan type wave depends_on files_modified autonomous must_haves
01-foundation-auth 03 execute 2
01-01
01-02
src/lib/schemas/auth.ts
src/app/actions/auth.ts
src/app/(auth)/layout.tsx
src/app/(auth)/register/page.tsx
src/app/(auth)/login/page.tsx
src/app/(auth)/verify-email/page.tsx
src/app/(auth)/reset-password/page.tsx
src/app/(auth)/update-password/page.tsx
src/app/auth/callback/route.ts
src/components/ui/button.tsx
src/components/ui/input.tsx
src/components/ui/card.tsx
src/components/auth/register-form.tsx
src/components/auth/login-form.tsx
true
truths artifacts key_links
User can register with email and password
User receives verification email after registration
User cannot access app until email is verified
User can log in with verified email/password
User sees specific error messages (not generic)
User can reset password via email link
path provides exports
src/app/actions/auth.ts Server actions for auth operations
registerUser
loginUser
resetPassword
updatePassword
path provides min_lines
src/app/(auth)/register/page.tsx Registration page 20
path provides min_lines
src/app/(auth)/login/page.tsx Login page 20
path provides exports
src/lib/schemas/auth.ts Zod validation schemas
registerSchema
loginSchema
from to via pattern
src/components/auth/register-form.tsx src/app/actions/auth.ts Server Action call registerUser
from to via pattern
src/app/auth/callback/route.ts Supabase Auth exchangeCodeForSession exchangeCodeForSession
Implement complete email/password authentication flow with registration, login, email verification, and password reset.

Purpose: Enable users to create accounts and log in with email/password per AUTH-01 requirement. Email verification is mandatory per user decision.

Output: Working auth flow where users can register, verify email, log in, and reset password.

<execution_context> @C:\Users\miche.claude/get-shit-done/workflows/execute-plan.md @C:\Users\miche.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/01-foundation-auth/01-CONTEXT.md @.planning/phases/01-foundation-auth/01-RESEARCH.md @.planning/phases/01-foundation-auth/01-01-SUMMARY.md @.planning/phases/01-foundation-auth/01-02-SUMMARY.md Task 1: Create validation schemas and server actions src/lib/schemas/auth.ts src/app/actions/auth.ts Create validation schemas in src/lib/schemas/auth.ts:
```typescript
import { z } from 'zod'

export const registerSchema = z.object({
  email: z.string()
    .email('Email non valida'),
  password: z.string()
    .min(8, 'La password deve contenere almeno 8 caratteri')
    .regex(/[0-9]/, 'La password deve contenere almeno un numero')
    .regex(/[A-Z]/, 'La password deve contenere almeno una lettera maiuscola'),
  confirmPassword: z.string()
}).refine((data) => data.password === data.confirmPassword, {
  message: 'Le password non coincidono',
  path: ['confirmPassword'],
})

export const loginSchema = z.object({
  email: z.string().email('Email non valida'),
  password: z.string().min(1, 'Password richiesta'),
})

export const resetPasswordSchema = z.object({
  email: z.string().email('Email non valida'),
})

export const updatePasswordSchema = z.object({
  password: z.string()
    .min(8, 'La password deve contenere almeno 8 caratteri')
    .regex(/[0-9]/, 'La password deve contenere almeno un numero')
    .regex(/[A-Z]/, 'La password deve contenere almeno una lettera maiuscola'),
  confirmPassword: z.string()
}).refine((data) => data.password === data.confirmPassword, {
  message: 'Le password non coincidono',
  path: ['confirmPassword'],
})

export type RegisterInput = z.infer<typeof registerSchema>
export type LoginInput = z.infer<typeof loginSchema>
export type ResetPasswordInput = z.infer<typeof resetPasswordSchema>
export type UpdatePasswordInput = z.infer<typeof updatePasswordSchema>
```

Create server actions in src/app/actions/auth.ts:

```typescript
'use server'

import { createClient } from '@/lib/supabase/server'
import { registerSchema, loginSchema, resetPasswordSchema, updatePasswordSchema } from '@/lib/schemas/auth'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'

export type ActionState = {
  error?: string
  fieldErrors?: Record<string, string[]>
  success?: boolean
  message?: string
}

export async function registerUser(
  prevState: ActionState,
  formData: FormData
): Promise<ActionState> {
  const supabase = await createClient()

  const parsed = registerSchema.safeParse({
    email: formData.get('email'),
    password: formData.get('password'),
    confirmPassword: formData.get('confirmPassword'),
  })

  if (!parsed.success) {
    return {
      fieldErrors: parsed.error.flatten().fieldErrors,
    }
  }

  const { data, error } = await supabase.auth.signUp({
    email: parsed.data.email,
    password: parsed.data.password,
    options: {
      emailRedirectTo: `${process.env.NEXT_PUBLIC_APP_URL}/auth/callback`,
    }
  })

  if (error) {
    // SPECIFIC error messages per user requirement
    if (error.message.includes('already registered')) {
      return { error: 'Questa email e gia registrata' }
    }
    if (error.message.includes('invalid')) {
      return { error: 'Email non valida' }
    }
    return { error: error.message }
  }

  return {
    success: true,
    message: 'Registrazione completata! Controlla la tua email per confermare l\'account.'
  }
}

export async function loginUser(
  prevState: ActionState,
  formData: FormData
): Promise<ActionState> {
  const supabase = await createClient()

  const parsed = loginSchema.safeParse({
    email: formData.get('email'),
    password: formData.get('password'),
  })

  if (!parsed.success) {
    return {
      fieldErrors: parsed.error.flatten().fieldErrors,
    }
  }

  const { data, error } = await supabase.auth.signInWithPassword({
    email: parsed.data.email,
    password: parsed.data.password,
  })

  if (error) {
    // SPECIFIC error messages per user requirement
    if (error.message.includes('Invalid login credentials')) {
      return { error: 'Email o password errata' }
    }
    if (error.message.includes('Email not confirmed')) {
      return { error: 'Devi confermare la tua email prima di accedere' }
    }
    return { error: error.message }
  }

  revalidatePath('/', 'layout')
  redirect('/dashboard')
}

export async function resetPassword(
  prevState: ActionState,
  formData: FormData
): Promise<ActionState> {
  const supabase = await createClient()

  const parsed = resetPasswordSchema.safeParse({
    email: formData.get('email'),
  })

  if (!parsed.success) {
    return {
      fieldErrors: parsed.error.flatten().fieldErrors,
    }
  }

  const { error } = await supabase.auth.resetPasswordForEmail(
    parsed.data.email,
    {
      redirectTo: `${process.env.NEXT_PUBLIC_APP_URL}/auth/callback?next=/update-password`,
    }
  )

  if (error) {
    return { error: error.message }
  }

  return {
    success: true,
    message: 'Se l\'email esiste, riceverai un link per reimpostare la password.'
  }
}

export async function updatePassword(
  prevState: ActionState,
  formData: FormData
): Promise<ActionState> {
  const supabase = await createClient()

  const parsed = updatePasswordSchema.safeParse({
    password: formData.get('password'),
    confirmPassword: formData.get('confirmPassword'),
  })

  if (!parsed.success) {
    return {
      fieldErrors: parsed.error.flatten().fieldErrors,
    }
  }

  const { error } = await supabase.auth.updateUser({
    password: parsed.data.password,
  })

  if (error) {
    return { error: error.message }
  }

  return {
    success: true,
    message: 'Password aggiornata con successo!'
  }
}

export async function signOut() {
  const supabase = await createClient()
  await supabase.auth.signOut()
  revalidatePath('/', 'layout')
  redirect('/login')
}
```

Password requirements per CONTEXT.md decision:
- Minimum 8 characters
- At least 1 number
- At least 1 uppercase letter

Error messages are SPECIFIC per user requirement (not generic "invalid credentials").
- Both files exist - Zod schemas validate password requirements correctly - Server actions export proper types - Error messages are in Italian and specific Validation schemas and server actions for all auth operations created. Task 2: Create auth callback route and UI components src/app/auth/callback/route.ts src/components/ui/button.tsx src/components/ui/input.tsx src/components/ui/card.tsx Create OAuth/email callback handler at src/app/auth/callback/route.ts:
```typescript
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'

export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url)
  const code = searchParams.get('code')
  const next = searchParams.get('next') ?? '/dashboard'

  if (code) {
    const supabase = await createClient()
    const { error } = await supabase.auth.exchangeCodeForSession(code)

    if (!error) {
      return NextResponse.redirect(`${origin}${next}`)
    }
  }

  // Return the user to an error page with instructions
  return NextResponse.redirect(`${origin}/login?error=auth_callback_error`)
}
```

Create basic UI components. Keep them minimal but functional:

src/components/ui/button.tsx:
```typescript
import { forwardRef, ButtonHTMLAttributes } from 'react'
import { cn } from '@/lib/utils'

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'default' | 'outline' | 'ghost'
  size?: 'default' | 'sm' | 'lg'
}

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant = 'default', size = 'default', ...props }, ref) => {
    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',
          'disabled:pointer-events-none disabled:opacity-50',
          {
            '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',
          },
          {
            'h-10 px-4 py-2': size === 'default',
            'h-8 px-3 text-sm': size === 'sm',
            'h-12 px-6 text-lg': size === 'lg',
          },
          className
        )}
        ref={ref}
        {...props}
      />
    )
  }
)
Button.displayName = 'Button'

export { Button }
```

src/components/ui/input.tsx:
```typescript
import { forwardRef, InputHTMLAttributes } from 'react'
import { cn } from '@/lib/utils'

export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
  error?: boolean
}

const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ className, error, ...props }, ref) => {
    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',
          'disabled:cursor-not-allowed disabled:opacity-50',
          error ? 'border-red-500' : 'border-gray-300',
          className
        )}
        ref={ref}
        {...props}
      />
    )
  }
)
Input.displayName = 'Input'

export { Input }
```

src/components/ui/card.tsx:
```typescript
import { HTMLAttributes, forwardRef } from 'react'
import { cn } from '@/lib/utils'

const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn(
        'rounded-lg border border-gray-200 bg-white shadow-sm',
        className
      )}
      {...props}
    />
  )
)
Card.displayName = 'Card'

const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn('flex flex-col space-y-1.5 p-6', className)}
      {...props}
    />
  )
)
CardHeader.displayName = 'CardHeader'

const CardTitle = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLHeadingElement>>(
  ({ className, ...props }, ref) => (
    <h3
      ref={ref}
      className={cn('text-2xl font-semibold leading-none tracking-tight', className)}
      {...props}
    />
  )
)
CardTitle.displayName = 'CardTitle'

const CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
  ({ className, ...props }, ref) => (
    <p
      ref={ref}
      className={cn('text-sm text-gray-500', className)}
      {...props}
    />
  )
)
CardDescription.displayName = 'CardDescription'

const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
  )
)
CardContent.displayName = 'CardContent'

const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn('flex items-center p-6 pt-0', className)}
      {...props}
    />
  )
)
CardFooter.displayName = 'CardFooter'

export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
```

Create utility function src/lib/utils.ts:
```typescript
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
```

Install clsx and tailwind-merge:
```bash
npm install clsx tailwind-merge
```
- Callback route handles code exchange - UI components render without errors - cn utility works for class merging Auth callback and reusable UI components created. Task 3: Create auth pages (register, login, verify, reset) src/app/(auth)/layout.tsx src/app/(auth)/register/page.tsx src/app/(auth)/login/page.tsx src/app/(auth)/verify-email/page.tsx src/app/(auth)/reset-password/page.tsx src/app/(auth)/update-password/page.tsx src/components/auth/register-form.tsx src/components/auth/login-form.tsx Create auth layout at src/app/(auth)/layout.tsx: ```typescript export default function AuthLayout({ children, }: { children: React.ReactNode }) { return (
{children}
) } ```
Create register form component at src/components/auth/register-form.tsx:
```typescript
'use client'

import { useActionState } from 'react'
import { registerUser, ActionState } from '@/app/actions/auth'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import Link from 'next/link'

const initialState: ActionState = {}

export function RegisterForm() {
  const [state, formAction, pending] = useActionState(registerUser, initialState)

  if (state.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">{state.message}</p>
        </div>
        <Link href="/login" className="text-blue-600 hover:underline">
          Torna al login
        </Link>
      </div>
    )
  }

  return (
    <form action={formAction} className="space-y-4">
      {state.error && (
        <div className="p-3 bg-red-50 border border-red-200 rounded-md">
          <p className="text-red-800 text-sm">{state.error}</p>
        </div>
      )}

      <div>
        <label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
          Email
        </label>
        <Input
          id="email"
          name="email"
          type="email"
          autoComplete="email"
          required
          placeholder="tu@esempio.com"
          error={!!state.fieldErrors?.email}
        />
        {state.fieldErrors?.email && (
          <p className="mt-1 text-sm text-red-600">{state.fieldErrors.email[0]}</p>
        )}
      </div>

      <div>
        <label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
          Password
        </label>
        <Input
          id="password"
          name="password"
          type="password"
          autoComplete="new-password"
          required
          placeholder="Minimo 8 caratteri"
          error={!!state.fieldErrors?.password}
        />
        {state.fieldErrors?.password && (
          <p className="mt-1 text-sm text-red-600">{state.fieldErrors.password[0]}</p>
        )}
        <p className="mt-1 text-xs text-gray-500">
          Almeno 8 caratteri, 1 numero, 1 maiuscola
        </p>
      </div>

      <div>
        <label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
          Conferma Password
        </label>
        <Input
          id="confirmPassword"
          name="confirmPassword"
          type="password"
          autoComplete="new-password"
          required
          placeholder="Ripeti la password"
          error={!!state.fieldErrors?.confirmPassword}
        />
        {state.fieldErrors?.confirmPassword && (
          <p className="mt-1 text-sm text-red-600">{state.fieldErrors.confirmPassword[0]}</p>
        )}
      </div>

      <Button type="submit" className="w-full" disabled={pending}>
        {pending ? 'Registrazione...' : 'Registrati'}
      </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>
  )
}
```

Create register page at src/app/(auth)/register/page.tsx:
```typescript
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { RegisterForm } from '@/components/auth/register-form'

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>
        <RegisterForm />
      </CardContent>
    </Card>
  )
}
```

Create login form at src/components/auth/login-form.tsx:
```typescript
'use client'

import { useActionState } from 'react'
import { loginUser, ActionState } from '@/app/actions/auth'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import Link from 'next/link'

const initialState: ActionState = {}

export function LoginForm() {
  const [state, formAction, pending] = useActionState(loginUser, initialState)

  return (
    <form action={formAction} className="space-y-4">
      {state.error && (
        <div className="p-3 bg-red-50 border border-red-200 rounded-md">
          <p className="text-red-800 text-sm">{state.error}</p>
        </div>
      )}

      <div>
        <label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
          Email
        </label>
        <Input
          id="email"
          name="email"
          type="email"
          autoComplete="email"
          required
          placeholder="tu@esempio.com"
          error={!!state.fieldErrors?.email}
        />
        {state.fieldErrors?.email && (
          <p className="mt-1 text-sm text-red-600">{state.fieldErrors.email[0]}</p>
        )}
      </div>

      <div>
        <label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
          Password
        </label>
        <Input
          id="password"
          name="password"
          type="password"
          autoComplete="current-password"
          required
          placeholder="La tua password"
          error={!!state.fieldErrors?.password}
        />
        {state.fieldErrors?.password && (
          <p className="mt-1 text-sm text-red-600">{state.fieldErrors.password[0]}</p>
        )}
      </div>

      <div className="flex items-center justify-end">
        <Link href="/reset-password" className="text-sm text-blue-600 hover:underline">
          Password dimenticata?
        </Link>
      </div>

      <Button type="submit" className="w-full" disabled={pending}>
        {pending ? 'Accesso...' : '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>
  )
}
```

Create login page at src/app/(auth)/login/page.tsx:
```typescript
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { LoginForm } from '@/components/auth/login-form'

export default function LoginPage({
  searchParams,
}: {
  searchParams: Promise<{ error?: string }>
}) {
  return (
    <Card>
      <CardHeader className="text-center">
        <CardTitle>Accedi a Leopost</CardTitle>
        <CardDescription>
          Inserisci le tue credenziali per continuare
        </CardDescription>
      </CardHeader>
      <CardContent>
        <LoginForm />
      </CardContent>
    </Card>
  )
}
```

Create verify-email page at src/app/(auth)/verify-email/page.tsx:
```typescript
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import Link from 'next/link'

export default function VerifyEmailPage() {
  return (
    <Card>
      <CardHeader className="text-center">
        <CardTitle>Verifica la tua email</CardTitle>
        <CardDescription>
          Ti abbiamo inviato un link di conferma
        </CardDescription>
      </CardHeader>
      <CardContent className="text-center space-y-4">
        <p className="text-gray-600">
          Controlla la tua casella email e clicca sul link per attivare il tuo account.
        </p>
        <p className="text-sm text-gray-500">
          Non hai ricevuto l'email? Controlla lo spam.
        </p>
        <Link href="/login" className="text-blue-600 hover:underline">
          Torna al login
        </Link>
      </CardContent>
    </Card>
  )
}
```

Create reset-password page at src/app/(auth)/reset-password/page.tsx:
```typescript
'use client'

import { useActionState } from 'react'
import { resetPassword, ActionState } from '@/app/actions/auth'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import Link from 'next/link'

const initialState: ActionState = {}

export default function ResetPasswordPage() {
  const [state, formAction, pending] = useActionState(resetPassword, initialState)

  return (
    <Card>
      <CardHeader className="text-center">
        <CardTitle>Recupera password</CardTitle>
        <CardDescription>
          Inserisci la tua email per ricevere il link di reset
        </CardDescription>
      </CardHeader>
      <CardContent>
        {state.success ? (
          <div className="text-center space-y-4">
            <div className="p-4 bg-green-50 border border-green-200 rounded-md">
              <p className="text-green-800">{state.message}</p>
            </div>
            <Link href="/login" className="text-blue-600 hover:underline">
              Torna al login
            </Link>
          </div>
        ) : (
          <form action={formAction} className="space-y-4">
            {state.error && (
              <div className="p-3 bg-red-50 border border-red-200 rounded-md">
                <p className="text-red-800 text-sm">{state.error}</p>
              </div>
            )}

            <div>
              <label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
                Email
              </label>
              <Input
                id="email"
                name="email"
                type="email"
                autoComplete="email"
                required
                placeholder="tu@esempio.com"
                error={!!state.fieldErrors?.email}
              />
              {state.fieldErrors?.email && (
                <p className="mt-1 text-sm text-red-600">{state.fieldErrors.email[0]}</p>
              )}
            </div>

            <Button type="submit" className="w-full" disabled={pending}>
              {pending ? 'Invio...' : 'Invia link di reset'}
            </Button>

            <p className="text-center text-sm text-gray-600">
              <Link href="/login" className="text-blue-600 hover:underline">
                Torna al login
              </Link>
            </p>
          </form>
        )}
      </CardContent>
    </Card>
  )
}
```

Create update-password page at src/app/(auth)/update-password/page.tsx:
```typescript
'use client'

import { useActionState } from 'react'
import { updatePassword, ActionState } from '@/app/actions/auth'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import Link from 'next/link'

const initialState: ActionState = {}

export default function UpdatePasswordPage() {
  const [state, formAction, pending] = useActionState(updatePassword, initialState)

  return (
    <Card>
      <CardHeader className="text-center">
        <CardTitle>Nuova password</CardTitle>
        <CardDescription>
          Inserisci la tua nuova password
        </CardDescription>
      </CardHeader>
      <CardContent>
        {state.success ? (
          <div className="text-center space-y-4">
            <div className="p-4 bg-green-50 border border-green-200 rounded-md">
              <p className="text-green-800">{state.message}</p>
            </div>
            <Link href="/login" className="text-blue-600 hover:underline">
              Vai al login
            </Link>
          </div>
        ) : (
          <form action={formAction} className="space-y-4">
            {state.error && (
              <div className="p-3 bg-red-50 border border-red-200 rounded-md">
                <p className="text-red-800 text-sm">{state.error}</p>
              </div>
            )}

            <div>
              <label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
                Nuova Password
              </label>
              <Input
                id="password"
                name="password"
                type="password"
                autoComplete="new-password"
                required
                placeholder="Minimo 8 caratteri"
                error={!!state.fieldErrors?.password}
              />
              {state.fieldErrors?.password && (
                <p className="mt-1 text-sm text-red-600">{state.fieldErrors.password[0]}</p>
              )}
              <p className="mt-1 text-xs text-gray-500">
                Almeno 8 caratteri, 1 numero, 1 maiuscola
              </p>
            </div>

            <div>
              <label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
                Conferma Password
              </label>
              <Input
                id="confirmPassword"
                name="confirmPassword"
                type="password"
                autoComplete="new-password"
                required
                placeholder="Ripeti la password"
                error={!!state.fieldErrors?.confirmPassword}
              />
              {state.fieldErrors?.confirmPassword && (
                <p className="mt-1 text-sm text-red-600">{state.fieldErrors.confirmPassword[0]}</p>
              )}
            </div>

            <Button type="submit" className="w-full" disabled={pending}>
              {pending ? 'Aggiornamento...' : 'Aggiorna password'}
            </Button>
          </form>
        )}
      </CardContent>
    </Card>
  )
}
```

All text is in Italian as per project requirement.
Uses React 19 useActionState (not deprecated useFormState).
- All auth pages render without errors - Forms submit using server actions - Error messages display correctly - Success states show appropriate messages - Navigation links work between pages Complete email/password auth flow with register, login, verify, and reset pages. After all tasks complete: 1. `npm run dev` starts without errors 2. /register page allows form submission 3. /login page allows form submission 4. /reset-password page sends reset email 5. /update-password page updates password 6. Validation messages appear in Italian 7. Specific error messages show (not generic)

<success_criteria>

  • User can complete registration flow (form -> email)
  • User can log in with email/password
  • User sees Italian error messages
  • Password validation enforces medium requirements
  • Password reset sends email link (not code)
  • All forms use Server Actions (not client API calls) </success_criteria>
After completion, create `.planning/phases/01-foundation-auth/01-03-SUMMARY.md`