atlp-rwanda/hackers-ec-Fe

View on GitHub
src/pages/ProductsPage.tsx

Summary

Maintainability
F
5 days
Test Coverage
A
96%
/* eslint-disable react-hooks/exhaustive-deps */
import { motion } from 'framer-motion';
import { useEffect, useState } from 'react';
import { FaCaretDown, FaStar } from 'react-icons/fa';
import { IoFilter } from 'react-icons/io5';
import { ScaleLoader } from 'react-spinners';
import { DynamicData } from '../@types/DynamicData';
import { searchInputs } from '../@types/SearchType';
import Button from '../components/buttons/Button';
import ProductPageAddToCart from '../components/carts/ProductPageAddToCart';
import CategoryModel from '../components/CategoryModel';
import Chat from '../components/chat/ChatComponent';
import FormInput from '../components/Forms/InputText';
import AddToWish from '../components/wishes/AddToWish';
import useToast from '../hooks/useToast';
import { ThemeContext } from '../hooks/useWishcontext';
import useWish from '../hooks/useWishlist';
import { getCarts } from '../redux/features/cartSlice';
import { getProducts } from '../redux/features/productSlice';
import {
    getSearchedProducts,
    manipulateSearchInput,
    search,
} from '../redux/features/SearchSlice';
import { useAppDispatch, useAppSelector } from '../redux/hooks/hooks';
import { depart_icon } from '../utils/images';
import fetchInfo from '../utils/userDetails';
import SearchResultNotFound from './SearchResultNotFound';

const ProductsPage = () => {
    const [openFilter, setOpenFilter] = useState(false);
    const { isLoading, products } = useAppSelector((state) => state.product);
    const { data, searchInputs } = useAppSelector((state) => state.search);
    const { carts } = useAppSelector((state) => state.cart);
    const [openModel, setOpenModel] = useState(false);
    const { showErrorMessage } = useToast();

    const toggleModel = () => {
        setOpenModel(!openModel);
    };
    const dispatch = useAppDispatch();

    const tokenInfo = fetchInfo();

    manipulateSearchInput({
        name: null,
        minPrice: null,
        maxPrice: null,
        categoryName: null,
    });

    useEffect(() => {
        const fetchProducts = async () => {
            try {
                if (products && !products.length && tokenInfo) {
                    await dispatch(getProducts()).unwrap();
                }
            } catch (error) {
                showErrorMessage('Error fetching products!');
            }
        };
        fetchProducts();
    }, [dispatch, products, tokenInfo]);

    useEffect(() => {
        const fetchCarts = async () => {
            if (tokenInfo?.role === 'BUYER') {
                try {
                    if (carts && !carts.length) {
                        await dispatch(getCarts()).unwrap();
                    }
                } catch (error) {
                    showErrorMessage('Error fetching carts!');
                }
            }
        };
        fetchCarts();
    }, [dispatch]);

    useEffect(() => {
        dispatch(getSearchedProducts(products));
    }, [products, dispatch]);

    useEffect(() => {
        dispatch(search(searchInputs)).unwrap();
    }, [dispatch, searchInputs]);

    const HandleSearch = async (searchInput: searchInputs) => {
        dispatch(manipulateSearchInput(searchInput));
    };
    const { data: wishes } = useWish();

    return (
        <>
            <div className="perent_products_container min-h-screen relative mt-10 mobile:mt-36">
                {isLoading ? (
                    <div className="w-full h-full flex items-center justify-center absolute">
                        <ScaleLoader color="#256490" role="progressbar" />
                    </div>
                ) : (
                    <div className="products_container flex flex-col items-center laptop:flex-row laptop:items-start pl-[5%] w-full mt-16 laptop:gap-5">
                        <div className="departments cursor-pointer flex ipad:p-4 w-[100%] mobile:w-[100%] ipad:w-[40%] laptop:w-[28%]">
                            <div className="depart_icon z-20 w-[60%] mobile:w-[50%] laptop:w-[90%] bg-primary-lightblue p-2 flex items-center h-12 justify-between gap-2 mt-6 rounded-t-md relative">
                                <img src={depart_icon} alt="depart_icon" className="w-6 h-10" />
                                <h1 className="text-md font-semibld text-neutral-white">
                                    {searchInputs.categoryName
                                        ? searchInputs.categoryName
                                        : 'All categories'}
                                </h1>
                                <FaCaretDown
                                    className="text-3xl text-neutral-white"
                                    onClick={toggleModel}
                                />
                                <CategoryModel
                                    setOpenModel={setOpenModel}
                                    openModel={openModel}
                                />
                            </div>
                        </div>
                        <div className="mr-10 tablet:w-full">
                            <div className=" mobile:relative absolute tablet:absolute laptop:relative w-full top-6 mobile:top-0 tablet:top-6 laptop:top-0  pr-[10%] mobile:pr-0 tablet:pr-[10%] laptop:pr-0 flex justify-end cursor-pointer">
                                <div
                                    onClick={() => setOpenFilter(!openFilter)}
                                    className=" text-primary-lightblue py-1 w-[30%] h-12 mobile:h-8 tablet:h-12 laptop:h-10 mobile:w-[10%] tablet:w-[20%] laptop:w-[12%] right-0 flex items-center gap-2 justify-center rounded-md border-2 border-primary-lightblue"
                                >
                                    {' '}
                                    <IoFilter color="#266491" size="20px" />
                                    <p className="text-md">Filters</p>
                                </div>
                            </div>
                            {openFilter && (
                                <motion.form
                                    initial={{ y: '100%', opacity: 1 }}
                                    animate={{ y: '0%' }}
                                    transition={{ ease: 'easeOut', duration: 1 }}
                                    className=" flex w-full gap-5 py-3"
                                >
                                    <FormInput
                                        type="number"
                                        placeholder="Minimum price"
                                        otherStyles="h-12 rounded-md pl-3 primary-lightblue"
                                        value={`${searchInputs.minPrice && searchInputs.minPrice}`}
                                        onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                                            e.target.value
                                                ? HandleSearch({ minPrice: e.target.value })
                                                : HandleSearch({ minPrice: null })
                                        }
                                    />
                                    <FormInput
                                        type="number"
                                        placeholder="Maximum price"
                                        otherStyles="h-12 rounded-md pl-3 primary-lightblue"
                                        value={`${searchInputs.maxPrice && searchInputs.maxPrice}`}
                                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                                            e.target.value
                                                ? HandleSearch({ maxPrice: e.target.value })
                                                : HandleSearch({ maxPrice: null });
                                        }}
                                    />
                                </motion.form>
                            )}
                            <div
                                className={` ${!data ? 'flex flex-1 justify-end items-center' : 'grid mobile:grid-cols-2 laptop:grid-cols-3 desktop:grid-cols-4 gap-10'} product_card_container max-h-[95vh] overflow-hidden overflow-y-scroll h-full py-2 place-items-center`}
                            >
                                {Array.isArray(data) ? (
                                    data.map((item: DynamicData, idx: number) => (
                                        <div
                                            className="product_card bg-neutral-white p-4 flex flex-col rounded-md shadow laptop:max-w-[100%] h-full"
                                            key={idx}
                                        >
                                            <div className="card_profile p-2 w-full flex-grow">
                                                <div className="w-full h-48 relative">
                                                    <div className="w-full overflow-hidden flex shadow h-48">
                                                        <img
                                                            src={item.images && item.images[0]}
                                                            alt="card_profile"
                                                            className="w-full h-full object-cover rounded-lg"
                                                        />
                                                    </div>
                                                    <ProductPageAddToCart productId={item.id} />
                                                    {item.discount > 0 && (
                                                        <div className="discount absolute p-1 rounded bg-action-warning text-neutral-white -right-2 -top-2 font-bold">
                                                            {item.discount}%
                                                        </div>
                                                    )}
                                                </div>
                                            </div>
                                            <div className="card_description pl-2 flex-grow">
                                                <h1 className="py-2">
                                                    {item.name.length > 20
                                                        ? item.name.slice(0, 20) + '...'
                                                        : item.name}
                                                </h1>
                                                <div className="ratings flex">
                                                    <span className="ml-2 flex items-center gap-2">
                                                        <FaStar />
                                                        {item.ratings} ratings
                                                    </span>
                                                </div>
                                                <div className="price_wish flex justify-between items-center mt-2 gap-2 flex-wrap py-2">
                                                    <h1 className="text-2xl font-bold">
                                                        {item.price}
                                                        <small className="text-base font-normal">
                                                            {' '}
                                                            RWF
                                                        </small>
                                                    </h1>
                                                    <div className="wish flex items-center cursor-pointer">
                                                        <span className="mr-1">add to wish</span>
                                                        <ThemeContext.Provider value={wishes}>
                                                            <AddToWish productId={item.id} />
                                                        </ThemeContext.Provider>
                                                    </div>
                                                </div>
                                            </div>
                                            <div className="btn flex justify-center">
                                                <Button
                                                    title="preview product"
                                                    url={`/products/${item.id}`}
                                                    otherStyles={
                                                        ' rounded-l-full rounded-r-full mt-2 font-semibold'
                                                    }
                                                    buttonType={'button'}
                                                />
                                            </div>
                                        </div>
                                    ))
                                ) : (
                                    <SearchResultNotFound />
                                )}
                            </div>
                        </div>
                    </div>
                )}
            </div>
            <Chat />
        </>
    );
};

export default ProductsPage;