This commit is contained in:
Timo Knuth
2025-10-18 17:55:32 +02:00
parent 254e6490b8
commit 91b78cb284
65 changed files with 4481 additions and 1078 deletions

View File

@@ -0,0 +1,164 @@
'use client';
import React, { useState } from 'react';
import { Button } from '@/components/ui/Button';
import { showToast } from '@/components/ui/Toast';
interface ChangePasswordModalProps {
isOpen: boolean;
onClose: () => void;
onSuccess: () => void;
}
export default function ChangePasswordModal({
isOpen,
onClose,
onSuccess,
}: ChangePasswordModalProps) {
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Validation
if (!currentPassword || !newPassword || !confirmPassword) {
showToast('Please fill in all fields', 'error');
return;
}
if (newPassword.length < 8) {
showToast('New password must be at least 8 characters', 'error');
return;
}
if (newPassword !== confirmPassword) {
showToast('New passwords do not match', 'error');
return;
}
if (currentPassword === newPassword) {
showToast('New password must be different from current password', 'error');
return;
}
setLoading(true);
try {
const response = await fetch('/api/user/password', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
currentPassword,
newPassword,
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to change password');
}
showToast('Password changed successfully!', 'success');
setCurrentPassword('');
setNewPassword('');
setConfirmPassword('');
onSuccess();
} catch (error: any) {
showToast(error.message || 'Failed to change password', 'error');
} finally {
setLoading(false);
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
{/* Backdrop */}
<div
className="fixed inset-0 bg-black bg-opacity-50 transition-opacity"
onClick={onClose}
/>
{/* Modal */}
<div className="flex min-h-screen items-center justify-center p-4">
<div className="relative bg-white rounded-lg shadow-xl max-w-md w-full p-6">
<div className="mb-6">
<h2 className="text-2xl font-bold text-gray-900">Change Password</h2>
<p className="text-gray-600 mt-2">
Enter your current password and choose a new one
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Current Password
</label>
<input
type="password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="Enter current password"
autoComplete="current-password"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
New Password
</label>
<input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="Enter new password (min. 8 characters)"
autoComplete="new-password"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Confirm New Password
</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
placeholder="Confirm new password"
autoComplete="new-password"
/>
</div>
<div className="flex space-x-3 pt-4">
<Button
type="button"
variant="outline"
onClick={onClose}
className="flex-1"
disabled={loading}
>
Cancel
</Button>
<Button
type="submit"
variant="primary"
className="flex-1"
disabled={loading}
>
{loading ? 'Updating...' : 'Update Password'}
</Button>
</div>
</form>
</div>
</div>
</div>
);
}