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 { useState } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { loginWithPassword, signUpWithPassword } from '@/lib/auth/client'
|
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 { BentoGrid, BentoCard } from '@/components/ui/bento-grid'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
|
||||||
@@ -62,6 +62,8 @@ export default function LoginPage() {
|
|||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const [confirmPassword, setConfirmPassword] = useState('') // Only for signup
|
const [confirmPassword, setConfirmPassword] = useState('') // Only for signup
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
|
||||||
const [message, setMessage] = useState<{
|
const [message, setMessage] = useState<{
|
||||||
type: 'success' | 'error'
|
type: 'success' | 'error'
|
||||||
text: string
|
text: string
|
||||||
@@ -107,7 +109,7 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
setMessage({
|
setMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
text: 'Ingelogd! Redirect naar EPD...'
|
text: 'Ingelogd! Je wordt doorgestuurd...'
|
||||||
})
|
})
|
||||||
|
|
||||||
// Redirect to EPD
|
// Redirect to EPD
|
||||||
@@ -140,6 +142,20 @@ export default function LoginPage() {
|
|||||||
setConfirmPassword('')
|
setConfirmPassword('')
|
||||||
}, 2000)
|
}, 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
|
// Generic error handling
|
||||||
else {
|
else {
|
||||||
setMessage({
|
setMessage({
|
||||||
@@ -303,16 +319,30 @@ export default function LoginPage() {
|
|||||||
>
|
>
|
||||||
Wachtwoord
|
Wachtwoord
|
||||||
</label>
|
</label>
|
||||||
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
id="password"
|
id="password"
|
||||||
type="password"
|
type={showPassword ? 'text' : 'password'}
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
required
|
required
|
||||||
minLength={8}
|
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"
|
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' && (
|
{mode === 'login' && (
|
||||||
<div className="mt-1 text-right">
|
<div className="mt-1 text-right">
|
||||||
<Link
|
<Link
|
||||||
@@ -333,16 +363,30 @@ export default function LoginPage() {
|
|||||||
>
|
>
|
||||||
Bevestig Wachtwoord
|
Bevestig Wachtwoord
|
||||||
</label>
|
</label>
|
||||||
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
id="confirmPassword"
|
id="confirmPassword"
|
||||||
type="password"
|
type={showConfirmPassword ? 'text' : 'password'}
|
||||||
value={confirmPassword}
|
value={confirmPassword}
|
||||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
required
|
required
|
||||||
minLength={8}
|
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"
|
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>
|
</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
|
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ export async function middleware(request: NextRequest) {
|
|||||||
const publicRoutes = [
|
const publicRoutes = [
|
||||||
'/',
|
'/',
|
||||||
'/login',
|
'/login',
|
||||||
|
'/reset-password',
|
||||||
|
'/update-password',
|
||||||
'/auth/callback',
|
'/auth/callback',
|
||||||
'/contact',
|
'/contact',
|
||||||
'/api/leads',
|
'/api/leads',
|
||||||
|
|||||||
Reference in New Issue
Block a user