diff --git a/src/App.tsx b/src/App.tsx index 300c26e..9511536 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ import { RouterProvider, createBrowserRouter } from 'react-router-dom'; import Login from './pages/Login'; import LandingPage from './pages/LandingPage'; import GoogleAuthSuccess from './components/authentication/GoogleAuthSucces'; +import EditUserProfile from './pages/EditUserProfile'; import { ToastContainer } from 'react-toastify'; import Searchpage from './containers/searchResults/SearchPage'; import { useDispatch } from 'react-redux'; @@ -68,6 +69,10 @@ const App = () => { path: 'auth/success/:token', element: , }, + { + path: 'profile', + element: , + }, { path: 'categories/:categoryId', element: , diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx index 311eb38..3152414 100644 --- a/src/components/common/Input.tsx +++ b/src/components/common/Input.tsx @@ -21,7 +21,12 @@ const Input = forwardRef( type={type} id={id} placeholder={placeholder} - className={cn('p-1 px-3 rounded-lg border border-blackColor outline-none font-normal', inputClasname)} + className={cn( + 'p-1 px-3 rounded-lg border border-blackColor outline-none font-normal', + + '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', + inputClasname + )} ref={ref} {...rest} /> diff --git a/src/components/navbar/Navbar.tsx b/src/components/navbar/Navbar.tsx index 1a1f7b8..b4dd3db 100644 --- a/src/components/navbar/Navbar.tsx +++ b/src/components/navbar/Navbar.tsx @@ -3,9 +3,12 @@ import { useNavigate } from 'react-router-dom'; import { DesktopNav, PopularCategory } from '../../containers/nav/NavbarComponents'; import WishNav from './wishNav/WishNav'; import CartNav from './cartNav/CartNav'; -import { LuBell } from 'react-icons/lu'; +import { LuBell, LuUser } from 'react-icons/lu'; import Notifications from './notifications/Notifications'; import { cn } from '../../utils'; +import { useGetUserByIdQuery } from '../../services/userApi'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../redux/store'; const Navbar: React.FC = () => { const [notificationOpen, setNotificationOpen] = useState(false); @@ -49,7 +52,6 @@ const Navbar: React.FC = () => { showScrollbar(); }); }, []); - useEffect(() => { const updateNavbarHeight = () => { setNavbarHeight(navbarRef.current?.offsetHeight as number); @@ -66,7 +68,32 @@ const Navbar: React.FC = () => { e.preventDefault(); navigate(`/search?searchQuery=${searchQuery}`); }; - + //HANDLE OPERATION OF ROUTING THE USER TO THE ACCOUNT OR SIGN IN + const [userInfo, setUserInfo] = useState([]); + const user = useSelector((state: RootState) => state.user); + const userId = user.userId ? user.userId.replace(/"/g, '') : ''; + const { + isLoading: isFetchingUser, + isSuccess: isUserFetched, + data: userData, + } = useGetUserByIdQuery(userId); + const handleNavigate = () => { + const token = localStorage.getItem('token') || null; + if (!token) { + navigate('/login'); + return; + } else { + setWish(state => !state); + } + }; + useEffect(() => { + if (isUserFetched && userData && userData.message) { + const { firstName, lastName } = userData.message; + setUserInfo([firstName, lastName]); + return; + } + setUserInfo(['Anynmous', 'Anoonymous']); + }, [isUserFetched]); return ( <> {(wish || cartOpen || notificationOpen) && ( @@ -121,23 +148,12 @@ const Navbar: React.FC = () => {
setWish(state => !state)} + // onClick={() => setWish(state => !state)} + onClick={handleNavigate} className='rounded-full transition-all ease-in-out delay-100 hover:bg-grayColor active:bg-greenColor p-1 active:text-blackColor hover:text-blackColor' > - - - + + {wish && (
{ @@ -148,31 +164,18 @@ const Navbar: React.FC = () => { }} className='absolute top-0 md:h-[115px] w-screen right-0 h-[100px] bg-[#0000000] z-40' > - +
)}
- {/* Favorite */} + setNotificationOpen(!notificationOpen)} > - {/* - - */} + - + {/* */}
SetCartOpen(state => !state)} diff --git a/src/components/navbar/notifications/Notifications.tsx b/src/components/navbar/notifications/Notifications.tsx index 8cf6652..d46c146 100644 --- a/src/components/navbar/notifications/Notifications.tsx +++ b/src/components/navbar/notifications/Notifications.tsx @@ -12,8 +12,8 @@ import { useEffect, useRef, useState } from 'react'; import { FaSpinner } from 'react-icons/fa6'; export const Notifications = () => { - const userId = useSelector((state: any) => state.userId) || localStorage.getItem('userId'); - // const userId = '06e0d866-2544-4cfa-83b0-2c3cede7a2f0'; + const user = useSelector((state: any) => state.user); + const userId = user.userId ? user.userId.replace(/"/g, '') : ''; const [mainMenuClicked, setMainMenuClicked] = useState(false); const mainMenuRef = useRef(null); diff --git a/src/components/navbar/wishNav/WishNav.tsx b/src/components/navbar/wishNav/WishNav.tsx index fc175fd..f6bc288 100644 --- a/src/components/navbar/wishNav/WishNav.tsx +++ b/src/components/navbar/wishNav/WishNav.tsx @@ -1,27 +1,54 @@ -import React from 'react' -import { Link } from 'react-router-dom'; - +import React from 'react'; +import { BiLoader } from 'react-icons/bi'; +import { LuUser } from 'react-icons/lu'; +import { useDispatch } from 'react-redux'; +import { Link, useNavigate } from 'react-router-dom'; +import { clearUserData } from '../../../redux/slices/userSlice'; interface WishNav { - setWish: React.Dispatch> + setWish: React.Dispatch>; + isLoading: boolean; + userInfo: string[]; } -const WishNav: React.FC = () => { - return ( - <> -
-
- - - - My Account -
-
- Not Ange? - -
+const WishNav: React.FC = ({ isLoading, userInfo }) => { + const navigate = useNavigate(); + const dispatch = useDispatch(); + const [firstName, lastName] = userInfo; + const handleSignOut = () => { + dispatch(clearUserData()); + navigate('/'); + }; + return ( + <> +
+ {isLoading ? ( +
+ +
+ ) : ( + <> +
+

{firstName + ' ' + lastName}

+
+ {' '} + + Account Settings + +
- - ) -} +
+ +
+ + )} +
+ + ); +}; -export default WishNav \ No newline at end of file +export default WishNav; diff --git a/src/components/profile/Profile.tsx b/src/components/profile/Profile.tsx new file mode 100644 index 0000000..bba5fc4 --- /dev/null +++ b/src/components/profile/Profile.tsx @@ -0,0 +1,188 @@ +import React, { useEffect, useState } from 'react'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import { useGetUserByIdQuery, useUpdateUserMutation } from '../../services/userApi'; +import Input from '../common/Input'; +import { useSelector } from 'react-redux'; + +export interface UserFormValues { + firstName: string; + lastName: string; + phoneNumber: string; + email: string; + photoUrl?: any; +} + +const Profile: React.FC = () => { + const [isEditing, setIsEditing] = useState(false); + const [showSuccessMessage, setShowSuccessMessage] = useState(false); + const id = useSelector((state: any) => state.user.userId); + + const { data } = useGetUserByIdQuery(id); + const [userData, setUserData] = useState(data?.message); + + const { + register, + handleSubmit, + setValue, + formState: { errors, isSubmitting }, + } = useForm(); + + useEffect(() => { + if (data?.message) { + setUserData(data.message); + } + }, [data]); + + useEffect(() => { + if (userData) { + setValue('firstName', userData.firstName); + setValue('lastName', userData.lastName); + setValue('email', userData.email); + setValue('phoneNumber', userData.phoneNumber); + } + }, [userData, setValue]); + + const [updateUser, { isLoading, isError, error }] = useUpdateUserMutation(); + + const onSubmit: SubmitHandler = async data => { + const formData = new FormData(); + formData.append('firstName', data.firstName); + formData.append('lastName', data.lastName); + formData.append('phoneNumber', data.phoneNumber); + formData.append('email', data.email); + formData.append('profileImage', data.photoUrl[0]); + // if (data.photoUrl && data.photoUrl[0]) { + // formData.append('photoUrl', data.photoUrl[0]); + // } + + try { + const response = await updateUser(formData).unwrap(); + setUserData(response.message); + setIsEditing(false); + setShowSuccessMessage(true); + setTimeout(() => setShowSuccessMessage(false), 3000); // Hide message after 3 seconds + } catch (err) { + console.error('Failed to update profile:', err); + } + }; + + const getErrorMessage = (error: any) => { + if ('data' in error) { + return error.data?.message || 'Error updating profile'; + } else { + return 'An unexpected error occurred'; + } + }; + + return ( +
+
+ {showSuccessMessage && ( +
+ Profile updated successfully! +
+ )} +

My Profile

+
+
+
+ {userData?.firstName} +
+
+ {userData?.firstName} {userData?.lastName} +
+

{userData?.role}

+
+
+ +
+
+ {isEditing && ( +
+

Personal Information

+ {isError && error && ( +

{getErrorMessage(error)}

+ )} +
+
+ + +
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ + +
+
+
+ )} +
+
+ ); +}; + +export default Profile; diff --git a/src/pages/EditUserProfile.tsx b/src/pages/EditUserProfile.tsx new file mode 100644 index 0000000..b817625 --- /dev/null +++ b/src/pages/EditUserProfile.tsx @@ -0,0 +1,13 @@ +import Navbar from '../components/navbar/Navbar'; +import UserProfileEdit from '../components/profile/Profile'; + +const EditUserProfile = () => { + return ( +
+ + +
+ ); +}; + +export default EditUserProfile; diff --git a/src/redux/slices/editUserSlice.ts b/src/redux/slices/editUserSlice.ts new file mode 100644 index 0000000..2a591a5 --- /dev/null +++ b/src/redux/slices/editUserSlice.ts @@ -0,0 +1,15 @@ +interface UserState { + user?: { + firstName: string; + lastName: string; + gender: string; + contactNumber: string; + email: string; + changePassword: boolean; + currentPassword: string; + newPassword: string; + }; + isLoading: boolean; + error?: string; +} + \ No newline at end of file diff --git a/src/redux/slices/userSlice.ts b/src/redux/slices/userSlice.ts index 951418b..29169ad 100644 --- a/src/redux/slices/userSlice.ts +++ b/src/redux/slices/userSlice.ts @@ -28,6 +28,7 @@ const userSlice = createSlice({ state.userId = action.payload; if (action.payload) { localStorage.setItem('user', JSON.stringify(action.payload)); + localStorage.setItem('user', action.payload); } else { localStorage.removeItem('user'); } diff --git a/src/services/index.ts b/src/services/index.ts index 777788a..6986bbb 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,9 +1,18 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { RootState } from '../redux/store'; export const mavericksApi = createApi({ reducerPath: 'mavericksApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://e-commerce-mavericcks-bn-staging-istf.onrender.com/api/', + // baseUrl: 'localhost:5000', + prepareHeaders: (headers, { getState }) => { + const token = (getState() as RootState).user.token; + if (token) { + headers.set('authorization', `${token.replace(/"/g, '')!}`); + } + return headers; + }, }), tagTypes: ['Notifications'], endpoints: () => ({}), diff --git a/src/services/userApi.ts b/src/services/userApi.ts index 21de108..8b668c4 100644 --- a/src/services/userApi.ts +++ b/src/services/userApi.ts @@ -1,4 +1,5 @@ import { mavericksApi } from '.'; +const id = localStorage.getItem('user'); export const userApi = mavericksApi.injectEndpoints({ endpoints: builder => ({ @@ -10,8 +11,20 @@ export const userApi = mavericksApi.injectEndpoints({ }, }), }), + updateUser: builder.mutation({ + query: data => ({ + url: `/users/edit/${id}`, + method: 'PATCH', + headers: { + authorization: localStorage.getItem('token') || '', + }, + body: data, + formData: true, + }), + }), }), + overrideExisting: false, }); -export const { useGetUserByIdQuery } = userApi; +export const { useGetUserByIdQuery, useUpdateUserMutation } = userApi;