-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cb78f72
commit 85a82b1
Showing
14 changed files
with
447 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
.shop-card { | ||
background-color: white; | ||
padding: 20px; | ||
border-radius: 8px; | ||
text-align: left; | ||
box-shadow: 0.1rem 0.1rem 0.5rem rgba(0, 0, 0, 0.2); | ||
background-color: $secondary-color-light; | ||
transform: none; | ||
&:hover { | ||
transform: scale(1.02) !important; | ||
box-shadow: 1rem 1rem 1rem rgba(0, 0, 0, 0.3); | ||
transition: transform 0.1s ease !important; | ||
} | ||
|
||
h2 { | ||
font-size: 1.5em; | ||
margin-bottom: 1em; | ||
} | ||
|
||
.items { | ||
display: grid; | ||
grid-template-columns: repeat(2, 1fr); | ||
gap: 20px; | ||
margin-bottom: 20px; | ||
|
||
.item { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
cursor: pointer; | ||
|
||
img { | ||
width: 100%; | ||
height: 10rem; | ||
object-fit: cover; | ||
} | ||
|
||
p { | ||
margin-top: 10px; | ||
font-size: 1em; | ||
color: #333; | ||
} | ||
} | ||
} | ||
|
||
.see-more { | ||
color: $primary-color; | ||
text-decoration: none; | ||
font-weight: bold; | ||
cursor: pointer; | ||
|
||
&:hover { | ||
text-decoration: underline; | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
.shop-container{ | ||
display: grid; | ||
grid-template-columns: repeat(auto-fill, minmax(30rem, 1fr)); | ||
gap: 3rem; | ||
padding: 4rem; | ||
.loader { | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
height: 100vh; | ||
width: 100vw; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* eslint-disable */ | ||
import React, { useEffect } from 'react'; | ||
import { useAppDispatch, useAppSelector } from '../../store/store'; | ||
import { fetchProductsByShopId } from '../../store/features/product/shopSlice'; | ||
import { useNavigate } from 'react-router-dom'; | ||
|
||
interface ShopCardProps { | ||
shopId: string; | ||
} | ||
|
||
const ShopCard: React.FC<ShopCardProps> = ({ shopId }) => { | ||
const dispatch = useAppDispatch(); | ||
const navigate = useNavigate(); | ||
|
||
// const shop = useAppSelector((state) => state.shop.shops.find((shop) => shop.id === shopId)); | ||
// const products = useAppSelector((state) => state.shop.shopProductsByShop?.[shopId] || []); | ||
const { | ||
shops, | ||
shopProductsByShop | ||
} = useAppSelector((state: any) => state.shop); | ||
|
||
const shop = shops.find((shop: any) => shop.id === shopId); | ||
const products = shopProductsByShop?.[shopId] || []; | ||
|
||
useEffect(() => { | ||
if (shopId) { | ||
dispatch(fetchProductsByShopId(shopId)); | ||
} | ||
}, [dispatch, shopId]); | ||
|
||
const truncateText = (text: string, length: number) => { | ||
return text.length > length ? `${text.substring(0, length)}...` : text; | ||
}; | ||
|
||
if (products.length < 4) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className="shop-card"> | ||
<h2>{shop?.name || 'Shop Name'}</h2> | ||
<div className="items"> | ||
{products.slice(0, 4).map((product) => ( | ||
<div | ||
key={product.id} | ||
className="item" | ||
onClick={() => navigate(`/product/${product.id}`)} | ||
> | ||
<img src={product.images[0]} alt={product.name} /> | ||
<p>{truncateText(product.name, 20)}</p> | ||
</div> | ||
))} | ||
</div> | ||
<button | ||
className="see-more" | ||
onClick={() => navigate(`/shops/${shopId}/products`)} | ||
> | ||
See more | ||
</button> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ShopCard; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/* eslint-disable */ | ||
import React, { useEffect, useState } from "react"; | ||
import { useParams, useNavigate } from "react-router-dom"; | ||
import { useAppDispatch, useAppSelector } from "../store/store"; | ||
import { fetchProductsByShopId } from "../store/features/product/shopSlice"; | ||
import Product from "../components/product/Product"; | ||
import { PuffLoader } from "react-spinners"; | ||
import { Meta } from "../components/Meta"; | ||
import { toast } from "react-toastify"; | ||
import { createCart, getUserCarts } from "../store/features/carts/cartSlice"; | ||
|
||
const ProductsByShopPage: React.FC = () => { | ||
const { shopId } = useParams<{ shopId: string }>(); | ||
const dispatch = useAppDispatch(); | ||
const navigate = useNavigate(); | ||
const [cartResponseData, setCartResponseData] = useState<any>(null); | ||
|
||
const { shops, shopProductsByShop, isLoadingProducts, isErrorProducts, isSuccessProducts, message } = useAppSelector( | ||
(state: any) => state.shop | ||
); | ||
|
||
const [priceRange, setPriceRange] = useState([0, 0]); | ||
const [maxPrice, setMaxPrice] = useState(0); | ||
const [minPrice, setMinPrice] = useState(0); | ||
const [visibleProducts, setVisibleProducts] = useState<number>(20); | ||
|
||
useEffect(() => { | ||
if (shopId) { | ||
dispatch(fetchProductsByShopId(shopId)); | ||
} | ||
}, [dispatch, shopId]); | ||
|
||
const shop = shops.find((shop: any) => shop.id === shopId); | ||
const products = shopProductsByShop ? shopProductsByShop[shopId] : []; | ||
|
||
useEffect(() => { | ||
if (products && products.length > 0) { | ||
const calculatedMaxPrice = products.reduce((max, product) => Math.max(max, product.price), 0); | ||
const calculatedMinPrice = products.reduce((min, product) => Math.min(min, product.price), calculatedMaxPrice); | ||
|
||
setMaxPrice(calculatedMaxPrice); | ||
setMinPrice(calculatedMinPrice); | ||
setPriceRange([calculatedMinPrice, calculatedMaxPrice]); | ||
} | ||
}, [products]); | ||
|
||
const handleAddProductToCart = async (productId: string, quantity = 1) => { | ||
try { | ||
const response = await dispatch( | ||
createCart({ productId, quantity }) | ||
).unwrap(); | ||
|
||
if (response.data) { | ||
toast.success(response.message); | ||
const updatedResponse = await dispatch(getUserCarts()).unwrap(); | ||
setCartResponseData(updatedResponse.data); | ||
} else { | ||
toast.error(response.message); | ||
} | ||
} catch (error: any) { | ||
if (error === "Not authorized") { | ||
localStorage.setItem("pendingCartProduct", productId); | ||
toast.error("Please login first"); | ||
navigate("/login"); | ||
} else { | ||
toast.error("Something went wrong. Please try again later."); | ||
} | ||
} | ||
}; | ||
|
||
const filteredProducts = products.filter((product: any) => { | ||
const price = parseFloat(product.price); | ||
return price >= priceRange[0] && price <= priceRange[1]; | ||
}); | ||
|
||
const handleLoadMore = () => { | ||
setVisibleProducts((prevVisibleProducts) => prevVisibleProducts + 20); | ||
}; | ||
|
||
return ( | ||
<> | ||
<Meta title={`Products - Shop ${shop?.name || 'Shop'}`} /> | ||
<div className="landing-container"> | ||
{isLoadingProducts ? ( | ||
<div className="loader"> | ||
<PuffLoader color="#ff6d18" size={300} loading={isLoadingProducts} /> | ||
</div> | ||
) : isErrorProducts ? ( | ||
<div className="error-message"> | ||
<p>{message || "Something went wrong. Please try again later."}</p> | ||
</div> | ||
) : ( | ||
<div> | ||
<div className="head"> | ||
<h1>{shop?.name || 'Shop'}</h1> | ||
</div> | ||
<div className="filters"> | ||
<div> | ||
<label>Price Range: </label> | ||
<input | ||
type="range" | ||
min={minPrice} | ||
max={maxPrice} | ||
value={priceRange[1]} | ||
onChange={(e) => | ||
setPriceRange([priceRange[0], Number(e.target.value)]) | ||
} | ||
/> | ||
<span className="span">{priceRange[0]}RWF - {priceRange[1]}RWF</span> | ||
</div> | ||
</div> | ||
<div className="product-list"> | ||
{isSuccessProducts && | ||
Array.isArray(filteredProducts) && | ||
filteredProducts | ||
.slice(0,visibleProducts) | ||
.map((product: any) => ( | ||
<Product | ||
key={product.id} | ||
id={product.id} | ||
images={product.images} | ||
name={product.name} | ||
price={product.price} | ||
stock={Number(product.quantity)} | ||
description={product.description} | ||
discount={Number(product.discount.replace("%", ""))} | ||
/> | ||
))} | ||
</div> | ||
{visibleProducts < products.length && ( | ||
<div className="load-more"> | ||
<button onClick={handleLoadMore}>Load More</button> | ||
</div> | ||
)} | ||
</div> | ||
)} | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default ProductsByShopPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* eslint-disable */ | ||
import React, { useEffect } from 'react'; | ||
import { useAppDispatch, useAppSelector } from '../store/store'; | ||
import { fetchAllShops } from '../store/features/product/shopSlice'; | ||
import { Meta } from "../components/Meta"; | ||
import ShopCard from '../components/product/ShopCard'; | ||
import { PuffLoader } from 'react-spinners'; | ||
|
||
const ShopPage: React.FC = () => { | ||
const dispatch = useAppDispatch(); | ||
const { shops, isLoadingShops, isErrorShops, isSuccessShops } = useAppSelector((state) => state.shop); | ||
|
||
useEffect(() => { | ||
dispatch(fetchAllShops()); | ||
}, [dispatch]); | ||
|
||
return ( | ||
<> | ||
<Meta title="Shops - E-Commerce Ninjas" /> | ||
<div className="shop-container"> | ||
{isLoadingShops ? ( | ||
<div className="loader"> | ||
<PuffLoader color="#ff6d18" size={300} loading={isLoadingShops} /> | ||
</div> | ||
) : isErrorShops ? ( | ||
<p>Something went wrong. Please try again later.</p> | ||
) : isSuccessShops && shops && shops.length > 0 ? ( | ||
shops.map((shop) => ( | ||
<ShopCard key={shop.id} shopId={shop.id} /> | ||
)) | ||
) : ( | ||
<p>No shops available</p> | ||
)} | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default ShopPage; |
Oops, something went wrong.