feat: enhance authentication UX with password visibility toggle and better error messages
Added comprehensive improvements to the login and authentication flow: **UI Improvements:** - Add password visibility toggle (Eye/EyeOff icons) for password fields - Improve error message consistency with Dutch translations - Fix syntax error in login page try-catch block **Error Handling:** - Add user-friendly error messages for invalid credentials - Add specific error codes: invalid_credentials, email_not_confirmed - Improve error message for non-existent accounts during login - Handle duplicate email detection during signup with auto-switch to login mode **Middleware:** - Add /reset-password and /update-password to public routes - Fix redirect issue preventing access to password reset flow **Password Reset Flow:** - Verify password reset works for magic link users (can set initial password) - Ensure existing password users can reset their password - Both flows use the same update mechanism via Supabase **Documentation:** - Add comprehensive release notes in docs/RELEASE_AUTH_IMPROVEMENTS.md All changes maintain backward compatibility with existing magic link users. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { loginWithPassword, signUpWithPassword } from '@/lib/auth/client'
|
||||
import { Brain, Zap, Target, Clock } from 'lucide-react'
|
||||
import { Brain, Zap, Target, Clock, Eye, EyeOff } from 'lucide-react'
|
||||
import { BentoGrid, BentoCard } from '@/components/ui/bento-grid'
|
||||
import Link from 'next/link'
|
||||
|
||||
@@ -62,6 +62,8 @@ export default function LoginPage() {
|
||||
const [password, setPassword] = useState('')
|
||||
const [confirmPassword, setConfirmPassword] = useState('') // Only for signup
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
|
||||
const [message, setMessage] = useState<{
|
||||
type: 'success' | 'error'
|
||||
text: string
|
||||
@@ -104,10 +106,10 @@ export default function LoginPage() {
|
||||
} else {
|
||||
// Login Flow
|
||||
await loginWithPassword(email, password)
|
||||
|
||||
|
||||
setMessage({
|
||||
type: 'success',
|
||||
text: 'Ingelogd! Redirect naar EPD...'
|
||||
text: 'Ingelogd! Je wordt doorgestuurd...'
|
||||
})
|
||||
|
||||
// Redirect to EPD
|
||||
@@ -115,7 +117,7 @@ export default function LoginPage() {
|
||||
router.push('/epd/clients')
|
||||
}, 1000)
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: any) {
|
||||
// Check for duplicate email error with auto-login
|
||||
if (error.code === 'user_already_registered' && error.data?.session) {
|
||||
// User was auto-logged in (client-side detection succeeded)
|
||||
@@ -140,6 +142,20 @@ export default function LoginPage() {
|
||||
setConfirmPassword('')
|
||||
}, 2000)
|
||||
}
|
||||
// Invalid credentials during login - suggest signup
|
||||
else if (error.code === 'invalid_credentials' && mode === 'login') {
|
||||
setMessage({
|
||||
type: 'error',
|
||||
text: error.message || 'Email of wachtwoord is onjuist.'
|
||||
})
|
||||
}
|
||||
// Email not confirmed
|
||||
else if (error.code === 'email_not_confirmed') {
|
||||
setMessage({
|
||||
type: 'error',
|
||||
text: error.message || 'Je email is nog niet geverifieerd. Check je inbox.'
|
||||
})
|
||||
}
|
||||
// Generic error handling
|
||||
else {
|
||||
setMessage({
|
||||
@@ -303,16 +319,30 @@ export default function LoginPage() {
|
||||
>
|
||||
Wachtwoord
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
minLength={8}
|
||||
className="w-full px-4 py-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-slate-400 focus:border-slate-400"
|
||||
/>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
minLength={8}
|
||||
className="w-full px-4 py-2.5 pr-11 border border-slate-300 rounded-lg focus:ring-2 focus:ring-slate-400 focus:border-slate-400"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors"
|
||||
aria-label={showPassword ? 'Verberg wachtwoord' : 'Toon wachtwoord'}
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeOff className="w-5 h-5" />
|
||||
) : (
|
||||
<Eye className="w-5 h-5" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{mode === 'login' && (
|
||||
<div className="mt-1 text-right">
|
||||
<Link
|
||||
@@ -333,16 +363,30 @@ export default function LoginPage() {
|
||||
>
|
||||
Bevestig Wachtwoord
|
||||
</label>
|
||||
<input
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
minLength={8}
|
||||
className="w-full px-4 py-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-slate-400 focus:border-slate-400"
|
||||
/>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="confirmPassword"
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
minLength={8}
|
||||
className="w-full px-4 py-2.5 pr-11 border border-slate-300 rounded-lg focus:ring-2 focus:ring-slate-400 focus:border-slate-400"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors"
|
||||
aria-label={showConfirmPassword ? 'Verberg wachtwoord' : 'Toon wachtwoord'}
|
||||
>
|
||||
{showConfirmPassword ? (
|
||||
<EyeOff className="w-5 h-5" />
|
||||
) : (
|
||||
<Eye className="w-5 h-5" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
67
docs/RELEASE_AUTH_IMPROVEMENTS.md
Normal file
67
docs/RELEASE_AUTH_IMPROVEMENTS.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Release Notes - Verbeterde Authenticatie Flow
|
||||
|
||||
**Datum:** 19 November 2025
|
||||
**Versie:** 1.1.0
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Authenticatie Verbeteringen
|
||||
|
||||
We hebben de login en registratie ervaring flink verbeterd met duidelijkere feedback en een gebruiksvriendelijkere interface.
|
||||
|
||||
### ✨ Nieuwe Features
|
||||
|
||||
#### 1. **Wachtwoord Zichtbaarheid Toggle**
|
||||
- Toon/verberg je wachtwoord met een klik op het oog-icoon
|
||||
- Werkt voor zowel wachtwoord als bevestig wachtwoord velden
|
||||
- Maakt het makkelijker om typefouten te voorkomen
|
||||
|
||||
#### 2. **Verbeterde Foutmeldingen**
|
||||
Alle foutmeldingen zijn nu duidelijker en helpen je beter op weg:
|
||||
|
||||
| Situatie | Nieuwe Melding |
|
||||
|----------|----------------|
|
||||
| Login met onjuist wachtwoord | "Email of wachtwoord is onjuist. Controleer je gegevens en probeer opnieuw." |
|
||||
| Registratie met bestaand email | "Dit emailadres is al geregistreerd. Probeer in te loggen." |
|
||||
| Email niet geverifieerd | "Je email is nog niet geverifieerd. Check je inbox voor de verificatie link." |
|
||||
|
||||
#### 3. **Automatische Account Herkenning**
|
||||
- Probeer je te registreren met een bestaand email? Je wordt automatisch naar de login modus gestuurd
|
||||
- Als het systeem detecteert dat je al bent ingelogd met dezelfde credentials, word je direct doorgestuurd
|
||||
|
||||
#### 4. **Wachtwoord Vergeten Flow**
|
||||
- De "Wachtwoord vergeten?" link werkt nu correct
|
||||
- Vraag eenvoudig een reset link aan via je email
|
||||
- Reset links zijn 1 uur geldig
|
||||
|
||||
### 🛠️ Technische Verbeteringen
|
||||
|
||||
- **Duplicate Email Detectie**: Auth hook met fallback mechanisme voorkomt verwarrende situaties
|
||||
- **Security**: Geen email enumeration - je kunt niet zien welke emails geregistreerd zijn
|
||||
- **Middleware Fix**: Reset password routes zijn nu correct toegankelijk voor niet-ingelogde gebruikers
|
||||
|
||||
### 🎨 UI/UX Verbeteringen
|
||||
|
||||
- Consistente Nederlandse taalgebruik door de hele flow
|
||||
- Visuele feedback bij hover op password toggle
|
||||
- Toegankelijke aria-labels voor screen readers
|
||||
- Smooth transitions en loading states
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Voor Ontwikkelaars
|
||||
|
||||
De volgende functies zijn toegevoegd aan `lib/auth/client.ts`:
|
||||
|
||||
- Verbeterde error handling met custom error codes
|
||||
- `invalid_credentials` error code voor login failures
|
||||
- `user_already_registered` error code met optionele auto-login data
|
||||
- `email_not_confirmed` error code voor verificatie issues
|
||||
|
||||
Middleware is uitgebreid met publieke routes:
|
||||
- `/reset-password` - Wachtwoord reset aanvragen
|
||||
- `/update-password` - Nieuw wachtwoord instellen
|
||||
|
||||
---
|
||||
|
||||
**Veel plezier met de verbeterde authenticatie ervaring!** 🎉
|
||||
@@ -158,7 +158,29 @@ export async function loginWithPassword(email: string, password: string) {
|
||||
password
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
if (error) {
|
||||
// Provide user-friendly error messages
|
||||
const errorMessage = error.message?.toLowerCase() || ''
|
||||
|
||||
// Invalid login credentials (account doesn't exist OR wrong password)
|
||||
if (errorMessage.includes('invalid login credentials') ||
|
||||
errorMessage.includes('invalid credentials') ||
|
||||
error.status === 400) {
|
||||
const friendlyError = new Error('Email of wachtwoord is onjuist. Controleer je gegevens en probeer opnieuw.')
|
||||
;(friendlyError as any).code = 'invalid_credentials'
|
||||
throw friendlyError
|
||||
}
|
||||
|
||||
// Email not confirmed
|
||||
if (errorMessage.includes('email not confirmed')) {
|
||||
const friendlyError = new Error('Je email is nog niet geverifieerd. Check je inbox voor de verificatie link.')
|
||||
;(friendlyError as any).code = 'email_not_confirmed'
|
||||
throw friendlyError
|
||||
}
|
||||
|
||||
// Generic error fallback
|
||||
throw error
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -46,6 +46,8 @@ export async function middleware(request: NextRequest) {
|
||||
const publicRoutes = [
|
||||
'/',
|
||||
'/login',
|
||||
'/reset-password',
|
||||
'/update-password',
|
||||
'/auth/callback',
|
||||
'/contact',
|
||||
'/api/leads',
|
||||
|
||||
Reference in New Issue
Block a user