Compare commits
3 Commits
4ce7d2ec07
...
d9868dd49f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9868dd49f | ||
|
|
2054e73e78 | ||
|
|
905a56888a |
@ -6,13 +6,13 @@
|
|||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useAuth } from '@/lib/context/auth-context';
|
import { useAuth } from '@/lib/context/auth-context';
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { updateUser } from '@/lib/api';
|
import { updateUser, changePassword } from '@/lib/api';
|
||||||
|
|
||||||
// Password update schema
|
// Password update schema
|
||||||
const passwordSchema = z
|
const passwordSchema = z
|
||||||
@ -44,7 +44,7 @@ const profileSchema = z.object({
|
|||||||
type ProfileFormData = z.infer<typeof profileSchema>;
|
type ProfileFormData = z.infer<typeof profileSchema>;
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
const { user, refreshUser } = useAuth();
|
const { user, refreshUser, loading } = useAuth();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [activeTab, setActiveTab] = useState<'profile' | 'password'>('profile');
|
const [activeTab, setActiveTab] = useState<'profile' | 'password'>('profile');
|
||||||
const [successMessage, setSuccessMessage] = useState('');
|
const [successMessage, setSuccessMessage] = useState('');
|
||||||
@ -63,8 +63,37 @@ export default function ProfilePage() {
|
|||||||
// Password form
|
// Password form
|
||||||
const passwordForm = useForm<PasswordFormData>({
|
const passwordForm = useForm<PasswordFormData>({
|
||||||
resolver: zodResolver(passwordSchema),
|
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
|
// Update profile mutation
|
||||||
const updateProfileMutation = useMutation({
|
const updateProfileMutation = useMutation({
|
||||||
mutationFn: (data: ProfileFormData) => {
|
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({
|
const updatePasswordMutation = useMutation({
|
||||||
mutationFn: async (data: PasswordFormData) => {
|
mutationFn: async (data: PasswordFormData) => {
|
||||||
// TODO: Add password update endpoint
|
return changePassword({
|
||||||
// return updatePassword(data);
|
currentPassword: data.currentPassword,
|
||||||
return Promise.resolve({ success: true });
|
newPassword: data.newPassword,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setSuccessMessage('Password updated successfully!');
|
setSuccessMessage('Password updated successfully!');
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
passwordForm.reset();
|
passwordForm.reset({
|
||||||
|
currentPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
});
|
||||||
setTimeout(() => setSuccessMessage(''), 3000);
|
setTimeout(() => setSuccessMessage(''), 3000);
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
@ -111,6 +145,35 @@ export default function ProfilePage() {
|
|||||||
updatePasswordMutation.mutate(data);
|
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 (
|
return (
|
||||||
<div className="max-w-4xl mx-auto space-y-6">
|
<div className="max-w-4xl mx-auto space-y-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@ -288,6 +351,7 @@ export default function ProfilePage() {
|
|||||||
{...passwordForm.register('currentPassword')}
|
{...passwordForm.register('currentPassword')}
|
||||||
type="password"
|
type="password"
|
||||||
id="currentPassword"
|
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"
|
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 && (
|
{passwordForm.formState.errors.currentPassword && (
|
||||||
@ -309,6 +373,7 @@ export default function ProfilePage() {
|
|||||||
{...passwordForm.register('newPassword')}
|
{...passwordForm.register('newPassword')}
|
||||||
type="password"
|
type="password"
|
||||||
id="newPassword"
|
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"
|
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 && (
|
{passwordForm.formState.errors.newPassword && (
|
||||||
@ -334,6 +399,7 @@ export default function ProfilePage() {
|
|||||||
{...passwordForm.register('confirmPassword')}
|
{...passwordForm.register('confirmPassword')}
|
||||||
type="password"
|
type="password"
|
||||||
id="confirmPassword"
|
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"
|
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 && (
|
{passwordForm.formState.errors.confirmPassword && (
|
||||||
|
|||||||
@ -103,7 +103,7 @@ export const usersApi = {
|
|||||||
/**
|
/**
|
||||||
* Change password
|
* Change password
|
||||||
*/
|
*/
|
||||||
async changePassword(data: ChangePasswordRequest): Promise<void> {
|
async changePassword(data: ChangePasswordRequest): Promise<{ message: string }> {
|
||||||
return apiClient.post<void>('/api/v1/users/change-password', data);
|
return apiClient.patch<{ message: string }>('/api/v1/users/me/password', data);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -53,8 +53,16 @@ export {
|
|||||||
type CsvBookingStatsResponse,
|
type CsvBookingStatsResponse,
|
||||||
} from './bookings';
|
} from './bookings';
|
||||||
|
|
||||||
// Users (6 endpoints)
|
// Users (7 endpoints)
|
||||||
export { listUsers, getUser, createUser, updateUser, deleteUser, restoreUser } from './users';
|
export {
|
||||||
|
listUsers,
|
||||||
|
getUser,
|
||||||
|
createUser,
|
||||||
|
updateUser,
|
||||||
|
deleteUser,
|
||||||
|
restoreUser,
|
||||||
|
changePassword,
|
||||||
|
} from './users';
|
||||||
|
|
||||||
// Organizations (4 endpoints)
|
// Organizations (4 endpoints)
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -78,3 +78,15 @@ export async function deleteUser(id: string): Promise<SuccessResponse> {
|
|||||||
export async function restoreUser(id: string): Promise<UserResponse> {
|
export async function restoreUser(id: string): Promise<UserResponse> {
|
||||||
return post<UserResponse>(`/api/v1/users/${id}/restore`);
|
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);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user