Skip to content

Commit

Permalink
Merge pull request #28 from atlp-rwanda/bg-fix-use-redux-store
Browse files Browse the repository at this point in the history
Bg fix use redux store
  • Loading branch information
niyontwali authored Jun 27, 2024
2 parents 1ab014e + 04d9601 commit b333734
Show file tree
Hide file tree
Showing 11 changed files with 456 additions and 238 deletions.
39 changes: 38 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,41 @@ import GoogleAuthSuccess from './components/authentication/GoogleAuthSucces';
import { ToastContainer } from 'react-toastify';
import AdminPage from './pages/Admin';
import Category from './pages/Admin/Category';

import Searchpage from './containers/searchResults/SearchPage';
import { useDispatch } from 'react-redux';
import { ProductResponse, Product } from './types/Types';
import { useEffect, useRef } from 'react';
import { useGetProductsQuery } from './services/productApi';
import { setError, setIsLoading, setProductFetched, setProductsDataList } from './redux/slices/productsSlice';
const App = () => {
const { data, error, isLoading } = useGetProductsQuery();
const dispatch = useDispatch();
const firstRender = useRef(true);

const productsData: ProductResponse = data as unknown as ProductResponse;

useEffect(() => {
const fetchProducts = async () => {
if (firstRender.current) {
firstRender.current = false;
return;
}
if (error) {
dispatch(setError(error));
dispatch(setIsLoading(false));
dispatch(setProductFetched(false));
return;
}
if (!isLoading && productsData) {
const productsList = productsData.data as Product[];
dispatch(setProductsDataList([...productsList]));
dispatch(setIsLoading(false));
dispatch(setProductFetched(true));
}
};
fetchProducts();
}, [productsData, isLoading, dispatch]);

const router = createBrowserRouter([
{
path: '/',
Expand Down Expand Up @@ -53,6 +86,10 @@ const App = () => {
},
],
},
{
path: 'search',
element: <Searchpage />,
},
]);
return (
<>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Products/ProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ProductCard: React.FC<ProductCardProps> = ({ product }) => {
<img
src={imageUrl}
alt={product.name}
className="h-48 w-full rounded-sm"
className="h-48 w-full rounded-sm object-cover"
/>
</div>
<div className="product-name-cart-button pt-2">
Expand Down
447 changes: 245 additions & 202 deletions src/components/navbar/Navbar.tsx

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions src/components/search/SearchHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useState } from 'react';
import { LuChevronDown } from 'react-icons/lu';
import { cn } from '../../utils';

interface SearchParamProps {
searchQuery: string;
}
const SearchHeader = ({ searchQuery }: SearchParamProps) => {
const [sortInfoHidden, setSortInfoHidden] = useState(true);

return (
<>
<div className='p-3 md:p-4 xl:px-10 '>
<p className='font-medium text-lg border-b-2 pb-1 md:text-xl md:border-b-[3px] md:pb-3 flex flex-col md:flex-row md:justify-between'>
<span>
Search Results for
<span className='text-xl font-bold md:text-2xl self-start justify-self-start ms-2'>"{searchQuery}"</span>
</span>
<button
className='w-fit p-2 font-medium text-sm rounded-lg border border-overlay
flex flex-row items-center gap-2 text-greenColor transition-all relative'
onClick={() => setSortInfoHidden(!sortInfoHidden)}
>
Sort By
<LuChevronDown className='w-5' />
<div
className={cn(
' rounded-lg bg-whiteColor absolute top-9 right-0 md:right-1 shadow-customShadow flex flex-col overflow-hidden transition-all z-10',
sortInfoHidden ? 'h-0 hidden' : 'h-fit flex'
)}
>
<span className='text-sm p-2 font-medium w-full text-wrap md:text-nowrap text-darkGreen hover:text-whiteColor hover:bg-overlay transition-all '>
Name Ascending
</span>
<span className='text-sm p-2 font-medium w-full text-wrap md:text-nowrap text-darkGreen hover:text-whiteColor hover:bg-overlay transition-all '>
Name Descending
</span>
<span className='text-sm p-2 font-medium w-full text-wrap md:text-nowrap text-darkGreen hover:text-whiteColor hover:bg-overlay transition-all '>
Date Added
</span>
</div>
</button>
</p>
</div>
</>
);
};

export default SearchHeader;
54 changes: 54 additions & 0 deletions src/components/search/SearchResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// import { useSelector } from 'react-redux';
import { useSelector } from 'react-redux';
import ProductCard from '../Products/ProductCard';
import Button from '../common/Button';
import { Product } from '../../types/Types';
import { useNavigate } from 'react-router-dom';
import { useEffect } from 'react';

interface SearchQueryProps {
searchQuery: string;
}
const SearchResults = ({ searchQuery }: SearchQueryProps) => {
const { isLoading, productsDataList: productsList } = useSelector((state: any) => state.products);
const filteredProducts = [
...productsList.filter((product: Product) => product.name.toLowerCase().includes(searchQuery.toLowerCase())),
...productsList.filter((product: Product) => product.description.toLowerCase().includes(searchQuery.toLowerCase())),
];
useEffect(() => {
if (searchQuery.length !== 0 && filteredProducts.length !== 0) {
const productNames = filteredProducts.map(product => product.name);
let frequentlySearched = JSON.parse(localStorage.getItem('frequentlySearched') as string) || [];
frequentlySearched = [...new Set(frequentlySearched)];
frequentlySearched = [...new Set([...frequentlySearched, ...productNames])];
localStorage.setItem('frequentlySearched', JSON.stringify(frequentlySearched));
}
}, [searchQuery]);


const navigate = useNavigate();
const handleNavigate = () => {
navigate('/');
};
return (
<div>

<div className='p-3 md:p-4 xl:px-10 flex flex-row flex-wrap gap-4'>
{isLoading ? (
'Loading....'
) : filteredProducts.length === 0 ? (
<div className='w-full '>
<p className='font-medium text-2xl italic'>
No matching products were found for <span className='font-bold'>{searchQuery}</span>
</p>
<Button text='Return to HomePage' className='mt-10 mx-auto block' onClick={handleNavigate} />
</div>
) : (
filteredProducts.map((product: Product) => <ProductCard key={product.id} product={product} />)
)}
</div>
</div>
);
};

export default SearchResults;
17 changes: 4 additions & 13 deletions src/containers/Arrivals/NewArrivals.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import ProductCard from '../../components/Products/ProductCard';
import ProductCardSkeleton from '../../components/Products/ProductCardSkeleton';
import { useState } from 'react';
import { useGetProductsQuery } from '../../services/productApi';
import { Product } from '../../types/Types';
import { BiSolidCircle } from 'react-icons/bi';
import { ProductResponse, Product } from '../../types/Types';
import { useSelector } from 'react-redux';

const perPage = 6;

export default function NewArrivals() {
const { data, error, isLoading } = useGetProductsQuery();
const [currentPage, setCurrentPage] = useState(0);

const productsData: ProductResponse = data as unknown as ProductResponse;

if (error) {
return <div>Error loading products</div>;
}

const productsList: Product[] = productsData ? productsData.data : [];

const { isLoading, productsDataList: productsList } = useSelector((state: any) => state.products);
const next = () => {
setCurrentPage(prevPage => Math.min(prevPage + 1, Math.floor(productsList.length / perPage)));
};
Expand All @@ -39,7 +30,7 @@ export default function NewArrivals() {
<div className='grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 gap-5'>
{isLoading
? Array.from({ length: perPage }).map((_, index) => <ProductCardSkeleton key={index} />)
: allProductsOnPage.map(product => <ProductCard key={product.id} product={product} />)}
: allProductsOnPage.map((product: Product) => <ProductCard key={product.id} product={product} />)}
</div>
<div className='next-previous flex justify-center items-center gap-2 p-7'>
<BiSolidCircle
Expand Down
25 changes: 4 additions & 21 deletions src/containers/FeaturedProducts/FeaturedProducts.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { useEffect } from 'react';
import ProductCard from '../../components/Products/ProductCard';
import ProductCardSkeleton from '../../components/Products/ProductCardSkeleton';
import { Product, ProductResponse } from '../../types/Types';
import { useGetProductsQuery } from '../../services/productApi';
import { useSelector } from 'react-redux';
import { Product } from '../../types/Types';

export default function FeaturedProduct() {
const { data, error, isLoading } = useGetProductsQuery();

useEffect(() => {}, [isLoading, error, data]);

if (isLoading) {
// Display skeleton loaders
const { isLoading, productsDataList: productsList } = useSelector((state: any) => state.products); if (isLoading) {
return (
<div className='featured-products pb-5'>
<div className='featured-header py-5'>
Expand All @@ -25,24 +19,13 @@ export default function FeaturedProduct() {
);
}

if (error) {
return <div>Error loading products</div>;
}

const productsData: ProductResponse = data as unknown as ProductResponse;

if (!productsData) {
return null;
}

const productsList: Product[] = productsData.data;
return (
<div className='featured-products pb-5'>
<div className='featured-header py-5'>
<h1 className='text-3xl font-bold'>Featured Products</h1>
</div>
<div className='grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 gap-5'>
{productsList.map(product => (
{productsList.map((product: Product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
Expand Down
22 changes: 22 additions & 0 deletions src/containers/searchResults/SearchPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Footer from '../../components/footer/Footer';
import Navbar from '../../components/navbar/Navbar';
import SearchHeader from '../../components/search/SearchHeader';
import SearchResults from '../../components/search/SearchResults';
import { useSearchParams } from 'react-router-dom';

const Searchpage = () => {
const [searchParams] = useSearchParams();
const searchParam = searchParams.get('searchQuery') as string;
return (
<div className='w-full'>
<span className='w-full h-screen block'>
<Navbar />
<SearchHeader searchQuery={searchParam} />
<SearchResults searchQuery={searchParam} />
</span>
<Footer />
</div>
);
};

export default Searchpage;
1 change: 1 addition & 0 deletions src/pages/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Footer from '../components/footer/Footer';
import Navbar from '../components/navbar/Navbar';

const LandingPage = () => {

return (
<div>
<Navbar />
Expand Down
36 changes: 36 additions & 0 deletions src/redux/slices/productsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Product } from '../../types/Types';

interface ProductsStateProps {
error: any;
isLoading: boolean;
productsFetched: boolean;
productsDataList: Product[];
}
const initialState: ProductsStateProps = {
isLoading: true,
error: null,
productsFetched: false,
productsDataList: [],
};
const productSlice = createSlice({
name: 'producs',
initialState,
reducers: {
setProductFetched: (state, action: PayloadAction<any>) => {
state.productsFetched = action.payload;
},
setProductsDataList: (state, action: PayloadAction<any>) => {
state.productsDataList = action.payload;
},
setIsLoading: (state, action: PayloadAction<any>) => {
state.isLoading = action.payload;
},
setError: (state, action: PayloadAction<any>) => {
state.error = action.payload;
},
},
});
export const { setProductFetched, setProductsDataList, setIsLoading,setError } = productSlice.actions;

export default productSlice.reducer;
2 changes: 2 additions & 0 deletions src/redux/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { mavericksApi } from '../services';
import { setupListeners } from '@reduxjs/toolkit/query';
import registerReducer from './slices/registerSlice';
import userReducer from './slices/userSlice';
import productReducer from './slices/productsSlice';

export const store = configureStore({
reducer: {
products: productReducer,
user: userReducer,
register: registerReducer,
[mavericksApi.reducerPath]: mavericksApi.reducer,
Expand Down

0 comments on commit b333734

Please sign in to comment.