"use client"; /* eslint-disable @next/next/no-img-element */ import { useEffect, useState, useRef } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { useAuth } from '@/context/AuthContext'; import { validateSwedishMobile, normalizeSwedishMobile, validatePersonnummer, validateAddress, lookupSwedishCity, lookupSwedishAddress } from '@/lib/validation'; import { Settings, X } from 'lucide-react'; import '@/styles/Auth.css'; export default function MePage() { const router = useRouter(); const { user, login, logout, loading: authLoading, isAuthenticated, refreshUser } = useAuth(); const [loading, setLoading] = useState(true); const [isEditing, setIsEditing] = useState(false); const [formData, setFormData] = useState({ name: '', personnummer: '', mobile: '', address: '', zip_code: '', city: '', country: '' }); // Account Management States const [actionLoading, setActionLoading] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showChangePassword, setShowChangePassword] = useState(false); const [passwords, setPasswords] = useState({ current: '', new: '', confirm: '' }); // 2FA Setup States const [show2FASetup, setShow2FASetup] = useState(false); const [qrCode, setQrCode] = useState(''); const [twoFactorCode, setTwoFactorCode] = useState(''); const [twoFactorToken, setTwoFactorToken] = useState(''); const [setupError, setSetupError] = useState(''); const [setupSuccess, setSetupSuccess] = useState(''); const successTimeoutRef = useRef(null); useEffect(() => { if (user) { setFormData({ name: user.name || '', personnummer: user.personnummer || '', mobile: user.mobile || '', address: user.address || '', zip_code: user.zip_code || '', city: user.city || '', country: user.country || '' }); } }, [user]); // Auto-fetch city when zip code is 5 digits useEffect(() => { const fetchCity = async () => { if (formData.zip_code.length === 5) { const city = await lookupSwedishCity(formData.zip_code); if (city) { setFormData(prev => ({ ...prev, city })); } } }; fetchCity(); }, [formData.zip_code]); useEffect(() => { if (!authLoading) { if (!isAuthenticated) { router.push('/login'); } else { setLoading(false); } } }, [authLoading, isAuthenticated, router]); const handleLogout = () => { logout(); }; const initiation2FASetup = async () => { setSetupError(''); setSetupSuccess(''); try { const token = localStorage.getItem('token'); const res = await fetch('/api/auth/2fa/setup', { headers: { 'Authorization': `Bearer ${token}` } }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Kunde inte initiera 2FA'); setQrCode(data.qrCodeDataUrl); setTwoFactorToken(data.secret); setShow2FASetup(true); } catch (err: unknown) { setSetupError(err instanceof Error ? err.message : 'Ett oväntat fel uppstod'); } }; const verifyAndEnable2FA = async (e: React.FormEvent) => { e.preventDefault(); setSetupError(''); setActionLoading(true); try { const token = localStorage.getItem('token'); const res = await fetch('/api/auth/2fa/verify', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ code: twoFactorCode }) }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Felaktig kod'); setSetupSuccess('Tvåfaktorsautentisering har aktiverats!'); setShow2FASetup(false); setTwoFactorCode(''); if (user) { const updatedUser = { ...user, two_factor_enabled: true }; login(token || '', updatedUser); } } catch (err: unknown) { setSetupError(err instanceof Error ? err.message : 'Ett oväntat fel uppstod'); } finally { setActionLoading(false); } }; const handleDisable2FA = async () => { if (!confirm('Är du säker på att du vill inaktivera 2FA? Detta gör ditt konto mindre säkert.')) return; setActionLoading(true); setSetupError(''); setSetupSuccess(''); try { const token = localStorage.getItem('token'); const res = await fetch('/api/auth/2fa/disable', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); if (!res.ok) throw new Error('Kunde inte inaktivera 2FA'); setSetupSuccess('2FA har inaktiverats.'); if (user) { const updatedUser = { ...user, two_factor_enabled: false }; login(token || '', updatedUser); } } catch (err: unknown) { setSetupError(err instanceof Error ? err.message : 'Ett fel uppstod'); } finally { setActionLoading(false); } }; const handleNewsletterToggle = async () => { const newValue = !user?.newsletter_subscribed; setActionLoading(true); setSetupError(''); try { const token = localStorage.getItem('token'); const res = await fetch('/api/auth/newsletter/toggle', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ subscribed: newValue }) }); if (!res.ok) throw new Error('Kunde inte uppdatera nyhetsbrev'); if (user) { const updatedUser = { ...user, newsletter_subscribed: newValue }; login(token || '', updatedUser); } if (newValue) { setSetupSuccess('Välkommen! Du prenumererar nu på vårt nyhetsbrev.'); } else { setSetupSuccess('Du har avprenumererat dig från vårt nyhetsbrev.'); } // Reset Timer: Clear existing and start fresh if (successTimeoutRef.current) clearTimeout(successTimeoutRef.current); successTimeoutRef.current = setTimeout(() => setSetupSuccess(''), 5000); } catch (err: unknown) { setSetupError(err instanceof Error ? err.message : 'Ett fel uppstod'); } finally { setActionLoading(false); } }; const handleUpdateProfile = async (e: React.FormEvent) => { e.preventDefault(); setSetupError(''); setSetupSuccess(''); setActionLoading(true); if (formData.name && formData.name.length < 2) { setSetupError('Namnet är för kort.'); setActionLoading(false); return; } if (formData.personnummer && !validatePersonnummer(formData.personnummer)) { setSetupError('Ogiltigt personnummer. Använd formatet YYYYMMDD-XXXX.'); setActionLoading(false); return; } if (formData.mobile && !validateSwedishMobile(formData.mobile)) { setSetupError('Mobilnummeret är ogiltigt. Det måste vara ett svenskt mobilnummer (t.ex. 070-123 45 67).'); setActionLoading(false); return; } if (formData.address && !validateAddress(formData.address)) { setSetupError('Ogiltig adress. Ange gatuadress och nummer.'); setActionLoading(false); return; } // Verify physical address exists (External API) try { const addressExists = await lookupSwedishAddress(formData.address, formData.zip_code, formData.city); if (!addressExists) { setSetupError('Adressen verkar inte existera. Vänligen kontrollera stavning och adressnummer.'); setActionLoading(false); return; } } catch (err) { console.error('Profile address verification error:', err); // Fail open on network errors } const zipRegex = /^\d{5}$/; if (formData.zip_code && !zipRegex.test(formData.zip_code.replace(/\s/g, ''))) { setSetupError('Postnummer måste bestå av 5 siffror'); setActionLoading(false); return; } try { const token = localStorage.getItem('token') || sessionStorage.getItem('token'); const res = await fetch('/api/auth/me', { method: 'PATCH', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ full_name: formData.name, personnummer: formData.personnummer, mobile: formData.mobile ? normalizeSwedishMobile(formData.mobile) : '', address: formData.address, zip_code: formData.zip_code, city: formData.city // Country is locked to 'Sverige' }) }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Kunde inte uppdatera profilen'); const updatedMobile = formData.mobile ? normalizeSwedishMobile(formData.mobile) : ''; if (user) { const updatedUser = { ...user, name: formData.name, personnummer: formData.personnummer, mobile: updatedMobile, address: formData.address, zip_code: formData.zip_code, city: formData.city, country: formData.country }; login(token || '', updatedUser); } setFormData(prev => ({ ...prev, mobile: updatedMobile })); setSetupSuccess('Profilen har uppdaterats!'); setIsEditing(false); if (successTimeoutRef.current) clearTimeout(successTimeoutRef.current); successTimeoutRef.current = setTimeout(() => setSetupSuccess(''), 5000); } catch (err: unknown) { setSetupError(err instanceof Error ? err.message : 'Ett fel uppstod'); } finally { setActionLoading(false); } }; const handleChangePassword = async (e: React.FormEvent) => { e.preventDefault(); setSetupError(''); setSetupSuccess(''); if (passwords.new !== passwords.confirm) { setSetupError('Lösenorden matchar inte'); return; } setActionLoading(true); try { const token = localStorage.getItem('token') || sessionStorage.getItem('token'); const res = await fetch('/api/auth/change-password', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ currentPassword: passwords.current, newPassword: passwords.new }) }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Gick inte att byta lösenord'); setSetupSuccess('Ditt lösenord har uppdaterats!'); setShowChangePassword(false); setPasswords({ current: '', new: '', confirm: '' }); // Update local user state if (user) { login(token || '', { ...user, has_password: true }); } } catch (err: unknown) { const msg = err instanceof Error ? err.message : "Ett fel uppstod"; setSetupError(msg); if (msg.includes("Nuvarande lösenord krävs") && !user?.has_password) { await refreshUser(); setPasswords({ current: "", new: "", confirm: "" }); } } finally { setActionLoading(false); } }; const handleDeleteAccount = async () => { setActionLoading(true); setSetupError(''); try { const token = localStorage.getItem('token') || sessionStorage.getItem('token'); const res = await fetch('/api/auth/account', { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); if (!res.ok) throw new Error('Kunde inte ta bort kontot'); logout(); router.push('/register?deleted=true'); } catch (err: unknown) { setSetupError(err instanceof Error ? err.message : 'Ett fel uppstod'); setActionLoading(false); } }; if (authLoading || loading) { return (
Laddar...
); } if (!user) return null; return (

Min Profil

{user.email_verified ? ( Verifierad ) : ( Ej Verifierad )}
{user.role === 'admin' && ( ADMIN VIEW )}
{(!user.personnummer || !user.address || !user.mobile) && (

Din profil är inte komplett. Vänligen ange personnummer och adress för att kunna använda alla tjänster.

)}

Personlig Information

{!isEditing ? ( ) : ( )}
{isEditing ? ( setFormData({ ...formData, name: e.target.value })} required /> ) : (
{user.name}
)}
{user.role}
{user.username}
{user.email}
{isEditing ? ( { let val = e.target.value.replace(/\D/g, ''); if (val.length > 8) { val = val.slice(0, 8) + '-' + val.slice(8, 12); } setFormData({ ...formData, personnummer: val }); }} /> ) : (
{user.personnummer || '-'}
)}
{isEditing ? ( setFormData({ ...formData, mobile: e.target.value })} /> ) : (
{user.mobile || '-'}
)}
{isEditing ? ( setFormData({ ...formData, address: e.target.value })} /> ) : (
{user.address || '-'}
)}
{isEditing ? ( setFormData({ ...formData, zip_code: e.target.value.replace(/\D/g, '') })} /> ) : (
{user.zip_code || '-'}
)}
{isEditing ? ( setFormData({ ...formData, city: e.target.value })} required /> ) : (
{user.city || '-'}
)}
Sverige
{new Date(user.created_at).toLocaleDateString('sv-SE')}
{isEditing && (
)}
{/* CARD 2: Settings & Security */}

Inställningar & Säkerhet

{/* Row 1: Password & Newsletter */} {/* Password Change */} {/* Row 1: Password & Newsletter */} {/* Password Change */}

{user.has_password ? 'Ändra Lösenord' : 'Ställ in Lösenord'}

{user.has_password ? 'Det är rekommenderat att byta lösenord med jämna mellanrum.' : 'Ditt konto saknar lösenord då du använder social inloggning. Ställ in ett för att kunna logga in utan Google.' }

{!showChangePassword ? ( ) : (
{user.has_password && (
Nuvarande lösenord
setPasswords({ ...passwords, current: e.target.value })} />
)}
Nytt lösenord
setPasswords({ ...passwords, new: e.target.value })} />
Bekräfta nytt lösenord
setPasswords({ ...passwords, confirm: e.target.value })} />
)}
{/* Newsletter */}

Nyhetsbrev

Få de senaste nordic-storium erbjudandena direkt i din inkorg.

{/* Row 2: 2FA & Remove Account */} {/* 2FA */}

Säker Inloggning

Säkra ditt konto med tvåfaktorsautentisering vid varje inloggning

{!show2FASetup ? ( ) : (
{/* 1. QR Code */}
2FA QR
{/* 2. Manual Code Copy Section */}

Alternativ kod för manuell inmatning

{ const element = e.currentTarget; try { await navigator.clipboard.writeText(twoFactorToken); // Add green flash animation element.classList.add('green-flash'); // Show compact feedback overlay const overlay = document.createElement('div'); overlay.className = 'copy-success-overlay'; overlay.innerHTML = `
Kopierad
`; element.appendChild(overlay); // Remove after 1.5 seconds setTimeout(() => { element.classList.remove('green-flash'); if (overlay.parentNode === element) { element.removeChild(overlay); } }, 1500); } catch (err) { console.error('Kunde inte kopiera:', err); } }} >
Klicka & kopiera
{twoFactorToken.replace(/(.{4})/g, '$1 ').trim()}
{/* 3. Verification Form */}
Autentiseringskod
setTwoFactorCode(e.target.value.replace(/\D/g, ''))} />
)}
{/* Remove Account */}

Radera Konto

Radera ditt konto och all data kopplade till kontot permanent.

{!showDeleteConfirm ? ( ) : (

Är du säker? Denna åtgärd går inte att ångra.

)}
{setupSuccess && (
{setupSuccess}
)} {setupError && (
{setupError}
)}
); }