Skip to content

Commit

Permalink
Adding cart global state (#140)
Browse files Browse the repository at this point in the history
- Adding cart state into local storage
- Quantity change now possible from cart page
- Delete item from cart
  • Loading branch information
jurabek authored Jan 3, 2024
1 parent 640e592 commit 948042a
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 122 deletions.
72 changes: 7 additions & 65 deletions src/backend/web/web.client/web-app-new/src/app/cart/page.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,15 @@
'use client'

import React from "react";
import Image from "next/image";
import { TrashIcon } from "@heroicons/react/24/solid";
import Link from "next/link";

// Type for the cart item
type CartItemProps = {
id: string;
title: string;
description: string;
quantity: number;
price: number;
imageUrl: string;
};

// A single cart item component
const CartItem: React.FC<CartItemProps> = ({
id,
title,
description,
quantity,
price,
imageUrl,
}) => {
// Functions to handle item quantity changes and removal will be added here
return (
<div className="flex items-center justify-between border-b border-gray-200 py-4">
<div className="flex items-center">
<Image
src={imageUrl}
alt={title}
width={60}
height={60}
className="rounded-full"
/>
<div className="ml-4">
<h3 className="text-lg font-bold">{title}</h3>
<p className="text-sm text-gray-600">{description}</p>
<p className="text-sm font-bold">Total: ${price.toFixed(2)}</p>
</div>
</div>
<div className="flex items-center">
<button className="text-gray-500 hover:text-red-500">
<TrashIcon className="h-6 w-6" />
</button>
<div className="ml-4">
<span className="text-lg font-bold">{quantity}</span>
</div>
</div>
</div>
);
};
import { useCart } from "@/context/CartContext";
import { CartItem } from "@/components/cart/CartItem";

const CartPage: React.FC = () => {
// TODO: handle state management for cart items and total price
const cartItems = [
{
id: "123",
price: 50,
quantity: 10,
title: "Hamburger",
description:
"A delicious hamburger with cheese, lettuce, tomato, bacon, onion, pickles, and chili.",
imageUrl:
"https://img.buzzfeed.com/thumbnailer-prod-us-east-1/video-api/assets/165384.jpg", // Replace with your image path
},
];
const { items } = useCart();

// Calculate the total price
const totalPrice = cartItems.reduce(
const totalPrice = items.reduce(
(total, item) => total + item.price * item.quantity,
0
);
Expand All @@ -79,7 +21,7 @@ const CartPage: React.FC = () => {
<h2 className="text-xl font-bold mb-4">
Total Price: ${totalPrice.toFixed(2)}
</h2>
{cartItems.map((item) => (
{items.map((item) => (
<CartItem key={item.id} {...item} />
))}
<Link href="/checkout">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import CategoriesSidebar from "@/components/CategoriesSidebar";
import FoodItem from "@/components/FoodItem";
import { FoodItems } from "../../lib/fetch";
import RightSidebar from "@/components/RightSidebar";
import { FoodItems } from "@/lib/types";

export const FoodsPage = ({ foodItems }: { foodItems: FoodItems }) => {
return (
Expand Down
34 changes: 5 additions & 29 deletions src/backend/web/web.client/web-app-new/src/components/FoodItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useCart } from "@/context/CartContext";
import React, { useState } from "react";
import Image from "next/image";
import { CartItemQuantity } from "./cart/CartItemQuantity";

type FoodItemProps = {
id: number;
Expand All @@ -19,21 +20,17 @@ const FoodItem: React.FC<FoodItemProps> = ({
price,
image,
}) => {
const [quantity, setQuantity] = useState(1);
const { addItem } = useCart();

const handleQuantityChange = (newQuantity: number) => {
if (newQuantity > 0) {
setQuantity(newQuantity);
}
};
const [quantity, setQuantity] = useState(1);

const addToCart = () => {
addItem({
id,
name,
description,
quantity,
price,
image,
});
};

Expand All @@ -58,28 +55,7 @@ const FoodItem: React.FC<FoodItemProps> = ({
<p className="text-gray-700 text-base">{description}</p>
<div className="flex justify-between items-center mt-4">
<span className="text-lg font-bold">Rs. {price}</span>
<div className="flex items-center">
<button
onClick={() => handleQuantityChange(quantity - 1)}
className="text-md bg-gray-200 text-gray-600 px-2 py-1 rounded-l"
>
-
</button>
<input
type="text"
className="w-12 text-center border-t border-b"
value={quantity}
onChange={(e) =>
handleQuantityChange(parseInt(e.target.value) || 0)
}
/>
<button
onClick={() => handleQuantityChange(quantity + 1)}
className="text-md bg-gray-200 text-gray-600 px-2 py-1 rounded-r"
>
+
</button>
</div>
<CartItemQuantity quantity={quantity} onQuantityChange={setQuantity} />
</div>
</div>
<div className="px-6 pt-4 pb-2">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Image from "next/image";
import { TrashIcon } from "@heroicons/react/24/solid";
import { useCart } from "@/context/CartContext";
import { CartItemQuantity } from "./CartItemQuantity";

// Type for the cart item
type CartItemProps = {
id: number;
name: string;
description: string;
quantity: number;
price: number;
image: string;
};

// A single cart item component
export const CartItem: React.FC<CartItemProps> = ({
id,
name,
description,
quantity,
price,
image,
}) => {
const { removeItem, setQuantity } = useCart();
return (
<div className="flex items-center justify-between border-b border-gray-200 py-4">
<div className="flex items-center">
<Image
src={image}
alt={name}
quality={75}
width={60}
height={60}
className="rounded-full"
/>
<div className="ml-4">
<h3 className="text-lg font-bold">{name}</h3>
<p className="text-sm text-gray-600">{description}</p>
<p className="text-sm font-bold">Total: ${price.toFixed(2)}</p>
</div>
</div>
<div className="flex items-center">
<CartItemQuantity
className="px-4 relative"
quantity={quantity}
onQuantityChange={(newQuantity: number) => {
setQuantity(id, newQuantity);
}}
/>
<button
className="text-gray-500 hover:text-red-500"
onClick={() => removeItem(id)}
>
<TrashIcon className="h-6 w-6" />
</button>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
type Props = {
onQuantityChange: (newQuantity: number) => void;
quantity: number;
className?: string;
};

export const CartItemQuantity: React.FC<Props> = ({
quantity,
onQuantityChange,
className,
}) => {
const handleQuantityChange = (newQuantity: number) => {
if (newQuantity > 0) {
onQuantityChange(newQuantity);
}
};

return (
<div className={className}>
<button
onClick={() => handleQuantityChange(quantity - 1)}
className="text-md bg-gray-200 text-gray-600 px-2 py-1 rounded-l"
>
-
</button>
<input
type="text"
className="w-12 text-center border-t border-b"
value={quantity}
onChange={(e) => handleQuantityChange(parseInt(e.target.value) || 0)}
/>
<button
onClick={() => handleQuantityChange(quantity + 1)}
className="text-md bg-gray-200 text-gray-600 px-2 py-1 rounded-r"
>
+
</button>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import React from "react";
import Link from "next/link";
import { UserCircleIcon } from "@heroicons/react/24/solid";
import { useCart } from "@/context/CartContext";
import OpenCart from "../cart/open-cart";
import OpenCart from "../cart/OpenCart";
import OpenUserProfile from "./user-profile";
import Search from "./search";
import { useSession } from "next-auth/react";

const Navbar: React.FC = () => {
const { items } = useCart();
const { status } = useSession();

return (
<nav className="bg-white shadow-md fixed w-full z-30 top-0 left-0">
Expand All @@ -32,7 +34,7 @@ const Navbar: React.FC = () => {
<Link href="/cart">
<OpenCart quantity={items.length} className="h-6 w-6" />
</Link>
<OpenUserProfile />
<OpenUserProfile authStatus={status} />
</div>
</div>
</nav>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { UserCircleIcon } from "@heroicons/react/24/solid";
import { signIn, signOut, useSession } from "next-auth/react";
import { useState } from "react";

export default function OpenUserProfile() {
export default function OpenUserProfile({
authStatus,
}: {
authStatus: string;
}) {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const { status } = useSession();

return (
<div className="relative">
<div className="flex h-11 w-11 items-center justify-center rounded-md border border-neutral-200 text-black transition-colors">
Expand All @@ -21,14 +23,14 @@ export default function OpenUserProfile() {

{isDropdownOpen && (
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-md shadow-xl">
{status === "unauthenticated" && (
{authStatus === "unauthenticated" && (
<button onClick={() => signIn()} className="w-full">
<div className="block text-left w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
Login
</div>
</button>
)}
{status === "authenticated" && (
{authStatus === "authenticated" && (
<button onClick={() => signOut()} className="w-full">
<div className="block text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
Log Out
Expand Down
42 changes: 38 additions & 4 deletions src/backend/web/web.client/web-app-new/src/context/CartContext.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
'use client'
"use client";

import React, { PropsWithChildren, createContext, useContext, useState } from "react";
import React, {
PropsWithChildren,
createContext,
useContext,
useEffect,
useState,
} from "react";

interface CartItem {
export interface CartItem {
id: number;
name: string;
description: string;
quantity: number;
price: number;
image: string;
}

interface CartContextType {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (itemId: number) => void;
clearCart: () => void;
setQuantity: (id: number, quantity: number) => void;
}

// Create the context
Expand All @@ -23,6 +32,23 @@ const CartContext = createContext<CartContextType | undefined>(undefined);
export const CartProvider = ({ children }: PropsWithChildren<{}>) => {
const [items, setItems] = useState<CartItem[]>([]);

useEffect(() => {
console.log("empty items effect");
const storedItems = localStorage.getItem("items");
if (storedItems) {
setItems(JSON.parse(storedItems));
}
}, []);

useEffect(() => {
console.log("has items effect");
if (items.length === 0) {
localStorage.removeItem("items");
} else {
localStorage.setItem("items", JSON.stringify(items));
}
}, [items]);

const addItem = (newItem: CartItem) => {
setItems((prevItems) => {
// Check if item is already in the cart
Expand All @@ -47,8 +73,16 @@ export const CartProvider = ({ children }: PropsWithChildren<{}>) => {
setItems([]);
};

const setQuantity = (id: number, quantity: number) => {
setItems((prevItems) =>
prevItems.map((item) => (item.id === id ? { ...item, quantity } : item))
);
};

return (
<CartContext.Provider value={{ items, addItem, removeItem, clearCart }}>
<CartContext.Provider
value={{ items, addItem, removeItem, clearCart, setQuantity: setQuantity }}
>
{children}
</CartContext.Provider>
);
Expand Down
Loading

0 comments on commit 948042a

Please sign in to comment.