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:
dwindown
2025-11-21 13:53:38 +07:00
parent 342104eeab
commit 909bddb23d
15 changed files with 8398 additions and 0 deletions

View 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 }),
}
)
);