feat: Implement Phase 2, 3, 4 - Module Settings System with Schema Forms and Addon API
Phase 2: Schema-Based Form System
- Add ModuleSettingsController with GET/POST/schema endpoints
- Create SchemaField component supporting 8 field types (text, textarea, email, url, number, toggle, checkbox, select)
- Create SchemaForm component for automatic form generation from schema
- Add ModuleSettings page with dynamic routing (/settings/modules/:moduleId)
- Add useModuleSettings React hook for settings management
- Implement NewsletterSettings as example with 8 configurable fields
- Add has_settings flag to module registry
- Settings stored as woonoow_module_{module_id}_settings
Phase 3: Advanced Features
- Create windowAPI.ts exposing React, hooks, components, icons, utils to addons via window.WooNooW
- Add DynamicComponentLoader for loading external React components
- Create TypeScript definitions (woonoow-addon.d.ts) for addon developers
- Initialize Window API in App.tsx on mount
- Enable custom React components for addon settings pages
Phase 4: Production Polish & Example
- Create complete Biteship addon example demonstrating both approaches:
* Schema-based settings (no build required)
* Custom React component (with build)
- Add comprehensive README with installation and testing guide
- Include package.json with esbuild configuration
- Demonstrate window.WooNooW API usage in custom component
Bug Fixes:
- Fix footer newsletter form visibility (remove redundant module check)
- Fix footer contact_data and social_links not saving (parameter name mismatch: snake_case vs camelCase)
- Fix useModules hook returning undefined (remove .data wrapper, add fallback)
- Add optional chaining to footer settings rendering
- Fix TypeScript errors in woonoow-addon.d.ts (use any for external types)
Files Added (15):
- includes/Api/ModuleSettingsController.php
- includes/Modules/NewsletterSettings.php
- admin-spa/src/components/forms/SchemaField.tsx
- admin-spa/src/components/forms/SchemaForm.tsx
- admin-spa/src/routes/Settings/ModuleSettings.tsx
- admin-spa/src/hooks/useModuleSettings.ts
- admin-spa/src/lib/windowAPI.ts
- admin-spa/src/components/DynamicComponentLoader.tsx
- types/woonoow-addon.d.ts
- examples/biteship-addon/biteship-addon.php
- examples/biteship-addon/src/Settings.jsx
- examples/biteship-addon/package.json
- examples/biteship-addon/README.md
- PHASE_2_3_4_SUMMARY.md
Files Modified (11):
- admin-spa/src/App.tsx
- admin-spa/src/hooks/useModules.ts
- admin-spa/src/routes/Appearance/Footer.tsx
- admin-spa/src/routes/Settings/Modules.tsx
- customer-spa/src/hooks/useModules.ts
- customer-spa/src/layouts/BaseLayout.tsx
- customer-spa/src/components/NewsletterForm.tsx
- includes/Api/Routes.php
- includes/Api/ModulesController.php
- includes/Core/ModuleRegistry.php
- woonoow.php
API Endpoints Added:
- GET /woonoow/v1/modules/{module_id}/settings
- POST /woonoow/v1/modules/{module_id}/settings
- GET /woonoow/v1/modules/{module_id}/schema
For Addon Developers:
- Schema-based: Define settings via woonoow/module_settings_schema filter
- Custom React: Build component using window.WooNooW API, externalize react/react-dom
- Both approaches use same storage and retrieval methods
- TypeScript definitions provided for type safety
- Complete working example (Biteship) included
This commit is contained in:
161
types/woonoow-addon.d.ts
vendored
Normal file
161
types/woonoow-addon.d.ts
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* WooNooW Addon TypeScript Definitions
|
||||
*
|
||||
* Type definitions for addon developers using the WooNooW API
|
||||
*
|
||||
* @package WooNooW
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
import type { ComponentType } from 'react';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
/**
|
||||
* WooNooW API exposed to addon developers
|
||||
*/
|
||||
WooNooW: {
|
||||
React: typeof import('react');
|
||||
ReactDOM: typeof import('react-dom/client');
|
||||
|
||||
hooks: {
|
||||
useQuery: any;
|
||||
useMutation: any;
|
||||
useQueryClient: any;
|
||||
useModules: () => {
|
||||
isEnabled: (moduleId: string) => boolean;
|
||||
modules: string[];
|
||||
};
|
||||
useModuleSettings: (moduleId: string) => {
|
||||
settings: Record<string, any>;
|
||||
isLoading: boolean;
|
||||
updateSettings: {
|
||||
mutate: (settings: Record<string, any>) => void;
|
||||
isPending: boolean;
|
||||
};
|
||||
saveSetting: (key: string, value: any) => void;
|
||||
};
|
||||
};
|
||||
|
||||
components: {
|
||||
Button: ComponentType<any>;
|
||||
Input: ComponentType<any>;
|
||||
Label: ComponentType<any>;
|
||||
Textarea: ComponentType<any>;
|
||||
Switch: ComponentType<any>;
|
||||
Select: ComponentType<any>;
|
||||
SelectContent: ComponentType<any>;
|
||||
SelectItem: ComponentType<any>;
|
||||
SelectTrigger: ComponentType<any>;
|
||||
SelectValue: ComponentType<any>;
|
||||
Checkbox: ComponentType<any>;
|
||||
Badge: ComponentType<any>;
|
||||
Card: ComponentType<any>;
|
||||
CardContent: ComponentType<any>;
|
||||
CardDescription: ComponentType<any>;
|
||||
CardFooter: ComponentType<any>;
|
||||
CardHeader: ComponentType<any>;
|
||||
CardTitle: ComponentType<any>;
|
||||
SettingsLayout: ComponentType<{
|
||||
title: string;
|
||||
description?: string;
|
||||
isLoading?: boolean;
|
||||
children: React.ReactNode;
|
||||
}>;
|
||||
SettingsCard: ComponentType<{
|
||||
title: string;
|
||||
description?: string;
|
||||
children: React.ReactNode;
|
||||
}>;
|
||||
SettingsSection: ComponentType<any>;
|
||||
SchemaForm: ComponentType<{
|
||||
schema: FormSchema;
|
||||
initialValues?: Record<string, any>;
|
||||
onSubmit: (values: Record<string, any>) => void | Promise<void>;
|
||||
isSubmitting?: boolean;
|
||||
submitLabel?: string;
|
||||
errors?: Record<string, string>;
|
||||
}>;
|
||||
SchemaField: ComponentType<any>;
|
||||
};
|
||||
|
||||
icons: {
|
||||
Settings: ComponentType<any>;
|
||||
Save: ComponentType<any>;
|
||||
Trash2: ComponentType<any>;
|
||||
Edit: ComponentType<any>;
|
||||
Plus: ComponentType<any>;
|
||||
X: ComponentType<any>;
|
||||
Check: ComponentType<any>;
|
||||
AlertCircle: ComponentType<any>;
|
||||
Info: ComponentType<any>;
|
||||
Loader2: ComponentType<any>;
|
||||
ChevronDown: ComponentType<any>;
|
||||
ChevronUp: ComponentType<any>;
|
||||
ChevronLeft: ComponentType<any>;
|
||||
ChevronRight: ComponentType<any>;
|
||||
};
|
||||
|
||||
utils: {
|
||||
api: {
|
||||
get: (endpoint: string) => Promise<any>;
|
||||
post: (endpoint: string, data?: any) => Promise<any>;
|
||||
put: (endpoint: string, data?: any) => Promise<any>;
|
||||
delete: (endpoint: string) => Promise<any>;
|
||||
};
|
||||
toast: {
|
||||
success: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
info: (message: string) => void;
|
||||
warning: (message: string) => void;
|
||||
};
|
||||
__: (text: string, domain?: string) => string;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form Schema Types
|
||||
*/
|
||||
export type FieldType = 'text' | 'textarea' | 'email' | 'url' | 'number' | 'toggle' | 'checkbox' | 'select';
|
||||
|
||||
export interface FieldSchema {
|
||||
type: FieldType;
|
||||
label: string;
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
default?: any;
|
||||
options?: Record<string, string>;
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
export type FormSchema = Record<string, FieldSchema>;
|
||||
|
||||
/**
|
||||
* Module Registration
|
||||
*/
|
||||
export interface ModuleRegistration {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
version: string;
|
||||
author: string;
|
||||
category: 'marketing' | 'customers' | 'products' | 'shipping' | 'payments' | 'analytics' | 'other';
|
||||
icon: string;
|
||||
features: string[];
|
||||
has_settings?: boolean;
|
||||
settings_component?: string;
|
||||
spa_bundle?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings Schema Registration
|
||||
*/
|
||||
export interface SettingsSchemaRegistration {
|
||||
[moduleId: string]: FormSchema;
|
||||
}
|
||||
|
||||
export {};
|
||||
Reference in New Issue
Block a user