feat(01-03): add validation schemas and server actions

- Add Zod validation schemas for auth operations
- Add server actions for register, login, reset, update password
- Add clsx and tailwind-merge for class utilities
- Password validation: 8+ chars, 1 number, 1 uppercase
- Error messages in Italian per user requirement
- Specific error messages (not generic 'invalid credentials')

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michele
2026-01-31 05:10:33 +01:00
parent bd0df408a5
commit d1156c7a03
4 changed files with 226 additions and 0 deletions

164
src/app/actions/auth.ts Normal file
View File

@@ -0,0 +1,164 @@
'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')
}