Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate new shop apis + bug fixes #229

Merged
merged 47 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
982baa2
fix: use product slug as URL for single product item
alvyynm Aug 7, 2024
079d5ae
chore: add slug to ProductCard types
alvyynm Aug 7, 2024
de7275c
fix: use slug as product URL
alvyynm Aug 8, 2024
dfc6534
fix: update api URL in useSwagList hook
alvyynm Aug 8, 2024
c63d656
Merge branch 'Dev' into checkoutPage
sonylomo Aug 8, 2024
4b172c6
feat: integrate api endpoints
alvyynm Aug 8, 2024
f11dcdc
fix: properly add selected item to localstorage
alvyynm Aug 8, 2024
43194c5
fix: add id, category, slug and image to cart data
alvyynm Aug 8, 2024
8b6d636
fix: multiply cart quantity by price of one unit
alvyynm Aug 8, 2024
aa071fb
feat: display cart products from local storage
alvyynm Aug 8, 2024
723b3b1
fix: delete dummy cart products
alvyynm Aug 8, 2024
d26b36f
fix: delete from cart functionality
alvyynm Aug 8, 2024
9339f0a
refactor: item quantity button
alvyynm Aug 8, 2024
05b2272
feat: add more dummy images
alvyynm Aug 8, 2024
3bfb852
fix: linting issues
alvyynm Aug 8, 2024
696c8ff
fix: use product slug as URL
alvyynm Aug 10, 2024
f66ef8f
fix: display 'item' or items depending on stock no
alvyynm Aug 10, 2024
12dce35
feat: handle different API status appropriately in BrowseProducts.jsx
alvyynm Aug 10, 2024
c47cb78
feat: handle different API status appropriately in NewProducts.jsx
alvyynm Aug 10, 2024
772306b
feat: display different message if no products are available
alvyynm Aug 10, 2024
e19e0e8
feat: handle different API statuses appropriately in /shop/item/:slug
alvyynm Aug 10, 2024
438d33c
refactor: PopularItemsSection component
alvyynm Aug 10, 2024
899667d
feat: handle API statuses appropriately in PopularItemsSection component
alvyynm Aug 10, 2024
2fbe97e
fix: linting issues
alvyynm Aug 10, 2024
d16bbb9
fix: prevent user from adding item to cart without selecting size
alvyynm Aug 10, 2024
fc73b2b
fix: 'image undefined' error if selected product variant lacks images
alvyynm Aug 10, 2024
220bffa
feat: display shopping cart when user clicks CartIcon
alvyynm Aug 10, 2024
96b1693
feat: prevent user from adding more items than available stock
alvyynm Aug 10, 2024
9ee74ce
fix: 'Maximum update depth exceeded' error in CartDrawer
alvyynm Aug 10, 2024
8d8656c
fix: use UUID as item id in cart to enforce uniqueness
alvyynm Aug 10, 2024
cc37f3b
feat: update api endpoints in useCartSwagg and useCartProducts hooks
alvyynm Aug 10, 2024
0711947
feat: add item to backend cart when user clicks AddToCart btn
alvyynm Aug 10, 2024
3feba99
feat: delete item from backend cart when user deletes an item locally
alvyynm Aug 10, 2024
412a8f0
feat: update api endpoint for useDeleteSwag mutation hook
alvyynm Aug 10, 2024
1ec8b26
fix: linting issues
alvyynm Aug 10, 2024
6a9527d
feat: update api endpoint for useMakeOrder mutation hook
alvyynm Aug 10, 2024
6e5e25f
feat: update payload data for making an order
alvyynm Aug 10, 2024
e6ddce6
feat: create useDeleteAllSwag mutation hook
alvyynm Aug 10, 2024
6283e97
feat: clear backend cart after order
alvyynm Aug 10, 2024
863c734
fix: clear backend cart after successful order
alvyynm Aug 10, 2024
240ad1f
feat: clear local cart after successful order
alvyynm Aug 10, 2024
ff2efee
fix: linting issues
alvyynm Aug 10, 2024
ba0030e
fix: item count update bug
alvyynm Aug 15, 2024
baf3f65
fix: linting issue
alvyynm Aug 15, 2024
4b2261d
fix: use item slug as URL in carousel
alvyynm Aug 15, 2024
995e934
fix: display item price from API in carousel
alvyynm Aug 15, 2024
6822a58
fix: linting issues
alvyynm Aug 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { LazyLoadImage } from "react-lazy-load-image-component";
import { Link, useLocation } from "react-router-dom";

import logo from "../assets/images/sytLogo.png";
import CartDrawer from "./shop/CartDrawer";
import CartIcon from "./shop/CartIcon";

const navLinks = [
Expand Down Expand Up @@ -52,6 +53,7 @@ const navLinks = [

function Header() {
const [showNavlinks, setShowNavlinks] = useState(false);
const [open, setOpen] = useState(false);

const { pathname } = useLocation();

Expand All @@ -66,7 +68,13 @@ function Header() {
{/* mobile menu */}
<div className="flex gap-4 items-center">
<div className="flex md:hidden">
<CartIcon />
<button
type="button"
aria-label="open cart"
onClick={() => setOpen(true)}
>
<CartIcon />
</button>
</div>
{showNavlinks ? (
<button
Expand Down Expand Up @@ -139,6 +147,7 @@ function Header() {
})}
</nav>
</header>
<CartDrawer open={open} setOpen={setOpen} />
</div>
);
}
Expand Down
114 changes: 31 additions & 83 deletions src/components/shop/CartDrawer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,27 @@ import { IoIosCloseCircleOutline } from "react-icons/io";
import { RiDeleteBin6Line } from "react-icons/ri";
import { LazyLoadImage } from "react-lazy-load-image-component";
import { Link, useNavigate } from "react-router-dom";
import { useDeleteSwag } from "../../hooks/Mutations/shop/useCartSwagg";
import formatPrice from "../../utilities/formatPrice";

function CartDrawer({ open, setOpen }) {
const navigate = useNavigate();

// Get the JSON string from localStorage
const [cartProducts, setCartProducts] = useState([]);

const dummyCartData = [
{
id: "271fcc1c-0337-44f4-9449-2bc35b6ffd01",
name: "Cityscape Jacket",
description:
"Introducing our Cityscape Jacket: a blend of urban flair and unbeatable comfort. Crafted with premium materials, it offers sleek design and weather resistance for city adventures. Stay stylish and protected with adjustable features and convenient pockets. Upgrade your urban wardrobe today!",
category: "Jackets",
image:
"https://apis.spaceyatech.com/media/product_images/main-sample_copy_Fud5OzF.png",
price: "3000.00",
stock: 10,
color: "brown",
},
{
id: "232437b9-3e64-4cad-a6c3-08158e118207",
name: "Cityscape Jacket - Mid",
description:
"Introducing our Cityscape Jacket: a blend of urban flair and unbeatable comfort. Crafted with premium materials, it offers sleek design and weather resistance for city adventures. Stay stylish and protected with adjustable features and convenient pockets. Upgrade your urban wardrobe today!",
category: "Jackets",
image:
"https://apis.spaceyatech.com/media/product_images/main-sample_copy_BRv17MK.png",
price: "1800.00",
stock: 11,
color: "brown",
},
{
id: "9cd9a601-0ed9-4685-8633-4b04e0811fc7",
name: "SYT Hoodie",
description:
"Unleash your tech-savvy style with our Tech-Fit Hoodie. Designed for the modern individual, it seamlessly integrates functionality and fashion. Crafted with cutting-edge materials, it offers unrivaled comfort and durability. Elevate your wardrobe with this essential piece that effortlessly combines innovation and style.",
category: "Hoodies",
image:
"https://apis.spaceyatech.com/media/product_images/sample1_copy_PXgn3MX.png",
price: "2000.00",
stock: 10,
color: "white",
},
];
const [cartProducts, setCartProducts] = useState(() => {
// Initialize state with the value from localStorage if it exists
const storedProducts = localStorage.getItem("swagList");
return storedProducts ? JSON.parse(storedProducts) : [];
});

useEffect(() => {
const storage = localStorage.getItem("swagList")
? JSON.parse(localStorage.getItem("swagList"))
: null;
setCartProducts(storage);
}, [cartProducts]);
if (open) {
const storedProducts = localStorage.getItem("swagList");
if (storedProducts) {
setCartProducts(JSON.parse(storedProducts));
}
}
}, [open]);

// const { data: cartProducts, isSuccess } = useProductsInCart();
useEffect(() => {
Expand All @@ -78,31 +46,23 @@ function CartDrawer({ open, setOpen }) {
};
}, []);

// const { mutate: deleteSwag } = useDeleteSwag();
const { mutate: removeSwagFromCart } = useDeleteSwag();

const deleteFromLocalStorage = (cartItemId) => {
// Parse it back to an array of objects
const swagList = cartProducts;

const idxToDelete = swagList.findIndex(
(swag) => swag.swagg_id === cartItemId
// Create a new array by filtering out the item to delete
const updatedSwagList = cartProducts.filter(
(swag) => swag.id !== cartItemId
);

// Check if the object was found
if (idxToDelete !== -1) {
// Remove the object from the swagList
swagList.splice(idxToDelete, 1);

// Convert the updated list to a JSON string
setCartProducts(JSON.stringify(swagList));
// Update the state with the new array
setCartProducts(updatedSwagList);

// Store the updated list back to localStorage
localStorage.setItem("swagList", cartProducts);
}
// Convert the updated list to a JSON string and store it in localStorage
localStorage.setItem("swagList", JSON.stringify(updatedSwagList));
};

const handleDeleteSwag = (cartItemId) => {
// deleteSwag(cartItemId);
removeSwagFromCart(cartItemId);
deleteFromLocalStorage(cartItemId);
};

Expand Down Expand Up @@ -167,25 +127,18 @@ function CartDrawer({ open, setOpen }) {
<div className="flow-root">
<ul className="-my-6 divide-y divide-gray-200 border-b">
{/* {isSuccess && */}
{dummyCartData?.length > 0 &&
(dummyCartData?.cart_items
? dummyCartData.cart_items
: dummyCartData
)?.map((cartProduct) => (
{cartProducts?.length > 0 &&
cartProducts?.map((cartProduct) => (
<li
key={crypto.randomUUID()}
className="flex py-6 space-x-4 sm:space-x-16"
>
<div className="h-32 w-28 flex-shrink-0 overflow-hidden rounded-2xl">
<LazyLoadImage
src={`${
cartProduct.image ||
cartProduct.product?.image
cartProduct.image || cartProduct?.image
}`}
alt={
cartProduct.name ||
cartProduct.product?.name
}
alt={cartProduct.name}
className="h-full w-full object-cover object-center"
/>
</div>
Expand All @@ -197,20 +150,18 @@ function CartDrawer({ open, setOpen }) {
<div className="flex justify-between">
<p className="flex justify-between items-center gap-1 font-medium bg-[#FEF3F2] text-[#B42318] text-sm rounded-full px-2 py-1">
<CiShoppingTag />
Hoodies
{cartProduct.category}
</p>
</div>
<h3>
<p className="text-base md:text-xl text-[#656767]">
{" "}
<Link
to={`/shop/item/${
cartProduct.productId ||
cartProduct.swagg_id
cartProduct.slug
}`}
>
{cartProduct.name ||
cartProduct.product?.name}
{cartProduct.name}
</Link>
</p>
</h3>
Expand All @@ -219,18 +170,15 @@ function CartDrawer({ open, setOpen }) {
type="button"
className="flex justify-end"
onClick={() => {
handleDeleteSwag(
cartProduct.id ||
cartProduct.swagg_id
);
handleDeleteSwag(cartProduct.id);
}}
>
{/* Delete icon */}
<RiDeleteBin6Line className="h-6 w-6 text-[#FC5555]" />
</button>
</div>
<div className="flex flex-row justify-between text-[#656767] text-sm sm:text-base font-medium">
<p>Qty: 1</p>
<p>Qty: {cartProduct.orderUnits}</p>
<p>
KES {formatPrice(cartProduct.price)}
</p>
Expand Down
12 changes: 9 additions & 3 deletions src/components/shop/Counter.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import PropTypes from "prop-types";

function Counter({ className, setCount, count }) {
function Counter({ className, setCount, count, maxStock }) {
const increment = () => setCount((prevCount) => prevCount + 1);
const decrement = () =>
setCount((prevCount) => (prevCount > 1 ? prevCount - 1 : prevCount));
// console.log(count)
return (
<div className={`flex rounded-lg ${className}`}>
<button
type="button"
data-action="decrement"
className="cursor-pointer outline-none w-20 border-y border-l border-l-[#EAECF0] border-y-[#EAECF0] rounded-l-md border-r"
onClick={() => setCount(count > 1 ? count - 1 : 1)}
onClick={decrement}
>
<span className=" text-base">−</span>
</button>
Expand All @@ -18,10 +22,11 @@ function Counter({ className, setCount, count }) {
{count}
</p>
<button
disabled={maxStock === count || count > maxStock}
type="button"
data-action="increment"
className="cursor-pointer outline-none w-20 border-y border-r border-r-[#EAECF0] border-y-[#EAECF0] rounded-r-md border-l"
onClick={() => setCount(count + 1)}
onClick={increment}
>
<span className="text-base">+</span>
</button>
Expand All @@ -39,4 +44,5 @@ Counter.propTypes = {
className: PropTypes.string,
setCount: PropTypes.func.isRequired,
count: PropTypes.number.isRequired,
maxStock: PropTypes.number.isRequired,
};
8 changes: 5 additions & 3 deletions src/components/shop/ProductCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function ProductCard({ product }) {
return (
<div className="border rounded-lg p-2.5 pr-1.5 shadow-sm group hover:bg-green-dark/10 bg-white flex flex-col gap-4 ">
<Link
to={`/shop/item/${product.id}`}
to={`/shop/item/${product.slug}`}
className="aspect-h-1 aspect-w-1 w-full bg-gray-200 lg:aspect-none group-hover:opacity-75 lg:h-96"
>
<LazyLoadImage
Expand All @@ -31,7 +31,7 @@ function ProductCard({ product }) {
/>
</Link>
<Link
to={`/shop/item/${product.id}`}
to={`/shop/item/${product.slug}`}
className="flex justify-between pr-1"
>
<h3 className="text-md uppercase font-medium text-gray-600">
Expand All @@ -41,7 +41,9 @@ function ProductCard({ product }) {
{totalStock > 0 ? (
<p className="text-green-dark font-medium text-sm px-1">
<span> {totalStock}</span>
<span className="ml-2">items left</span>
<span className="ml-2">
{totalStock === 1 ? "item" : "items"} left
</span>
</p>
) : (
<div className=" text-red-800 p-1 rounded-lg bg-red-800/20 font-bold text-sm">
Expand Down
34 changes: 31 additions & 3 deletions src/hooks/Mutations/shop/useCartSwagg.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const useAddSwagToCart = () => {

return useMutation({
mutationFn: async (cartItems) => {
const response = await publicAxios.post("/cart/swaggs/", cartItems, {
const response = await publicAxios.post("/cart-items/", cartItems, {
headers: {
"Content-Type": "application/json",
// Authorization: `Bearer ${auth?.access}`,
Expand All @@ -36,7 +36,7 @@ const useDeleteSwag = () => {

return useMutation({
mutationFn: async (id) => {
const response = await publicAxios.delete(`/cart/swaggs/${id}/`, {
const response = await publicAxios.delete(`/cart-items/${id}/`, {
headers: {
"Content-Type": "application/json",
// Authorization: `Bearer ${auth?.access}`,
Expand All @@ -58,4 +58,32 @@ const useDeleteSwag = () => {
});
};

export { useAddSwagToCart, useDeleteSwag };
const useDeleteAllSwag = () => {
const { logout } = useAuth();
const queryClient = useQueryClient();

return useMutation({
mutationFn: async () => {
const response = await publicAxios.delete("/cart-items/clear_cart/", {
headers: {
"Content-Type": "application/json",
// Authorization: `Bearer ${auth?.access}`,
},
});

return response.data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["productsInCart"] });
},
onError: (error) => {
// eslint-disable-next-line no-console
console.error("Unable to delete all cart items");
if (error.response.status === 401) {
logout();
}
},
});
};

export { useAddSwagToCart, useDeleteSwag, useDeleteAllSwag };
12 changes: 6 additions & 6 deletions src/hooks/Mutations/shop/useMakeOrder.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import privateAxios from "../../../api/privateAxios";
import useAuth from "../../useAuth";
// import useAuth from "../../useAuth";

// POST: https://apis.spaceyatech.com/api/orders/
// POST: https://apis.spaceyatech.com/api/checkout/
const useMakeOrder = () => {
const { auth, logout } = useAuth();
// const { auth, logout } = useAuth();
const queryClient = useQueryClient();

return useMutation({
mutationFn: async (customerInfo) => {
const response = await privateAxios.post("/orders/", customerInfo, {
const response = await privateAxios.post("/checkout/", customerInfo, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${auth?.access}`,
// Authorization: `Bearer ${auth?.access}`,
},
});
return response.data;
Expand All @@ -25,7 +25,7 @@ const useMakeOrder = () => {
// eslint-disable-next-line no-console
console.error("Unable to add availability");
if (error.response.status === 401) {
logout();
// logout();
}
},
});
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/Queries/shop/useCartProducts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const fetchProductsInCart = async () => {
// const { access } = authObject;

try {
const response = await publicAxios.get("/cart/swaggs/", {
const response = await publicAxios.get("/cart-items/", {
headers: {
"Content-Type": "application/json",
// Authorization: `Bearer ${access}`,
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/Queries/shop/useSwagList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const useSwagList = () =>

const fetchSingleSwag = async (id) => {
try {
const response = await publicAxios.get(`/swaggs/${id}`);
const response = await publicAxios.get(`/swaggsnew/${id}`);
return response.data;
} catch (error) {
toast.error("Error fetching swag list");
Expand Down
Loading
Loading