feat: Order & Checkout System - cart, checkout, order history, tracking
- Backend: Add GET /api/orders/:id, PUT /api/orders/:id/cancel, GET /api/orders/:id/tracking with mock Yamato data - Frontend: Zustand cart store with localStorage persistence (key 'cart') - Frontend: CartPage (/cart) with quantity controls, item removal, JPY totals - Frontend: CheckoutPage (/checkout) with shipping address form, auth guard, POST to /api/orders - Frontend: OrderHistoryPage (/account/orders) with Japanese status labels, auth guard - Frontend: OrderDetailPage (/account/orders/:id) with cancel button, tracking section, auth guard - Updated App.tsx with all four new routes - Updated ProductDetail.tsx to use cart store with View Cart link after adding - Updated Navbar.tsx with cart icon badge (item count) and 注文履歴 order history link
This commit is contained in:
70
client/src/store/cart.ts
Normal file
70
client/src/store/cart.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
export interface CartItem {
|
||||
product_id: string
|
||||
name: string
|
||||
price: number
|
||||
quantity: number
|
||||
image?: string
|
||||
}
|
||||
|
||||
interface CartState {
|
||||
items: CartItem[]
|
||||
total: number
|
||||
addItem: (item: CartItem) => void
|
||||
removeItem: (product_id: string) => void
|
||||
updateQuantity: (product_id: string, qty: number) => void
|
||||
clearCart: () => void
|
||||
}
|
||||
|
||||
function calcTotal(items: CartItem[]): number {
|
||||
return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
||||
}
|
||||
|
||||
export const useCartStore = create<CartState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
items: [],
|
||||
total: 0,
|
||||
|
||||
addItem: (item: CartItem) => {
|
||||
const existing = get().items.find(i => i.product_id === item.product_id)
|
||||
let newItems: CartItem[]
|
||||
if (existing) {
|
||||
newItems = get().items.map(i =>
|
||||
i.product_id === item.product_id
|
||||
? { ...i, quantity: i.quantity + item.quantity }
|
||||
: i
|
||||
)
|
||||
} else {
|
||||
newItems = [...get().items, item]
|
||||
}
|
||||
set({ items: newItems, total: calcTotal(newItems) })
|
||||
},
|
||||
|
||||
removeItem: (product_id: string) => {
|
||||
const newItems = get().items.filter(i => i.product_id !== product_id)
|
||||
set({ items: newItems, total: calcTotal(newItems) })
|
||||
},
|
||||
|
||||
updateQuantity: (product_id: string, qty: number) => {
|
||||
if (qty <= 0) {
|
||||
get().removeItem(product_id)
|
||||
return
|
||||
}
|
||||
const newItems = get().items.map(i =>
|
||||
i.product_id === product_id ? { ...i, quantity: qty } : i
|
||||
)
|
||||
set({ items: newItems, total: calcTotal(newItems) })
|
||||
},
|
||||
|
||||
clearCart: () => {
|
||||
set({ items: [], total: 0 })
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'cart',
|
||||
}
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user