Hanzo
CommerceStorefront Development

Cart

Create and manage shopping carts in your storefront

This guide covers cart creation, item management, and coupon application in your storefront.

Cart Context

Create a React context to manage cart state across your app:

'use client'

import { createContext, useContext, useState, useEffect } from 'react'
import { commerce } from '@/lib/commerce'
import type { Cart } from '@hanzo/commerce'

const CartContext = createContext<{
  cart: Cart | null
  addItem: (variantId: string, quantity?: number) => Promise<void>
  removeItem: (itemId: string) => Promise<void>
  updateQuantity: (itemId: string, quantity: number) => Promise<void>
  applyCoupon: (code: string) => Promise<void>
}>({} as any)

export function CartProvider({ children, regionId }: {
  children: React.ReactNode
  regionId: string
}) {
  const [cart, setCart] = useState<Cart | null>(null)

  useEffect(() => {
    const cartId = localStorage.getItem('cartId')
    if (cartId) {
      commerce.carts.get(cartId).then(setCart).catch(() => {
        localStorage.removeItem('cartId')
      })
    }
  }, [])

  async function getOrCreateCart() {
    if (cart) return cart
    const newCart = await commerce.carts.create({ regionId })
    localStorage.setItem('cartId', newCart.id)
    setCart(newCart)
    return newCart
  }

  async function addItem(variantId: string, quantity = 1) {
    const c = await getOrCreateCart()
    const updated = await commerce.carts.addItem(c.id, { variantId, quantity })
    setCart(updated)
  }

  async function removeItem(itemId: string) {
    if (!cart) return
    const updated = await commerce.carts.removeItem(cart.id, itemId)
    setCart(updated)
  }

  async function updateQuantity(itemId: string, quantity: number) {
    if (!cart) return
    const updated = await commerce.carts.updateItem(cart.id, itemId, { quantity })
    setCart(updated)
  }

  async function applyCoupon(code: string) {
    if (!cart) return
    const updated = await commerce.carts.applyPromotion(cart.id, { code })
    setCart(updated)
  }

  return (
    <CartContext.Provider value={{ cart, addItem, removeItem, updateQuantity, applyCoupon }}>
      {children}
    </CartContext.Provider>
  )
}

export const useCart = () => useContext(CartContext)

Add to Cart Button

'use client'

import { useCart } from '@/components/cart-provider'

export function AddToCartButton({ variantId }: { variantId: string }) {
  const { addItem } = useCart()
  const [loading, setLoading] = useState(false)

  async function handleClick() {
    setLoading(true)
    await addItem(variantId)
    setLoading(false)
  }

  return (
    <button onClick={handleClick} disabled={loading}>
      {loading ? 'Adding...' : 'Add to Cart'}
    </button>
  )
}

Cart Drawer

'use client'

import { useCart } from '@/components/cart-provider'

export function CartDrawer() {
  const { cart, removeItem, updateQuantity } = useCart()
  if (!cart || cart.items.length === 0) return <p>Your cart is empty</p>

  return (
    <div>
      {cart.items.map(item => (
        <div key={item.id} className="flex justify-between py-4">
          <div>
            <p className="font-medium">{item.productName}</p>
            <p className="text-sm text-gray-500">
              ${(item.unitPrice / 100).toFixed(2)} x {item.quantity}
            </p>
          </div>
          <div className="flex items-center gap-2">
            <input
              type="number" min={1} value={item.quantity}
              onChange={e => updateQuantity(item.id, parseInt(e.target.value))}
              className="w-16 border rounded px-2"
            />
            <button onClick={() => removeItem(item.id)}>Remove</button>
          </div>
        </div>
      ))}
      <div className="border-t pt-4 mt-4">
        <p>Subtotal: ${(cart.subtotal / 100).toFixed(2)}</p>
        {cart.discount > 0 && <p>Discount: -${(cart.discount / 100).toFixed(2)}</p>}
        <p className="font-bold">Total: ${(cart.total / 100).toFixed(2)}</p>
      </div>
    </div>
  )
}

Apply Coupon

'use client'

import { useState } from 'react'
import { useCart } from '@/components/cart-provider'

export function CouponForm() {
  const { applyCoupon } = useCart()
  const [code, setCode] = useState('')
  const [error, setError] = useState('')

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    try {
      await applyCoupon(code)
      setCode('')
      setError('')
    } catch (err: any) {
      setError(err.message || 'Invalid coupon code')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={code} onChange={e => setCode(e.target.value)} placeholder="Coupon code" />
      <button type="submit">Apply</button>
      {error && <p className="text-red-500 text-sm">{error}</p>}
    </form>
  )
}

Cart state is persisted server-side. Store only the cartId in localStorage and fetch the full cart on page load.

How is this guide?

Last updated on

On this page