Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
124 lines
3.1 KiB
TypeScript
124 lines
3.1 KiB
TypeScript
'use client'
|
|
|
|
import { createContext, useContext, useState, useEffect, ReactNode, useMemo, useCallback } from 'react'
|
|
|
|
export interface CartItem {
|
|
id: string
|
|
name: string
|
|
price: number
|
|
priceId: string
|
|
image: string
|
|
quantity: number
|
|
}
|
|
|
|
interface CartContextType {
|
|
items: CartItem[]
|
|
addItem: (item: Omit<CartItem, 'quantity'> & { quantity?: number }) => void
|
|
removeItem: (id: string) => void
|
|
updateQuantity: (id: string, quantity: number) => void
|
|
clearCart: () => void
|
|
getTotal: () => number
|
|
getItemCount: () => number
|
|
}
|
|
|
|
const CartContext = createContext<CartContextType | undefined>(undefined)
|
|
|
|
const CART_STORAGE_KEY = 'rmv-cart'
|
|
|
|
export function CartProvider({ children }: { children: ReactNode }) {
|
|
const [items, setItems] = useState<CartItem[]>([])
|
|
const [isLoaded, setIsLoaded] = useState(false)
|
|
|
|
// Load cart from localStorage on mount
|
|
useEffect(() => {
|
|
try {
|
|
const stored = localStorage.getItem(CART_STORAGE_KEY)
|
|
if (stored) {
|
|
setItems(JSON.parse(stored))
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading cart from localStorage:', error)
|
|
} finally {
|
|
setIsLoaded(true)
|
|
}
|
|
}, [])
|
|
|
|
// Save cart to localStorage whenever it changes
|
|
useEffect(() => {
|
|
if (isLoaded) {
|
|
try {
|
|
localStorage.setItem(CART_STORAGE_KEY, JSON.stringify(items))
|
|
} catch (error) {
|
|
console.error('Error saving cart to localStorage:', error)
|
|
}
|
|
}
|
|
}, [items, isLoaded])
|
|
|
|
const addItem = (item: Omit<CartItem, 'quantity'> & { quantity?: number }) => {
|
|
setItems((prevItems) => {
|
|
const existingItem = prevItems.find((i) => i.id === item.id)
|
|
if (existingItem) {
|
|
return prevItems.map((i) =>
|
|
i.id === item.id
|
|
? { ...i, quantity: i.quantity + (item.quantity || 1) }
|
|
: i
|
|
)
|
|
}
|
|
return [...prevItems, { ...item, quantity: item.quantity || 1 }]
|
|
})
|
|
}
|
|
|
|
const removeItem = (id: string) => {
|
|
setItems((prevItems) => prevItems.filter((i) => i.id !== id))
|
|
}
|
|
|
|
const updateQuantity = (id: string, quantity: number) => {
|
|
if (quantity <= 0) {
|
|
removeItem(id)
|
|
return
|
|
}
|
|
setItems((prevItems) =>
|
|
prevItems.map((i) => (i.id === id ? { ...i, quantity } : i))
|
|
)
|
|
}
|
|
|
|
const clearCart = () => {
|
|
setItems([])
|
|
}
|
|
|
|
// Memoize cart totals to prevent unnecessary re-renders in consuming components
|
|
const cartTotal = useMemo(() => {
|
|
return items.reduce((total, item) => total + item.price * item.quantity, 0)
|
|
}, [items])
|
|
|
|
const cartItemCount = useMemo(() => {
|
|
return items.reduce((count, item) => count + item.quantity, 0)
|
|
}, [items])
|
|
|
|
const value: CartContextType = {
|
|
items,
|
|
addItem,
|
|
removeItem,
|
|
updateQuantity,
|
|
clearCart,
|
|
getTotal: () => cartTotal,
|
|
getItemCount: () => cartItemCount,
|
|
}
|
|
|
|
return (
|
|
<CartContext.Provider value={value}>
|
|
{children}
|
|
</CartContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useCart() {
|
|
const context = useContext(CartContext)
|
|
if (context === undefined) {
|
|
throw new Error('useCart must be used within a CartProvider')
|
|
}
|
|
return context
|
|
}
|
|
|
|
export { CartProvider }
|
|
|