From 8869151d6fcab543f69aac518ccd6589dba41172 Mon Sep 17 00:00:00 2001 From: NPL Codes <104311071+amin-leon@users.noreply.github.com> Date: Sat, 13 Jul 2024 17:08:07 +0200 Subject: [PATCH] [finishes #187943570] buyer dashboard --- src/App.tsx | 5 + src/components/chat/Chat.tsx | 4 +- src/components/navbar/wishNav/WishNav.tsx | 2 +- src/containers/buyer/Account.tsx | 87 +++++++++ .../buyer/BuyerRestrictedRoutes.tsx | 34 ++++ src/containers/buyer/ConfirmationModal.tsx | 37 ++++ src/containers/buyer/ErrorPage.tsx | 11 ++ src/containers/buyer/FeedBack.tsx | 159 ++++++++++++++++ src/containers/buyer/FeedbackModal.tsx | 112 ++++++++++++ src/containers/buyer/InTransit.tsx | 73 ++++++++ src/containers/buyer/OderDetails.tsx | 171 ++++++++++++++++++ src/containers/buyer/Orders.tsx | 94 ++++++++++ src/containers/buyer/PendingPayments.tsx | 73 ++++++++ src/containers/buyer/Profile.tsx | 134 ++++++++++++++ src/containers/buyer/ResetPassword.tsx | 101 +++++++++++ src/containers/buyer/ReturnRefund.tsx | 7 + src/containers/buyer/WishList.tsx | 65 +++++++ src/pages/BuyerProfile.tsx | 161 +++++++++++++++++ src/redux/slices/buyerDashboard.tsx | 30 +++ src/redux/slices/cartSlice.ts | 29 +++ src/redux/slices/feedbackSlice.tsx | 34 ++++ src/redux/slices/orderItemsSlice.ts | 39 ++++ src/redux/slices/orderSlice.tsx | 42 +++++ src/redux/slices/userSlice.ts | 30 ++- src/redux/slices/wishlistSlice.ts | 33 ++++ src/redux/store.ts | 11 ++ src/services/authAPI.ts | 12 +- src/services/cartApi.ts | 5 +- src/services/categoryApi.ts | 12 +- src/services/feedbackApi.ts | 29 +++ src/services/orderItemsApi.ts | 20 ++ src/services/ordersAPI.ts | 30 +++ src/services/userApi.ts | 18 +- src/services/wishlistApi.ts | 13 +- src/types/Types.ts | 69 +++++++ src/utils/DateFormater.tsx | 23 +++ src/utils/schemas.ts | 1 + 37 files changed, 1795 insertions(+), 15 deletions(-) create mode 100644 src/containers/buyer/Account.tsx create mode 100644 src/containers/buyer/BuyerRestrictedRoutes.tsx create mode 100644 src/containers/buyer/ConfirmationModal.tsx create mode 100644 src/containers/buyer/ErrorPage.tsx create mode 100644 src/containers/buyer/FeedBack.tsx create mode 100644 src/containers/buyer/FeedbackModal.tsx create mode 100644 src/containers/buyer/InTransit.tsx create mode 100644 src/containers/buyer/OderDetails.tsx create mode 100644 src/containers/buyer/Orders.tsx create mode 100644 src/containers/buyer/PendingPayments.tsx create mode 100644 src/containers/buyer/Profile.tsx create mode 100644 src/containers/buyer/ResetPassword.tsx create mode 100644 src/containers/buyer/ReturnRefund.tsx create mode 100644 src/containers/buyer/WishList.tsx create mode 100644 src/pages/BuyerProfile.tsx create mode 100644 src/redux/slices/buyerDashboard.tsx create mode 100644 src/redux/slices/cartSlice.ts create mode 100644 src/redux/slices/feedbackSlice.tsx create mode 100644 src/redux/slices/orderItemsSlice.ts create mode 100644 src/redux/slices/orderSlice.tsx create mode 100644 src/redux/slices/wishlistSlice.ts create mode 100644 src/services/feedbackApi.ts create mode 100644 src/services/orderItemsApi.ts create mode 100644 src/services/ordersAPI.ts create mode 100644 src/utils/DateFormater.tsx diff --git a/src/App.tsx b/src/App.tsx index a26f730..829d692 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -37,6 +37,7 @@ import Cart from './components/cart/Cart'; import { cartApi } from './services/cartApi'; import VerifyOTPPage from './pages/VerifyOTPPage'; +import BuyerRestrictedRoutes from './containers/buyer/BuyerRestrictedRoutes'; const App = () => { const { data, error, isLoading } = useGetProductsQuery(); const dispatch = useDispatch(); @@ -114,6 +115,10 @@ const App = () => { path: "/checkoutbag", element: }, + { + path: 'buyer-profile', + element: , + } ], }, { diff --git a/src/components/chat/Chat.tsx b/src/components/chat/Chat.tsx index b2bfa27..6b7f4ab 100644 --- a/src/components/chat/Chat.tsx +++ b/src/components/chat/Chat.tsx @@ -14,12 +14,12 @@ import { useGetUserByIdQuery } from '../../services/userApi'; import { setProfile } from '../../redux/slices/userSlice'; const Chat: React.FC = () => { const [showChat, setShowChat] = useState(false); - const userToken = useAppSelector(state => state.user.token); + const userToken:any = useAppSelector(state => state.user.token); const dispatch = useAppDispatch(); const navigate = useNavigate(); const scrollDown = useRef(null); // set profile image - const profileImage: string | null = useAppSelector(state => state.user.photoUrl); + const profileImage: File | null = useAppSelector(state => state.user.photoUrl); const userId = useAppSelector(state => state.user.userId); const { data, isError, isSuccess, isLoading } = useGetMessagesQuery(userToken as string, { skip: !userToken }); const { data: userData } = useGetUserByIdQuery(userId, { skip: !userId }); diff --git a/src/components/navbar/wishNav/WishNav.tsx b/src/components/navbar/wishNav/WishNav.tsx index f6bc288..6f25fa9 100644 --- a/src/components/navbar/wishNav/WishNav.tsx +++ b/src/components/navbar/wishNav/WishNav.tsx @@ -31,7 +31,7 @@ const WishNav: React.FC = ({ isLoading, userInfo }) => {

{firstName + ' ' + lastName}

{' '} - + Account Settings
diff --git a/src/containers/buyer/Account.tsx b/src/containers/buyer/Account.tsx new file mode 100644 index 0000000..2c6751c --- /dev/null +++ b/src/containers/buyer/Account.tsx @@ -0,0 +1,87 @@ +// components/menu/Account.tsx +import { FaUserEdit, FaHourglassHalf } from "react-icons/fa"; +import { FaTruckFast } from "react-icons/fa6"; +import { MdOutlineFeedback } from "react-icons/md"; +import { PiKeyReturn } from "react-icons/pi"; +import { useDispatch } from "react-redux"; +import { setActiveMenu } from "../../redux/slices/buyerDashboard"; + +interface AccountProps { + lastName: string; + photoUrl: string; + firstName: string; + email?: string; + orderss: any[]; + data: any[]; + Role: any +} + +const image: string = 'https://images.unsplash.com/photo-1533636721434-0e2d61030955?q=80&w=1740&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D' + +const Account: React.FC = ({ firstName, Role, lastName, photoUrl, orderss }) => { + const dispatch = useDispatch(); + const {data}:any = orderss + + return ( +
+ {/* User Profile Section */} +
+
+ profile +
+

{lastName} {firstName}

+

{Role? Role.name: 'buyer'}

+
+
+ dispatch(setActiveMenu({ activeMenu: "profile" }))} /> +
+ + {/* Cards Section */} +
+ {/* Pending Card */} +
dispatch(setActiveMenu({ activeMenu: "pendingpayments" }))}> +
+ {data.filter((order: { status: string; }) => order.status === 'pending').length} +
+ +
+

Pending Payments

+
+
+ + {/* In Transit Card */} +
dispatch(setActiveMenu({ activeMenu: "intransit" }))}> +
+ {data.filter((order: { status: string; }) => order.status === 'in-transit').length} +
+ +
+

In Transit

+
+
+ + {/* Feedback Card */} +
+ +
+

Feedback

+
+
+ + {/* Refund & Return Card */} +
+ +
+

Return & Refund

+
+
+
+
+ ); +}; + +export default Account; diff --git a/src/containers/buyer/BuyerRestrictedRoutes.tsx b/src/containers/buyer/BuyerRestrictedRoutes.tsx new file mode 100644 index 0000000..660b4e4 --- /dev/null +++ b/src/containers/buyer/BuyerRestrictedRoutes.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../redux/store'; +import AdminLayout from '../../components/admin/AdminLayout'; +import BuyerProfile from '../../pages/BuyerProfile'; + +interface RestrictedRouteProp { + role: 'admin' | 'buyer' | 'seller'; +} + +const BuyerRestrictedRoutes: React.FC = ({ role }) => { + const userRole = useSelector((state: RootState) => state.user.role); + const token = useSelector((state: RootState) => state.user.token); + +// orders +// user profile + + + if (!token || userRole !== role) { + return ; + } + + switch (role) { + case 'admin': + return ; + case 'buyer': + return ; + default: + return ; + } +}; + +export default BuyerRestrictedRoutes; diff --git a/src/containers/buyer/ConfirmationModal.tsx b/src/containers/buyer/ConfirmationModal.tsx new file mode 100644 index 0000000..4af40fb --- /dev/null +++ b/src/containers/buyer/ConfirmationModal.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +interface ConfirmationModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + message: string; +} + +const ConfirmationModal: React.FC = ({ isOpen, onClose, onConfirm, message }) => { + if (!isOpen) return null; + + return ( +
+
+

Confirm Action

+

{message}

+
+ + +
+
+
+ ); +}; + +export default ConfirmationModal; diff --git a/src/containers/buyer/ErrorPage.tsx b/src/containers/buyer/ErrorPage.tsx new file mode 100644 index 0000000..7b5aff0 --- /dev/null +++ b/src/containers/buyer/ErrorPage.tsx @@ -0,0 +1,11 @@ +// ErrorPage.js + +const ErrorPage = () => { + return ( +
+ 'An unexpected error occurred +
+ ); +}; + +export default ErrorPage; diff --git a/src/containers/buyer/FeedBack.tsx b/src/containers/buyer/FeedBack.tsx new file mode 100644 index 0000000..f751851 --- /dev/null +++ b/src/containers/buyer/FeedBack.tsx @@ -0,0 +1,159 @@ +import React, { useState } from 'react'; +import FeedbackModal from './FeedbackModal'; +import { FaStar, FaRegStar } from 'react-icons/fa'; + +type Rating = number; + +interface Product { + id: string; + name: string; + price: string; + manufacturer: string; + ratings: Rating[]; +} + +const productsData: Product[] = [ + { + id: '1', + name: 'Product 1', + price: '$100', + manufacturer: 'Manufacturer A', + ratings: [], + }, + { + id: '2', + name: 'Product 2', + price: '$200', + manufacturer: 'Manufacturer B', + ratings: [4], + }, + { + id: '3', + name: 'Product 3', + price: '$300', + manufacturer: 'Manufacturer C', + ratings: [3], + }, +]; + +const FeedBack: React.FC = () => { + const [products, setProducts] = useState(productsData); + const [modalOpen, setModalOpen] = useState(false); + const [currentProduct, setCurrentProduct] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const productsPerPage = 3; + + const indexOfLastProduct = currentPage * productsPerPage; + const indexOfFirstProduct = indexOfLastProduct - productsPerPage; + const currentProducts = products.slice(indexOfFirstProduct, indexOfLastProduct); + + const totalPages = Math.ceil(products.length / productsPerPage); + + const paginate = (pageNumber: number) => setCurrentPage(pageNumber); + + const handleAddFeedback = (product: Product) => { + setCurrentProduct(product); + setModalOpen(true); + }; + + const handleModalClose = () => { + setModalOpen(false); + setCurrentProduct(null); + }; + + const handleFeedbackSubmit = (feedback: { message: string; image: File | null; rating: number }) => { + console.log('Feedback submitted for product:', currentProduct?.id); + console.log('Feedback details:', feedback); + }; + + const handleDeleteFeedback = (productId: string, feedbackIndex: number) => { + setProducts((prevProducts) => + prevProducts.map((product) => + product.id === productId + ? { + ...product, + ratings: product.ratings.filter((_, index) => index !== feedbackIndex), + } + : product + ) + ); + }; + + const renderStars = (rating: number) => { + const stars = []; + for (let i = 1; i <= 5; i++) { + stars.push(i <= rating ? : ); + } + return stars; + }; + + return ( +
+

Purchased Products

+ {currentProducts.map((product, index) => ( +
+
+
+

{product.name}

+
+
+ {product.ratings.length === 0 ? ( + + ) : ( +
+ {product.ratings.map((rating, index) => ( +
+
+
{renderStars(rating)}
+
+ +
+ ))} +
+ )} +
+
+
+ ))} +
+ {Array.from({ length: totalPages }, (_, index) => ( + + ))} +
+ {currentProduct && ( + + )} +
+ ); +}; + +export default FeedBack; diff --git a/src/containers/buyer/FeedbackModal.tsx b/src/containers/buyer/FeedbackModal.tsx new file mode 100644 index 0000000..2c5fcfb --- /dev/null +++ b/src/containers/buyer/FeedbackModal.tsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react'; +import { useSubmitFeedbackMutation } from '../../services/feedbackApi'; + +interface FeedbackModalProps { + productName: string; + productId: string; + isOpen: boolean; + onClose: () => void; + onSubmit: (feedback: { message: string; image: File | null; rating: number }) => void; +} + +const FeedbackModal: React.FC = ({ productName, productId, isOpen, onClose, onSubmit }) => { + const [message, setMessage] = useState(''); + const [image, setImage] = useState(null); + const [rating, setRating] = useState(0); + const [submitFeedback, { isLoading, error }] = useSubmitFeedbackMutation(); + + const handleImageChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + setImage(e.target.files[0]); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + const dummyImage = 'https://via.placeholder.com/150'; + const feedbackImage: any = image || dummyImage; + + try { + await submitFeedback({ productId, feedbackData: { message, image:feedbackImage, rating } }).unwrap(); + onSubmit({ message, image, rating }); + onClose(); + } catch (error) { + console.error('Failed to submit feedback:', error); + } + }; + + if (!isOpen) return null; + + return ( +
+
+

{productName}

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ {error &&

You already Provided feedback.

} +
+
+
+ ); +}; + +export default FeedbackModal; diff --git a/src/containers/buyer/InTransit.tsx b/src/containers/buyer/InTransit.tsx new file mode 100644 index 0000000..a036115 --- /dev/null +++ b/src/containers/buyer/InTransit.tsx @@ -0,0 +1,73 @@ +import { useState } from "react"; +import { useDispatch } from "react-redux"; +import { setActiveMenu } from "../../redux/slices/buyerDashboard"; + +function InTransitOrders({orderss}: any) { + const dispatch = useDispatch(); + + const {data} = orderss; + const inTransitOrders = data.filter((order:any) => order.status === "paid"); + + const [currentPage, setCurrentPage] = useState(1); + const ordersPerPage = 3; + + const indexOfLastOrder = currentPage * ordersPerPage; + const indexOfFirstOrder = indexOfLastOrder - ordersPerPage; + const currentOrders = inTransitOrders.slice(indexOfFirstOrder, indexOfLastOrder); + + const totalPages = Math.ceil(inTransitOrders.length / ordersPerPage); + + const paginate = (pageNumber:any) => setCurrentPage(pageNumber); + + return ( +
+

Paid Orders

+
+ {currentOrders.length === 0 ? ( +
+ No orders found. +
+ ):( + currentOrders.map((order:any) => ( +
+
+
+

Id:

+

{order.id}

+
+
+

Status:

+

{order.status}

+
+
+
+

{order.updatedAt}

+
+

Total Price:

+

{order.totalPrice}

+
+ +
+
+ )) + )} +
+
+ {Array.from({ length: totalPages }, (_, index) => ( + + ))} +
+
+ ); +} + +export default InTransitOrders; diff --git a/src/containers/buyer/OderDetails.tsx b/src/containers/buyer/OderDetails.tsx new file mode 100644 index 0000000..9839fc1 --- /dev/null +++ b/src/containers/buyer/OderDetails.tsx @@ -0,0 +1,171 @@ +import { useState, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useGetOrderItemsQuery } from '../../services/orderItemsApi'; +import { useCancelOrderMutation } from '../../services/ordersAPI'; +import { setIsOrderItemsFetching, setOrderItems } from '../../redux/slices/orderItemsSlice'; +import FeedbackModal from './FeedbackModal'; +import ConfirmationModal from './ConfirmationModal'; +import { setActiveMenu } from '../../redux/slices/buyerDashboard'; + + +function OrderDetails({ orderss }: any) { + const dispatch = useDispatch(); + const { data } = orderss; + const [cancelOrderMutation] = useCancelOrderMutation(); + const activeMenuId = useSelector((state: any) => state.activeMenu.id); + const { data: orderDetails, error, isLoading } = useGetOrderItemsQuery(activeMenuId); + + // State for feedback modal + const [modalOpen, setModalOpen] = useState(false); + const [currentProduct, setCurrentProduct] = useState(null); + + // State for confirmation modal + const [confirmModalOpen, setConfirmModalOpen] = useState(false); + const [orderIdToCancel, setOrderIdToCancel] = useState(null); + + // Find the active order with 'delivered' or 'pending' status + const activeOrder = data.find((order: any) => order.id === activeMenuId); + const isPending = activeOrder?.status === 'pending'; + const isDelivered = activeOrder?.status === 'delivered'; + + // Handle API call and Redux state updates + useEffect(() => { + if (isLoading) { + dispatch(setIsOrderItemsFetching(true)); + } else if (orderDetails) { + dispatch(setOrderItems(orderDetails.orderItems)); + dispatch(setIsOrderItemsFetching(false)); + } + }, [isLoading, error, orderDetails, dispatch]); + + // Redux state for order items and loading/error status + const orderItems = useSelector((state: any) => state.orderItems.orderItems); + const isOrderItemsFetching = useSelector((state: any) => state.orderItems.isOrderItemsFetching); + const orderItemsError = useSelector((state: any) => state.orderItems.orderItemsError); + + if (isOrderItemsFetching) { + return

Loading...

; + } + + if (orderItemsError) { + return

Error loading order details: {orderItemsError}

; + } + + if (orderItems.length === 0) { + return

No order details found

; + } + + const handleCancelOrder = async (orderId: string) => { + setOrderIdToCancel(orderId); + setConfirmModalOpen(true); + }; + + const confirmCancelOrder = async () => { + if (!orderIdToCancel) return; + + try { + await cancelOrderMutation(orderIdToCancel).unwrap(); + dispatch(setActiveMenu({ activeMenu: "orders" })) + setConfirmModalOpen(false); + } catch (error) { + console.error('Error cancelling order:', error); + } + }; + + const handleAddFeedback = (product: any) => { + setCurrentProduct(product); + setModalOpen(true); + }; + + const handleModalClose = () => { + setModalOpen(false); + setCurrentProduct(null); + }; + + const handleFeedbackSubmit = (feedback: { message: string; image: File | null; rating: number }) => { + console.log('Feedback submitted for product:', currentProduct?.id); + console.log('Feedback details:', feedback); + setModalOpen(false); + setCurrentProduct(null); + }; + + return ( +
+

Order Details

+
+ {/* Order Items */} +
+

Items

+
+ {orderItems.map((product: any) => ( +
+
+ {product.name} + {product.name} +
+
+ Quantity: + {product.quantity} +
+
+ Price: + ${product.sizes[0].price} +
+ {/* Conditionally render the review button */} + {isDelivered && ( +
+ +
+ )} +
+ ))} +
+
+ + {/* Actions */} +
+ {isPending && ( + <> + + + + )} +
+
+ {currentProduct && ( + + )} + setConfirmModalOpen(false)} + onConfirm={confirmCancelOrder} + message="Are you sure you want to cancel this order?" + /> +
+ ); +} + +export default OrderDetails; + diff --git a/src/containers/buyer/Orders.tsx b/src/containers/buyer/Orders.tsx new file mode 100644 index 0000000..b49c0c3 --- /dev/null +++ b/src/containers/buyer/Orders.tsx @@ -0,0 +1,94 @@ +import { useState } from "react"; +import { useDispatch } from "react-redux"; +import { setActiveMenu } from "../../redux/slices/buyerDashboard"; +import DateFormatter from "../../utils/DateFormater"; + +function Orders({orderss}:any) { + const dispatch = useDispatch(); + const {data} = orderss + + const [currentPage, setCurrentPage] = useState(1); + const [filter, setFilter] = useState("All"); + + const ordersPerPage = 3; + + const handleFilterChange = (e:any) => { + setFilter(e.target.value); + setCurrentPage(1); + }; + + const filteredOrders = data.filter((order:any) => { + if (filter === "All") return true; + if (filter === "Pending Payments") return order.status === "pending"; + if (filter === "cancelled") return order.status === "cancelled"; + if (filter === "delivered") return order.status === "delivered"; + if (filter === "paid") return order.status === "paid"; + + + if (filter === "In Transit") return order.status === "paid" || order.status === "processing"; + return true; + }); + + const indexOfLastOrder = currentPage * ordersPerPage; + const indexOfFirstOrder = indexOfLastOrder - ordersPerPage; + const currentOrders = filteredOrders.slice(indexOfFirstOrder, indexOfLastOrder); + + const totalPages = Math.ceil(filteredOrders.length / ordersPerPage); + + const paginate = (pageNumber:any) => setCurrentPage(pageNumber); + + return ( +
+

My Orders

+
+ +
+
+ {currentOrders.map((order:any) => ( +
+
+
+

Id:

+

{order.id}

+
+
+

Status:

+

{order.status}

+
+
+
+

+
+

Total Price:

+

${order.totalPrice}

+
+ +
+
+ ))} +
+
+ {Array.from({ length: totalPages }, (_, index) => ( + + ))} +
+
+ ); +} + +export default Orders; diff --git a/src/containers/buyer/PendingPayments.tsx b/src/containers/buyer/PendingPayments.tsx new file mode 100644 index 0000000..b3bd2ba --- /dev/null +++ b/src/containers/buyer/PendingPayments.tsx @@ -0,0 +1,73 @@ +import { useState } from "react"; +import { useDispatch } from "react-redux"; +import { setActiveMenu } from "../../redux/slices/buyerDashboard"; +import DateFormatter from "../../utils/DateFormater"; + +function PendingPayments({orderss}:any) { + const dispatch = useDispatch(); + const {data} = orderss + const orders = data.filter((order:any) => order.status === "pending"); + + + const [currentPage, setCurrentPage] = useState(1); + const ordersPerPage = 4; + + const indexOfLastOrder = currentPage * ordersPerPage; + const indexOfFirstOrder = indexOfLastOrder - ordersPerPage; + const currentOrders = orders.slice(indexOfFirstOrder, indexOfLastOrder); + + const totalPages = Math.ceil(orders.length / ordersPerPage); + + const paginate = (pageNumber:any) => setCurrentPage(pageNumber); + + return ( +
+

Pending Payments

+
+ {currentOrders.length === 0 ? ( +
+ No orders found. +
+ ):( + currentOrders.map((order:any) => ( +
+
+
+

Id:

+

{order.id}

+
+
+

Status:

+

{order.status}

+
+
+
+

+
+

Total Price:

+

{order.totalPrice}

+
+ +
+
+ )))} +
+
+ {Array.from({ length: totalPages }, (_, index) => ( + + ))} +
+
+ ); +} + +export default PendingPayments; diff --git a/src/containers/buyer/Profile.tsx b/src/containers/buyer/Profile.tsx new file mode 100644 index 0000000..4687cac --- /dev/null +++ b/src/containers/buyer/Profile.tsx @@ -0,0 +1,134 @@ +import React, { useState, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { useUpdateUserMutation } from '../../services/userApi'; +import { updateUserInfo } from '../../redux/slices/userSlice'; +import { useDispatch } from 'react-redux'; + +const Profile = () => { + const dispatch = useDispatch(); + const userInfo = useSelector((state: any) => state.user.userInfo); + const [email, setEmail] = useState(''); + const [profilePhoto, setProfilePhoto] = useState(null); + const [selectedImage, setSelectedImage] = useState(); + const [firstNameState, setFirstName] = useState(''); + const [lastNameState, setLastName] = useState(''); + + const [updateUser] = useUpdateUserMutation(); + + useEffect(() => { + if (userInfo) { + setEmail(userInfo.email || ''); + setProfilePhoto(userInfo.photoUrl || null); + setFirstName(userInfo.firstName || ''); + setLastName(userInfo.lastName || ''); + } + }, [userInfo]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + try { + const formData = new FormData(); + formData.append('email', email); + formData.append('firstName', firstNameState); + formData.append('lastName', lastNameState); + if (selectedImage) { + formData.append('profileImage', selectedImage); + } + if (userInfo?.id) { + const res = await updateUser({ userId: userInfo.id, data: formData }).unwrap(); + dispatch(updateUserInfo(res.data)); + alert('Profile updated successfully'); + } else { + alert('User ID is not available'); + } + } catch (error) { + alert('Failed to update profile'); + } + }; + + const handleProfilePhotoChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + setSelectedImage(e.target.files[0]); + const reader = new FileReader(); + reader.onload = () => { + setProfilePhoto(reader.result as string); + }; + reader.readAsDataURL(e.target.files[0]); + } + }; + + if (!userInfo) return
Loading...
; + + return ( +
+

Update Profile

+
+
+ + setFirstName(e.target.value)} + required + /> +
+
+ + setLastName(e.target.value)} + required + /> +
+
+ + setEmail(e.target.value)} + required + /> +
+
+ + + {profilePhoto && Profile} +
+
+ +
+
+
+ ); +}; + +export default Profile; diff --git a/src/containers/buyer/ResetPassword.tsx b/src/containers/buyer/ResetPassword.tsx new file mode 100644 index 0000000..da8eafe --- /dev/null +++ b/src/containers/buyer/ResetPassword.tsx @@ -0,0 +1,101 @@ +import React, { useState } from 'react'; +import { useUpdatePasswordMutation } from '../../services/authAPI'; // Import the mutation hook + +function ResetPassword() { + const [oldPassword, setOldPassword] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [updatePassword] = useUpdatePasswordMutation(); // Use the mutation hook + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (newPassword !== confirmPassword) { + alert('New password and confirmation do not match!'); + return; + } + + // Confirmation box + const isConfirmed = window.confirm('Are you sure you want to reset your password?'); + + if (!isConfirmed) { + return; + } + + try { + const response = await updatePassword({ oldPassword, newPassword }).unwrap(); + alert(response.message); + } catch (error) { + console.log('Failed to update password', error); + } + }; + + return ( +
+

Reset Password

+
+
+ + setOldPassword(e.target.value)} + className="w-full px-4 h-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500" + placeholder="Current password" + required + /> +
+
+ + setNewPassword(e.target.value)} + className="w-full px-4 h-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500" + placeholder="New password" + required + /> +
+
+ + setConfirmPassword(e.target.value)} + className="w-full px-4 h-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500" + placeholder="Confirm new password" + required + /> +
+
+ +
+
+
+ ); +} + +export default ResetPassword; diff --git a/src/containers/buyer/ReturnRefund.tsx b/src/containers/buyer/ReturnRefund.tsx new file mode 100644 index 0000000..dfad87c --- /dev/null +++ b/src/containers/buyer/ReturnRefund.tsx @@ -0,0 +1,7 @@ +function ReturnRefund() { + return ( +
Return and Refund
+ ) +} + +export default ReturnRefund \ No newline at end of file diff --git a/src/containers/buyer/WishList.tsx b/src/containers/buyer/WishList.tsx new file mode 100644 index 0000000..7b823b3 --- /dev/null +++ b/src/containers/buyer/WishList.tsx @@ -0,0 +1,65 @@ +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useGetUserWishlistQuery, useClearWishlistMutation } from '../../services/wishlistApi'; +import { clearWishLists, setWishLists } from '../../redux/slices/wishlistSlice'; + +function WishList() { + const dispatch = useDispatch(); + const { data, isLoading } = useGetUserWishlistQuery(); + const [clearWishlist] = useClearWishlistMutation(); + const wishlistItems = useSelector((state: any) => state.wishlist.items); + + const handleClearWishlist = async () => { + try { + await clearWishlist().unwrap(); + dispatch(clearWishLists()); + } catch (error) { + } + }; + + useEffect(() => { + if (data) { + dispatch(setWishLists(data.data)); + } + }, [data, dispatch]); + + if (isLoading) return
Loading...
; + + return ( +
+

Wish List

+ + {wishlistItems.length === 0 ? ( +
No items in your wishlist
+ ) : ( +
+ {wishlistItems.map((item:any) => ( +
+
+ {item.name} +
+
+

{item.name}

+

${item.price}

+
+
+ ))} +
+ )} +
+ ); +} + +export default WishList; diff --git a/src/pages/BuyerProfile.tsx b/src/pages/BuyerProfile.tsx new file mode 100644 index 0000000..20d3bf5 --- /dev/null +++ b/src/pages/BuyerProfile.tsx @@ -0,0 +1,161 @@ +import { useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { useGetUserByIdQuery } from '../services/userApi'; +import { useGetOrdersQuery } from '../services/ordersAPI'; +import { setAllOrders, setIsOrdersFetched } from '../redux/slices/orderSlice'; +import { setFetchedUser } from '../redux/slices/userSlice'; +import Navbar from "../components/navbar/Navbar"; +import Footer from "../components/footer/Footer"; +import NotFoundPage from "./NotFoundPage"; +import ErrorPage from "../containers/buyer/ErrorPage"; +import Account from "../containers/buyer/Account"; +import ResetPassword from "../containers/buyer/ResetPassword"; +import Orders from "../containers/buyer/Orders"; +import PendingPayments from "../containers/buyer/PendingPayments"; +import FeedBack from "../containers/buyer/FeedBack"; +import InTransit from "../containers/buyer/InTransit"; +import ReturnRefund from "../containers/buyer/ReturnRefund"; +import Profile from "../containers/buyer/Profile"; +import OrderDetails from "../containers/buyer/OderDetails"; +import WishList from "../containers/buyer/WishList"; +import { setActiveMenu } from "../redux/slices/buyerDashboard"; + +function BuyerProfile() { + const activeMenu = useSelector((state: any) => state.activeMenu.activeMenu); + const dispatch = useDispatch(); + + // Fetch user info from Redux store + const userInfo = useSelector((state: any) => state.user.userInfo) || { + firstName: 'Anonymous', + lastName: 'Anonymous', + photoUrl: '', + email: 'anonymous', + Role: 'Guest' + }; + + const user = useSelector((state: any) => state.user); + const userId = user.userId ? user.userId.replace(/"/g, '') : ''; + + // Fetch user data + const { isLoading: isFetchingUser, isSuccess: isUserFetched, isError: isUserError, error: userError, data: userData } = useGetUserByIdQuery(userId); + + // Fetch orders data + const { allOrders }: any = useSelector((state: any) => state.orders); + const { isLoading: isFetchingOrders, isError: isOrdersError, error: ordersError, data: orders } = useGetOrdersQuery(); + + // Set user info when user data is fetched + useEffect(() => { + if (isUserFetched && userData?.message) { + const { id,firstName, lastName, photoUrl, email, Role } = userData.message; + dispatch(setFetchedUser({ id,firstName, lastName, photoUrl, email, Role, phoneNumber: '' })); + } else { + dispatch(setFetchedUser({ + id: 'Anonymous', + firstName: 'Anonymous', + lastName: 'Anonymous', + phoneNumber: '', + email: 'anonymous', + Role: 'Guest' + })); + } + }, [isUserFetched, userData, dispatch]); + + // Orders on page load + useEffect(() => { + if (!isFetchingOrders && orders) { + dispatch(setAllOrders(orders)); + dispatch(setIsOrdersFetched(true)); + } + }, [orders, dispatch, isFetchingOrders]); + + // Early return for loading and not found states + if (isFetchingUser || isFetchingOrders) { + return
Loading...
; + } + + if (isUserError || userError) { + return ; + } + + if (isOrdersError || ordersError) { + return ; + } + + if (!isUserFetched || userData?.message.Role.name !== 'buyer') { + return
; + } + + const ordersToDisplay = allOrders.length ? allOrders.data : orders; + + const renderContent = () => { + switch (activeMenu) { + case "account": + return ; + case "orders": + return ; + case "wish": + return ; + case "pendingpayments": + return ; + case "returnrefund": + return ; + case "feedback": + return ; + case "intransit": + return ; + case "reset": + return ; + case "profile": + return ; + case "order-details": + return ; + default: + return
Invalid Selection
; + } + }; + + return ( +
+ +
+
+
+ Buyer Dashboard +
+
    +
  • dispatch(setActiveMenu({ activeMenu: "account" }))} + > + My Account +
  • +
  • dispatch(setActiveMenu({ activeMenu: "orders" }))} + > + My Orders +
  • +
  • dispatch(setActiveMenu({ activeMenu: "wish" }))} + > + Wish List +
  • +
  • dispatch(setActiveMenu({ activeMenu: "reset" }))} + > + Reset Password +
  • +
+
+
+ {renderContent()} +
+
+
+
+ ); +} + +export default BuyerProfile; diff --git a/src/redux/slices/buyerDashboard.tsx b/src/redux/slices/buyerDashboard.tsx new file mode 100644 index 0000000..c6035d8 --- /dev/null +++ b/src/redux/slices/buyerDashboard.tsx @@ -0,0 +1,30 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; + +interface ActiveMenuState { + activeMenu: string; + id?: string; + status?: string +} + +const initialState: ActiveMenuState = { + activeMenu: "account", +}; + +const activeMenuSlice = createSlice({ + name: "activeMenu", + initialState, + reducers: { + setActiveMenu: (state, action: PayloadAction<{ activeMenu: string; id?: string }>) => { + state.activeMenu = action.payload.activeMenu; + if (action.payload.id) { + state.id = action.payload.id; + } else { + delete state.id; + } + }, + }, +}); + +export const { setActiveMenu } = activeMenuSlice.actions; + +export default activeMenuSlice.reducer; diff --git a/src/redux/slices/cartSlice.ts b/src/redux/slices/cartSlice.ts new file mode 100644 index 0000000..fa3493d --- /dev/null +++ b/src/redux/slices/cartSlice.ts @@ -0,0 +1,29 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { Product } from '../../types/Types' + +interface CartState { + products: Product[]; + isCartFetched: boolean; +} + +const initialState: CartState = { + products: [], + isCartFetched: false, +}; + +const cartSlice = createSlice({ + name: 'cart', + initialState, + reducers: { + setCartProducts: (state, action: PayloadAction) => { + state.products = action.payload; + }, + setIsCartFetched: (state, action: PayloadAction) => { + state.isCartFetched = action.payload; + } + }, +}); + +export const { setCartProducts, setIsCartFetched } = cartSlice.actions; + +export default cartSlice.reducer; diff --git a/src/redux/slices/feedbackSlice.tsx b/src/redux/slices/feedbackSlice.tsx new file mode 100644 index 0000000..9c92585 --- /dev/null +++ b/src/redux/slices/feedbackSlice.tsx @@ -0,0 +1,34 @@ +// redux/slices/feedbackSlice.ts +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface FeedbackState { + isSubmitting: boolean; + error: string | null; +} + +const initialState: FeedbackState = { + isSubmitting: false, + error: null, +}; + +const feedbackSlice = createSlice({ + name: 'feedback', + initialState, + reducers: { + submitFeedbackStart(state) { + state.isSubmitting = true; + state.error = null; + }, + submitFeedbackSuccess(state) { + state.isSubmitting = false; + }, + submitFeedbackFailure(state, action: PayloadAction) { + state.isSubmitting = false; + state.error = action.payload; + }, + }, +}); + +export const { submitFeedbackStart, submitFeedbackSuccess, submitFeedbackFailure } = feedbackSlice.actions; + +export default feedbackSlice.reducer; diff --git a/src/redux/slices/orderItemsSlice.ts b/src/redux/slices/orderItemsSlice.ts new file mode 100644 index 0000000..06c7e25 --- /dev/null +++ b/src/redux/slices/orderItemsSlice.ts @@ -0,0 +1,39 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { OrderItem } from '../../types/Types' + +interface OrderItemsState { + orderItems: OrderItem[]; + isOrderItemsFetching: boolean; + orderItemsError: string | null; +} + +const initialState: OrderItemsState = { + orderItems: [], + isOrderItemsFetching: false, + orderItemsError: null, +}; + +const orderItemsSlice = createSlice({ + name: 'orderItems', + initialState, + reducers: { + setOrderItems: (state, action: PayloadAction) => { + state.orderItems = action.payload; + }, + setIsOrderItemsFetching: (state, action: PayloadAction) => { + state.isOrderItemsFetching = action.payload; + }, + setOrderItemsError: (state, action: PayloadAction) => { + state.orderItemsError = action.payload; + }, + clearOrderItems: (state) => { + state.orderItems = []; + state.isOrderItemsFetching = false; + state.orderItemsError = null; + }, + }, +}); + +export const {setOrderItems, setIsOrderItemsFetching, setOrderItemsError, clearOrderItems} = orderItemsSlice.actions; + +export default orderItemsSlice.reducer; diff --git a/src/redux/slices/orderSlice.tsx b/src/redux/slices/orderSlice.tsx new file mode 100644 index 0000000..e9718e0 --- /dev/null +++ b/src/redux/slices/orderSlice.tsx @@ -0,0 +1,42 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { Order } from '../../types/Types'; + +interface OrdersState { + isOrdersFetched: boolean; + error: string | null; + allOrders: Order[]; +} + +const initialState: OrdersState = { + isOrdersFetched: false, + error: null, + allOrders: [], +}; + +const ordersSlice = createSlice({ + name: 'orders', + initialState, + reducers: { + setLoading: (state, action: PayloadAction) => { + state.isOrdersFetched = action.payload; + }, + setIsOrdersFetched: (state, action: PayloadAction) => { + state.isOrdersFetched = action.payload; + }, + setError: (state, action: PayloadAction) => { + state.error = action.payload; + }, + cancelOrder: (state, action: PayloadAction) => { + state.allOrders = state.allOrders.map(order => + order.id === action.payload ? { ...order, status: 'cancelled' } : order + ); + }, + setAllOrders: (state, action: PayloadAction) => { + state.allOrders = action.payload; + }, + }, +}); + +export const { setLoading, setError, setAllOrders, setIsOrdersFetched, cancelOrder } = ordersSlice.actions; + +export default ordersSlice.reducer; diff --git a/src/redux/slices/userSlice.ts b/src/redux/slices/userSlice.ts index 98e00bf..b723635 100644 --- a/src/redux/slices/userSlice.ts +++ b/src/redux/slices/userSlice.ts @@ -1,11 +1,15 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { UserFormValues } from '../../types/Types'; export interface UserState { token: string | null; userId: string | null; role: string | null; - photoUrl: string | null; is2FAEnabled: boolean; + photoUrl: File | null; + userInfo: UserFormValues | null | string; + isLoading: boolean; + error: string | null; } const initialState: UserState = { @@ -14,6 +18,9 @@ const initialState: UserState = { role: 'buyer', photoUrl: null, is2FAEnabled: false, + userInfo: null, + isLoading: false, + error: null, }; const userSlice = createSlice({ @@ -39,7 +46,7 @@ const userSlice = createSlice({ setRole: (state, action: PayloadAction) => { state.role = action.payload; }, - setProfile: (state, action: PayloadAction) => { + setProfile: (state, action: PayloadAction) => { state.photoUrl = action.payload; }, enable2FA: (state, action: PayloadAction) => { @@ -50,12 +57,29 @@ const userSlice = createSlice({ localStorage.removeItem('user'); state.token = null; state.userId = null; + state.role = 'guest'; state.photoUrl = null; state.is2FAEnabled = false; + state.userInfo = null; + state.isLoading = false; + state.error = null; }, + setLoading: (state, action: PayloadAction) => { + state.isLoading = action.payload; + }, + setError: (state, action: PayloadAction) => { + state.error = action.payload; + }, + updateUserInfo: (state, action: PayloadAction) => { + state.userInfo = action.payload; + state.photoUrl = action.payload.photoUrl || state.photoUrl; + }, + setFetchedUser: (state, action: PayloadAction) => { + state.userInfo = action.payload; + } }, }); -export const { setToken, setUser, clearUserData, setRole, setProfile, enable2FA } = userSlice.actions; +export const { setToken, setUser, clearUserData, setRole, setProfile, setLoading, setError, updateUserInfo, setFetchedUser, enable2FA } = userSlice.actions; export default userSlice.reducer; diff --git a/src/redux/slices/wishlistSlice.ts b/src/redux/slices/wishlistSlice.ts new file mode 100644 index 0000000..ca75b06 --- /dev/null +++ b/src/redux/slices/wishlistSlice.ts @@ -0,0 +1,33 @@ +// src/features/wishlist/wishlistSlice.ts +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { WishlistItem } from '../../types/Types'; // Adjust the path to your type definitions + +interface WishlistState { + isFetched: boolean; + items: WishlistItem[]; +} + +const initialState: WishlistState = { + isFetched: false, + items: [], +}; + +const wishlistSlice = createSlice({ + name: 'wishlist', + initialState, + reducers: { + setIsFetched: (state, action: PayloadAction) => { + state.isFetched = action.payload; + }, + setWishLists: (state, action: PayloadAction) => { + state.items = action.payload; + }, + clearWishLists: (state) => { + state.items = []; + }, + }, +}); + +export const { setIsFetched, setWishLists, clearWishLists } = wishlistSlice.actions; + +export default wishlistSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts index a5aba40..d1fee24 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -7,6 +7,12 @@ import userReducer from './slices/userSlice'; import productReducer from './slices/productsSlice'; import categoriesReducer from './slices/categorySlice'; import sidebarSlice from './slices/sidebarSlice'; +import activeMenuReducer from './slices/buyerDashboard'; +import orderReducer from './slices/orderSlice'; +import cartReducer from './slices/cartSlice'; +import orderItemsReducer from './slices/orderItemsSlice'; +import wishlistReducer from './slices/wishlistSlice'; + export const store = configureStore({ reducer: { @@ -15,6 +21,11 @@ export const store = configureStore({ register: registerReducer, sidebar: sidebarSlice, category: categoriesReducer, + activeMenu: activeMenuReducer, + orders: orderReducer, + orderItems: orderItemsReducer, + cart: cartReducer, + wishlist: wishlistReducer, [mavericksApi.reducerPath]: mavericksApi.reducer, }, middleware: getDefaultMiddleware => getDefaultMiddleware().concat(mavericksApi.middleware), diff --git a/src/services/authAPI.ts b/src/services/authAPI.ts index ec0b9f2..6031965 100644 --- a/src/services/authAPI.ts +++ b/src/services/authAPI.ts @@ -21,7 +21,17 @@ const authAPI = mavericksApi.injectEndpoints({ url: '/auth/google/callback', }), }), + updatePassword: builder.mutation({ + query: (passwordData) => ({ + url: `/auth/update-password`, + method: 'PUT', + body: passwordData, + headers: { + Authorization: `Bearer ${localStorage.getItem('token')}` + }, + }), + }), }), }); -export const { useRegisterUserMutation, useLoginUserMutation, useGoogleAuthenticationQuery } = authAPI; +export const { useRegisterUserMutation, useLoginUserMutation, useGoogleAuthenticationQuery, useUpdatePasswordMutation } = authAPI; diff --git a/src/services/cartApi.ts b/src/services/cartApi.ts index 7644c5c..1febb9c 100644 --- a/src/services/cartApi.ts +++ b/src/services/cartApi.ts @@ -1,8 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { mavericksApi } from '.'; -import { DeleteCartQueryParams, ICart, ICartsResponse } from '../utils/schemas'; - - +import { DeleteCartQueryParams, ICartsResponse } from '../utils/schemas'; +import { ICart } from '../utils/schemas'; export const cartApi = mavericksApi.injectEndpoints({ endpoints: builder => ({ diff --git a/src/services/categoryApi.ts b/src/services/categoryApi.ts index dbbeca7..1f87769 100644 --- a/src/services/categoryApi.ts +++ b/src/services/categoryApi.ts @@ -22,8 +22,18 @@ export const categoryApi = mavericksApi.injectEndpoints({ }, }), }), + getOrders: builder.query({ + query: () => ({ + url: 'orders', + method: 'GET', + headers: { + Accept: 'application/json', + authorization: localStorage.getItem('token') || '', + }, + }), + }), }), overrideExisting: false, }); -export const { useCreateCategoryMutation, useGetCategoryQuery } = categoryApi; +export const { useCreateCategoryMutation, useGetCategoryQuery, useGetOrdersQuery } = categoryApi; diff --git a/src/services/feedbackApi.ts b/src/services/feedbackApi.ts new file mode 100644 index 0000000..fc4819c --- /dev/null +++ b/src/services/feedbackApi.ts @@ -0,0 +1,29 @@ +// services/feedbackApi.ts +import { mavericksApi } from '.'; + +export const feedbackApi = mavericksApi.injectEndpoints({ + endpoints: builder => ({ + submitFeedback: builder.mutation({ + query: ({ productId, feedbackData }: { productId: string; feedbackData: { message: string; image: File | null; rating: number } }) => { + const formData = new FormData(); + formData.append('feedback', feedbackData.message); + formData.append('rating', feedbackData.rating.toString()); + formData.append('productId', productId); + if (feedbackData.image) formData.append('feedbackImage', feedbackData.image); // Match field name with multerUpload + + return { + url: `products/${productId}/review`, + method: 'POST', + headers: { + Accept: 'multipart/form-data', + authorization: localStorage.getItem('token') || '', + }, + body: formData, + }; + }, + }), + }), + overrideExisting: false, +}); + +export const { useSubmitFeedbackMutation } = feedbackApi; diff --git a/src/services/orderItemsApi.ts b/src/services/orderItemsApi.ts new file mode 100644 index 0000000..92a2935 --- /dev/null +++ b/src/services/orderItemsApi.ts @@ -0,0 +1,20 @@ +import { mavericksApi } from '.'; +import { Order } from '../types/Types'; + +export const orderApi = mavericksApi.injectEndpoints({ + endpoints: (builder) => ({ + getOrderItems: builder.query({ + query: (orderId) => ({ + url: `orders/${orderId}`, + method: 'GET', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${localStorage.getItem('token') || ''}`, + }, + }), + }), + }), + overrideExisting: false, +}); + +export const { useGetOrderItemsQuery } = orderApi; diff --git a/src/services/ordersAPI.ts b/src/services/ordersAPI.ts new file mode 100644 index 0000000..07cb797 --- /dev/null +++ b/src/services/ordersAPI.ts @@ -0,0 +1,30 @@ +import { mavericksApi } from '.'; +import { Order } from '../types/Types' + +export const orderApi = mavericksApi.injectEndpoints({ + endpoints: (builder) => ({ + getOrders: builder.query({ + query: () => ({ + url: 'orders', + method: 'GET', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${localStorage.getItem('token') || ''}`, + }, + }), + }), + cancelOrder: builder.mutation({ + query: (orderId) => ({ + url: `orders/${orderId}/cancel`, + method: 'PUT', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${localStorage.getItem('token') || ''}`, + }, + }), + }), + }), + overrideExisting: false, +}); + +export const { useGetOrdersQuery, useCancelOrderMutation } = orderApi; diff --git a/src/services/userApi.ts b/src/services/userApi.ts index 6eb4769..7d95144 100644 --- a/src/services/userApi.ts +++ b/src/services/userApi.ts @@ -43,8 +43,24 @@ export const userApi = mavericksApi.injectEndpoints({ body: { roleId }, }), }), + updateUser: builder.mutation({ + query: ({ userId, data }) => { + return { + url: `users/edit/${userId}`, + method: 'PATCH', + body: data, + }; + }, + }), }), overrideExisting: false, }); -export const { useGetUserByIdQuery, useGetUsersQuery,useGetSellersQuery, useGetBuyersQuery, useUpdateUserRoleMutation } = userApi; +export const { + useGetUserByIdQuery, + useGetUsersQuery, + useGetSellersQuery, + useGetBuyersQuery, + useUpdateUserRoleMutation, + useUpdateUserMutation, +} = userApi; diff --git a/src/services/wishlistApi.ts b/src/services/wishlistApi.ts index 17d3534..f48ef40 100644 --- a/src/services/wishlistApi.ts +++ b/src/services/wishlistApi.ts @@ -1,21 +1,28 @@ import { mavericksApi } from '.'; +import { ApiResponse } from '../utils/schemas'; export const wishlistApi = mavericksApi.injectEndpoints({ endpoints: builder => ({ // GET USERS' WISHLIST - getUserWishlist: builder.query({ + getUserWishlist: builder.query({ query: () => '/wishlist/get-wishlist', }), - // ADD PRODUCT TO WISHLIST addProductToWishlist: builder.mutation({ query: (sizeId: string) => ({ url: `wishlist/add-wishlist/${sizeId}`, method: 'POST', }), }), + + clearWishlist: builder.mutation({ + query: () => ({ + url: '/wishlist/clear', + method: 'DELETE', + }), + }), }), overrideExisting: false, }); -export const { useAddProductToWishlistMutation, useGetUserWishlistQuery } = wishlistApi; +export const { useAddProductToWishlistMutation, useGetUserWishlistQuery, useClearWishlistMutation } = wishlistApi; diff --git a/src/types/Types.ts b/src/types/Types.ts index c522a98..75c7355 100644 --- a/src/types/Types.ts +++ b/src/types/Types.ts @@ -72,6 +72,8 @@ export interface NotificationProps { } export interface User { + data: UserFormValues; + message(message: any): unknown; id: string; firstName: string; lastName: string; @@ -101,3 +103,70 @@ export type ChatMessage = { senderId: string; User: ChatUser; }; +export interface Order { + orderId: string; + orderItems: any; + id: string; + status: string; + shippingAddress1: string; + shippingAddress2: string | null; + phone: string; + zipCode: string | null; + city: string; + country: string; + userId: string; + totalPrice: number; + expectedDeliveryDate: string | null; + createdAt: string; + updatedAt: string; +}; + +interface CartProduct { + id: string; + name: string; + sizes: Size[]; + sellerId: string; + image: string; + quantity: number; + createdAt: string; +} + +export interface Cart { + cartId: string; + cartProducts: CartProduct[]; +} + +export interface OrderItem { + id: string; + name: string; + sizes: Size[]; + sellerId: string; + images: string[]; + quantity: number; + price: number; + createdAt: string; +} + +export interface OrderResponse { + ok: boolean; + orderId: string; + orderItems: OrderItem[]; +} + +export interface UserFormValues { + id:string + firstName: string; + lastName: string; + phoneNumber: string; + email: string; + photoUrl?: File; + Role: string +} + +export interface WishlistItem { + id: string; + image: string; + name: string; + price: number; + productId: string; +} \ No newline at end of file diff --git a/src/utils/DateFormater.tsx b/src/utils/DateFormater.tsx new file mode 100644 index 0000000..1f0ab84 --- /dev/null +++ b/src/utils/DateFormater.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +interface DateFormatterProps { + date: Date | string; +} + +const DateFormatter: React.FC = ({ date }) => { + const dateObj = typeof date === 'string' ? new Date(date) : date; + + const formatDate = (date: Date) => { + const day = String(date.getDate()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const year = date.getFullYear(); + + return `${day}/${month}/${year}`; + }; + + return ( + {formatDate(dateObj)} + ); +}; + +export default DateFormatter; diff --git a/src/utils/schemas.ts b/src/utils/schemas.ts index 13a7599..2cb2eae 100644 --- a/src/utils/schemas.ts +++ b/src/utils/schemas.ts @@ -193,3 +193,4 @@ export const chatSchema = z.object({ }); export type ChatData = z.infer; +