diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx new file mode 100644 index 0000000..23c815b --- /dev/null +++ b/src/app/(auth)/layout.tsx @@ -0,0 +1,13 @@ +export default function AuthLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( +
+
+ {children} +
+
+ ) +} diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx new file mode 100644 index 0000000..676ba9d --- /dev/null +++ b/src/app/(auth)/login/page.tsx @@ -0,0 +1,30 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { LoginForm } from '@/components/auth/login-form' +import { GoogleSignInButton } from '@/components/auth/google-button' + +export default function LoginPage() { + return ( + + + Accedi a Leopost + + Inserisci le tue credenziali per continuare + + + + + +
+
+ +
+
+ oppure +
+
+ + +
+
+ ) +} diff --git a/src/app/(auth)/register/page.tsx b/src/app/(auth)/register/page.tsx new file mode 100644 index 0000000..2e2bc7c --- /dev/null +++ b/src/app/(auth)/register/page.tsx @@ -0,0 +1,30 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { RegisterForm } from '@/components/auth/register-form' +import { GoogleSignInButton } from '@/components/auth/google-button' + +export default function RegisterPage() { + return ( + + + Crea il tuo account + + Inizia a usare Leopost gratuitamente + + + + + +
+
+ +
+
+ oppure +
+
+ + +
+
+ ) +} diff --git a/src/components/auth/login-form.tsx b/src/components/auth/login-form.tsx new file mode 100644 index 0000000..12bc0ee --- /dev/null +++ b/src/components/auth/login-form.tsx @@ -0,0 +1,103 @@ +'use client' + +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import Link from 'next/link' +import { useState } from 'react' +import { createClient } from '@/lib/supabase/client' +import { useRouter } from 'next/navigation' + +export function LoginForm() { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState(null) + const [loading, setLoading] = useState(false) + const router = useRouter() + const supabase = createClient() + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + setError(null) + setLoading(true) + + const { error: signInError } = await supabase.auth.signInWithPassword({ + email, + password, + }) + + if (signInError) { + // SPECIFIC error messages per user requirement + if (signInError.message.includes('Invalid login credentials')) { + setError('Email o password errata') + } else if (signInError.message.includes('Email not confirmed')) { + setError('Devi confermare la tua email prima di accedere') + } else { + setError(signInError.message) + } + setLoading(false) + return + } + + router.push('/dashboard') + router.refresh() + } + + return ( +
+ {error && ( +
+

{error}

+
+ )} + +
+ + setEmail(e.target.value)} + /> +
+ +
+ + setPassword(e.target.value)} + /> +
+ +
+ + Password dimenticata? + +
+ + + +

+ Non hai un account?{' '} + + Registrati + +

+
+ ) +} diff --git a/src/components/auth/register-form.tsx b/src/components/auth/register-form.tsx new file mode 100644 index 0000000..6ca0018 --- /dev/null +++ b/src/components/auth/register-form.tsx @@ -0,0 +1,172 @@ +'use client' + +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import Link from 'next/link' +import { useState } from 'react' +import { createClient } from '@/lib/supabase/client' + +export function RegisterForm() { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [confirmPassword, setConfirmPassword] = useState('') + const [error, setError] = useState(null) + const [fieldErrors, setFieldErrors] = useState>({}) + const [loading, setLoading] = useState(false) + const [success, setSuccess] = useState(false) + const supabase = createClient() + + function validatePassword(pwd: string): string | null { + if (pwd.length < 8) { + return 'La password deve contenere almeno 8 caratteri' + } + if (!/[0-9]/.test(pwd)) { + return 'La password deve contenere almeno un numero' + } + if (!/[A-Z]/.test(pwd)) { + return 'La password deve contenere almeno una lettera maiuscola' + } + return null + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + setError(null) + setFieldErrors({}) + setLoading(true) + + // Validate password + const passwordError = validatePassword(password) + if (passwordError) { + setFieldErrors({ password: passwordError }) + setLoading(false) + return + } + + // Validate confirm password + if (password !== confirmPassword) { + setFieldErrors({ confirmPassword: 'Le password non coincidono' }) + setLoading(false) + return + } + + const { error: signUpError } = await supabase.auth.signUp({ + email, + password, + options: { + emailRedirectTo: `${window.location.origin}/auth/callback`, + } + }) + + if (signUpError) { + // SPECIFIC error messages per user requirement + if (signUpError.message.includes('already registered')) { + setError('Questa email e gia registrata') + } else if (signUpError.message.includes('invalid')) { + setError('Email non valida') + } else { + setError(signUpError.message) + } + setLoading(false) + return + } + + setSuccess(true) + setLoading(false) + } + + if (success) { + return ( +
+
+

+ Registrazione completata! Controlla la tua email per confermare l'account. +

+
+ + Torna al login + +
+ ) + } + + return ( +
+ {error && ( +
+

{error}

+
+ )} + +
+ + setEmail(e.target.value)} + /> +
+ +
+ + setPassword(e.target.value)} + error={!!fieldErrors.password} + /> + {fieldErrors.password && ( +

{fieldErrors.password}

+ )} +

+ Almeno 8 caratteri, 1 numero, 1 maiuscola +

+
+ +
+ + setConfirmPassword(e.target.value)} + error={!!fieldErrors.confirmPassword} + /> + {fieldErrors.confirmPassword && ( +

{fieldErrors.confirmPassword}

+ )} +
+ + + +

+ Hai gia un account?{' '} + + Accedi + +

+
+ ) +} diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..98d0e5f --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,28 @@ +import { forwardRef, InputHTMLAttributes } from 'react' +import { cn } from '@/lib/utils' + +export interface InputProps extends InputHTMLAttributes { + error?: boolean +} + +const Input = forwardRef( + ({ className, error, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = 'Input' + +export { Input }