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:
122
customer-spa/src/lib/api/client.ts
Normal file
122
customer-spa/src/lib/api/client.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* API Client for WooNooW Customer SPA
|
||||
* Handles all HTTP requests to WordPress REST API
|
||||
*/
|
||||
|
||||
// Get API base URL from WordPress
|
||||
const getApiBase = (): string => {
|
||||
// @ts-ignore - WordPress global
|
||||
return window.woonoowCustomer?.apiUrl || '/wp-json/woonoow/v1';
|
||||
};
|
||||
|
||||
// Get nonce for authentication
|
||||
const getNonce = (): string => {
|
||||
// @ts-ignore - WordPress global
|
||||
return window.woonoowCustomer?.nonce || '';
|
||||
};
|
||||
|
||||
interface RequestOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
body?: any;
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
class ApiClient {
|
||||
private baseUrl: string;
|
||||
|
||||
constructor() {
|
||||
this.baseUrl = getApiBase();
|
||||
}
|
||||
|
||||
private async request<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {
|
||||
const { method = 'GET', body, headers = {} } = options;
|
||||
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
|
||||
const config: RequestInit = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': getNonce(),
|
||||
...headers,
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
};
|
||||
|
||||
if (body) {
|
||||
config.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, config);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }));
|
||||
throw new Error(error.message || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('[API Error]', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// GET request
|
||||
async get<T>(endpoint: string, params?: Record<string, any>): Promise<T> {
|
||||
let url = endpoint;
|
||||
if (params) {
|
||||
const query = new URLSearchParams(params).toString();
|
||||
url += `?${query}`;
|
||||
}
|
||||
return this.request<T>(url, { method: 'GET' });
|
||||
}
|
||||
|
||||
// POST request
|
||||
async post<T>(endpoint: string, body?: any): Promise<T> {
|
||||
return this.request<T>(endpoint, { method: 'POST', body });
|
||||
}
|
||||
|
||||
// PUT request
|
||||
async put<T>(endpoint: string, body?: any): Promise<T> {
|
||||
return this.request<T>(endpoint, { method: 'PUT', body });
|
||||
}
|
||||
|
||||
// DELETE request
|
||||
async delete<T>(endpoint: string): Promise<T> {
|
||||
return this.request<T>(endpoint, { method: 'DELETE' });
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const api = new ApiClient();
|
||||
|
||||
// Export API endpoints
|
||||
export const endpoints = {
|
||||
// Shop
|
||||
products: '/shop/products',
|
||||
product: (id: number) => `/shop/products/${id}`,
|
||||
categories: '/shop/categories',
|
||||
search: '/shop/search',
|
||||
|
||||
// Cart
|
||||
cart: '/cart',
|
||||
cartAdd: '/cart/add',
|
||||
cartUpdate: '/cart/update',
|
||||
cartRemove: '/cart/remove',
|
||||
cartCoupon: '/cart/apply-coupon',
|
||||
|
||||
// Checkout
|
||||
checkoutCalculate: '/checkout/calculate',
|
||||
checkoutCreate: '/checkout/create-order',
|
||||
paymentMethods: '/checkout/payment-methods',
|
||||
shippingMethods: '/checkout/shipping-methods',
|
||||
|
||||
// Account
|
||||
orders: '/account/orders',
|
||||
order: (id: number) => `/account/orders/${id}`,
|
||||
downloads: '/account/downloads',
|
||||
profile: '/account/profile',
|
||||
password: '/account/password',
|
||||
addresses: '/account/addresses',
|
||||
};
|
||||
Reference in New Issue
Block a user