From 6aae252cff453a99ecc4a904dc7f483040a9d954 Mon Sep 17 00:00:00 2001 From: Adam Kwiatkowski Date: Mon, 5 Jun 2023 01:53:38 +0200 Subject: [PATCH 1/3] Add checkout page --- .../client/src/components/CartItemPanel.tsx | 5 +- .../client/src/components/CartPopover.tsx | 4 +- .../client/src/components/CategoryFilters.tsx | 4 +- .../client/src/components/OrderSummary.tsx | 8 +- frontend/client/src/main.tsx | 5 + frontend/client/src/pages/CheckoutPage.tsx | 178 ++++++++++++++++++ 6 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 frontend/client/src/pages/CheckoutPage.tsx diff --git a/frontend/client/src/components/CartItemPanel.tsx b/frontend/client/src/components/CartItemPanel.tsx index fe27d92..6e6d298 100644 --- a/frontend/client/src/components/CartItemPanel.tsx +++ b/frontend/client/src/components/CartItemPanel.tsx @@ -14,7 +14,8 @@ export default function CartItemPanel({item, removeItem, updateItem}: CartItemPa return (
  • - {item.product.name}/ + {item.product.name}/
    @@ -28,7 +29,7 @@ export default function CartItemPanel({item, removeItem, updateItem}: CartItemPa

    {currencyFormat(item.product.price)}

    -
    +
    { const value = parseInt(e.target.value); diff --git a/frontend/client/src/components/CartPopover.tsx b/frontend/client/src/components/CartPopover.tsx index aef059d..438b424 100644 --- a/frontend/client/src/components/CartPopover.tsx +++ b/frontend/client/src/components/CartPopover.tsx @@ -65,13 +65,13 @@ export default function CartPopover() { ))}
    - + Checkout - + diff --git a/frontend/client/src/components/CategoryFilters.tsx b/frontend/client/src/components/CategoryFilters.tsx index 20e136f..45ef4e7 100644 --- a/frontend/client/src/components/CategoryFilters.tsx +++ b/frontend/client/src/components/CategoryFilters.tsx @@ -131,7 +131,7 @@ export function CategoryFilters({children}: { children: React.ReactNode }) {
    @@ -242,7 +242,7 @@ export function CategoryFilters({children}: { children: React.ReactNode }) { diff --git a/frontend/client/src/components/OrderSummary.tsx b/frontend/client/src/components/OrderSummary.tsx index ff8b569..7aa5540 100644 --- a/frontend/client/src/components/OrderSummary.tsx +++ b/frontend/client/src/components/OrderSummary.tsx @@ -3,9 +3,9 @@ import {Link} from "react-router-dom"; import {currencyFormat} from "../utilities/currencyFormat"; export default function OrderSummary({items}: { items: CartItem[] }) { - const subTotal = items.reduce((acc, item) => acc + item.product.price * item.quantity, 0) + const subtotal = items.reduce((acc, item) => acc + item.product.price * item.quantity, 0) const shipping = items.length > 0 ? 5 : 0 - const total = subTotal + shipping + const total = subtotal + shipping return (

    Order summary

    @@ -13,7 +13,7 @@ export default function OrderSummary({items}: { items: CartItem[] }) {
    Subtotal
    - {currencyFormat(subTotal)} + {currencyFormat(subtotal)}
    @@ -31,7 +31,7 @@ export default function OrderSummary({items}: { items: CartItem[] }) {
    + className="inline-flex items-center px-6 py-3 justify-center border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-neutral-950 hover:bg-neutral-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-900 w-full transition-all"> Checkout
    diff --git a/frontend/client/src/main.tsx b/frontend/client/src/main.tsx index c3d9476..ced7c59 100644 --- a/frontend/client/src/main.tsx +++ b/frontend/client/src/main.tsx @@ -8,6 +8,7 @@ import ProductsPage, {loader as productListLoader} from "./pages/ProductsPage"; import LoginPage from "./pages/LoginPage"; import ProductDetailsPage, {loader as productLoader} from "./pages/ProductDetailsPage"; import CartOverviewPage from "./pages/CartOverviewPage"; +import CheckoutPage from "./pages/CheckoutPage"; const router = createBrowserRouter([ { @@ -32,6 +33,10 @@ const router = createBrowserRouter([ { path: "cart", element: + }, + { + path: "checkout", + element: } ] }, diff --git a/frontend/client/src/pages/CheckoutPage.tsx b/frontend/client/src/pages/CheckoutPage.tsx new file mode 100644 index 0000000..289c12f --- /dev/null +++ b/frontend/client/src/pages/CheckoutPage.tsx @@ -0,0 +1,178 @@ +import React from "react"; +import {useShoppingCart} from "../context/ShoppingCartContext"; +import {Form, Link} from "react-router-dom"; +import {currencyFormat} from "../utilities/currencyFormat"; + +export default function CheckoutPage() { + const {items} = useShoppingCart(); + const subtotal = items.reduce((total, item) => total + item.product.price * item.quantity, 0); + + return (
    +
    +

    Checkout

    + + Return to cart + +
    +
    + {/* payment and shipping details */} +
    +
    +
    +

    + Contact information +

    +
    + +
    + +
    +
    +
    +
    +

    + Shipping address +

    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +

    + Payment details +

    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
      + {items.map((item, index) => (
    • +
      + {item.product.name} +
      +
      +

      {item.product.name}

      +

      {item.product.description}

      +

      {currencyFormat(item.product.price)} x {item.quantity}

      +
      +
    • ))} +
    +
    + {/* subtotal */} +
    +
    +

    + Subtotal +

    +

    {currencyFormat(subtotal)}

    +
    +
    +
    +
    +
    ); +} \ No newline at end of file From 91e69fccfac856b132bdbe1cf693f82487934d19 Mon Sep 17 00:00:00 2001 From: Adam Kwiatkowski Date: Mon, 5 Jun 2023 01:59:25 +0200 Subject: [PATCH 2/3] Make checkout page responsive --- frontend/client/src/pages/CartOverviewPage.tsx | 2 +- frontend/client/src/pages/CheckoutPage.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/client/src/pages/CartOverviewPage.tsx b/frontend/client/src/pages/CartOverviewPage.tsx index e75c158..ece8330 100644 --- a/frontend/client/src/pages/CartOverviewPage.tsx +++ b/frontend/client/src/pages/CartOverviewPage.tsx @@ -8,7 +8,7 @@ export default function CartOverviewPage() { const {items, removeItem, updateItem} = useShoppingCart(); return ( -
    +

    Shopping Cart

    diff --git a/frontend/client/src/pages/CheckoutPage.tsx b/frontend/client/src/pages/CheckoutPage.tsx index 289c12f..27dac9e 100644 --- a/frontend/client/src/pages/CheckoutPage.tsx +++ b/frontend/client/src/pages/CheckoutPage.tsx @@ -16,7 +16,7 @@ export default function CheckoutPage() { Return to cart
    -
    +
    {/* payment and shipping details */}
    @@ -110,7 +110,7 @@ export default function CheckoutPage() { id="card-number" name="card-number" autoComplete="card-number"/>
    -
    +
    @@ -119,7 +119,7 @@ export default function CheckoutPage() { id="card-holder" name="card-holder" autoComplete="card-holder"/>
    -
    +
    -
    +
    Date: Thu, 8 Jun 2023 00:33:54 +0200 Subject: [PATCH 3/3] Use debouncing for product updates --- .../client/src/components/CartPopover.tsx | 2 +- .../src/context/ShoppingCartContext.tsx | 25 +++++++++++++------ frontend/client/src/main.tsx | 5 ++-- .../client/src/pages/CartOverviewPage.tsx | 2 +- frontend/client/src/pages/ProductsPage.tsx | 9 +------ 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/frontend/client/src/components/CartPopover.tsx b/frontend/client/src/components/CartPopover.tsx index 438b424..a01699b 100644 --- a/frontend/client/src/components/CartPopover.tsx +++ b/frontend/client/src/components/CartPopover.tsx @@ -37,7 +37,7 @@ export default function CartPopover() { leaveTo="opacity-0 translate-y-1" > + className="fixed right-0 z-10 mt-3 w-screen sm:max-w-sm sm:right-4 transform px-4 sm:px-0 lg:max-w-sm ">
    {items.length > 0 ? (<>
    diff --git a/frontend/client/src/context/ShoppingCartContext.tsx b/frontend/client/src/context/ShoppingCartContext.tsx index e69354c..8a9004a 100644 --- a/frontend/client/src/context/ShoppingCartContext.tsx +++ b/frontend/client/src/context/ShoppingCartContext.tsx @@ -1,5 +1,6 @@ import {createContext, ReactNode, useContext, useEffect, useState} from "react"; import {CartItem} from "../models/CartItem"; +import {debounce} from "@mui/material"; type ShoppingCartProviderProps = { children: ReactNode @@ -15,6 +16,16 @@ type ShoppingCart = { const ShoppingCartContext = createContext({} as ShoppingCart) +async function sendItemUpdate(item: CartItem) { + await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/basket/${item.product.productID}`, { + method: 'PUT', headers: { + 'Content-Type': 'application/json' + }, body: JSON.stringify(item) + }) +} + +const debouncedUpdateItem = debounce(sendItemUpdate, 500) + export function useShoppingCart() { return useContext(ShoppingCartContext) } @@ -26,6 +37,9 @@ export function ShoppingCartProvider({children}: ShoppingCartProviderProps) { useEffect(() => { async function fetchBasket() { const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/basket`) + if (!response.ok) { + return + } const items = await response.json() setCartItems(items) } @@ -48,21 +62,16 @@ export function ShoppingCartProvider({children}: ShoppingCartProviderProps) { } } + async function updateItem(item: CartItem) { - await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/basket/${item.product.productID}`, { - method: 'PUT', headers: { - 'Content-Type': 'application/json' - }, body: JSON.stringify(item) - }) setCartItems([...cartItems.map((i) => i.product.productID === item.product.productID ? item : i)]) + await debouncedUpdateItem(item) } async function removeItem(item: CartItem) { await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/basket/${item.product.productID}`, { method: 'DELETE' }) - console.log(item) - console.log(cartItems) setCartItems([...cartItems.filter((i) => i.product.productID !== item.product.productID)]) } @@ -75,7 +84,7 @@ export function ShoppingCartProvider({children}: ShoppingCartProviderProps) { return + }}> {children} } \ No newline at end of file diff --git a/frontend/client/src/main.tsx b/frontend/client/src/main.tsx index ced7c59..48c59cf 100644 --- a/frontend/client/src/main.tsx +++ b/frontend/client/src/main.tsx @@ -4,7 +4,7 @@ import {createBrowserRouter, RouterProvider,} from "react-router-dom"; import './index.css' import Root from "./pages/Root"; import ErrorPage from "./pages/ErrorPage"; -import ProductsPage, {loader as productListLoader} from "./pages/ProductsPage"; +import ProductsPage from "./pages/ProductsPage"; import LoginPage from "./pages/LoginPage"; import ProductDetailsPage, {loader as productLoader} from "./pages/ProductDetailsPage"; import CartOverviewPage from "./pages/CartOverviewPage"; @@ -18,8 +18,7 @@ const router = createBrowserRouter([ children: [ { path: "/", - element: , - loader: productListLoader, + element: }, { path: "login", diff --git a/frontend/client/src/pages/CartOverviewPage.tsx b/frontend/client/src/pages/CartOverviewPage.tsx index ece8330..4b9bcda 100644 --- a/frontend/client/src/pages/CartOverviewPage.tsx +++ b/frontend/client/src/pages/CartOverviewPage.tsx @@ -14,7 +14,7 @@ export default function CartOverviewPage() {

    Shopping Cart

    -
    +
      {items.map((item, index) => diff --git a/frontend/client/src/pages/ProductsPage.tsx b/frontend/client/src/pages/ProductsPage.tsx index ac55f2e..206a4bb 100644 --- a/frontend/client/src/pages/ProductsPage.tsx +++ b/frontend/client/src/pages/ProductsPage.tsx @@ -5,13 +5,6 @@ import {CategoryFilters} from "../components/CategoryFilters"; import {useLocation} from "react-router-dom"; import SpinnerIcon from "../resources/SpinnerIcon"; - -export function loader({request}: { request: Request }) { - let url = new URL(request.url); - console.log(url) - return null; -} - function useQuery() { const {search} = useLocation(); @@ -51,7 +44,7 @@ export default function ProductsPage() { url.searchParams.append(key, value); } - fetch(url).then(res => res.json()).then((data) => { + fetch(url).then(res => res.ok ? res.json() : Promise.reject(res)).then((data) => { setProducts((prevProducts) => [...prevProducts, ...data]); setHasMore(data.length > 0); setLoading(false);