From c3cdba94baf747ed2c1c2d5332eb203e961b3b1a Mon Sep 17 00:00:00 2001 From: tchami <131756888+tchamianest@users.noreply.github.com> Date: Thu, 18 Jul 2024 10:33:29 +0200 Subject: [PATCH] bugFix(allPage): Fix all remain bug into champs Bay --- src/app/(profile)/profile-edit/page.tsx | 67 ++--- src/app/(profile)/profile/page.tsx | 2 - src/app/dashboard/profile/page.tsx | 1 - src/app/globals.css | 3 + src/app/products/[id]/page.tsx | 58 ++-- src/components/AssigningRoles.tsx | 2 +- src/components/BuyerWishlist.tsx | 349 +++++++++++++---------- src/components/Card.tsx | 87 +++--- src/components/DashNavbar.tsx | 4 +- src/components/EditProfile.tsx | 68 ++--- src/components/GoogleButton.tsx | 2 +- src/components/LatestCard.tsx | 6 +- src/components/Product/editProduct.tsx | 11 +- src/components/ReviewCard.tsx | 75 +++-- src/components/Table.tsx | 39 ++- src/components/chartssection.tsx | 72 ++--- src/components/headerDash.tsx | 2 +- src/components/profile/About.tsx | 31 +- src/components/profile/ActiveUsers.tsx | 61 ---- src/components/profile/Order.tsx | 103 ++++--- src/components/profile/ProfileHeader.tsx | 177 ++++-------- src/components/profile/wishlist.tsx | 38 ++- src/components/singleproduct.tsx | 38 ++- src/utils/axios.ts | 2 +- src/validations/productValidation.tsx | 71 +++-- src/validations/userProfileSchema.ts | 71 ++--- 26 files changed, 741 insertions(+), 699 deletions(-) delete mode 100644 src/components/profile/ActiveUsers.tsx diff --git a/src/app/(profile)/profile-edit/page.tsx b/src/app/(profile)/profile-edit/page.tsx index a597b53..169c3e9 100644 --- a/src/app/(profile)/profile-edit/page.tsx +++ b/src/app/(profile)/profile-edit/page.tsx @@ -3,24 +3,19 @@ import React, { useEffect, useState, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { AppDispatch, RootState } from '@/redux/store'; import { getUserProfile, updateUserProfile } from '@/redux/slices/profileSlice'; - import { toast } from 'react-toastify'; import { showToast } from '@/helpers/toast'; import { useForm, SubmitHandler, useWatch } from 'react-hook-form'; - import updateSchema from '@/validations/userProfileSchema'; import { useRouter } from 'next/navigation'; import type { z } from 'zod'; - import { zodResolver } from '@hookform/resolvers/zod'; - import InputBox from '@/components/InputBox'; import UpdatePasswords from '@/components/updatepassword'; import Header from '@/components/Header'; import Footer from '@/components/Footer'; -import { User, Cake, Award } from 'lucide-react' +import { User, Cake, Award } from 'lucide-react'; type FormSchemaType = z.infer; - const UserProfileForm: React.FC = () => { const route = useRouter(); const [showlModal, setShowmodal] = useState(false); @@ -43,26 +38,22 @@ const UserProfileForm: React.FC = () => { firstName: '', lastName: '', phone: '', - address:'', + address: '', birthDate: '', preferredLanguage: '', whereYouLive: '', - preferredCurrency: '', + preferredCurrency: 'RWf', billingAddress: '', }, }); - const watchedValues = useWatch({ control }); - const [profileImage, setProfileImage] = useState(''); const [imageFile, setImageFile] = useState(null); const [isLoading, setIsLoading] = useState(false); const fileInputRef = useRef(null); - useEffect(() => { dispatch(getUserProfile()); }, [dispatch]); - useEffect(() => { if (user && user.User) { setValue('firstName', user.User.firstName || ''); @@ -76,7 +67,6 @@ const UserProfileForm: React.FC = () => { setProfileImage(user.User.profileImage || ''); } }, [user, setValue]); - const handleImageChange = (e: React.ChangeEvent) => { if (e.target.files && e.target?.files?.length === 1) { const file = e.target.files[0]; @@ -88,14 +78,11 @@ const UserProfileForm: React.FC = () => { reader.readAsDataURL(file); } }; - const handleImageClick = () => { fileInputRef.current?.click(); }; - const getExistingImage = async (): Promise => { if (!user?.User?.profileImage) return null; - try { const response = await fetch(user.User.profileImage); const blob = await response.blob(); @@ -105,26 +92,20 @@ const UserProfileForm: React.FC = () => { return null; } }; - const onSubmit: SubmitHandler = async (data) => { const formDataToSend = new FormData(); - Object.entries(data).forEach(([key, value]) => { formDataToSend.append(key, value as string); }); - let imageToUpload: File | null = null; - if (imageFile) { imageToUpload = imageFile; } else { imageToUpload = await getExistingImage(); } - if (imageToUpload) { formDataToSend.append('profileImage', imageToUpload); } - try { setIsLoading(true); const response = await dispatch(updateUserProfile(formDataToSend)); @@ -137,7 +118,6 @@ const UserProfileForm: React.FC = () => { currentProfile.User = response.payload.User; localStorage.setItem('profile', JSON.stringify(currentProfile)); } - toast.success('Profile updated successfully'); route.push('/profile'); } else if (updateUserProfile.rejected.match(response)) { @@ -158,7 +138,6 @@ const UserProfileForm: React.FC = () => { toast.error(`Failed to update profile: ${errorMessage}`); } }; - if (error) { console.error('Error fetching user data:', error); showToast( @@ -166,22 +145,23 @@ const UserProfileForm: React.FC = () => { 'error', ); } - if (!user) { - return(
-
-
) + return ( +
+
+
+ ); } const getCurrentDate = () => { const today = new Date(); - const year = today.getFullYear() - 10; + const year = today.getFullYear() - 10; const month = String(today.getMonth() + 1).padStart(2, '0'); const day = String(today.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }; - function convertToNormalDate(isoTimestamp:any) { + function convertToNormalDate(isoTimestamp: any) { const date = new Date(isoTimestamp); - const options:any = { year: 'numeric', month: 'long', day: 'numeric' }; + const options: any = { year: 'numeric', month: 'long', day: 'numeric' }; return date.toLocaleDateString('en-US', options); } function formatDate(dateString: string) { @@ -191,10 +171,8 @@ const UserProfileForm: React.FC = () => { const month = (date.getMonth() + 1).toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0'); const formattedDate = `${day}-${month}-${year}`; - return formattedDate; } - return (
@@ -206,13 +184,12 @@ const UserProfileForm: React.FC = () => {
Profile { e.currentTarget.src = '/unknown.jpg'; }} - /> { {user?.User?.Role.name || 'buyer'}

-
  • { d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /> - {convertToNormalDate(watchedValues.birthDate) || watchedValues.birthDate || 'YYYY-MM-DD'} + {watchedValues.birthDate + ? watchedValues.birthDate.match(/^\d{2}-\d{2}-\d{4}$/) + ? watchedValues.birthDate + : formatDate(watchedValues.birthDate) + : 'YYYY-MM-DD'}
  • {
-

Update Profile

@@ -361,7 +340,6 @@ const UserProfileForm: React.FC = () => { placeholder="Birth date" {...register('birthDate')} error={errors.birthDate?.message} - max={getCurrentDate()} />
@@ -370,7 +348,7 @@ const UserProfileForm: React.FC = () => { nameuse="Address" type="text" placeholder="Address" - error ={errors.address?.message} + error={errors.address?.message} />
@@ -431,14 +409,11 @@ const UserProfileForm: React.FC = () => {
-
- +
+
- -
); }; - -export default UserProfileForm; \ No newline at end of file +export default UserProfileForm; diff --git a/src/app/(profile)/profile/page.tsx b/src/app/(profile)/profile/page.tsx index ff7b43a..a7e77e9 100644 --- a/src/app/(profile)/profile/page.tsx +++ b/src/app/(profile)/profile/page.tsx @@ -1,7 +1,6 @@ 'use client'; // pages/profile.tsx import About from '@/components/profile/About'; -import ActiveUser from '@/components/profile/ActiveUsers'; import Order from '@/components/profile/Order'; import ProfileHeader from '@/components/profile/ProfileHeader'; import Wishlist from '@/components/profile/wishlist'; @@ -23,7 +22,6 @@ const ProfilePage = () => {
-
diff --git a/src/app/dashboard/profile/page.tsx b/src/app/dashboard/profile/page.tsx index 41f0003..e2576f4 100644 --- a/src/app/dashboard/profile/page.tsx +++ b/src/app/dashboard/profile/page.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react'; import LayoutDashboard from '@/components/LayoutDashboard'; import About from '@/components/profile/About'; -import ActiveUser from '@/components/profile/ActiveUsers'; import Order from '@/components/profile/Order'; import ProfileHeader from '@/components/profile/ProfileHeader'; import Wishlist from '@/components/profile/wishlist'; diff --git a/src/app/globals.css b/src/app/globals.css index 58148d9..2aae8bc 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -29,6 +29,9 @@ body {} .hide-scrollbar::-webkit-scrollbar { display: none; } +.capitalize-first::first-letter { + text-transform: uppercase; +} /* Hide scrollbar for IE, Edge and Firefox */ .hide-scrollbar { diff --git a/src/app/products/[id]/page.tsx b/src/app/products/[id]/page.tsx index 2b54f7d..add074b 100644 --- a/src/app/products/[id]/page.tsx +++ b/src/app/products/[id]/page.tsx @@ -28,23 +28,19 @@ import ReviewWrapper from '@/components/ReviewsWrapper'; //import StripeProvider from '@/components/StripeProvider'; function Page() { - const { wishNumber } = useAppSelector( - (state: RootState) => state.wishlist - ) + const { wishNumber } = useAppSelector((state: RootState) => state.wishlist); const [thumbsSwiper, setThumbsSwiper] = useState(null); - const [addProductToCart, setAddProductToCart]=useState(false) - - const {cart} = useAppSelector( - (state: RootState) => state.userCartData, - ); + const [addProductToCart, setAddProductToCart] = useState(false); + + const { cart } = useAppSelector((state: RootState) => state.userCartData); - const carts=cart as IUSERCART - const { id } :any= useParams(); + const carts = cart as IUSERCART; + const { id }: any = useParams(); const handleSwiper = (swiper: any) => { setThumbsSwiper(swiper); }; const _id: string = id.toLocaleString(); - const { data, isLoading, error , refetch} = useQuery({ + const { data, isLoading, error, refetch } = useQuery({ queryKey: ['product', id], queryFn: async () => { try { @@ -71,17 +67,20 @@ function Page() { const productId = data.product.id; dispatch(handleUserAddCart({ productPrice, productId })); }; - const handleAddRemoveWish = async(event: { preventDefault: () => void; })=>{ + const handleAddRemoveWish = async (event: { preventDefault: () => void }) => { event.preventDefault(); - const response:any = await request.post('/wishes', { productId:id }); - if(response.status == 200 || response.status == 203){ + const response: any = await request.post('/wishes', { productId: id }); + if (response.status == 200 || response.status == 203) { const { status } = response; - dispatch(handleWishlistCount(status == 200 ? await wishNumber + 1 : await wishNumber - 1)); - showToast(response.message, 'success') + dispatch( + handleWishlistCount( + status == 200 ? (await wishNumber) + 1 : (await wishNumber) - 1, + ), + ); + showToast(response.message, 'success'); } - console.log('this is response', response) - - } + + }; return (
{/* // */} @@ -163,18 +162,22 @@ function Page() {
-

+

{productName}

-
+
-
item.product ===data.product.id)) ?' bg-red-500 pointer-events-none':'pointer-events-auto bg-gray-200'}`}> +
item.product === data.product.id) ? ' bg-red-500 pointer-events-none' : 'pointer-events-auto bg-gray-200'}`} + > { handleNewItem(); }} @@ -227,10 +230,13 @@ function Page() {
- +
-
)} @@ -239,4 +245,4 @@ function Page() {
); } -export default Page; \ No newline at end of file +export default Page; diff --git a/src/components/AssigningRoles.tsx b/src/components/AssigningRoles.tsx index d78ea20..6d36602 100644 --- a/src/components/AssigningRoles.tsx +++ b/src/components/AssigningRoles.tsx @@ -54,7 +54,7 @@ const AssigningRole: React.FC = ({ <> {isLoading || mutation.isPending ? (
-
+
) : ( { {user?.User?.Role.name || 'buyer'}

-
  • { d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /> - {convertToNormalDate(watchedValues.birthDate) || watchedValues.birthDate || 'YYYY-MM-DD'} + {watchedValues.birthDate + ? watchedValues.birthDate.match(/^\d{2}-\d{2}-\d{4}$/) + ? watchedValues.birthDate + : formatDate(watchedValues.birthDate) + : 'YYYY-MM-DD'}
  • {
-

Update Profile

@@ -359,7 +337,6 @@ const UserProfileForm: React.FC = () => { placeholder="Birth date" {...register('birthDate')} error={errors.birthDate?.message} - max={getCurrentDate()} />
@@ -368,7 +345,7 @@ const UserProfileForm: React.FC = () => { nameuse="Address" type="text" placeholder="Address" - error ={errors?.address?.message} + error={errors.address?.message} />
@@ -429,13 +406,10 @@ const UserProfileForm: React.FC = () => {
-
- +
+
- -
); }; - -export default UserProfileForm; \ No newline at end of file +export default UserProfileForm; diff --git a/src/components/GoogleButton.tsx b/src/components/GoogleButton.tsx index c364b47..3841ff5 100644 --- a/src/components/GoogleButton.tsx +++ b/src/components/GoogleButton.tsx @@ -6,7 +6,7 @@ const GoogleButton: React.FC = () => { Google (window.location.href = `${api_base_url}/users/google`)} diff --git a/src/components/LatestCard.tsx b/src/components/LatestCard.tsx index 889f4ed..d5e69df 100644 --- a/src/components/LatestCard.tsx +++ b/src/components/LatestCard.tsx @@ -15,7 +15,7 @@ const LatestCard: React.FC = ({ }) => { return (
-
+
= ({
-

{name}

+

+ {name} +

Price:{price.toLocaleString()} RWF

diff --git a/src/components/Product/editProduct.tsx b/src/components/Product/editProduct.tsx index b12d011..142511d 100644 --- a/src/components/Product/editProduct.tsx +++ b/src/components/Product/editProduct.tsx @@ -181,7 +181,7 @@ const ProductPopup: React.FC = ({ try { const result = await handleUpdateProduct(data, productId); showToast('Product has been updated', 'success'); - router.push('/dashboard'); + router.push('/dashboard/product'); console.log(result); onClose(); @@ -214,10 +214,7 @@ const ProductPopup: React.FC = ({ const file = e.target.files[0]; const totalFiles = files.length + 1; - console.log( - '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++filessss', - files, - ); + // Check for maximum file limit if (totalFiles > 8) { setUploadError('You can upload a maximum of 8 pictures.'); @@ -475,7 +472,9 @@ const ProductPopup: React.FC = ({ onClick={onClose} className="bg-blue-500 text-white px-4 py-3 rounded-l-lg rounded-r-none flex-grow flex items-center justify-center mb-2 md:mb-0" > - Close + router.back()}> + Close + + +
+ )} +
+ Not found + No Product Available +
+
+ ); return ( <>
+ {Role === 'seller' && ( +
+ {' '} + + + +
+ )} @@ -103,7 +135,7 @@ const ProductsTable: React.FC = ({ Role }) => { - {data.map( + {data?.map( ( product: { id: string; @@ -129,7 +161,8 @@ const ProductsTable: React.FC = ({ Role }) => {
{product.productName} {product.stockLevel} - {product.productPrice?.toLocaleString()} {product.productCurrency} + {product.productPrice?.toLocaleString()}{' '} + {product.productCurrency} {product.productDiscount} diff --git a/src/components/chartssection.tsx b/src/components/chartssection.tsx index 8a0fa94..746fab9 100644 --- a/src/components/chartssection.tsx +++ b/src/components/chartssection.tsx @@ -18,56 +18,48 @@ export const options = { title: '', }; const Chartssection: React.FC = ({ user, data, categories, users }) => { - const ProductCategory = useRef(null); const data1 = [ ['Task', 'Hours per Day'], [ 'availableProducts', - data?.availableProducts > 1 ? data?.availableProducts : 0.2, + data?.availableProducts >= 1 ? data?.availableProducts : 0, ], - [ - 'expiredProducts', - data?.expiredProducts > 1 ? data?.expiredProducts : 0.2, - ], - ['wishesStats', data?.wishesStats > 1 ? data?.wishesStats : 0.2], - ['productsStats', data?.productsStats > 1 ? data?.productsStats : 0.2], + ['expiredProducts', data?.expiredProducts >= 1 ? data?.expiredProducts : 0], + ['wishesStats', data?.wishesStats >= 1 ? data?.wishesStats : 0], + ['productsStats', data?.productsStats >= 1 ? data?.productsStats : 0], ]; - - const mutation = useMutation({ mutationFn: (product_categories: string) => { - return request.post(`/categories`, {categoryName:product_categories}) + return request.post(`/categories`, { categoryName: product_categories }); }, onError: (error: any) => { showToast(error.response.data.error, 'error'); }, - onSuccess: async (result:any) => { - window.location.reload(); - }, - - onSettled: (result, error) => { - showToast(error.response.data.message, 'error'); - }, - }) + onSuccess: async (result: any) => { + window.location.reload(); + }, - const handleAddCategory= async () => { + onSettled: (result, error) => { + showToast(error.response.data.message, 'error'); + }, + }); - const product_categories=ProductCategory.current?.value as string - if(product_categories.length>2){ - - await mutation.mutate(product_categories); - - }else{ - showToast('Product category must be atleast 3 characters length', 'error'); + const handleAddCategory = async () => { + const product_categories = ProductCategory.current?.value as string; + if (product_categories.length > 2) { + await mutation.mutate(product_categories); + } else { + showToast( + 'Product category must be atleast 3 characters length', + 'error', + ); } //alert(product_categories) - }; - return (
{user && user?.Role?.name === 'seller' ? ( @@ -127,14 +119,22 @@ const Chartssection: React.FC = ({ user, data, categories, users }) => {

All Categolie On Market

- -
- - -
+ {user && user?.Role?.name === 'admin' && ( +
+ + +
+ )}
-
    {categories && categories?.map((el: any) => ( @@ -157,4 +157,4 @@ const Chartssection: React.FC = ({ user, data, categories, users }) => { ); }; -export default Chartssection; \ No newline at end of file +export default Chartssection; diff --git a/src/components/headerDash.tsx b/src/components/headerDash.tsx index 98b42a9..38a05e4 100644 --- a/src/components/headerDash.tsx +++ b/src/components/headerDash.tsx @@ -61,7 +61,7 @@ const HeaderDash: React.FC = ({ pageName }) => { />

    - {userdata?.firstName ? userdata?.firstName : 'waiting...'} + {userdata?.firstName ? userdata?.firstName : ''}

    {userdata?.Role?.name} diff --git a/src/components/profile/About.tsx b/src/components/profile/About.tsx index df0f372..8a3da4f 100644 --- a/src/components/profile/About.tsx +++ b/src/components/profile/About.tsx @@ -7,37 +7,48 @@ import { useDispatch, useSelector } from 'react-redux'; import { AppDispatch, RootState } from '@/redux/store'; import { getUserProfile } from '@/redux/slices/profileSlice'; import request from '@/utils/axios'; - function About() { const dispatch = useDispatch(); const { user, loading, error } = useSelector( (state: RootState) => state.userProfile, ); - useEffect(() => { const fetchData = async () => { await dispatch(getUserProfile()); }; fetchData(); }, [dispatch]); - if (error) return
    Error: {error}
    ; if (!user) { - return
    Loading...
    ; + return ( +
    + {' '} +
    + {[...Array(3)].map((_, index) => ( +
    +
    +
    +
    + ))} +
    +
    + ); } - function convertToNormalDate(isoTimestamp:any) { + function convertToNormalDate(isoTimestamp: any) { const date = new Date(isoTimestamp); - const options:any = { year: 'numeric', month: 'long', day: 'numeric' }; + const options: any = { year: 'numeric', month: 'long', day: 'numeric' }; return date.toLocaleDateString('en-US', options); } const items: any = [ { icon: , - details: `${convertToNormalDate(user.User?.birthDate) || user.User?.birthDate || 'YYYY-MM-DD'} `, + details: user.User?.birthDate + ? convertToNormalDate(user.User.birthDate) + : 'YYYY-MM-DD', }, { icon: , - details: `${user.User?.whereYouLive || "Where You Live"}`, + details: `${user.User?.whereYouLive || 'Where You Live'}`, }, { icon: , @@ -48,7 +59,6 @@ function About() { details: `${user.User?.phone || 'Contact Number'}`, }, ]; - return (

    @@ -72,5 +82,4 @@ function About() {

    ); } - -export default About; \ No newline at end of file +export default About; diff --git a/src/components/profile/ActiveUsers.tsx b/src/components/profile/ActiveUsers.tsx deleted file mode 100644 index 055318c..0000000 --- a/src/components/profile/ActiveUsers.tsx +++ /dev/null @@ -1,61 +0,0 @@ -'use client' -import React from 'react' -function ActiveUser() { - const orders: any = [ - { - name: "Shelby Goode", - image: "https://png.pngtree.com/thumb_back/fh260/background/20230612/pngtree-man-wearing-glasses-is-wearing-colorful-background-image_2905240.jpg", - status: "online", - time: "2 minutes ago" - }, - { - name: "Robert Bacins", - image: "https://t4.ftcdn.net/jpg/03/64/21/11/360_F_364211147_1qgLVxv1Tcq0Ohz3FawUfrtONzz8nq3e.jpg", - status: "online", - time: "2 minutes ago" - }, - { - name: "John Carilo", - image: "https://images.pexels.com/photos/415829/pexels-photo-415829.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500", - status: "online", - time: "2 minutes ago" - }, - { - name: "Adreine Watson", - image: "https://imgv3.fotor.com/images/gallery/a-man-profile-picture-with-blue-and-green-background-made-by-LinkedIn-Profile-Picture-Maker.jpg", - status: "online", - time: "2 minutes ago" - }, - ] - return ( -
    -
    -

    Active Seller

    -
    -
    - { - orders.map((order: any, index: any) => { - return ( -
    -
    -
    - profile -
    -
    -
    - {order.name} - {order.status} -
    -
    -
    - {order.time} -
    -
    - ) - }) - } -
    -
    - ) -} -export default ActiveUser \ No newline at end of file diff --git a/src/components/profile/Order.tsx b/src/components/profile/Order.tsx index 5e60e28..3ba4e8b 100644 --- a/src/components/profile/Order.tsx +++ b/src/components/profile/Order.tsx @@ -1,49 +1,82 @@ 'use client'; import React, { useEffect, useState } from 'react'; import request from '@/utils/axios'; - +interface Product { + productName: string; + productPrice: number; + productCurrency: string; +} +interface Order { + id: string; + Product: Product; + quantity: number; + totalAmount: number; + deliveryStatus: string; +} function Order() { - // const [orderss, setOrders] = useState([]); - // useEffect(() => { - // const fetchData = async () => { - // const response: any = await request.get('/orders'); - // setOrders(response.orders); - // }; - // fetchData(); - // }, []) - const orders: any = [ - { - name: 'Sneakers N12', - desc: "Men's Shoes", - }, - { - name: 'Toyota', - desc: 'Best car in city', - }, - { - name: 'Girl Dress', - desc: 'tkacheanton dress', - }, - ]; - useEffect(() => {}, []); + const [orders, setOrders] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const getOrders = async () => { + try { + const response: any = await request.get('/orders'); + if (response && Array.isArray(response.orders)) { + setOrders(response.orders); + } else { + setError('Invalid data format received'); + } + } catch (err) { + setError('Failed to fetch orders'); + console.error(err); + } finally { + setLoading(false); + } + }; + useEffect(() => { + getOrders(); + }, []); + if (loading) { + return ( +
    +
    +

    +
    +
    + {[...Array(3)].map((_, index) => ( +
    +
    +
    +
    + ))} +
    +
    + ); + } + if (error) { + return
    No orders
    ; + } return ( -
    -
    +
    +

    Orders

    - {orders?.map((order: any, index: any) => { - return ( -
    - - {order.name} + {orders.length === 0 ? ( +
    + Your orders will appear here +
    + ) : ( + orders.map((order) => ( +
    + + {order.Product.productName} - - {order.desc} + + {`${order.quantity} x ${order.Product.productPrice} ${order.Product.productCurrency} - ${order.deliveryStatus}`}
    - ); - })} + )) + )}
    ); diff --git a/src/components/profile/ProfileHeader.tsx b/src/components/profile/ProfileHeader.tsx index eaaed72..089892e 100644 --- a/src/components/profile/ProfileHeader.tsx +++ b/src/components/profile/ProfileHeader.tsx @@ -1,77 +1,38 @@ 'use client'; - -import React, { useEffect, useState } from 'react'; - +import React, { useEffect } from 'react'; import { GreenButton } from '../Button'; - -import { MessageSquareText } from 'lucide-react'; - import { useDispatch, useSelector } from 'react-redux'; - import { AppDispatch, RootState } from '@/redux/store'; - import { getUserProfile } from '@/redux/slices/profileSlice'; -import { zodResolver } from '@hookform/resolvers/zod'; import { useRouter } from 'next/navigation'; -import GlobarPopUp from '@/components/UsablePopUp'; -import { Button } from '@/components/Button'; -import InputBox from '@/components/InputBox'; -import PopUpModels from '@/components/PopUpModels'; -import { Updatepassword } from '@/validations/Updatepassword'; -import UpdatePassword from '@/hooks/updatepassword'; -import Header from '@/components/Header'; -import { ProductWithFilter } from '@/components/allProducts'; -import Link from 'next/link'; -import { useForm } from 'react-hook-form'; -export interface FormDataType { - confirmPassword: string; - newPassword: string; - oldPassword: string; +function ProfileHeaderSkeleton() { + return ( +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ); } - function ProfileHeader() { - const onSubmit = (data: FormDataType) => { - submit(data); - }; - const [showlModal, setShowmodal] = useState(false); const dispatch = useDispatch(); - let { submit, erro, loadings, success, handlemoduleButton } = - UpdatePassword(); - const { - register, - handleSubmit, - formState: { errors }, - reset, - } = useForm({ - resolver: zodResolver(Updatepassword), - }); const { user, loading, error } = useSelector( (state: RootState) => state.userProfile, ); - const handleshow = () => { - setShowmodal(!showlModal); - reset(); - }; useEffect(() => { const fetchData = async () => { await dispatch(getUserProfile()); }; - fetchData(); }, [dispatch]); - - if (loading) - return ( -
    -
    -
    - ); - - if (!user) return
    No user found
    ; - - const backgroundImageUrl = - 'https://res.cloudinary.com/dv9cz01fi/image/upload/v1719873858/coverImages/aiqbh0c3oiwh2ijb3drb.jpg'; - const router = useRouter(); const handleEditProfile = () => { if (user?.User.Role.name !== 'buyer') { @@ -80,83 +41,41 @@ function ProfileHeader() { router.push('/profile-edit'); } }; - + if (loading || !user) { + return ; + } + const backgroundImageUrl = + 'https://res.cloudinary.com/dv9cz01fi/image/upload/v1719873858/coverImages/aiqbh0c3oiwh2ijb3drb.jpg'; return ( - <> -
    -
    - -
    - Profile +
    +
    + Profile { + e.currentTarget.src = '/unknown.jpg'; + }} + /> +
    +

    + {user.User?.firstName} {user.User?.lastName} +

    +

    {user.User?.Role.name}

    +
    +
    + - -
    -

    - {user.User?.firstName} {user.User?.lastName} -

    - -

    {user.User?.Role.name}

    -
    - -
    - -
    - {showlModal && ( - -
    - - - - -

    {erro}

    -
    -
    - -
    -
    - )} - {success && ( - - )} - +
    ); } - export default ProfileHeader; diff --git a/src/components/profile/wishlist.tsx b/src/components/profile/wishlist.tsx index b91005c..559d774 100644 --- a/src/components/profile/wishlist.tsx +++ b/src/components/profile/wishlist.tsx @@ -1,6 +1,5 @@ 'use client'; import React, { useEffect, useState } from 'react'; -import { HiDotsHorizontal } from 'react-icons/hi'; import { VscTag } from 'react-icons/vsc'; import { useDispatch, useSelector } from 'react-redux'; import { AppDispatch, RootState } from '@/redux/store'; @@ -10,7 +9,6 @@ import { Cylinder, Trash, Loader } from 'lucide-react'; import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import Link from 'next/link'; - interface Product { id: string; productThumbnail: string; @@ -19,7 +17,6 @@ interface Product { productPrice: number; productCurrency: string; } - interface Wish { id: string; userId: string; @@ -28,7 +25,30 @@ interface Wish { updatedAt: string; product: Product; } - +const WishlistSkeleton = () => ( +
    +
    +
    +
    +
    + {[...Array(3)].map((_, index) => ( +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ))} +
    +
    +); const Wishlist: React.FC = () => { const dispatch = useDispatch(); const { user, loading, error } = useSelector( @@ -36,7 +56,6 @@ const Wishlist: React.FC = () => { ); const [wishlist, setWishlist] = useState([]); const [loadingWishId, setLoadingWishId] = useState(null); - useEffect(() => { const fetchData = async () => { try { @@ -47,7 +66,6 @@ const Wishlist: React.FC = () => { }; fetchData(); }, [dispatch]); - const getWishlist = async () => { try { const response: any = await request.get('/wishes'); @@ -56,11 +74,9 @@ const Wishlist: React.FC = () => { console.error(err); } }; - useEffect(() => { getWishlist(); }, []); - const removeWish = async (wishId: string) => { setLoadingWishId(wishId); try { @@ -74,10 +90,8 @@ const Wishlist: React.FC = () => { setLoadingWishId(null); } }; - if (error) return
    Error: {error}
    ; - if (!user) return
    No user found
    ; - + if (!user || loading) return ; return (
    @@ -95,7 +109,6 @@ const Wishlist: React.FC = () => { className="flex flex-col items-center gap-4 border-b pb-4" >
    - {/* */}
    ); }; - export default Wishlist; diff --git a/src/components/singleproduct.tsx b/src/components/singleproduct.tsx index 1cc8383..351e3a9 100644 --- a/src/components/singleproduct.tsx +++ b/src/components/singleproduct.tsx @@ -19,6 +19,7 @@ import { averageReviews } from '@/utils/averageReviews'; import request from '@/utils/axios'; import ConfirmDelete from './confirmDeletePopup'; import Link from 'next/link'; +import ReviewCard from './ReviewCard'; interface Properties { role: any; } @@ -55,7 +56,14 @@ const Singleproduct: React.FC = ({ role }) => { } }, }); - if (error) return Error: {error.message}; + + if (error) + return ( +
    + Not found + Error Happening 💀 +
    + ); const handleSwiper = (swiper: any) => { setThumbsSwiper(swiper); }; @@ -66,9 +74,15 @@ const Singleproduct: React.FC = ({ role }) => { productDescription, reviews, } = data?.product || {}; + if (isLoading) + return ( +
    +
    +
    + ); return ( <> -
    +
    @@ -137,7 +151,7 @@ const Singleproduct: React.FC = ({ role }) => {
    -

    +

    {productName}

    @@ -197,6 +211,24 @@ const Singleproduct: React.FC = ({ role }) => { ) : ( '' )} +
    +

    Reviews:

    +
    + {reviews && reviews.length > 0 ? ( + reviews.map((review: any) => ( + + )) + ) : ( +

    No ratings yet.

    + )} +
    +
    {' '} ); }; diff --git a/src/utils/axios.ts b/src/utils/axios.ts index 698eba5..ffb0441 100644 --- a/src/utils/axios.ts +++ b/src/utils/axios.ts @@ -32,7 +32,7 @@ axiosInstance.interceptors.response.use( if (!error.response) { showToast('Server Network Error!', 'error'); } - if (error.response.status === 401) { + if (error?.response?.status === 401) { if (typeof window !== 'undefined') { localStorage.clear(); window.location.href = '/auth/login'; diff --git a/src/validations/productValidation.tsx b/src/validations/productValidation.tsx index 7d6f5e6..1cc7df9 100644 --- a/src/validations/productValidation.tsx +++ b/src/validations/productValidation.tsx @@ -1,24 +1,57 @@ -import { z } from "zod"; +import { z } from 'zod'; const productSchema = z.object({ - productName: z.string().min(1, "Product name is required").min(3, "Product name must be at least 3 characters").max(50, "Product name must not exceed 50 characters"), - stockLevel: z.string().min(1, "Stock level is required").regex(/[0-9]$/, "Stock level must be a number"), - productCategory: z.string().uuid("Invalid product category ID"), - productPrice: z.string().min(1, "Product Price is required").regex(/[0-9]$/, "Product price must be a number"), - discount: z.string().regex(/[0-9]$/, "Product discount must be a number").optional(), - currency: z.string().min(3, "Currency must be 3 characters").max(3, "Currency must be 3 characters").regex(/^[^0-9]+$/, "Currency must be a string with no numbers"), - description: z.string().min(1, "Description is required").min(20, "Description must be at least 20 characters").max(500, "Description must not exceed 500 characters"), - - expireDate: z.string().min(1,"Expire date is required").refine((data) => { - if (!data) return false; - const expireDate = new Date(data); - const currentDate = new Date(); - return expireDate > currentDate; - }, { - message: `The expire date must be a future date` - }) + productName: z + .string() + .min(1, 'Product name is required') + .min(3, 'Product name must be at least 3 characters') + .max(50, 'Product name must not exceed 50 characters'), + stockLevel: z + .string() + .min(1, 'Stock level is required') + .regex(/[0-9]$/, 'Stock level must be a number'), + productCategory: z.string().uuid('Invalid product category ID'), + productPrice: z + .string() + .min(1, 'Product Price is required') + .regex(/[0-9]$/, 'Product price must be a number'), + discount: z + .string() + .regex(/[0-9]$/, 'Product discount must be a number') + .optional(), + currency: z + .string() + .min(3, 'Currency must be 3 characters') + .max(3, 'Currency must be 3 characters') + .regex(/^[^0-9]+$/, 'Currency must be a string with no numbers') + .refine((value) => /^[A-Z]/.test(value), { + message: 'Preferred Currency must start with a capital letter', + }) + .refine((value) => value === 'RWf' || value === 'USD', { + message: "Preferred Currency must be either 'RWf' or 'USD'", + }), + description: z + .string() + .min(1, 'Description is required') + .min(20, 'Description must be at least 20 characters') + .max(500, 'Description must not exceed 500 characters'), + + expireDate: z + .string() + .min(1, 'Expire date is required') + .refine( + (data) => { + if (!data) return false; + const expireDate = new Date(data); + const currentDate = new Date(); + return expireDate > currentDate; + }, + { + message: `The expire date must be a future date`, + }, + ), }); -const productUpdateSchema = productSchema; +const productUpdateSchema = productSchema; -export { productSchema, productUpdateSchema }; \ No newline at end of file +export { productSchema, productUpdateSchema }; diff --git a/src/validations/userProfileSchema.ts b/src/validations/userProfileSchema.ts index 0176f13..303c538 100644 --- a/src/validations/userProfileSchema.ts +++ b/src/validations/userProfileSchema.ts @@ -3,48 +3,49 @@ import { z } from 'zod'; const updateSchema = z.object({ firstName: z - .string({ required_error: "FirstName is required" }) - .min(3, "Use at least 3 characters for firstName") - .max(50, "No more than 50 characters for firstName"), + .string({ required_error: 'FirstName is required' }) + .min(3, 'Use at least 3 characters for firstName') + .max(50, 'No more than 50 characters for firstName'), lastName: z - .string({ required_error: "LastName is required" }) - .min(3, "Use at least 3 characters for lastName") - .max(50, "No more than 50 characters for lastName"), + .string({ required_error: 'LastName is required' }) + .min(3, 'Use at least 3 characters for lastName') + .max(50, 'No more than 50 characters for lastName'), phone: z - .string({ required_error: "Phone number is required" }) - .min(10, "Minimum number for phone field is 10") - .max(20, "Maximum number for phone field is 20") - .regex(/^\d+$/, "Phone number must be numeric"), -// birthDate: z.coerce -// .date({ required_error: "The date is required" }) -// .refine((data) => data < new Date(), { -// message: "The date must be in the past" -// }), -birthDate: z.string().optional(), + .string({ required_error: 'Phone number is required' }) + .min(10, 'Minimum number for phone field is 10') + .max(20, 'Maximum number for phone field is 20') + .regex(/^\d+$/, 'Phone number must be numeric'), + // birthDate: z.coerce + // .date({ required_error: "The date is required" }) + // .refine((data) => data < new Date(), { + // message: "The date must be in the past" + // }), + birthDate: z.string().optional(), profileImage: z.any(), preferredLanguage: z - .string({ required_error: "Preferred Language is required" }) - .min(3, "Use at least 3 characters for preferredLanguage") - .max(70, "No more than 70 characters for preferredLanguage"), + .string({ required_error: 'Preferred Language is required' }) + .min(10, 'Use at least 3 characters for preferredLanguage') + .max(20, 'No more than 20 characters for preferredLanguage'), whereYouLive: z - .string({ required_error: "Where you live is required" }) - .max(70, "No more than 70 characters for whereYouLive"), - address: z - .string({ required_error: "Address is required" }) - .max(70, "No more than 70 characters for Adress"), + .string({ required_error: 'Where you live is required' }) + .max(70, 'No more than 70 characters for whereYouLive'), + address: z + .string({ required_error: 'Address is required' }) + .max(70, 'No more than 70 characters for Address'), preferredCurrency: z - .string({ required_error: "Preferred Currency is required" }) - .min(2, "Use at least 2 characters for preferredCurrency") - .max(10, "No more than 10 characters for preferredCurrency"), + .string({ required_error: 'Preferred Currency is required' }) + .min(2, 'Use at least 2 characters for preferredCurrency') + .max(3, 'No more than 3 characters for preferredCurrency') + .refine((value) => /^[A-Z]/.test(value), { + message: 'Preferred Currency must start with a capital letter', + }) + .refine((value) => value === 'RWf' || value === 'USD', { + message: "Preferred Currency must be either 'RWf' or 'USD'", + }), billingAddress: z - .string({ required_error: "Billing Address is required" }) - .min(5, "Use at least 5 characters for billingAddress") - .max(70, "No more than 70 characters for billingAddress") + .string({ required_error: 'Billing Address is required' }) + .min(5, 'Use at least 5 characters for billingAddress') + .max(70, 'No more than 70 characters for billingAddress'), }); export default updateSchema; - - - - -