From a62037d993e11531f4a314a65eafa1480149dde9 Mon Sep 17 00:00:00 2001 From: Dwindi Ramadhana Date: Fri, 27 Feb 2026 23:15:10 +0700 Subject: [PATCH] feat/fix: checkout email tracing, UI tweaks for add-to-cart, cart page overflow fix, implement hide admin bar setting --- .agent/plans/software-distribution.md | 379 +++++++++++++++ admin-spa/src/App.tsx | 2 + .../Pages/components/CanvasSection.tsx | 22 +- .../Pages/components/InspectorPanel.tsx | 218 ++++++--- .../Pages/store/usePageEditorStore.ts | 4 + .../Products/SoftwareVersions/index.tsx | 347 +++++++++++++ customer-spa/src/components/ProductCard.tsx | 138 +++--- customer-spa/src/lib/sectionStyles.ts | 47 ++ customer-spa/src/pages/Cart/index.tsx | 126 +++-- .../DynamicPage/sections/HeroSection.tsx | 9 +- customer-spa/src/pages/Product/index.tsx | 4 +- includes/Admin/AppearanceController.php | 1 + includes/Api/OnboardingController.php | 9 +- includes/Api/Routes.php | 4 + includes/Api/SoftwareController.php | 321 ++++++++++++ includes/Compat/NavigationRegistry.php | 1 + includes/Frontend/PageSSR.php | 205 ++++---- includes/Modules/Software/SoftwareManager.php | 456 ++++++++++++++++++ includes/Modules/Software/SoftwareModule.php | 200 ++++++++ includes/Modules/SoftwareSettings.php | 113 +++++ templates/updater/class-woonoow-updater.php | 391 +++++++++++++++ woonoow.php | 8 +- 22 files changed, 2711 insertions(+), 294 deletions(-) create mode 100644 .agent/plans/software-distribution.md create mode 100644 admin-spa/src/routes/Products/SoftwareVersions/index.tsx create mode 100644 customer-spa/src/lib/sectionStyles.ts create mode 100644 includes/Api/SoftwareController.php create mode 100644 includes/Modules/Software/SoftwareManager.php create mode 100644 includes/Modules/Software/SoftwareModule.php create mode 100644 includes/Modules/SoftwareSettings.php create mode 100644 templates/updater/class-woonoow-updater.php diff --git a/.agent/plans/software-distribution.md b/.agent/plans/software-distribution.md new file mode 100644 index 0000000..27f4971 --- /dev/null +++ b/.agent/plans/software-distribution.md @@ -0,0 +1,379 @@ +# Software Distribution System Plan (Simplified) + +Extends WooNooW Licensing with software versioning, changelog, and update checker API. Works for **any software type** - not just WordPress plugins/themes. + +--- + +## Core Principle + +**Reuse existing WooCommerce infrastructure:** +- ✅ Downloadable Product = file hosting (already exists) +- ✅ Licensing Module = license validation (already exists) +- ➕ NEW: Version tracking + changelog per product +- ➕ NEW: Update checker API endpoint + +--- + +## What We Add (Minimal) + +| Field | Type | Location | +|-------|------|----------| +| `Current Version` | Text (e.g., "1.2.3") | Product Edit | +| `Changelog` | Rich Text/Markdown | Product Edit | +| `Software Slug` | Text (unique identifier) | Product Edit | +| `Enable WP Integration` | Checkbox | Product Edit | + +**If "Enable WP Integration" is checked:** +| Field | Type | +|-------|------| +| `Requires WP` | Text | +| `Tested WP` | Text | +| `Requires PHP` | Text | +| `Icon URL` | Image | +| `Banner URL` | Image | + +--- + +## Database Schema + +**Table: `wp_woonoow_software_versions`** + +```sql +CREATE TABLE wp_woonoow_software_versions ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + product_id BIGINT UNSIGNED NOT NULL, + version VARCHAR(50) NOT NULL, + changelog LONGTEXT, + release_date DATETIME NOT NULL, + is_current TINYINT(1) DEFAULT 0, + download_count INT DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + + INDEX idx_product (product_id), + INDEX idx_current (product_id, is_current), + UNIQUE KEY unique_version (product_id, version) +); +``` + +**Product Meta (stored in `wp_postmeta`):** +``` +_woonoow_software_enabled: yes/no +_woonoow_software_slug: my-awesome-plugin +_woonoow_software_current_version: 1.2.3 +_woonoow_software_wp_enabled: yes/no +_woonoow_software_requires_wp: 6.0 +_woonoow_software_tested_wp: 6.7 +_woonoow_software_requires_php: 7.4 +_woonoow_software_icon: attachment_id or URL +_woonoow_software_banner: attachment_id or URL +``` + +--- + +## API Endpoints + +### 1. Update Check (Generic) + +`GET /wp-json/woonoow/v1/software/check` + +**Request:** +```json +{ + "license_key": "XXXX-XXXX-XXXX-XXXX", + "slug": "my-software", + "version": "1.0.0", + "site_url": "https://client-site.com" +} +``` + +**Response (Generic - Any Software):** +```json +{ + "success": true, + "update_available": true, + "product": { + "name": "My Awesome Software", + "slug": "my-software" + }, + "current_version": "1.0.0", + "latest_version": "1.2.3", + "download_url": "https://vendor.com/wp-json/woonoow/v1/software/download?token=abc123", + "changelog_url": "https://vendor.com/wp-json/woonoow/v1/software/changelog?slug=my-software", + "changelog": "## 1.2.3\n- Fixed bug\n- Added feature", + "release_date": "2026-02-06" +} +``` + +**Response (WordPress Plugin/Theme - Extended):** +```json +{ + "success": true, + "update_available": true, + "product": { + "name": "My WordPress Plugin", + "slug": "my-wp-plugin" + }, + "current_version": "1.0.0", + "latest_version": "1.2.3", + "download_url": "https://vendor.com/wp-json/woonoow/v1/software/download?token=abc123", + "changelog_url": "https://vendor.com/wp-json/woonoow/v1/software/changelog?slug=my-wp-plugin", + "changelog": "## 1.2.3\n- Fixed bug", + "release_date": "2026-02-06", + + "wordpress": { + "requires": "6.0", + "tested": "6.7", + "requires_php": "7.4", + "icons": { + "1x": "https://vendor.com/icon-128.png", + "2x": "https://vendor.com/icon-256.png" + }, + "banners": { + "low": "https://vendor.com/banner-772x250.jpg", + "high": "https://vendor.com/banner-1544x500.jpg" + } + } +} +``` + +**Response (No Update):** +```json +{ + "success": true, + "update_available": false, + "current_version": "1.2.3", + "latest_version": "1.2.3" +} +``` + +**Response (License Invalid):** +```json +{ + "success": false, + "error": "license_expired", + "message": "Your license has expired. Renew to receive updates." +} +``` + +--- + +### 2. Download + +`GET /wp-json/woonoow/v1/software/download` + +**Request:** +``` +?token=abc123&license_key=XXXX-XXXX-XXXX-XXXX +``` + +**Response:** Binary file stream (the downloadable file from WooCommerce product) + +**Token Validation:** +- One-time use token +- 5-minute expiry +- Tied to specific license + +--- + +### 3. Changelog + +`GET /wp-json/woonoow/v1/software/changelog` + +**Request:** +``` +?slug=my-software&version=1.2.3 (optional) +``` + +**Response:** +```json +{ + "slug": "my-software", + "versions": [ + { + "version": "1.2.3", + "release_date": "2026-02-06", + "changelog": "## 1.2.3\n- Fixed critical bug\n- Added dark mode" + }, + { + "version": "1.2.0", + "release_date": "2026-01-15", + "changelog": "## 1.2.0\n- Major feature release" + } + ] +} +``` + +--- + +## Client Integration Examples + +### A. WordPress Plugin (Auto-Updates) + +Documented in: **woonoow-docs** → Developer → Software Updates + +```php +// Include the updater class (provided by WooNooW) +require_once 'class-woonoow-updater.php'; + +new WooNooW_Updater([ + 'api_url' => 'https://your-store.com/', + 'slug' => 'my-plugin', + 'version' => MY_PLUGIN_VERSION, + 'license_key' => get_option('my_plugin_license'), + 'plugin_file' => __FILE__, +]); +``` + +### B. Generic Software (Manual Check) + +For desktop apps, SaaS, scripts, etc: + +```javascript +// Check for updates +const response = await fetch('https://vendor.com/wp-json/woonoow/v1/software/check', { + method: 'POST', + body: JSON.stringify({ + license_key: 'XXXX-XXXX-XXXX-XXXX', + slug: 'my-desktop-app', + version: '1.0.0' + }) +}); + +const data = await response.json(); +if (data.update_available) { + console.log(`New version ${data.latest_version} available!`); + console.log(`Download: ${data.download_url}`); +} +``` + +```python +# Python example +import requests + +response = requests.post('https://vendor.com/wp-json/woonoow/v1/software/check', json={ + 'license_key': 'XXXX-XXXX-XXXX-XXXX', + 'slug': 'my-python-tool', + 'version': '1.0.0' +}) + +data = response.json() +if data.get('update_available'): + print(f"Update available: {data['latest_version']}") +``` + +--- + +## Implementation Flow + +### Product Setup (Seller) + +1. Create WooCommerce product (Simple, Downloadable) +2. Enable Licensing ✓ +3. Enable Software Distribution ✓ +4. Set software slug: `my-software` +5. Upload downloadable file (uses existing WC functionality) +6. Set version: `1.0.0` +7. Add changelog +8. (Optional) Enable WordPress Integration → fill WP-specific fields + +### Release New Version + +1. Upload new file to product +2. Click "Add New Version" +3. Enter version number: `1.1.0` +4. Write changelog +5. Save → Previous version archived, new version active + +### Client Updates + +1. Client software calls `/software/check` with license key +2. API validates license → returns latest version info +3. Client downloads via `/software/download` with token +4. Client installs update (WordPress = automatic, others = manual/custom) + +--- + +## Files to Create + +``` +includes/ +├── Api/ +│ └── SoftwareController.php # REST endpoints +├── Modules/ +│ └── Software/ +│ ├── SoftwareModule.php # Bootstrap +│ └── SoftwareManager.php # Core logic +└── Templates/ + └── updater/ + └── class-woonoow-updater.php # WP client library + +admin-spa/src/routes/Settings/ +└── Software.tsx # Version manager UI +``` + +--- + +## Documentation (woonoow-docs) + +Create new section: **Developer → Software Distribution** + +1. **Overview** - What is software distribution +2. **Setup Guide** - How to configure products +3. **API Reference** - Endpoint documentation +4. **WordPress Integration** - How to use the updater class +5. **Generic Integration** - Examples for non-WP software +6. **Client Libraries** - Download the updater class + +--- + +## Implementation Phases + +### Phase 1: Core ✅ +- [x] Database table + product meta fields (`wp_woonoow_software_versions`, `wp_woonoow_software_downloads`) +- [x] SoftwareManager class (`includes/Modules/Software/SoftwareManager.php`) +- [x] SoftwareModule class (`includes/Modules/Software/SoftwareModule.php`) +- [x] SoftwareSettings class (`includes/Modules/SoftwareSettings.php`) +- [x] Product edit fields in WooCommerce + +### Phase 2: API ✅ +- [x] SoftwareController (`includes/Api/SoftwareController.php`) +- [x] `/software/check` endpoint (GET/POST) +- [x] `/software/download` endpoint with token +- [x] `/software/changelog` endpoint + +### Phase 3: Admin UI ✅ +- [x] Version release manager in Admin SPA (`routes/Products/SoftwareVersions/index.tsx`) +- [x] Changelog editor (Dialog with Textarea) +- [x] Version history table + +### Phase 4: Client Library ✅ +- [x] `class-woonoow-updater.php` for WP (`templates/updater/class-woonoow-updater.php`) +- [x] Supports both plugins and themes +- [x] Includes license status checking + +### Phase 5: Documentation ✅ +- [x] Add docs to woonoow-docs (`contents/docs/developer/software-updates/index.mdx`) +- [x] API reference +- [x] Integration examples (WordPress, JavaScript, Python) + +--- + +## Security + +| Measure | Description | +|---------|-------------| +| License Validation | All endpoints verify active license | +| Download Tokens | One-time use, 5-min expiry | +| Rate Limiting | Max 10 requests/minute per license | +| Signed URLs | HMAC signature for download links | + +--- + +## Questions Before Implementation + +1. ✅ Generic for any software (not just WP) +2. ✅ Reuse WooCommerce downloadable files +3. ✅ WordPress fields are optional +4. ✅ Document client library in woonoow-docs + +Ready to proceed with Phase 1? diff --git a/admin-spa/src/App.tsx b/admin-spa/src/App.tsx index a8b7ad1..c6d43a2 100644 --- a/admin-spa/src/App.tsx +++ b/admin-spa/src/App.tsx @@ -23,6 +23,7 @@ import ProductTags from '@/routes/Products/Tags'; import ProductAttributes from '@/routes/Products/Attributes'; import Licenses from '@/routes/Products/Licenses'; import LicenseDetail from '@/routes/Products/Licenses/Detail'; +import SoftwareVersions from '@/routes/Products/SoftwareVersions'; import SubscriptionsIndex from '@/routes/Subscriptions'; import SubscriptionDetail from '@/routes/Subscriptions/Detail'; import CouponsIndex from '@/routes/Marketing/Coupons'; @@ -590,6 +591,7 @@ function AppRoutes() { } /> } /> } /> + } /> {/* Orders */} } /> diff --git a/admin-spa/src/routes/Appearance/Pages/components/CanvasSection.tsx b/admin-spa/src/routes/Appearance/Pages/components/CanvasSection.tsx index 474adf4..0c2d826 100644 --- a/admin-spa/src/routes/Appearance/Pages/components/CanvasSection.tsx +++ b/admin-spa/src/routes/Appearance/Pages/components/CanvasSection.tsx @@ -81,15 +81,18 @@ export function CanvasSection({ > {/* Section content with Styles */}
{/* Background Image & Overlay */} - {section.styles?.backgroundImage && ( + {section.styles?.backgroundType === 'image' && section.styles?.backgroundImage && ( <>
)} + {/* Legacy: show bg image even without backgroundType set */} + {!section.styles?.backgroundType && section.styles?.backgroundImage && ( + <> +
+
+ + )} {/* Content Wrapper */}

{__('Background')}

+ + {/* Background Type Selector */}
- -
-
-
+ +
+ {(['solid', 'gradient', 'image'] as const).map((t) => ( + + ))} +
+
+ + {/* Solid Color */} + {(!selectedSection.styles?.backgroundType || selectedSection.styles?.backgroundType === 'solid') && ( +
+ +
+
+
+ onSectionStylesChange({ backgroundColor: e.target.value })} + /> +
onSectionStylesChange({ backgroundColor: e.target.value })} />
- onSectionStylesChange({ backgroundColor: e.target.value })} +
+ )} + + {/* Gradient Controls */} + {selectedSection.styles?.backgroundType === 'gradient' && ( +
+ {/* Live Preview Swatch */} +
-
-
- -
- - onSectionStylesChange({ backgroundImage: url })}> - {selectedSection.styles?.backgroundImage ? ( -
- Background -
- {__('Change')} +
+
+ +
+
+
+ onSectionStylesChange({ gradientFrom: e.target.value })} + /> +
+ onSectionStylesChange({ gradientFrom: e.target.value })} + />
-
- ) : ( - - )} - -
- -
-
- - {selectedSection.styles?.backgroundOverlay ?? 0}% +
+ +
+
+
+ onSectionStylesChange({ gradientTo: e.target.value })} + /> +
+ onSectionStylesChange({ gradientTo: e.target.value })} + /> +
+
+
+
+
+ + {selectedSection.styles?.gradientAngle ?? 135}° +
+ onSectionStylesChange({ gradientAngle: vals[0] })} + /> +
- onSectionStylesChange({ backgroundOverlay: vals[0] })} - /> -
+ )} + + {/* Image Background */} + {selectedSection.styles?.backgroundType === 'image' && ( + <> +
+ + onSectionStylesChange({ backgroundImage: url })}> + {selectedSection.styles?.backgroundImage ? ( +
+ Background +
+ {__('Change')} +
+ +
+ ) : ( + + )} +
+
+ +
+
+ + {selectedSection.styles?.backgroundOverlay ?? 0}% +
+ onSectionStylesChange({ backgroundOverlay: vals[0] })} + /> +
+ + )}
setSearch(e.target.value)} + className="pl-9" + /> +
+
+ +
+ {productsLoading ? ( +
+ +
+ ) : filteredProducts.length === 0 ? ( +
+ +

{__('No software products found')}

+

+ {__('Enable software distribution on a downloadable product')} +

+
+ ) : ( +
+ {filteredProducts.map((product) => ( + + ))} +
+ )} +
+
+ + {/* Version Details */} +
+ {!selectedProduct ? ( +
+
+ +

{__('Select a product to view versions')}

+
+
+ ) : versionsLoading ? ( +
+ +
+ ) : ( + <> + {/* Version Header */} +
+
+

{selectedProductData?.name}

+

+ {__('Current version')}: {versionsData?.config?.current_version || '—'} +

+
+ + + + + + + {__('Add New Version')} + + {__('Release a new version of')} {selectedProductData?.name} + + +
+
+ + setNewVersion(prev => ({ ...prev, version: e.target.value }))} + /> +

+ {__('Use semantic versioning (e.g., 1.0.0, 1.2.3)')} +

+
+
+ +