feat: Create customer-spa core foundation (Sprint 1)
Sprint 1 - Foundation Complete! ✅ Created Core Files: ✅ src/main.tsx - Entry point ✅ src/App.tsx - Main app with routing ✅ src/index.css - Global styles (TailwindCSS) ✅ index.html - Development HTML Pages Created (Placeholders): ✅ pages/Shop/index.tsx - Product listing ✅ pages/Product/index.tsx - Product detail ✅ pages/Cart/index.tsx - Shopping cart ✅ pages/Checkout/index.tsx - Checkout process ✅ pages/Account/index.tsx - My Account with sub-routes Library Setup: ✅ lib/api/client.ts - API client with endpoints ✅ lib/cart/store.ts - Cart state management (Zustand) ✅ types/index.ts - TypeScript definitions Configuration: ✅ .gitignore - Ignore node_modules, dist, logs ✅ README.md - Documentation Features Implemented: 1. Routing (React Router v7) - /shop - Product listing - /shop/product/:id - Product detail - /shop/cart - Shopping cart - /shop/checkout - Checkout - /shop/account/* - My Account (dashboard, orders, profile) 2. API Client - Fetch wrapper with error handling - WordPress nonce authentication - Endpoints for shop, cart, checkout, account - TypeScript typed responses 3. Cart State (Zustand) - Add/update/remove items - Cart drawer (open/close) - LocalStorage persistence - Quantity management - Coupon support 4. Type Definitions - Product, Order, Customer types - Address, ShippingMethod, PaymentMethod - Cart, CartItem types - Window interface for WordPress globals 5. React Query Setup - QueryClient configured - 5-minute stale time - Retry on error - No refetch on window focus 6. Toast Notifications - Sonner toast library - Top-right position - Rich colors Tech Stack: - React 18 + TypeScript - Vite (port 5174) - React Router v7 - TanStack Query - Zustand (state) - TailwindCSS - shadcn/ui - React Hook Form + Zod Dependencies Installed: ✅ 437 packages installed ✅ All peer dependencies resolved ✅ Ready for development Next Steps (Sprint 2): - Implement Shop page with product grid - Create ProductCard component - Add filters and search - Implement pagination - Connect to WordPress API Ready to run: ```bash cd customer-spa npm run dev # Opens https://woonoow.local:5174 ```
This commit is contained in:
114
customer-spa/src/lib/cart/store.ts
Normal file
114
customer-spa/src/lib/cart/store.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export interface CartItem {
|
||||
key: string;
|
||||
product_id: number;
|
||||
variation_id?: number;
|
||||
quantity: number;
|
||||
name: string;
|
||||
price: number;
|
||||
image?: string;
|
||||
attributes?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface Cart {
|
||||
items: CartItem[];
|
||||
subtotal: number;
|
||||
tax: number;
|
||||
shipping: number;
|
||||
total: number;
|
||||
coupon?: {
|
||||
code: string;
|
||||
discount: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface CartStore {
|
||||
cart: Cart;
|
||||
isOpen: boolean;
|
||||
|
||||
// Actions
|
||||
setCart: (cart: Cart) => void;
|
||||
addItem: (item: CartItem) => void;
|
||||
updateQuantity: (key: string, quantity: number) => void;
|
||||
removeItem: (key: string) => void;
|
||||
clearCart: () => void;
|
||||
openCart: () => void;
|
||||
closeCart: () => void;
|
||||
toggleCart: () => void;
|
||||
}
|
||||
|
||||
const initialCart: Cart = {
|
||||
items: [],
|
||||
subtotal: 0,
|
||||
tax: 0,
|
||||
shipping: 0,
|
||||
total: 0,
|
||||
};
|
||||
|
||||
export const useCartStore = create<CartStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
cart: initialCart,
|
||||
isOpen: false,
|
||||
|
||||
setCart: (cart) => set({ cart }),
|
||||
|
||||
addItem: (item) =>
|
||||
set((state) => {
|
||||
const existingItem = state.cart.items.find((i) => i.key === item.key);
|
||||
|
||||
if (existingItem) {
|
||||
// Update quantity if item exists
|
||||
return {
|
||||
cart: {
|
||||
...state.cart,
|
||||
items: state.cart.items.map((i) =>
|
||||
i.key === item.key
|
||||
? { ...i, quantity: i.quantity + item.quantity }
|
||||
: i
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Add new item
|
||||
return {
|
||||
cart: {
|
||||
...state.cart,
|
||||
items: [...state.cart.items, item],
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
updateQuantity: (key, quantity) =>
|
||||
set((state) => ({
|
||||
cart: {
|
||||
...state.cart,
|
||||
items: state.cart.items.map((item) =>
|
||||
item.key === key ? { ...item, quantity } : item
|
||||
),
|
||||
},
|
||||
})),
|
||||
|
||||
removeItem: (key) =>
|
||||
set((state) => ({
|
||||
cart: {
|
||||
...state.cart,
|
||||
items: state.cart.items.filter((item) => item.key !== key),
|
||||
},
|
||||
})),
|
||||
|
||||
clearCart: () => set({ cart: initialCart }),
|
||||
|
||||
openCart: () => set({ isOpen: true }),
|
||||
closeCart: () => set({ isOpen: false }),
|
||||
toggleCart: () => set((state) => ({ isOpen: !state.isOpen })),
|
||||
}),
|
||||
{
|
||||
name: 'woonoow-cart',
|
||||
partialize: (state) => ({ cart: state.cart }),
|
||||
}
|
||||
)
|
||||
);
|
||||
Reference in New Issue
Block a user