Skip to content

Commit e25b055

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

File tree

8 files changed

+252
-7
lines changed

8 files changed

+252
-7
lines changed

src/App.tsx

Lines changed: 5 additions & 0 deletions
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

Lines changed: 7 additions & 1 deletion
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

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

src/pages/editUserProfile.tsx

Lines changed: 13 additions & 0 deletions
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

Lines changed: 14 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 6 additions & 5 deletions
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

Lines changed: 20 additions & 1 deletion
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)