Skip to content

Commit 4eed766

Browse files
Finishes #187354206-edit-user-profile
1 parent c54df12 commit 4eed766

File tree

8 files changed

+253
-7
lines changed

8 files changed

+253
-7
lines changed

src/App.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { RouterProvider, createBrowserRouter } from 'react-router-dom';
55
import Login from './pages/Login';
66
import LandingPage from './pages/LandingPage';
77
import GoogleAuthSuccess from './components/authentication/GoogleAuthSucces';
8+
import EditUserProfile from './pages/editUserProfile';
89
import { ToastContainer } from 'react-toastify';
910
import Searchpage from './containers/searchResults/SearchPage';
1011
import { useDispatch } from 'react-redux';
@@ -72,6 +73,10 @@ const App = () => {
7273
path: 'auth/success/:token',
7374
element: <GoogleAuthSuccess />,
7475
},
76+
{
77+
path: 'profile',
78+
element: <EditUserProfile />,
79+
},
7580
{
7681
path: 'categories/:categoryId',
7782
element: <CategoriesPage />,

src/components/common/Input.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
2121
type={type}
2222
id={id}
2323
placeholder={placeholder}
24-
className={cn('p-1 px-3 rounded-lg border border-blackColor outline-none font-normal', inputClasname)}
24+
className={cn('p-1 px-3 rounded-lg border border-blackColor outline-none font-normal',
25+
26+
'p-1 px-3 rounded-lg border border-blackColor outline-none font-normal',
27+
28+
'p-1 px-3 rounded-lg border border-blackColor outline-none font-normal disabled:cursor-not-allowed disabled:opacity-50 focus:ring-grayColor/40 disabled:border-grayColor',
29+
inputClasname
30+
)}
2531
ref={ref}
2632
{...rest}
2733
/>

src/components/profile/Profile.tsx

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { useForm, SubmitHandler } from 'react-hook-form';
3+
import { useGetUserByIdQuery, useUpdateUserMutation } from '../../services/userApi';
4+
import Input from '../common/Input';
5+
import { useSelector } from 'react-redux';
6+
7+
export interface UserFormValues {
8+
firstName: string;
9+
lastName: string;
10+
phoneNumber: string;
11+
email: string;
12+
photoUrl?: any;
13+
}
14+
15+
const Profile: React.FC = () => {
16+
const [isEditing, setIsEditing] = useState(false);
17+
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
18+
const id = useSelector((state: any) => state.user.userId);
19+
20+
const { data } = useGetUserByIdQuery(id);
21+
const [userData, setUserData] = useState(data?.message);
22+
23+
const {
24+
register,
25+
handleSubmit,
26+
setValue,
27+
formState: { errors, isSubmitting },
28+
} = useForm<UserFormValues>();
29+
30+
useEffect(() => {
31+
if (data?.message) {
32+
setUserData(data.message);
33+
}
34+
}, [data]);
35+
36+
useEffect(() => {
37+
if (userData) {
38+
setValue('firstName', userData.firstName);
39+
setValue('lastName', userData.lastName);
40+
setValue('email', userData.email);
41+
setValue('phoneNumber', userData.phoneNumber);
42+
}
43+
}, [userData, setValue]);
44+
45+
const [updateUser, { isLoading, isError, error }] = useUpdateUserMutation();
46+
47+
const onSubmit: SubmitHandler<UserFormValues> = async data => {
48+
const formData = new FormData();
49+
formData.append('firstName', data.firstName);
50+
formData.append('lastName', data.lastName);
51+
formData.append('phoneNumber', data.phoneNumber);
52+
formData.append('email', data.email);
53+
if (data.photoUrl && data.photoUrl.length > 0) {
54+
formData.append('profileImage', data.photoUrl[0]);
55+
}
56+
57+
try {
58+
const response = await updateUser(formData).unwrap();
59+
setUserData(response.message);
60+
setIsEditing(false);
61+
setShowSuccessMessage(true);
62+
setTimeout(() => setShowSuccessMessage(false), 3000);
63+
} catch (err) {
64+
console.error('Failed to update profile:', err);
65+
}
66+
};
67+
68+
const getErrorMessage = (error: any) => {
69+
if ('data' in error) {
70+
return error.data?.message || 'Error updating profile';
71+
} else {
72+
return 'An unexpected error occurred';
73+
}
74+
};
75+
76+
return (
77+
<div className='container mx-auto px-4'>
78+
<div className='flex flex-col items-center'>
79+
{showSuccessMessage && (
80+
<div className='w-full md:w-8/12 lg:w-6/12 xl:w-5/12 bg-[#38a169] text-[#ffffff] p-4 rounded-lg mb-6 text-center'>
81+
Profile updated successfully!
82+
</div>
83+
)}
84+
<h1 className='text-3xl font-bold my-6 text-[#000000]'>My Profile</h1>
85+
<div className='w-full md:w-8/12 lg:w-6/12 xl:w-5/12 border border-[#d1d5db] p-6 rounded-lg shadow-md mb-6'>
86+
<div className='flex items-center justify-between'>
87+
<div className='flex items-center'>
88+
<img
89+
className='h-24 w-24 rounded-full'
90+
src={userData?.photoUrl || '/default-profile.png'}
91+
alt={userData?.firstName}
92+
/>
93+
<div className='ml-4'>
94+
<div className='text-2xl font-medium text-[#000000]'>
95+
{userData?.firstName} {userData?.lastName}
96+
</div>
97+
<p className='text-[#6b7280] font-light mt-1'>{userData?.role}</p>
98+
</div>
99+
</div>
100+
<button
101+
type='button'
102+
className='p-3 rounded-lg bg-[#d1d5db] hover:bg-[#9ca3af] transition-all text-[#000000] font-bold'
103+
onClick={() => setIsEditing(true)}
104+
>
105+
Edit
106+
</button>
107+
</div>
108+
</div>
109+
{isEditing && (
110+
<div className='w-full md:w-8/12 lg:w-6/12 xl:w-5/12 border border-[#d1d5db] rounded-xl p-8 shadow-md'>
111+
<h2 className='text-xl font-semibold mb-4 text-[#000000]'>Personal Information</h2>
112+
{isError && error && (
113+
<p className='text-lg bg-[#f56565] text-[#ffffff] mt-4 py-2 rounded-lg px-3'>{getErrorMessage(error)}</p>
114+
)}
115+
<form onSubmit={handleSubmit(onSubmit)} className='space-y-6'>
116+
<div>
117+
<label className='block text-sm font-medium text-[#000000]'>Profile Picture</label>
118+
<input type='file' accept='image/*' className='mt-2' {...register('photoUrl')} />
119+
</div>
120+
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4'>
121+
<div className='text-[#000000]'>
122+
<Input
123+
id='firstName'
124+
label='First Name'
125+
type='text'
126+
placeholder='Enter first name'
127+
{...register('firstName')}
128+
error={errors?.firstName?.message}
129+
/>
130+
</div>
131+
<div className='text-[#000000]'>
132+
<Input
133+
id='lastName'
134+
label='Last Name'
135+
type='text'
136+
placeholder='Enter last name'
137+
{...register('lastName')}
138+
error={errors?.lastName?.message}
139+
/>
140+
</div>
141+
</div>
142+
<div className='text-[#000000]'>
143+
<Input
144+
id='phoneNumber'
145+
label='Contact Number'
146+
type='tel'
147+
placeholder='Enter your contact number'
148+
{...register('phoneNumber')}
149+
error={errors?.phoneNumber?.message}
150+
/>
151+
</div>
152+
<div className='text-[#000000]'>
153+
<Input
154+
id='email'
155+
label='Email'
156+
type='email'
157+
disabled
158+
placeholder='Enter your email'
159+
{...register('email')}
160+
error={errors?.email?.message}
161+
/>
162+
</div>
163+
<div className='flex justify-between gap-4'>
164+
<button
165+
type='submit'
166+
className='p-3 rounded-lg bg-[#38a169] hover:bg-[#2f855a] transition-all text-[#ffffff] font-bold'
167+
disabled={isSubmitting || isLoading}
168+
>
169+
{isSubmitting || isLoading ? 'Loading...' : 'Save'}
170+
</button>
171+
<button
172+
type='button'
173+
onClick={() => setIsEditing(false)}
174+
className='p-3 rounded-lg bg-[#d1d5db] hover:bg-[#9ca3af] transition-all text-[#000000]'
175+
>
176+
Cancel
177+
</button>
178+
</div>
179+
</form>
180+
</div>
181+
)}
182+
</div>
183+
</div>
184+
);
185+
};
186+
187+
export default Profile;

src/pages/editUserProfile.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Navbar from '../components/navbar/Navbar';
2+
import UserProfileEdit from '../components/profile/Profile';
3+
4+
const EditUserProfile = () => {
5+
return (
6+
<div>
7+
<Navbar />
8+
<UserProfileEdit />
9+
</div>
10+
);
11+
};
12+
13+
export default EditUserProfile;

src/redux/slices/editUserSlice.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
interface UserState {
2+
user?: {
3+
firstName: string;
4+
lastName: string;
5+
gender: string;
6+
contactNumber: string;
7+
email: string;
8+
changePassword: boolean;
9+
currentPassword: string;
10+
newPassword: string;
11+
};
12+
isLoading: boolean;
13+
error?: string;
14+
}

src/redux/slices/userSlice.ts

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const userSlice = createSlice({
2828
state.userId = action.payload;
2929
if (action.payload) {
3030
localStorage.setItem('user', action.payload);
31+
localStorage.setItem('user', action.payload);
3132
} else {
3233
localStorage.removeItem('user');
3334
}

src/services/index.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ export const mavericksApi = createApi({
55
reducerPath: 'mavericksApi',
66
baseQuery: fetchBaseQuery({
77
baseUrl: 'https://e-commerce-mavericcks-bn-staging-istf.onrender.com/api/',
8-
prepareHeaders: (headers, { getState }) => {
9-
const token = (getState() as RootState).user.token;
10-
if (token) {
11-
headers.set('authorization', `${token.replace(/"/g, '')!}`);
12-
}
8+
// baseUrl: 'localhost:5000',
9+
prepareHeaders: (headers, { getState }) => {
10+
const token = (getState() as RootState).user.token;
11+
if (token) {
12+
headers.set('authorization', `${token.replace(/"/g, '')!}`);
13+
}
1314
return headers;
1415
},
1516
}),

src/services/userApi.ts

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { mavericksApi } from '.';
22
import { User } from '../types/Types';
33

4+
const id = localStorage.getItem('user');
5+
46
export const userApi = mavericksApi.injectEndpoints({
57
endpoints: builder => ({
68
getUserById: builder.query({
@@ -16,6 +18,17 @@ export const userApi = mavericksApi.injectEndpoints({
1618
},
1719
}),
1820
}),
21+
updateUser: builder.mutation({
22+
query: data => ({
23+
url: `/users/edit/${id}`,
24+
method: 'PATCH',
25+
headers: {
26+
authorization: localStorage.getItem('token') || '',
27+
},
28+
body: data,
29+
formData: true,
30+
}),
31+
}),
1932
getSellers: builder.query({
2033
query: () => ({
2134
url: 'users/role/seller',
@@ -39,4 +52,10 @@ export const userApi = mavericksApi.injectEndpoints({
3952
overrideExisting: false,
4053
});
4154

42-
export const { useGetUserByIdQuery, useGetUsersQuery,useGetSellersQuery, useUpdateUserRoleMutation } = userApi;
55+
export const {
56+
useGetUserByIdQuery,
57+
useUpdateUserMutation,
58+
useGetUsersQuery,
59+
useGetSellersQuery,
60+
useUpdateUserRoleMutation,
61+
} = userApi;

0 commit comments

Comments
 (0)