```
**Rules:**
1. β **Always use `p-3`** for table cells (NOT `px-3 py-2`)
2. β **Always add `hover:bg-muted/30`** to body rows
3. β **Always use `bg-muted/50`** for table headers
4. β **Always use `font-medium`** for header cells
5. β **Always use `last:border-0`** to remove last row border
6. β **Always use `overflow-hidden`** on table container
7. β **Never mix padding styles** between modules
8. β **Never omit hover effects** on interactive rows
**Responsive Behavior:**
- Desktop: Show table with `hidden md:block`
- Mobile: Show cards with `md:hidden`
- Both views must support same actions (select, edit, delete)
#### Variable Product Handling in Order Forms
When adding products to orders, variable products MUST follow the Tokopedia/Shopee pattern:
**Desktop Pattern:**
```
[Search Product...]
β
[Product Name - Variable Product]
ββ [Select Variation βΌ] β Dropdown: Red, Blue, Green
[Add to Order]
```
**Mobile Pattern:**
```
[Search Product...]
β
[Product Card]
Product Name
[Select Variation β] β Opens drawer with variation chips
[Add]
```
**Cart Display (Each variation = separate row):**
```
β Anker Earbuds
White Rp296,000 [-] 1 [+] [ποΈ]
β Anker Earbuds
Black Rp296,000 [-] 1 [+] [ποΈ]
```
**Rules:**
1. β Each variation is a **separate line item**
2. β Show variation name clearly next to product name
3. β Allow adding same product multiple times with different variations
4. β Mobile: Click variation to open drawer for selection
5. β Don't auto-select first variation
6. β Don't hide variation selector
**Implementation:**
- Product search shows variable products
- If variable, show variation selector (dropdown/drawer)
- User must select variation before adding
- Each selected variation becomes separate cart item
- Can repeat for different variations
### 5.8 Mobile Responsiveness & UI Controls
WooNooW enforces a mobileβfirst responsive standard across all SPA interfaces to ensure usability on small screens.
**Control Sizing Standard (`.ui-ctrl`)**
- All interactive controls β input, select, button, and dropdown options β must include the `.ui-ctrl` class or equivalent utility for consistent sizing.
- Default height: `h-11` (mobile), `md:h-9` (desktop).
- This sizing improves tap area accessibility and maintains visual alignment between mobile and desktop.
**Responsive Layout Rules**
- On mobile view, even in fullscreen mode, the layout uses **Topbar navigation** instead of Sidebar for better reachability.
- The Sidebar layout is applied **only** in desktop fullscreen mode.
- Sticky top layers (`App Bar`, `Menu Bar`) remain visible while subβcontent scrolls independently.
- Tables and grids must support horizontal scroll (`overflow-x-auto`) and collapse to cards when screen width < 640px.
**Tokens & Global Styles**
- File: `admin-spa/src/ui/tokens.css` defines base CSS variables for control sizing.
- File: `admin-spa/src/index.css` imports `./ui/tokens.css` and applies the `.ui-ctrl` rules globally.
These rules ensure consistent UX across device classes while maintaining WooNooW's design hierarchy.
### 5.8 Dialog Behavior Pattern
WooNooW uses **Radix UI Dialog** with specific patterns for preventing accidental dismissal.
**Core Principle:** Prevent outside-click and escape-key dismissal for dialogs with unsaved changes or complex editing.
**Dialog Types:**
| Type | Outside Click | Escape Key | Use Case | Example |
|------|---------------|------------|----------|---------|
| **Informational** | β Allow | β Allow | Simple info, confirmations | Alert dialogs |
| **Quick Edit** | β Allow | β Allow | Single field edits | Rename, quick settings |
| **Heavy Edit** | β Prevent | β Prevent | Multi-field forms, rich content | Email builder, template editor |
| **Destructive** | β Prevent | β Prevent | Delete confirmations with input | Delete with confirmation text |
**Implementation:**
```typescript
// Heavy Edit Dialog - Prevent accidental dismissal
// Quick Edit Dialog - Allow dismissal
```
**Rules:**
1. β **Prevent dismissal** when:
- Dialog contains unsaved form data
- User is editing rich content (WYSIWYG, code editor)
- Dialog has multiple steps or complex state
- Action is destructive and requires confirmation
2. β **Allow dismissal** when:
- Dialog is purely informational
- Single field with auto-save
- No data loss risk
- Quick actions (view, select)
3. β **Always provide explicit close buttons**:
- Cancel button to close without saving
- Save button to commit changes
- X button in header (Radix default)
**Examples:**
- β Prevent: `admin-spa/src/components/EmailBuilder/EmailBuilder.tsx` - Block edit dialog
- β Prevent: Template editor dialogs with rich content
- β Allow: Simple confirmation dialogs
- β Allow: View-only information dialogs
**Best Practice:**
When in doubt, **prevent dismissal** for editing dialogs. It's better to require explicit Cancel/Save than risk data loss.
**Responsive Dialog/Drawer Pattern:**
For settings pages and forms, use **ResponsiveDialog** component that automatically switches between Dialog (desktop) and Drawer (mobile):
```typescript
import { ResponsiveDialog } from '@/components/ui/responsive-dialog';
}
>
{/* Form content */}
```
**Behavior:**
- **Desktop (β₯768px)**: Shows centered Dialog
- **Mobile (<768px)**: Shows bottom Drawer for better reachability
**Component:** `admin-spa/src/components/ui/responsive-dialog.tsx`
### 5.9 Settings Page Layout Pattern
WooNooW enforces a **consistent layout pattern** for all settings pages to ensure predictable UX and maintainability.
**Core Principle:** All settings pages MUST use `SettingsLayout` component with contextual header.
**Implementation Pattern:**
```typescript
import { SettingsLayout } from './components/SettingsLayout';
export default function MySettingsPage() {
const [settings, setSettings] = useState({...});
const [isLoading, setIsLoading] = useState(true);
const handleSave = async () => {
// Save logic
};
if (isLoading) {
return (
);
}
return (
{/* Settings content - automatically boxed with max-w-5xl */}
{/* Form fields */}
);
}
```
**SettingsLayout Props:**
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `title` | `string \| ReactNode` | Yes | Page title shown in contextual header |
| `description` | `string` | No | Subtitle/description below title |
| `onSave` | `() => Promise` | No | Save handler - shows Save button in header |
| `saveLabel` | `string` | No | Custom label for save button (default: "Save changes") |
| `isLoading` | `boolean` | No | Shows loading state |
| `action` | `ReactNode` | No | Custom action buttons (e.g., Back button) |
**Layout Behavior:**
1. **Contextual Header** (Mobile + Desktop)
- Shows page title and description
- Shows Save button if `onSave` provided
- Shows custom actions if `action` provided
- Sticky at top of page
2. **Content Area**
- Automatically boxed with `max-w-5xl mx-auto`
- Responsive padding and spacing
- Consistent with other admin pages
3. **No Inline Header**
- When using `onSave` or `action`, inline header is hidden
- Title/description only appear in contextual header
- Saves vertical space
**Rules for Settings Pages:**
1. β **Always use SettingsLayout** - Never create custom layout
2. β **Pass title/description to layout** - Don't render inline headers
3. β **Use onSave for save actions** - Don't render save buttons in content
4. β **Use SettingsCard for sections** - Consistent card styling
5. β **Show loading state** - Use `isLoading` prop during data fetch
6. β **Never use full-width layout** - Content is always boxed
7. β **Never duplicate save buttons** - One save button in header only
**Examples:**
- β Good: `admin-spa/src/routes/Settings/Customers.tsx`
- β Good: `admin-spa/src/routes/Settings/Notifications/Staff.tsx`
- β Good: `admin-spa/src/routes/Settings/Notifications/Customer.tsx`
**Files:**
- Layout component: `admin-spa/src/routes/Settings/components/SettingsLayout.tsx`
- Card component: `admin-spa/src/routes/Settings/components/SettingsCard.tsx`
### 5.9 Mobile Contextual Header Pattern
WooNooW implements a **dual-header system** for mobile-first UX, ensuring actionable pages have consistent navigation and action buttons.
**Concept: Two Headers on Mobile**
1. **Contextual Header** (Mobile + Desktop)
- Common actions that work everywhere
- Format: `[Back Button] Page Title [Primary Action]`
- Always visible (sticky)
- Examples: Back, Edit, Save, Create
2. **Page Header / Extra Actions** (Desktop Only)
- Additional desktop-specific actions
- Hidden on mobile (`hidden md:flex`)
- Examples: Print, Invoice, Label, Export
**Implementation Pattern**
```typescript
import { usePageHeader } from '@/contexts/PageHeaderContext';
import { Button } from '@/components/ui/button';
export default function MyPage() {
const { setPageHeader, clearPageHeader } = usePageHeader();
const nav = useNavigate();
// Set contextual header
useEffect(() => {
const actions = (
);
}
```
**Rules for CRUD Pages**
| Page Type | Contextual Header | Page Header |
|-----------|-------------------|-------------|
| **List** | None (list page) | Filters, Search |
| **Detail** | [Back] Title [Edit] | Print, Invoice, Label |
| **New** | [Back] Title [Create] | None |
| **Edit** | [Back] Title [Save] | None |
**Form Submit Pattern**
For New/Edit pages, move submit button to contextual header:
```typescript
// Use formRef to trigger submit from header
const formRef = useRef(null);
const actions = (
);
```
**Best Practices**
1. **No Duplication** - If action is in contextual header, remove from page body
2. **Mobile First** - Contextual header shows essential actions only
3. **Desktop Enhancement** - Extra actions in page header (desktop only)
4. **Consistent Pattern** - All CRUD pages follow same structure
5. **Loading States** - Buttons show loading state during mutations
**Files**
- `admin-spa/src/contexts/PageHeaderContext.tsx` - Context provider
- `admin-spa/src/hooks/usePageHeader.ts` - Hook for setting headers
- `admin-spa/src/components/PageHeader.tsx` - Header component
### 5.8 Error Handling & User Notifications
WooNooW implements a centralized, user-friendly error handling system that ensures consistent UX across all features.
**Core Principles**
1. **Never expose technical details** to end users (no "API 500", stack traces, or raw error codes)
2. **Use appropriate notification types** based on context
3. **Provide actionable feedback** with clear next steps
4. **Maintain consistency** across all pages and features
**Notification Types**
| Context | Component | Use Case | Example |
|---------|-----------|----------|---------|
| **Page Load Errors** | `` | Query failures, data fetch errors | "Failed to load orders" with retry button |
| **Action Errors** | `toast.error()` | Mutation failures, form submissions | "Failed to create order. Please check all required fields." |
| **Action Success** | `toast.success()` | Successful mutations | "Order created successfully" |
| **Inline Validation** | `` | Form field errors | "Email address is required" |
**Implementation**
```typescript
// For mutations (create, update, delete)
import { showErrorToast, showSuccessToast } from '@/lib/errorHandling';
const mutation = useMutation({
mutationFn: OrdersApi.create,
onSuccess: (data) => {
showSuccessToast('Order created successfully', `Order #${data.number} created`);
},
onError: (error) => {
showErrorToast(error); // Automatically extracts user-friendly message
}
});
// For queries (page loads)
import { ErrorCard } from '@/components/ErrorCard';
import { getPageLoadErrorMessage } from '@/lib/errorHandling';
if (query.isError) {
return query.refetch()}
/>;
}
```
**Error Message Mapping**
Backend errors are mapped to user-friendly messages in `lib/errorHandling.ts`:
```typescript
const friendlyMessages = {
'no_items': 'Please add at least one product to the order',
'create_failed': 'Failed to create order. Please check all required fields.',
'update_failed': 'Failed to update order. Please check all fields.',
'not_found': 'The requested item was not found',
'forbidden': 'You do not have permission to perform this action',
};
```
**Toast Configuration**
- **Position:** Bottom-right
- **Duration:** 4s (success), 6s (errors)
- **Theme:** Light mode with colored backgrounds
- **Colors:** Green (success), Red (error), Amber (warning), Blue (info)
**Files**
- `admin-spa/src/lib/errorHandling.ts` β Centralized error utilities
- `admin-spa/src/components/ErrorCard.tsx` β Page load error component
- `admin-spa/src/components/ui/sonner.tsx` β Toast configuration
### 5.9 Data Validation & Required Fields
WooNooW enforces strict validation rules to ensure data integrity and provide clear feedback to users.
**Order Creation Validation**
All orders must include:
| Field | Requirement | Error Message |
|-------|-------------|---------------|
| **Products** | At least 1 product | "At least one product is required" |
| **Billing First Name** | Required | "Billing first name is required" |
| **Billing Last Name** | Required | "Billing last name is required" |
| **Billing Email** | Required & valid format | "Billing email is required" / "Billing email is not valid" |
| **Billing Address** | Required | "Billing address is required" |
| **Billing City** | Required | "Billing city is required" |
| **Billing Postcode** | Required | "Billing postcode is required" |
| **Billing Country** | Required | "Billing country is required" |
**Backend Validation Response**
When validation fails, the API returns:
```json
{
"error": "validation_failed",
"message": "Please complete all required fields",
"fields": [
"Billing first name is required",
"Billing email is required",
"Billing address is required"
]
}
```
**Frontend Display**
The error handling utility automatically formats field errors as a bulleted list:
```
β Please complete all required fields
β’ Billing first name is required
β’ Billing email is required
β’ Billing address is required
β’ Billing city is required
β’ Billing postcode is required
```
Each field error appears as a bullet point on its own line, making it easy for users to scan and see exactly what needs to be fixed.
**Implementation Location**
- Backend validation: `includes/Api/OrdersController.php` create() method
- Frontend handling: `admin-spa/src/lib/errorHandling.ts` getErrorMessage()
### 5.10 Internationalization (i18n)
WooNooW follows WordPress translation standards to ensure all user-facing strings are translatable.
**Text Domain:** `woonoow`
**Backend (PHP)**
Use WordPress translation functions:
```php
// Simple translation
__( 'Billing first name', 'woonoow' )
// Translation with sprintf
sprintf( __( '%s is required', 'woonoow' ), $field_label )
// Translators comment for context
/* translators: %s: field label */
sprintf( __( '%s is required', 'woonoow' ), $label )
```
**Frontend (TypeScript/React)**
Use the i18n utility wrapper:
```typescript
import { __, sprintf } from '@/lib/i18n';
// Simple translation
__('Failed to load data')
// Translation with sprintf (placeholders)
sprintf(__('Order #%s created'), orderNumber)
sprintf(__('Edit Order #%s'), orderId)
// In components
{sprintf(__('Order #%s'), order.number)}
// In error messages
const title = __('Please complete all required fields');
const message = sprintf(__('Order #%s has been created'), data.number);
```
**Translation Files**
- Backend strings: Extracted to `languages/woonoow.pot`
- Frontend strings: Loaded via `wp.i18n` (WordPress handles this)
- Translation utilities: `admin-spa/src/lib/i18n.ts`
**Best Practices**
1. **Never hardcode user-facing strings** - Always use translation functions
2. **Use translators comments** for context when using placeholders
3. **Keep strings simple** - Avoid complex concatenation
4. **Test in English first** - Ensure strings make sense before translation
---
## 5.11 Loading States
WooNooW provides a **consistent loading UI system** across the application to ensure a polished user experience.
**Component:** `admin-spa/src/components/LoadingState.tsx`
### Loading Components
**1. LoadingState (Default)**a
```typescript
import { LoadingState } from '@/components/LoadingState';
// Default loading
// Custom message
// Different sizes
// default
// Full screen overlay
```
**2. PageLoadingState**
```typescript
import { PageLoadingState } from '@/components/LoadingState';
// For full page loads
if (isLoading) {
return ;
}
```
**3. InlineLoadingState**
```typescript
import { InlineLoadingState } from '@/components/LoadingState';
// For inline loading within components
{isLoading && }
```
**4. CardLoadingSkeleton**
```typescript
import { CardLoadingSkeleton } from '@/components/LoadingState';
// For loading card content
{isLoading && }
```
**5. TableLoadingSkeleton**
```typescript
import { TableLoadingSkeleton } from '@/components/LoadingState';
// For loading table rows
{isLoading && }
```
### Usage Guidelines
**Page-Level Loading:**
```typescript
// β Good - Use PageLoadingState for full page loads
if (orderQ.isLoading || countriesQ.isLoading) {
return ;
}
// β Bad - Don't use plain text
if (isLoading) {
return
Loading...
;
}
```
**Inline Loading:**
```typescript
// β Good - Use InlineLoadingState for partial loads
{q.isLoading && }
// β Bad - Don't use custom spinners
{q.isLoading &&
Loading...
}
```
**Table Loading:**
```typescript
// β Good - Use TableLoadingSkeleton for tables
{q.isLoading && }
// β Bad - Don't show empty state while loading
{q.isLoading &&
Loading data...
}
```
### Best Practices
1. **Always use i18n** - All loading messages must be translatable
```typescript
```
2. **Be specific** - Use descriptive messages
```typescript
// β Good
// β Bad
```
3. **Choose appropriate size** - Match the context
- `sm` - Inline, buttons, small components
- `md` - Default, cards, sections
- `lg` - Full page, important actions
4. **Use skeletons for lists** - Better UX than spinners
```typescript
{isLoading ? :
}
```
5. **Responsive design** - Loading states work on all screen sizes
- Mobile: Optimized spacing and sizing
- Desktop: Full layout preserved
### Pattern Examples
**Order Edit Page:**
```typescript
export default function OrdersEdit() {
const orderQ = useQuery({ ... });
if (orderQ.isLoading) {
return ;
}
return ;
}
```
**Order Detail Page:**
```typescript
export default function OrderDetail() {
const q = useQuery({ ... });
return (
);
}
```
### π Checklist for New CRUD Module
**Backend:**
- [ ] Create `{Module}Controller.php` with all CRUD endpoints
- [ ] Register routes in `Routes.php`
- [ ] Add permission checks (`Permissions::check_admin`)
- [ ] Implement pagination, filters, search
- [ ] Return consistent response format
- [ ] Add i18n for all error messages
**Frontend:**
- [ ] Create `routes/{Module}/index.tsx` (list view)
- [ ] Create `routes/{Module}/New.tsx` (create)
- [ ] Create `routes/{Module}/Edit.tsx` (edit)
- [ ] Create `routes/{Module}/Detail.tsx` (optional view)
- [ ] Create `components/{Module}Card.tsx` (mobile)
- [ ] Create `partials/{Module}Form.tsx` (reusable form)
- [ ] Add to navigation tree (`nav/tree.ts`)
- [ ] Configure FAB (`useFABConfig`)
- [ ] Add all i18n strings
- [ ] Implement bulk delete
- [ ] Add filters (status, date, search)
- [ ] Add pagination
- [ ] Test mobile responsive
- [ ] Test error states
- [ ] Test loading states
**Testing:**
- [ ] Create item
- [ ] Edit item
- [ ] Delete item
- [ ] Bulk delete
- [ ] Search
- [ ] Filter by status
- [ ] Pagination
- [ ] Mobile view
- [ ] Error handling
- [ ] Permission checks
---
## 7. π¨ Admin Interface Modes
WooNooW provides **three distinct admin interface modes** to accommodate different workflows and user preferences:
### **1. Normal Mode (wp-admin)**
- **Access:** `/wp-admin/admin.php?page=woonoow`
- **Layout:** Traditional WordPress admin with WooNooW SPA in content area
- **Use Case:** Standard WordPress admin workflow
- **Features:**
- WordPress admin bar and sidebar visible
- Full WordPress admin functionality
- WooNooW SPA integrated seamlessly
- Settings submenu hidden (use WooCommerce settings)
- **When to use:** When you need access to other WordPress admin features alongside WooNooW
### **2. Fullscreen Mode**
- **Access:** Toggle button in WooNooW header
- **Layout:** WooNooW SPA only (no WordPress chrome)
- **Use Case:** Focused work sessions, order processing
- **Features:**
- Maximized workspace
- Distraction-free interface
- All WooNooW features accessible
- Settings submenu hidden
- **When to use:** When you want to focus exclusively on WooNooW tasks
### **3. Standalone Mode** β¨
- **Access:** `https://yoursite.com/admin`
- **Layout:** Complete standalone application with custom login
- **Use Case:** Quick daily access, mobile-friendly, bookmark-able
- **Features:**
- Custom login page (`/admin#/login`)
- WordPress authentication integration
- Settings submenu visible (SPA settings pages)
- "WordPress" button to access wp-admin
- "Logout" button in header
- Admin bar link in wp-admin to standalone
- Session persistence across modes
- **When to use:** As your primary WooNooW interface, especially on mobile or for quick access
### **Mode Switching**
- **From wp-admin to Standalone:** Click "WooNooW" in admin bar
- **From Standalone to wp-admin:** Click "WordPress" button in header
- **To Fullscreen:** Click fullscreen toggle in any mode
- **Session persistence:** Login state is shared across all modes
### **Settings Submenu Behavior**
- **Normal Mode:** No settings submenu (use WooCommerce settings in wp-admin)
- **Fullscreen Mode:** No settings submenu
- **Standalone Mode:** Full settings submenu visible with SPA pages
**Implementation:** Settings submenu uses dynamic getter in `admin-spa/src/nav/tree.ts`:
```typescript
get children() {
const isStandalone = (window as any).WNW_CONFIG?.standaloneMode;
if (!isStandalone) return [];
return [ /* settings items */ ];
}
```
---
## 8. π€ AI Agent Collaboration Rules
When using an AI IDE agent (ChatGPT, Claude, etc.):
### Step 1: Context Injection
Always load:
- `README.md`
- `PROJECT_SOP.md`
- The specific file(s) being edited
### Step 2: Editing Rules
1. All AI edits must be **idempotent** β never break structure or naming conventions.
2. Always follow PSRβ12 PHP standard and React code conventions.
3. When unsure about a design decision, **refer back to this S.O.P.** before guessing.
4. New files must be registered in the correct namespace path.
5. When editing React components, ensure build compatibility with Vite.
### Step 3: Communication
AI agents must:
- Explain each patch clearly.
- Never autoβremove code without reason.
- Maintain English for all code comments, Markdown for docs.
---
## 7. π¦ Release Steps
1. Run all builds:
```bash
npm run build && npm run pack
```
2. Test in LocalWP with a sample Woo store.
3. Validate HPOS compatibility and order creation flow.
4. Push final `woonoow.zip` to release channel (Sejoli, member.dwindi.com, or manual upload).
5. Tag version using semantic versioning (e.g. `v0.2.0-beta`).
---
## 8. π§ Decision Hierarchy
| Category | Decision Reference |
|-----------|--------------------|
| Code Style | Follow PSRβ12 (PHP) & Airbnb/React rules |
| Architecture | PSRβ4 + modular single responsibility |
| UI/UX | Modern minimal style, standardized using Tailwind + Shadcn UI. Recharts for data visualization. |
| Icons | Use lucide-react via npm i lucide-react. Icons should match Shadcn UI guidelines. Always import directly (e.g. import { Package } from 'lucide-react'). Maintain consistent size (16β20px) and stroke width (1.5px). Use Tailwind classes for color states. |
| **Navigation Pattern** | **CRUD pages MUST follow consistent back button navigation: New Order: Index β New. Edit Order: Index β Detail β Edit. Back button always goes to parent page, not index. Use ArrowLeft icon from lucide-react. Toolbar format: `