Compare commits

...

3 Commits

Author SHA1 Message Date
David
d9868dd49f fix: prevent password fields from being pre-filled in profile page
All checks were successful
CI/CD Pipeline / Backend - Build, Test & Push (push) Successful in 2m42s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Successful in 27m20s
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Deployment Summary (push) Successful in 1s
CI/CD Pipeline / Deploy to Portainer (push) Successful in 12s
CI/CD Pipeline / Discord Notification (Failure) (push) Has been skipped
CI/CD Pipeline / Discord Notification (Success) (push) Successful in 2s
Fixed issue where password form fields (especially "New Password")
were being pre-filled with values, either from browser autocomplete
or residual form state.

Changes:
1. Added explicit empty defaultValues to password form
   - currentPassword: ''
   - newPassword: ''
   - confirmPassword: ''

2. Added autoComplete attributes to prevent browser pre-fill:
   - currentPassword: autoComplete="current-password"
   - newPassword: autoComplete="new-password"
   - confirmPassword: autoComplete="new-password"

3. Added useEffect to reset password form when switching tabs:
   - Ensures clean state when navigating to "Change Password" tab
   - Prevents stale values from persisting

4. Explicit reset values on successful password change:
   - Previously used passwordForm.reset() without values
   - Now explicitly sets all fields to empty strings

This ensures password fields are always empty and never pre-filled
by the browser or by residual form state.

Refs: apps/frontend/app/dashboard/profile/page.tsx:64-70,85-95

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 18:24:13 +01:00
David
2054e73e78 fix: resolve profile page data persistence and password change issues
Fixed critical issues with the profile page (/dashboard/profile):

1. **Form data not persisting on page refresh**:
   - Added useEffect to update form values when user data loads
   - Forms now properly populate after auth context loads user data

2. **Blank page on refresh**:
   - Added loading and error states for better UX
   - Handle case where user is not loaded yet (loading spinner)
   - Handle case where user fails to load (retry button)

3. **Password change API endpoint correction**:
   - Fixed: POST /api/v1/users/change-password (incorrect)
   - Corrected to: PATCH /api/v1/users/me/password (matches backend)
   - Updated return type to include { message: string }

The root cause was that useForm defaultValues were set once at
component mount when user was still null. The form never updated
when user data was subsequently loaded by the auth context.

Now the form properly resets with user data via useEffect, and
proper loading/error states prevent showing a blank page.

Refs: apps/frontend/app/dashboard/profile/page.tsx:68-78

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 18:18:04 +01:00
David
905a56888a fix: implement password change functionality in profile page
Fix password change feature that was previously non-functional:
- Add changePassword function in frontend API (src/lib/api/users.ts)
- Update API endpoint to match backend: PATCH /api/v1/users/me/password
- Connect profile page to real API instead of mock implementation
- Export changePassword function from API index

The backend endpoint was already implemented but frontend was using
a placeholder Promise.resolve(). Now properly calls the backend API.

Refs: apps/frontend/app/dashboard/profile/page.tsx:87-105

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 17:56:10 +01:00
4 changed files with 98 additions and 12 deletions

View File

@ -6,13 +6,13 @@
'use client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useAuth } from '@/lib/context/auth-context';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { updateUser } from '@/lib/api';
import { updateUser, changePassword } from '@/lib/api';
// Password update schema
const passwordSchema = z
@ -44,7 +44,7 @@ const profileSchema = z.object({
type ProfileFormData = z.infer<typeof profileSchema>;
export default function ProfilePage() {
const { user, refreshUser } = useAuth();
const { user, refreshUser, loading } = useAuth();
const queryClient = useQueryClient();
const [activeTab, setActiveTab] = useState<'profile' | 'password'>('profile');
const [successMessage, setSuccessMessage] = useState('');
@ -63,8 +63,37 @@ export default function ProfilePage() {
// Password form
const passwordForm = useForm<PasswordFormData>({
resolver: zodResolver(passwordSchema),
defaultValues: {
currentPassword: '',
newPassword: '',
confirmPassword: '',
},
});
// Update form values when user data loads
useEffect(() => {
if (user) {
profileForm.reset({
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user]);
// Reset password form when switching to password tab
useEffect(() => {
if (activeTab === 'password') {
passwordForm.reset({
currentPassword: '',
newPassword: '',
confirmPassword: '',
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeTab]);
// Update profile mutation
const updateProfileMutation = useMutation({
mutationFn: (data: ProfileFormData) => {
@ -84,17 +113,22 @@ export default function ProfilePage() {
},
});
// Update password mutation (you'll need to add this endpoint)
// Update password mutation
const updatePasswordMutation = useMutation({
mutationFn: async (data: PasswordFormData) => {
// TODO: Add password update endpoint
// return updatePassword(data);
return Promise.resolve({ success: true });
return changePassword({
currentPassword: data.currentPassword,
newPassword: data.newPassword,
});
},
onSuccess: () => {
setSuccessMessage('Password updated successfully!');
setErrorMessage('');
passwordForm.reset();
passwordForm.reset({
currentPassword: '',
newPassword: '',
confirmPassword: '',
});
setTimeout(() => setSuccessMessage(''), 3000);
},
onError: (error: any) => {
@ -111,6 +145,35 @@ export default function ProfilePage() {
updatePasswordMutation.mutate(data);
};
// Show loading state while user data is being fetched
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">Loading profile...</p>
</div>
</div>
);
}
// Show error if user is not found after loading
if (!loading && !user) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<p className="text-red-600 mb-4">Unable to load user profile</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Retry
</button>
</div>
</div>
);
}
return (
<div className="max-w-4xl mx-auto space-y-6">
{/* Header */}
@ -288,6 +351,7 @@ export default function ProfilePage() {
{...passwordForm.register('currentPassword')}
type="password"
id="currentPassword"
autoComplete="current-password"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
{passwordForm.formState.errors.currentPassword && (
@ -309,6 +373,7 @@ export default function ProfilePage() {
{...passwordForm.register('newPassword')}
type="password"
id="newPassword"
autoComplete="new-password"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
{passwordForm.formState.errors.newPassword && (
@ -334,6 +399,7 @@ export default function ProfilePage() {
{...passwordForm.register('confirmPassword')}
type="password"
id="confirmPassword"
autoComplete="new-password"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
{passwordForm.formState.errors.confirmPassword && (

View File

@ -103,7 +103,7 @@ export const usersApi = {
/**
* Change password
*/
async changePassword(data: ChangePasswordRequest): Promise<void> {
return apiClient.post<void>('/api/v1/users/change-password', data);
async changePassword(data: ChangePasswordRequest): Promise<{ message: string }> {
return apiClient.patch<{ message: string }>('/api/v1/users/me/password', data);
},
};

View File

@ -53,8 +53,16 @@ export {
type CsvBookingStatsResponse,
} from './bookings';
// Users (6 endpoints)
export { listUsers, getUser, createUser, updateUser, deleteUser, restoreUser } from './users';
// Users (7 endpoints)
export {
listUsers,
getUser,
createUser,
updateUser,
deleteUser,
restoreUser,
changePassword,
} from './users';
// Organizations (4 endpoints)
export {

View File

@ -78,3 +78,15 @@ export async function deleteUser(id: string): Promise<SuccessResponse> {
export async function restoreUser(id: string): Promise<UserResponse> {
return post<UserResponse>(`/api/v1/users/${id}/restore`);
}
/**
* Change own password
* PATCH /api/v1/users/me/password
* Requires: Authentication
*/
export async function changePassword(data: {
currentPassword: string;
newPassword: string;
}): Promise<{ message: string }> {
return patch<{ message: string }>('/api/v1/users/me/password', data);
}