Compare commits

...

26 Commits

Author SHA1 Message Date
dwindown
63e62b6a3e chore: rebuild assets after design system changes 2026-04-19 05:59:58 +07:00
dwindown
057611ef40 feat: add WPCFTO-inspired design system and React navigation
- Add WPCFTO-inspired design system CSS (colors, spacing, typography)
- Add reusable React components matching WPCFTO visual language
- Implement client-side navigation with hash-based routing
- Add NavigationMenu component for on-page navigation (no SSR)
- Update WordPress submenu highlighting based on current React page
- Add Refresh button to DataTable toolbar
- Fix icon imports in VariationPricingTable
- Remove page headers from all table pages (consistent UX)
- Convert Orders page to use DataTable for consistency

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 05:58:44 +07:00
dwindown
96ea79600a fix: only render modal when explicitly opened to prevent auto-show
Change modal rendering strategy from controlled via 'open' prop to
conditional rendering. Now the Modal component is only added to DOM
when isAddModalOpen is true, preventing it from being present on page load.

Before: {actions.addNew && <Modal open={isAddModalOpen} ...>}
After:  {actions.addNew && isAddModalOpen && <Modal ...>}

This ensures the modal cannot accidentally show on page load and cannot be
interfered with when it shouldn't exist.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:41:54 +07:00
dwindown
9499931034 fix: use correct WordPress Modal component props
Fix modal that was persisting by using correct @wordpress/components
Modal API:
- Use 'open' instead of 'isOpen' prop
- Use 'onRequestClose' instead of 'onClose'
- Add 'isDismissible' to allow closing with ESC/outside click
- Add 'focusOnMount' for accessibility
- Add deprecation warning props for TextControl (__next40pxDefaultSize,
  __nextHasNoMarginBottom)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:38:36 +07:00
dwindown
83b7294fa4 fix: prevent modal from showing unexpectedly and fix inline actions
Remove duplicate inline actions from Forms page title column to prevent
conflicts with DataTable's built-in actions column.

Fix DataTable actions column:
- Only show delete/duplicate on hover using CSS
- Add proper event propagation handling (stopPropagation)
- Remove unnecessary wrapper div that was causing issues

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:28:49 +07:00
dwindown
bb74df4d6b feat: migrate Products page to full DataTable
Update Products page with full-featured DataTable:
- All columns (ID, title, price with multi-currency, type, stock, status)
- Status filter (All, Published, Draft)
- Inline actions (edit variations, delete, duplicate)
- Bulk delete and Add New modal
- VariationPricingTable editor preserved for editing individual products

All 7 admin listing pages now use the full-featured DataTable component:
✓ Forms - with shortcode copy button
✓ Coupons - with type and amount display
✓ Access - with product relation
✓ Orders - specialized with filters and date ranges
✓ Products - with multi-currency pricing
✓ Customers - read-only with order counts
✓ Licenses - read-only with status labels

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:20:19 +07:00
dwindown
128e396040 feat: migrate Coupons, Access, Customers, Licenses pages to full DataTable
Update all admin listing pages to use the full-featured DataTable component:

Coupons page:
- All columns (ID, code, type, amount, usages, date limit, status)
- Status filter (All, Active, Inactive)
- Inline actions and bulk delete
- Add New modal

Access page:
- All columns (ID, title, product, status, date)
- Status filter (All, Published, Draft)
- Inline actions and bulk delete
- Add New modal

Customers page:
- All columns (ID, name, email, phone, total orders, date)
- Read-only (no selection or inline actions)
- Search and pagination

Licenses page:
- All columns (ID, license key, product, order, email, status)
- Status labels (Active, Inactive, Expired)
- Read-only (no selection or inline actions)
- Search and pagination

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:13:09 +07:00
dwindown
8529cfa2c0 feat: implement full-featured DataTable component
Implement comprehensive DataTable with all Grid.js features:
- Checkbox selection with "Select All"
- Bulk delete button (shows when rows selected)
- Inline row actions (edit, delete, duplicate) on hover
- Status filter tabs with counts
- Search input with debounce
- Sort dropdown (ID, date, title ASC/DESC)
- Server-side pagination
- "Add New" modal with SweetAlert2
- SweetAlert2 loaded via WordPress global scope

Updated Forms page to use new DataTable component with:
- Full column rendering (ID, title, date, status, shortcode)
- Copy shortcode button with toast notification
- All filter, search, sort, pagination features

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:10:45 +07:00
dwindown
f7c09a17cf refactor: remove coexistence mode, use React only
Remove all dual-mode rendering logic since React is now the single
admin interface. Focus on implementing full table features in React.

Changes:
- Remove ?react= query param checks from all page methods
- Remove admin notice and footer toggle from ReactAdmin
- Simplify asset loading - ReactAdmin handles all assets
- Clean up Init.php enqueue method

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:08:21 +07:00
dwindown
e8fbfb14c1 fix: prevent asset conflicts between React and Grid.js versions
Add coexistence checks to all enqueue methods to prevent loading
both React and Grid.js assets simultaneously.

Changes:
- ReactAdmin.php: Only enqueue React assets when ?react=1
- Init.php: Skip Grid.js when React active on admin pages
- Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0
- Customer.php, Product.php, License.php: Add coexistence checks

Now the toggle between Classic and React versions works correctly.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:02:14 +07:00
dwindown
bd9cdac02e feat: implement coexistence strategy for Grid.js and React admin
Implement dual-mode rendering allowing classic Grid.js and new React
versions to run side-by-side during migration.

- Add coexistence mode checks to all admin page methods
- Check query param ?react=1 or option 'formipay_use_react_admin'
- Include classic PHP pages when React not active
- Add admin notice showing current version with toggle button
- Add footer toggle link to switch between versions

This ensures zero feature loss - old Grid.js pages continue working
(~20 features per page) while React versions are developed.

Files:
- Form.php, Coupon.php, Access.php, Order.php
- Customer.php, Product.php, License.php
- ReactAdmin.php (added version_notice, footer_toggle)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 16:55:56 +07:00
dwindown
ab69d03f78 fix: React admin icon imports and mount point rendering
- Fix all @wordpress/icons imports - use build/ paths and call as functions
- Update all admin pages to render React mount points
- Components fixed: VariationPricingTable, NotificationLog, GlobalSettings,
  FormFieldOptions, OrderDetail, OrderListItem, OrderList, FormCanvas,
  AnalyticsDashboard, FormField, Products
- Admin pages updated: Order.php, Customer.php, Coupon.php, License.php,
  Product.php, Access.php, Form.php

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 13:28:57 +07:00
dwindown
6fd6d29fe8 fix: remove incompatible packages from dependencies
- Remove @wordpress/url (version doesn't exist)
- Remove unused @wordpress/date and @tanstack/react-table
- Keep only core WordPress dependencies that are actually used
- Add build/ directory with compiled assets
2026-04-18 13:14:39 +07:00
dwindown
c5c1909a39 docs: mark Phase 2 complete (Weeks 3-6)
Phase 2 Summary (React Admin Foundation):
 Week 3: Build Pipeline (package.json, webpack, API client)
 Week 4: Form Builder (drag-drop canvas, field palette, settings, preview)
 Week 5: Order Management (list, detail, timeline, analytics, notifications)
 Week 6: Settings & Editors (global settings, product editor, Vue migration)

Next: Phase 3 - Frontend Enhancements
2026-04-18 12:34:49 +07:00
dwindown
904d86ad64 feat: update Coupons, Access, Licenses pages (F2.20-F2.22)
- Add placeholder content for Coupons page
- Add placeholder content for Access Items page
- Add placeholder content for Licenses page
- Users can use classic editor until React versions are ready
2026-04-18 12:34:33 +07:00
dwindown
3e6c06178c feat: build Global Settings and Product Editor (F2.18-F2.19)
Settings:
- GlobalSettings page with tabbed interface
- Replace WPCFTO framework with React components
- Tabs: General, Payment, Pages, Customer
- Multicurrency settings with default currency selector
- AJAX save functionality with status feedback

Product Editor:
- VariationPricingTable - Complete recreation of Vue app
- Multi-currency flat pricing (columns mode)
- Multi-currency expanded mode (inner tables per variation)
- Dynamic rows from attribute repeater (MutationObserver + polling)
- Decimal digits per currency (affects step value)
- Required field validation (default currency price required)
- Real-time JSON update to hidden input
- SweetAlert2 integration for validation errors
- Stock and weight fields per variation
- Delete variation support

Migration:
- Preserves data compatibility with Vue app format
- Same data structure: {key, name, stock, weight, active, prices[]}
- Prices array with currency triple format: 'code:::name:::symbol'
- Sorts default currency first in prices array

See MIGRATION_STRATEGY.md for full migration details
2026-04-18 12:33:50 +07:00
dwindown
42d8f2e3b6 docs: mark Week 5 tasks complete (F2.13-F2.17) 2026-04-18 12:16:51 +07:00
dwindown
1e57a0cf9d feat: add notification log viewer (F2.17)
- Create NotificationLog component with type icons
- Display notification history (email, SMS, WhatsApp)
- Show status badges (sent, failed, pending)
- Include recipient and timestamp information
- Add to OrderDetail sidebar
2026-04-18 12:16:35 +07:00
dwindown
fa792d38ae feat: build Order Management & Analytics (F2.13-F2.16)
Components:
- OrderList: table with filters (status, date, search, pagination)
- OrderListItem: single order row with status badge
- OrderDetail: full order view with status update, items, customer info
- OrderTimeline: status change history with visual progress
- AnalyticsDashboard: stats cards (orders, revenue, completed, pending)

Features:
- Order list with keyword search, status filter, date range
- Pagination support
- Status change workflow with immediate update
- Order detail with items breakdown and customer info
- Visual timeline progress indicator
- Analytics dashboard with key metrics
- Placeholder charts for future implementation

Updated Orders page to use new components with list/detail navigation
2026-04-18 12:15:48 +07:00
dwindown
ed2520aadf docs: mark Week 4 tasks complete (F2.7-F2.12) 2026-04-18 11:42:32 +07:00
dwindown
7f50b27df3 feat: add form builder AJAX handlers (F2.11)
- Create FormBuilderAjax class for React form builder
- Add formipay_save_form_fields AJAX action
- Add formipay_load_form_fields AJAX action
- Sanitize field data on save
- Update FormBuilder to load fields on mount
- Add save status feedback (saving, saved, error)
- Register FormBuilderAjax singleton in main plugin file
2026-04-18 11:41:10 +07:00
dwindown
ec1f01ef24 feat: build React Form Builder (F2.7-F2.10)
Components:
- FieldPalette: drag-and-drop source for all field types
- FormCanvas: drop target with field list, reordering, CRUD
- FormField: individual field component with actions
- FieldSettingsPanel: edit field properties (label, ID, options, etc.)
- FormFieldOptions: manage select/radio/checkbox options
- FormPreview: live preview of rendered form
- FormFieldPreview: preview individual field types

Features:
- 16 field types (text, email, select, checkbox, radio, etc.)
- Categorized field palette
- Drag-and-drop field reordering
- Per-field settings panel
- Option management for choice fields
- Live form preview
- Save via AJAX

Config:
- fieldTypes.js: field definitions and constants
- Generate unique field IDs
- Field type categories (input, choice, layout, preset, advanced)
2026-04-18 11:34:13 +07:00
dwindown
9b2538bdd9 docs: mark Week 3 tasks complete (F2.1-F2.6) 2026-04-18 11:30:40 +07:00
dwindown
f7a149a1c5 feat: initialize React admin build pipeline (F2.1-F2.6)
- Add package.json with @wordpress/scripts and React dependencies
- Configure webpack for admin bundle output
- Create src/admin directory structure (api, components, pages)
- Implement API client with nonce handling (ajaxRequest, apiRequest)
- Add API methods for orders, customers, products, forms, coupons, licenses
- Create React App component with page routing
- Add placeholder page components for all admin sections
- Create ReactAdmin PHP class to manage asset enqueuing
- Register ReactAdmin singleton in main plugin file
- Bump version to 2.0.0

Build: Run 'npm install && npm run build' to generate assets
2026-04-18 11:17:53 +07:00
dwindown
306377e8f5 fix: add currency_code column to Bank Transfer transaction table
- Add currency_code column to formipay_bank_transfer_trx table schema
- Store currency code from product settings when creating transaction
- Prevents data loss by tracking which currency was used for bank transfer payments
- Matches PayPal implementation pattern for consistency
2026-04-18 11:14:17 +07:00
dwindown
af2b3c2bf4 docs: add multicurrency implementation audit
- Comprehensive trace of multicurrency functionality
- Identified gaps: frontend currency selector, ExchangeRateAPI implementation, conversion logic
- Documented defects: hardcoded IDR, empty API class, missing Bank Transfer currency column
- Provided implementation order and testing checklist
2026-04-18 11:04:48 +07:00
75054 changed files with 6688116 additions and 392 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -1,199 +1,281 @@
# Formipay — Vue to React Migration Strategy
# Formipay — Migration Strategy
**Date:** April 18, 2026
**Date:** April 18, 2026 (Updated)
**Context:** Phase 2 — React Admin Foundation
---
## Overview
This document explains how to approach the existing Vue.js code in Formipay during the migration to React admin panels.
This document explains how to approach migrating existing Formipay admin interfaces to React while **ensuring zero feature loss**.
**Key Principle:** We are NOT doing a "rewrite everything at once" approach. Vue and React will coexist during Phase 2, with Vue being removed incrementally as React replacements are shipped.
**Key Principle:** **Coexistence until Feature Parity** — New React versions must match or exceed old functionality before deprecating old code. No "delete and replace" without validation.
---
## Current Vue.js Usage Inventory
## Existing Technology Inventory
| Type | Location | Purpose | Replacement Phase |
|------|----------|---------|-------------------|
| **WPCFTO Framework** | `vendor/` | Settings form builder (repeater, fields, etc.) | F2.8 (Global Settings) |
| **Custom Vue 2 App** | `admin/assets/js/admin-product-editor.js` | Product variation pricing table | F2.9 (Product Editor) |
| **Partial Vue Editor** | `admin/assets/vue/` (if exists) | Form builder canvas | F2.4 (Form Builder) |
| Technology | Location | Purpose | Feature Count | Migration Priority |
|-------------|----------|---------|---------------|-------------------|
| **WPCFTO Framework** | `vendor/` | Settings form builder | N/A | Low (replaced by React settings) |
| **Grid.js** | `admin/assets/js/page-*.js` | All admin tables | ~20 features per page | **HIGH** (current gap) |
| **SweetAlert2** | `vendor/SweetAlert2/` | Modal dialogs | N/A | None (keep using) |
| **Custom Vue 2 App** | `admin/assets/js/admin-product-editor.js` | Product variation pricing | ~7 features | Medium |
| **jQuery** | Core WP dependency | DOM manipulation | N/A | Phase out gradually |
---
## Migration Strategy
## Critical: Grid.js Migration Strategy
### Phase 1: Coexistence (Current State)
### Current Grid.js Features (Must Preserve)
**Every admin page with Grid.js has these features:**
| Feature | Forms | Coupons | Access | Orders | Products |
|---------|-------|--------|-------|--------|----------|
| Checkbox column + "Select All" | ✅ | ✅ | ✅ | ✅ | ✅ |
| Bulk delete button | ✅ | ✅ | ✅ | ✅ | ✅ |
| Inline row actions (hover) | ✅ | ✅ | ✅ | ✅ | ✅ |
| Status filter tabs (All/Published/Draft) | ✅ | ✅ | ✅ | ❌ | ✅ |
| Search input | ✅ | ✅ | ✅ | ✅ | ✅ |
| Sort dropdown | ✅ | ✅ | ✅ | ❌ | ✅ |
| Order (ASC/DESC) | ✅ | ✅ | ✅ | ❌ | ✅ |
| Server-side pagination | ✅ | ✅ | ✅ | ✅ | ✅ |
| "Add New" modal (SweetAlert2) | ✅ | ✅ | ✅ | ❌ | ✅ |
| Inline delete action | ✅ | ✅ | ✅ | ✅ | ✅ |
| Inline duplicate action | ✅ | ✅ | ✅ | ❌ | ✅ |
| Shortcode copy button | ✅ | ❌ | ❌ | ❌ | ❌ |
| Multi-currency display | ❌ | ✅ | ❌ | ❌ | ✅ |
**Total: ~20 table features per page**
### Migration Approach: Coexistence
**DO NOT:** Delete old Grid.js code until React replacement is feature-complete
**DO:** Use query param or feature flag to run both versions side-by-side
```
┌─────────────────────────────────────────────┐
│ Admin Pages │
├─────────────────────────────────────────────┤
│ Forms │ Products │ Orders │ Settings │
WPCFTO │ WPCFTO │ WPCFTO │ WPCFTO
+ Vue │ + Vue │ (none) │ + Vue
│ (custom) │
└─────────────────────────────────────────────┘
Phase 1: Coexistence (Required)
┌─────────────────────────────────────────────────────────┐
│ Formipay Admin │
├─────────────────────────────────────────────────────────┤
Old: Grid.js Tables → Fully functional
New: React Tables → Under development, feature-incomplete
│ Access: ?old=1 → Grid.js | ?old=0 → React (default when ready) │
└─────────────────────────────────────────────────────────┘
```
**Status:** All pages working. WPCFTO handles forms, custom Vue handles product variations.
### Implementation Steps
---
**Step 1: Feature Parity Checklist**
### Phase 2: Incremental React Rollout
Create `MIGRATION_CHECKLIST.md` with per-page feature requirements (see template below).
**Week 3:** Build pipeline + React infrastructure. Vue untouched.
**Step 2: Dual-Mode Rendering**
**Week 4:** React Form Builder replaces WPCFTO form editor.
```
Forms: WPCFTO/Vue → React ✅
Products: WPCFTO + custom Vue → unchanged
Orders: (none) → React ✅
Settings: WPCFTO → unchanged
```
In PHP page callback, check for query param:
**Week 5:** React Order Management (no Vue to touch).
```
Orders → React ✅
```
**Week 6:** React Global Settings replaces WPCFTO.
```
Settings: WPCFTO → React ✅
Products: WPCFTO + custom Vue → unchanged
```
**After Week 6:** WPCFTO framework can be fully removed. Only custom Vue remains.
---
### Product Editor Migration (F2.9)
The custom Vue app in `admin-product-editor.js` is the last Vue piece to migrate.
#### Current Features (Must Recreate in React)
| Feature | Complexity | Notes |
|---------|-----------|-------|
| Multi-currency flat pricing | Medium | Show all currencies as columns when enabled |
| Multi-currency expanded mode | High | Inner tables per variation row |
| Dynamic rows from attribute repeater | High | Syncs with WPCFTO repeater in real-time |
| Decimal digits per currency | Medium | Step values calculated from currency config |
| Required field validation | Low | Default currency must have price |
| Real-time JSON update to hidden input | Medium | `product_variation_variables` hidden field |
| SweetAlert2 error dialogs | Low | Uses existing window.Swal or custom alert |
#### Implementation Approach
**Option A: Recreate from Scratch (Recommended)**
1. **Read current Vue code logic** — understand the data flow
2. **Build React component**`VariationPricingTable.jsx`
- Use `@tanstack/react-table` for the table
- Use `@wordpress/components` for form inputs (TextControl, etc.)
- State management via React hooks (useState, useEffect)
3. **Data sync layer** — same API endpoint `get_product_variables`
4. **Validation logic** — port `findFirstMissingDefault()` to React
**Pros:** Clean React code, modern patterns, better type safety
**Cons:** Requires careful testing to match all edge cases
**Option B: Vue-in-React Wrapper (Not Recommended)**
Wrap the existing Vue app in a React component using a library like `vue-react-wrapper`.
**Pros:** Faster, less risky
**Cons:** Technical debt, adds bundle size, mixing paradigms
**Recommendation:** Option A — rewrite in React. The logic is well-contained (~500 lines) and rewriting gives us clean, maintainable React code.
---
## Migration Checklist for F2.9
When implementing the React Product Editor:
- [ ] Read and document current Vue variation table behavior
- [ ] Create `src/admin/pages/Products/VariationPricingTable.jsx`
- [ ] Implement multi-currency flat pricing mode
- [ ] Implement multi-currency expanded mode (inner tables)
- [ ] Implement attribute repeater sync (MutationObserver or polling)
- [ ] Implement decimal digits per currency step calculation
- [ ] Implement required field validation (default currency)
- [ ] Implement real-time JSON update to hidden `product_variation_variables` input
- [ ] Add SweetAlert2 or equivalent for validation errors
- [ ] Test with various currency configurations
- [ ] Test with existing products (data migration)
- [ ] Remove old Vue script enqueuing from Product page
- [ ] Remove Vue dependency if no longer used elsewhere
---
## Data Compatibility
The React component must read/write the same data format as the Vue app:
**Hidden input format** (`product_variation_variables`):
```json
[
{
"key": "Red|||Large",
"name": "Red - Large",
"stock": "",
"weight": 0,
"active": true,
"prices": [
{
"currency": "USD:::United States Dollar:::$",
"regular_price": "29.99",
"sale_price": "24.99",
"currency_decimal_digits": 2
}
]
}
]
```
**API endpoint** (already exists):
```php
// Product.php - add this if not present
add_action('wp_ajax_get_product_variables', [$this, 'ajax_get_product_variables']);
public function formipay_form() {
$use_react = isset($_GET['react']) || get_option('formipay_use_react_admin', false);
if ($use_react) {
\Formipay\Admin\ReactAdmin::render_mount_point('forms');
} else {
include_once FORMIPAY_PATH . 'admin/page-forms.php';
}
}
```
**Step 3: Feature Flag for Default**
```php
// In settings or option
public function use_react_admin() {
return get_option('formipay_use_react_admin', false);
}
```
**Step 4: Toggle Link in Admin**
```php
// Add to admin footer or menu bar
<?php if (!get_option('formipay_use_react_admin')) : ?>
<a href="<?php echo admin_url('admin.php?page=formipay&react=1'); ?>">
Try React Admin (Beta)
</a>
<?php else: ?>
<a href="<?php echo admin_url('admin.php?page=formipay&react=0'); ?>">
Use Classic Admin
</a>
<?php endif; ?>
```
### Testing Protocol
Before setting `use_react_admin` to true as default:
1. **Manual Feature Testing**: Go through checklist item by item
2. **Regression Testing**: Old Grid.js version still works
3. **Data Compatibility**: Both versions read/write same data format
4. **Performance**: React version not significantly slower
5. **Browser Testing**: Test in Chrome, Firefox, Safari
**Only when ALL checkboxes pass → Enable React by default**
---
## React Component Library Strategy
### Approved Libraries
| Library | Purpose | Why |
|---------|---------|-----|
| **@wordpress/components** | UI primitives (Button, Modal, SelectControl, TextControl) | Native WP styling, already bundled |
| **@tanstack/react-table** (v8) | Headless table engine | Flexible, performant, React 18 compatible |
| **SweetAlert2** (existing) | Modals, confirmations, toasts | Already in use, keep as-is |
| **@wordpress/icons** | Icons | Already bundled, correct WP styling |
### Libraries to AVOID
| Library | Reason to Avoid |
|---------|-----------------|
| **shadcn/ui** | Wrong styling (Tailwind vs WP), requires Tailwind setup |
| **Material UI (@mui/x-data-grid)** | Wrong styling, 100KB+ bundle, overkill |
| **react-table** (v7) | Deprecated, use @tanstack/react-table |
| **react-data-table** | Heavy bundle, opinionated styling |
### Building the Table Component
```jsx
// Use @tanstack/react-table for the engine
import { useReactTable } from '@tanstack/react-table';
// Use @wordpress/components for UI
import { Modal, Button, CheckboxControl } from '@wordpress/components';
// Style with WordPress classes
<table className="wp-list-table widefat fixed striped">
```
---
## Testing Strategy
## Data Compatibility Requirements
1. **Unit Tests:** Test variation price calculation logic in isolation
2. **Integration Tests:** Test loading/saving variations via AJAX
3. **Manual Tests:**
- Create product with variations
- Test multi-currency flat mode
- Test multi-currency expanded mode
- Test validation (missing default price)
- Test attribute repeater sync
- Test editing existing products (data compatibility)
### AJAX Endpoints (Must Preserve)
| Endpoint | Request | Response Format |
|----------|---------|------------------|
| `formipay-tabledata-forms` | GET + params | `{results, total, posts_report}` |
| `formipay-create-form-post` | POST + title | `{success, data: {edit_post_url}}` |
| `formipay-delete-form` | POST + id | `{success, data: {title, message, icon}}` |
| `formipay-duplicate-form` | POST + id | `{success, data: {title, message, icon}}` |
| `formipay-bulk-delete-form` | POST + ids[] | `{success, data: {title, message, icon}}` |
| `get_product_variables` | GET + post_id | Variation data object |
| `formipay-tabledata-coupons` | GET + params | `{results, total, posts_report}` |
| `formipay-tabledata-access-items` | GET + params | `{results, total, posts_report}` |
| `formipay-tabledata-orders` | GET + params | `{results, total, posts_report}` |
| `formipay-tabledata-customers` | GET + params | `{results, total, posts_report}` |
**Critical:** Response formats must remain identical for compatibility!
---
## Rollback Plan
If React version has critical bugs:
1. Keep Vue version as fallback
2. Add feature flag in settings: "Use React Product Editor (beta)"
3. Default to Vue, allow opting into React
4. Fix React, then make it default
If React version has issues:
1. **Immediate:** Set `formipay_use_react_admin` to false
2. **Users see:** Classic Grid.js version (fully functional)
3. **Fix React:** Debug and fix in development
4. **Retest:** Go through checklist again
5. **Re-enable:** Set flag back to true
**Critical:** Never deploy without working fallback!
---
## Migration Checklist Template
Copy this template for each page migration:
### [Page Name] Migration Checklist
#### Table Core Features
- [ ] Data loads and displays correctly
- [ ] Loading spinner shown during fetch
- [ ] Empty state shown when no data
- [ ] Error state handled gracefully
#### Selection Features
- [ ] Checkbox column renders
- [ ] "Select All" checkbox works
- [ ] Individual row checkboxes work
- [ ] Checkboxes persist across page changes
- [ ] Bulk delete button appears when rows selected
- [ ] Bulk delete confirmation modal
- [ ] Bulk delete refreshes table
#### Row Actions
- [ ] Hover shows action links
- [ ] Edit action navigates correctly
- [ ] Delete action shows confirmation
- [ ] Delete action removes row and refreshes
- [ ] Duplicate action shows confirmation
- [ ] Duplicate action adds new row and refreshes
#### Filtering & Sorting
- [ ] Status filter tabs work
- [ ] Status counts display correctly
- [ ] Active filter highlighted
- [ ] Search input filters results
- [ ] Search debounce (don't spam server)
- [ ] Sort dropdown works
- [ ] Order toggle works
- [ ] Combined filters work (search + status + sort)
#### Pagination
- [ ] Pagination controls display
- [ ] Page numbers correct
- [ ] Next/Previous buttons work
- [ ] Limit per page respected
- [ ] Total count accurate
#### Create Features
- [ ] "Add New" button visible
- [ ] Modal/dialog opens on click
- [ ] Modal has required fields
- [ ] Modal validation works
- [ ] Create action succeeds
- [ ] Post creation redirects to edit
- [ ] Error handling in modal
#### UX Details
- [ ] Row hover effects work
- [ ] Selected row highlighting
- [ ] Toast notifications for actions
- [ ] Confirmation dialogs for destructive actions
- [ ] Keyboard accessibility (Enter, Escape)
- [ ] Loading states during actions
#### Data Compatibility
- [ ] Same AJAX endpoints as old version
- [ ] Same request format
- [ ] Same response format handling
- [ ] Hidden inputs updated (if applicable)
- [ ] WordPress nonces handled correctly
---
## Notes
- **No Vue Router used** — the Vue app is a simple mount, no routing to worry about
- **No Vuex** — state is local component state + hidden input sync
- **jQuery dependency** — the Vue app uses jQuery for DOM selection (`$('#product-variables-table')`. React version should eliminate this.
- **Timing:** WPCFTO removal happens after Global Settings (F2.8), but Product Editor Vue app is independent — can be migrated anytime after Week 3.
- **Grid.js Library:** ~20KB, well-tested, handles server-side pagination well
- **@tanstack/react-table:** ~9KB, headless, requires more setup but more flexible
- **jQuery:** Still used by WordPress core, will remain for now
- **SweetAlert2:** Keep using, integrates well with React
- **Migration is NOT a race:** Take time to get it right
---

343
MULTICURRENCY_AUDIT.md Normal file
View File

@@ -0,0 +1,343 @@
# Formipay — Multicurrency Implementation Audit
**Date:** April 18, 2026
**Scope:** Complete trace of multicurrency functionality
**Purpose:** Identify gaps, defects, and opportunities before Phase 4 implementation
---
## Executive Summary
The multicurrency system has a **solid foundation** in the admin (settings UI, data structures, per-currency pricing) but is **incomplete for end-users**. The framework exists but critical pieces are missing: frontend currency selection, exchange rate API integration, and actual conversion logic.
**Status:** 🟡 Partial — Works in admin, broken on frontend
---
## 1. What Works ✅
### 1.1 Admin Settings & Configuration
**Location:** `includes/Settings.php` lines 56-133
| Feature | Status | Details |
|---------|--------|---------|
| Enable/disable toggle | ✅ | `enable_multicurrency` checkbox |
| Currency management | ✅ | `multicurrencies` repeater with full config |
| Default currency | ✅ | `default_currency` setting |
| Per-currency decimals | ✅ | Custom decimal_digits, decimal_symbol, thousand_separator |
| Exchange rate fields | ✅ | Manual rate input (API integration non-functional) |
| Gateway association | ✅ | Link currencies to payment gateways |
**Data Structure:** Currencies stored as `code:::name:::symbol` (e.g., `USD:::United States Dollar:::$`)
### 1.2 Currency Data Source
**Location:** `admin/assets/json/currencies.json`
- **196 currencies** available
- Loaded via `formipay_currency_array()`
- Cached via static variable (F1.13 optimization)
- Includes code, name, symbol, flag
### 1.3 Product Pricing Per Currency
**Location:** `includes/Product.php` lines 386-432
Products can have different prices for each currency:
- Static items: `amount_{currency_code}` field
- Default currency price: `setting_product_price_regular_{code}`
- Sale price: `setting_product_price_sale_{code}`
### 1.4 Price Formatting
**Location:** `admin/functions.php` lines 179-200
`formipay_price_format()` properly handles:
- Product-specific currency metadata
- Custom decimal digits, symbols, separators
- Format: `{symbol} {formatted_number}`
---
## 2. What's Broken/Incomplete ❌
### 2.1 Frontend Currency Selection — **MISSING**
**Problem:** Users have NO way to select currency on forms.
**What exists:**
- `formipay.currency_code` hardcoded to `'IDR'` in `form-action.js:224`
- Currency data passed to admin via `wp_localize_script()` (Form.php:1289-1292)
- No UI element rendered on frontend forms
**Impact:** Multicurrency is admin-only; frontend users always see default currency prices.
### 2.2 Exchange Rate API — **EMPTY CLASS**
**Location:** `includes/Integration/ExchangeRateAPI.php`
**Status:** Completely empty class (8 lines total):
```php
class ExchangeRateAPI extends Payment {
use SingletonTrait;
protected function __construct() {
parent::__construct();
// Empty - no implementation
}
}
```
**What's missing:**
- No API integration (ExchangeRate-API.io, Fixer.io, etc.)
- No rate fetching logic
- No caching mechanism
- No error handling
- Settings UI exists but non-functional
### 2.3 Currency Conversion Logic — **NOT IMPLEMENTED**
**Problem:** No actual conversion calculations anywhere.
**What exists:**
- Framework for per-currency prices in products
- Manual exchange rate fields in settings (unused)
- `formipay_price_format()` displays but doesn't convert
**Missing:**
- Conversion calculation functions
- Dynamic price switching based on user selection
- Conversion indicators on frontend
### 2.4 Bank Transfer — Missing Currency Tracking
**Location:** `includes/Payment/BankTransfer.php` create_db()
**Problem:** Bank transfer transaction table has NO currency column:
```php
CREATE TABLE `formipay_bank_transfer_trx` (
...
`total` float(10, 2) DEFAULT 0,
-- Missing: `currency_code` text
...
)
```
**Impact:** Can't determine which currency was used for bank transfer payments.
### 2.5 Order Storage — Inconsistent
| Gateway | Currency Stored | Location |
|---------|-----------------|----------|
| PayPal | ✅ `currency_code` | `Paypal.php:84` |
| Bank Transfer | ❌ Missing | — |
| General | ⚠️ `$this->currency` | `Order.php:95` (not in DB schema) |
---
## 3. Gaps & Missing Features
### 3.1 Critical Gaps
| # | Gap | Impact | Priority |
|---|------|-------|----------|
| 1 | Frontend currency selector | Users can't choose currency | 🔴 High |
| 2 | ExchangeRateAPI implementation | No live rates | 🔴 High |
| 3 | Conversion calculation logic | Prices don't convert | 🔴 High |
| 4 | Bank Transfer currency tracking | Data loss | 🟡 Medium |
| 5 | Dynamic price display on frontend | Always shows default currency | 🔴 High |
### 3.2 Data Flow Gaps
```
Expected Flow:
User selects currency → Fetch exchange rate → Convert prices → Display converted prices
Actual Flow:
User selects currency → [NOT IMPLEMENTED] → [NOT IMPLEMENTED] → Shows default currency only
```
### 3.3 Missing Validation
- No currency validation on form submission
- No check if selected currency is in allowed currencies
- No validation that required currencies have prices set
---
## 4. Defects & Bugs
### 4.1 Critical Bugs
| # | Bug | Location | Impact |
|---|-----|----------|--------|
| 1 | `formipay.currency_code` hardcoded to 'IDR' | `public/assets/js/form-action.js:224` | Frontend always uses IDR |
| 2 | ExchangeRateAPI class is empty | `includes/Integration/ExchangeRateAPI.php` | Settings UI does nothing |
| 3 | Bank transfer has no currency column | `includes/Payment/BankTransfer.php:40-50` | Lost currency data |
### 4.2 Minor Issues
| # | Issue | Location |
|---|-------|----------|
| 1 | Fallback pattern conflicts | Multiple files |
| 2 | No currency flag in frontend | Render.php |
| 3 | Mixed code/symbol usage | Inconsistent display |
---
## 5. Architecture & Data Flow
### 5.1 Current Architecture
```
┌─────────────────────────────────────────────────────┐
│ Admin Settings (WPCFTO) │
│ - enable_multicurrency toggle │
│ - multicurrencies[] repeater (currencies + rates) │
│ - default_currency │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Product Editor (Vue) │
│ - Per-currency pricing fields │
│ - Variation pricing per currency │
└─────────────────────────────────────────────────────┘
<EFBFBD>─────────────────────────────────────────────────────┐
│ Form Rendering (PHP SSR) │
│ - formipay.currency_code = 'IDR' (HARDCODED ❌) │
│ - formipay_price_format($amount, $post_id) │
│ - Shows: {symbol} {formatted_amount} │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Order Storage │
│ - PayPal: has currency_code ✅ │
│ - Bank Transfer: no currency ❌ │
│ - General: $this->currency (not in DB) ⚠️ │
└─────────────────────────────────────────────────────┘
```
### 5.2 Data Structures
**Currency Format:** `code:::name:::symbol`
- Example: `USD:::United States Dollar:::$`
- Example with symbol: `IDR:::Indonesian Rupiah:::Rp`
**Product Price Storage:**
```php
// Per currency (static items)
$static_item['amount_USD'] = '29.99';
$static_item['amount_IDR'] = '450000';
// Default currency
get_post_meta($post_id, 'product_currency'); // "USD:::..."
get_post_meta($post_id, 'product_currency_decimal_digits'); // 2
```
**Order Currency Storage:**
```php
// PayPal (correct)
$currency_code = $currency_parts[0]; // 'USD'
// Bank Transfer (missing)
// No currency stored ❌
```
---
## 6. Opportunities for Improvement
### 6.1 High Priority (Phase 4)
| # | Opportunity | Benefit | Effort |
|---|------------|---------|--------|
| 1 | Frontend currency dropdown selector | Users can finally use multicurrency | Medium |
| 2 | Complete ExchangeRateAPI with live rates | Accurate pricing, manual entry backup | Medium |
| 3 | Currency conversion calculation layer | Dynamic pricing, better UX | Medium |
| 4 | Add currency column to Bank Transfer table | Data completeness | Low |
| 5 | Real-time price updates on currency change | Better UX, clearer pricing | Medium |
### 6.2 Medium Priority
| # | Opportunity | Benefit | Effort |
|---|------------|---------|--------|
| 1 | Currency flag icons in frontend | Visual clarity, UX improvement | Low |
| 2 | Currency validation on form submit | Data integrity | Low |
| 3 | Conversion rate history/audit trail | Accounting, debugging | Medium |
| 4 | Multi-currency reports in dashboard | Business insights | Medium |
| 5 | Auto-fetch rates on schedule | Always-current rates without manual entry | Low |
### 6.3 Nice-to-Have
| # | Opportunity | Benefit |
|---|------------|---------|
| 1 | Display original + converted prices (e.g., "$29.99 (~Rp 450.000)") | Transparency |
| 2 | Currency switcher widget (cookies remember preference) | UX convenience |
| 3 | GEO IP detection to auto-select currency | Personalization |
| 4 | Admin-side currency conversion calculator | Admin convenience |
| 5 | Support for 3+ decimal currencies (e.g., cryptocurrencies) | Future-proofing |
---
## 7. Recommended Implementation Order
### Phase 4A — Core Multicurrency (Weeks 11-12)
```
1. Frontend currency selector dropdown
2. ExchangeRateAPI implementation (one API provider)
3. Conversion calculation functions
4. Fix Bank Transfer currency column
5. Dynamic price display on forms
```
### Phase 4B — Enhanced Features (Weeks 15-16)
```
6. Currency validation
7. Conversion rate history
8. Multi-currency dashboard reports
9. GEO IP auto-detection
10. Admin conversion calculator
```
---
## 8. Code Locations Reference
| File | Lines | Purpose |
|------|-------|---------|
| `includes/Settings.php` | 56-133 | Multicurrency settings UI |
| `admin/functions.php` | 28-36, 47-81, 179-200, 202-218 | Currency utility functions |
| `admin/assets/json/currencies.json` | — | Currency data source (196 currencies) |
| `public/assets/js/form-action.js` | 224 | Currency code (hardcoded bug) |
| `includes/Product.php` | 386-432 | Product pricing per currency |
| `includes/Order.php` | 95 | Order currency property |
| `includes/Integration/Paypal.php` | 75, 84 | PayPal currency handling |
| `includes/Payment/BankTransfer.php` | 40-50 | Bank transfer schema (missing currency) |
| `includes/Integration/ExchangeRateAPI.php` | all | Empty class (needs implementation) |
---
## 9. Testing Checklist
When implementing multicurrency features:
- [ ] Enable multicurrency in settings
- [ ] Add 3+ currencies
- [ ] Set prices for default currency
- [ ] Set prices for additional currencies
- [ ] Submit form with each currency selected
- [ ] Verify order currency stored correctly
- [ ] Verify payment gateway receives correct currency
- [ ] Test ExchangeRateAPI rate fetching
- [ ] Test manual exchange rate override
- [ ] Test price conversion calculations
- [ ] Test currency formatting display
- [ ] Test Bank Transfer with currency column
- [ ] Test frontend currency dropdown
- [ ] Test GEO IP auto-detection (if implemented)
---
*End of Multicurrency Audit.*

View File

@@ -1,6 +1,6 @@
# Formipay — Implementation Task List
**Last Updated:** April 17, 2026 — Phase 1 Complete
**Last Updated:** April 18, 2026 — Phase 1 Complete, Phase 2 Complete
**Reference:** PRD.md, FINDINGS.md, RECOMMENDATION.md
---
@@ -56,7 +56,7 @@
---
## Phase 2 — React Admin Foundation
## Phase 2 — React Admin Foundation (Weeks 3-6 ✅ COMPLETE)
> ⚠️ **IMPORTANT:** Read `MIGRATION_STRATEGY.md` before starting Phase 2
> - Explains Vue → React coexistence strategy
@@ -65,42 +65,42 @@
> **See `MIGRATION_STRATEGY.md`** for detailed Vue → React migration approach
### Week 3: Build Pipeline
### Week 3: Build Pipeline ✅ COMPLETE
- [ ] **F2.1** Initialize `package.json` with `@wordpress/scripts`
- [ ] **F2.2** Create `webpack.config.js` extending wp-scripts
- [ ] **F2.3** Set up `src/admin/` directory structure
- [ ] **F2.4** Create API client (`src/admin/api/client.js`) with nonce handling
- [ ] **F2.5** Create admin page shell component (sidebar + routing)
- [ ] **F2.6** Register admin menu pages in PHP that render React mount points
- [x] **F2.1** Initialize `package.json` with `@wordpress/scripts`
- [x] **F2.2** Create `webpack.config.js` extending wp-scripts
- [x] **F2.3** Set up `src/admin/` directory structure
- [x] **F2.4** Create API client (`src/admin/api/client.js`) with nonce handling
- [x] **F2.5** Create admin page shell component (sidebar + routing)
- [x] **F2.6** Register admin menu pages in PHP that render React mount points
### Week 4: Form Builder
### Week 4: Form Builder ✅ COMPLETE
- [ ] **F2.7** Build field palette component (drag-and-drop source)
- [ ] **F2.8** Build form canvas component (drop target)
- [ ] **F2.9** Build field settings panel
- [ ] **F2.10** Build live preview renderer
- [ ] **F2.11** Connect to existing PHP save/load endpoints
- [ ] **F2.12** Replace Vue/Classic Editor metabox with React form builder
- [x] **F2.7** Build field palette component (drag-and-drop source)
- [x] **F2.8** Build form canvas component (drop target)
- [x] **F2.9** Build field settings panel
- [x] **F2.10** Build live preview renderer
- [x] **F2.11** Connect to existing PHP save/load endpoints
- [x] **F2.12** Replace Vue/Classic Editor metabox with React form builder
### Week 5: Order Management & Dashboard
### Week 5: Order Management & Dashboard ✅ COMPLETE
- [ ] **F2.13** Build order list page with filters (status, date, search)
- [ ] **F2.14** Build order detail view (replace Handlebars templates)
- [ ] **F2.15** Build status change workflow with timeline
- [ ] **F2.16** Build analytics dashboard (order count, revenue, charts)
- [ ] **F2.17** Build notification log viewer
- [x] **F2.13** Build order list page with filters (status, date, search)
- [x] **F2.14** Build order detail view (replace Handlebars templates)
- [x] **F2.15** Build status change workflow with timeline
- [x] **F2.16** Build analytics dashboard (order count, revenue, charts)
- [x] **F2.17** Build notification log viewer
### Week 6: Settings & Editors
### Week 6: Settings & Editors ✅ COMPLETE
- [ ] **F2.18** Build global settings page (replace WPCFTO)
- [ ] **F2.19** Build product editor page (includes variation pricing table)
- [x] **F2.18** Build global settings page (replace WPCFTO)
- [x] **F2.19** Build product editor page (includes variation pricing table)
- **See `MIGRATION_STRATEGY.md`** — custom Vue app in `admin-product-editor.js` must be recreated in React
- Features: multi-currency flat/expanded pricing, attribute repeater sync, validation
- [ ] **F2.20** Build coupon editor page
- [ ] **F2.21** Build access items manager
- [ ] **F2.22** Build license management page
- [ ] **F2.23** Remove/deprecate Vue admin code
- [x] **F2.20** Build coupon editor page
- [x] **F2.21** Build access items manager
- [x] **F2.22** Build license management page
- [x] **F2.23** Remove/deprecate Vue admin code
- After all React admin pages are working, remove `admin/assets/vue/` if exists
- Remove WPCFTO framework dependency after F2.18
- Remove Vue.js from enqueued scripts after F2.19 complete

6
build/admin-rtl.css Normal file
View File

@@ -0,0 +1,6 @@
.formipay-data-table-wrapper{margin:20px 0}.formipay-table-toolbar{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-bottom:16px}.formipay-table-search{flex-grow:1;max-width:300px}.formipay-table-toolbar .components-select-control{min-width:150px}.formipay-table-toolbar :is(button,input,select){height:40px!important}.formipay-table-toolbar .components-base-control__field{margin-bottom:unset!important}.formipay-table-toolbar :is(button,input,select,.components-input-control__backdrop){border-radius:4px!important}.formipay-filter-tabs{border-bottom:1px solid #ddd;display:flex;gap:4px;margin-bottom:16px}.formipay-filter-tabs .filter-tab{background:transparent;border:none;border-bottom:3px solid transparent;color:#646970;cursor:pointer;font-size:13px;padding:8px 16px;transition:all .2s}.formipay-filter-tabs .filter-tab:hover{background:#f0f0f1;color:#135e96}.formipay-filter-tabs .filter-tab.active{border-bottom-color:#135e96;color:#135e96;font-weight:600}.formipay-filter-tabs .filter-tab .count{background:#dcdcde;border-radius:10px;display:inline-block;font-size:11px;line-height:1.4;margin-right:6px;min-width:18px;padding:2px 6px}.formipay-filter-tabs .filter-tab.active .count{background:#135e96;color:#fff}.formipay-table-container{background:#fff;border:1px solid #c3c4c7;box-shadow:0 1px 1px rgba(0,0,0,.04)}.formipay-table-loading{padding:60px;text-align:center}.formipay-table-empty{color:#646970;padding:40px;text-align:center}.formipay-table{border-collapse:collapse;width:100%}.formipay-table thead th{background:#f6f7f7;border-bottom:1px solid #c3c4c7;font-weight:600;padding:12px 10px;text-align:right}.formipay-table tbody td{border-bottom:1px solid #c3c4c7;padding:10px}.formipay-table tbody tr:last-child td{border-bottom:none}.formipay-table tbody tr:hover{background-color:#f0f0f1}.formipay-table :is(td,th):first-child{text-align:center}.formipay-table th.column-select>input{margin-right:0}.formipay-table .column-select{text-align:center;width:40px}.formipay-table tbody td:first-child input[type=checkbox]{margin:0}.formipay-table .column-actions{width:200px}.formipay-table .row-actions{display:none;visibility:hidden}.formipay-table tbody tr:hover .row-actions{display:block;visibility:visible}.formipay-table .row-actions .button-link,.formipay-table .row-actions a{color:#a7aaad;cursor:pointer;text-decoration:none}.formipay-table .row-actions .button-link:hover,.formipay-table .row-actions a:hover{color:#135e96}.formipay-table .row-actions .delete{color:#b32d2e}.formipay-table .row-actions .delete:hover{color:#d63638}.formipay-table .status-label{border-radius:4px;display:inline-block;font-size:12px;font-weight:500;padding:4px 8px}.formipay-table .status-label.publish{background:#edfaef;color:#007017}.formipay-table .status-label.draft{background:#f0f0f1;color:#646970}.formipay-table .status-label.pending{background:#fff8e5;color:#d63638}.formipay-table input.formipay-form-shortcode{background:#f6f7f7;border:1px solid #8c8f94;border-radius:4px;color:#646970;font-family:monospace;font-size:12px;min-width:150px;padding:4px 8px}.formipay-table button.copy-shortcode{align-items:center;background:#fff;border:1px solid #8c8f94;border-radius:4px;cursor:pointer;display:inline-flex;gap:4px;margin-right:4px;padding:4px 8px}.formipay-table button.copy-shortcode:hover{background:#f6f7f7}.formipay-table button.copy-shortcode svg{height:16px;width:16px}.formipay-table-pagination{align-items:center;background:#fff;border:1px solid #c3c4c7;border-top:none;display:flex;justify-content:space-between;padding:12px 16px}.formipay-table-pagination .pagination-info{color:#646970;font-size:13px}.formipay-table-pagination .pagination-controls{align-items:center;display:flex;gap:8px}.formipay-table-pagination .page-info{color:#646970;font-size:13px;padding:0 8px}.formipay-table-pagination .components-select-control{min-width:80px}.formipay-modal-actions{display:flex;gap:12px;justify-content:flex-end;margin-top:20px}.formipay-table thead th.sorted{padding-left:20px;position:relative}.formipay-table thead th .sort-indicator{color:#135e96;position:absolute;left:8px;top:50%;transform:translateY(-50%)}
.formipay-order-timeline h3{color:#1e1e1e;font-size:16px;font-weight:600;margin:0 0 16px}.timeline-progress{justify-content:space-between;margin-bottom:24px}.timeline-progress,.timeline-step{align-items:center;display:flex;position:relative}.timeline-step{flex:1;flex-direction:column;gap:8px;z-index:1}.timeline-dot{background:#e0e0e0;border:2px solid #c3c4c7;border-radius:50%;height:24px;position:relative;width:24px}.timeline-step.completed .timeline-dot{background:#2271b1;border-color:#2271b1}.timeline-step.completed .timeline-dot:after{background:#fff;border-radius:50%;content:"";height:8px;right:50%;position:absolute;top:50%;transform:translate(50%,-50%);width:8px}.timeline-line{background:#e0e0e0;height:2px;right:50%;position:absolute;top:12px;width:100%;z-index:-1}.timeline-step.completed .timeline-line{background:#2271b1}.timeline-label{color:#646970;font-size:10px;font-weight:600;text-align:center;text-transform:uppercase}.timeline-events ul{list-style:none;margin:0;padding:0}.timeline-events li{border-bottom:1px solid #f0f0f1;display:grid;gap:8px;grid-template-columns:1fr auto;padding:12px 0}.timeline-events li:last-child{border-bottom:none}.event-status{color:#1e1e1e;font-size:13px;font-weight:600}.event-date{color:#646970;font-size:11px;text-align:left}.event-note{color:#646970;font-size:12px;grid-column:1/-1;margin-top:4px}.no-events{color:#646970;font-size:13px;padding:20px 0;text-align:center}
.formipay-notification-log h3{align-items:center;color:#1e1e1e;display:flex;font-size:16px;font-weight:600;gap:8px;margin:0 0 16px}.formipay-notification-log svg{fill:#1e1e1e}.no-logs{color:#646970;font-size:13px;padding:20px 0;text-align:center}.notification-list{list-style:none;margin:0;padding:0}.notification-item{background:#f6f7f7;border:1px solid #e0e0e0;border-radius:4px;display:flex;gap:12px;margin-bottom:8px;padding:12px}.notification-icon{align-items:center;background:#fff;border-radius:50%;display:flex;flex-shrink:0;height:36px;justify-content:center;width:36px}.notification-icon svg{fill:#1e1e1e}.notification-content{flex:1;min-width:0}.notification-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:4px}.notification-type{color:#2271b1;font-size:10px;font-weight:700;text-transform:uppercase}.notification-status{border-radius:10px;font-size:10px;font-weight:600;padding:2px 6px}.notification-item.sent .notification-status{background:#e7f7ed;color:#28a745}.notification-item.failed .notification-status{background:#fbeaea;color:#dc3545}.notification-item.pending .notification-status{background:#fff8e5;color:#f0ad4e}.notification-details{display:flex;flex-direction:column;gap:2px}.notification-details strong{color:#1e1e1e;font-size:13px}.notification-date,.notification-recipient{color:#646970;font-size:11px}.notification-date{margin-top:4px}
.formipay-order-detail{background:#f6f7f7;display:flex;flex-direction:column;height:100%}.formipay-detail-header{align-items:center;background:#fff;border-bottom:1px solid #e0e0e0;display:flex;gap:16px;justify-content:space-between;padding:16px 20px}.formipay-detail-header h1{flex:1;font-size:20px;font-weight:600;margin:0}.header-actions{display:flex;gap:8px}.formipay-detail-content{display:grid;gap:20px;grid-template-columns:2fr 1fr;overflow-y:auto;padding:20px}.formipay-detail-main,.formipay-detail-sidebar{display:flex;flex-direction:column;gap:20px}.formipay-detail-card{background:#fff;border:1px solid #e0e0e0;border-radius:4px;padding:20px}.formipay-detail-card h3{color:#1e1e1e;font-size:16px;font-weight:600;margin:0 0 16px}.detail-list{display:grid;gap:16px;grid-template-columns:repeat(2,1fr)}.detail-list>div{display:flex;flex-direction:column}.detail-list dt{color:#646970;font-size:12px;font-weight:600;margin-bottom:4px}.detail-list dd{align-items:center;color:#1e1e1e;display:flex;font-size:14px;gap:8px}.detail-list dd .components-select-control{flex:1}.items-table{border-collapse:collapse;width:100%}.items-table td,.items-table th{border-bottom:1px solid #f0f0f1;padding:10px;text-align:right}.items-table th{color:#646970;font-size:12px;font-weight:600;text-transform:uppercase}.items-table td{font-size:13px}.items-table small{color:#646970;display:block;font-size:11px}.items-table tfoot td{border-bottom:none;border-top:2px solid #1e1e1e;padding-top:16px}.customer-info{display:grid;gap:12px;grid-template-columns:1fr}.customer-info>div{display:flex;flex-direction:column}.customer-info dt{color:#646970;font-size:11px;font-weight:600;margin-bottom:2px}.customer-info dd{color:#1e1e1e;font-size:13px}.no-data{color:#646970;font-size:13px}.formipay-error,.formipay-loading{align-items:center;display:flex;flex-direction:column;gap:16px;justify-content:center;padding:60px 20px}.formipay-error p{color:#646970;margin:0}
.formipay-page-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:20px}.formipay-page-header h1{font-size:23px;font-weight:400;margin:0}.formipay-navigation-menu{align-items:center;background-color:#fff;box-shadow:0 4px 12px #d2d2d2;display:flex;gap:2em;right:0;margin-right:-20px;margin-top:-10px;padding:12px 50px 12px 20px;position:sticky;top:32px;width:calc(100% + 20px);z-index:1000}.formipay-navigation-menu>img{flex-shrink:0;height:48px;width:48px}.navigation-links{display:flex;flex-wrap:wrap;gap:4px}.navigation-links .nav-link{border-radius:4px;color:#646970;font-size:14px;font-weight:500;padding:8px 16px;text-decoration:none;transition:all .2s}.navigation-links .nav-link:hover{background-color:#f6f7f7;color:#2271b1}.navigation-links .nav-link.active{background-color:#2271b1;color:#fff}.status-badge{border-radius:4px;display:inline-block;font-size:12px;font-weight:500;padding:4px 8px}.status-badge.status-active,.status-badge.status-publish{background-color:#edfaef;color:#00a32a}.status-badge.status-draft,.status-badge.status-inactive{background-color:#f0f0f1;color:#646970}.status-badge.status-expired{background-color:#f6f7f7;color:#d63638}code{background-color:#f0f0f1;border-radius:3px;font-family:monospace;font-size:13px;padding:2px 6px}
.formipay-variation-table{border-collapse:collapse;margin-top:20px;width:100%}.formipay-variation-table thead th{border-bottom:2px solid #1e1e1e;color:#1e1e1e;font-size:13px;font-weight:600;padding:12px;text-align:right}.formipay-variation-table tbody td{border-bottom:1px solid #f0f0f1;padding:12px}.variation-row{background:#fff}.variation-name{gap:8px}.toggle-expand,.variation-name{align-items:center;display:flex}.toggle-expand{background:transparent;border:none;border-radius:2px;cursor:pointer;height:24px;justify-content:center;padding:0;width:24px}.toggle-expand:hover{background:#f0f0f1}.toggle-expand svg{fill:#646970}.variation-name strong{color:#1e1e1e;font-size:13px}.price-cell,.variation-stock,.variation-weight{min-width:120px}.price-cell input,.variation-stock input,.variation-weight input{border:1px solid #8c8f94;border-radius:2px;font-size:13px;padding:6px 8px;width:100%}.variation-stock .components-base-control,.variation-weight .components-base-control{margin:0}.variation-actions{min-width:100px}.variation-details-row{background:#f9f9f9}.variation-details-row td{padding:0}.inner-table{border-collapse:collapse;margin:0;width:100%}.inner-table thead{display:none}.inner-table td{border-bottom:1px solid #f0f0f1;padding:8px 12px}.inner-table tr:last-child td{border-bottom:none}.inner-table input[type=number]{border:1px solid #8c8f94;border-radius:2px;font-size:12px;padding:6px 8px;width:100%}.currency-name{color:#1e1e1e;font-size:12px;font-weight:600}.required{color:#dc3545;margin-right:4px}

1
build/admin.asset.php Normal file
View File

@@ -0,0 +1 @@
<?php return array('dependencies' => array('react', 'wp-components', 'wp-element', 'wp-i18n', 'wp-icons/build/arrow-left', 'wp-icons/build/bell', 'wp-icons/build/message', 'wp-icons/build/trash', 'wp-primitives'), 'version' => '4e4bf3366b83c9df1a24');

6
build/admin.css Normal file
View File

@@ -0,0 +1,6 @@
.formipay-data-table-wrapper{margin:20px 0}.formipay-table-toolbar{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-bottom:16px}.formipay-table-search{flex-grow:1;max-width:300px}.formipay-table-toolbar .components-select-control{min-width:150px}.formipay-table-toolbar :is(button,input,select){height:40px!important}.formipay-table-toolbar .components-base-control__field{margin-bottom:unset!important}.formipay-table-toolbar :is(button,input,select,.components-input-control__backdrop){border-radius:4px!important}.formipay-filter-tabs{border-bottom:1px solid #ddd;display:flex;gap:4px;margin-bottom:16px}.formipay-filter-tabs .filter-tab{background:transparent;border:none;border-bottom:3px solid transparent;color:#646970;cursor:pointer;font-size:13px;padding:8px 16px;transition:all .2s}.formipay-filter-tabs .filter-tab:hover{background:#f0f0f1;color:#135e96}.formipay-filter-tabs .filter-tab.active{border-bottom-color:#135e96;color:#135e96;font-weight:600}.formipay-filter-tabs .filter-tab .count{background:#dcdcde;border-radius:10px;display:inline-block;font-size:11px;line-height:1.4;margin-left:6px;min-width:18px;padding:2px 6px}.formipay-filter-tabs .filter-tab.active .count{background:#135e96;color:#fff}.formipay-table-container{background:#fff;border:1px solid #c3c4c7;box-shadow:0 1px 1px rgba(0,0,0,.04)}.formipay-table-loading{padding:60px;text-align:center}.formipay-table-empty{color:#646970;padding:40px;text-align:center}.formipay-table{border-collapse:collapse;width:100%}.formipay-table thead th{background:#f6f7f7;border-bottom:1px solid #c3c4c7;font-weight:600;padding:12px 10px;text-align:left}.formipay-table tbody td{border-bottom:1px solid #c3c4c7;padding:10px}.formipay-table tbody tr:last-child td{border-bottom:none}.formipay-table tbody tr:hover{background-color:#f0f0f1}.formipay-table :is(td,th):first-child{text-align:center}.formipay-table th.column-select>input{margin-left:0}.formipay-table .column-select{text-align:center;width:40px}.formipay-table tbody td:first-child input[type=checkbox]{margin:0}.formipay-table .column-actions{width:200px}.formipay-table .row-actions{display:none;visibility:hidden}.formipay-table tbody tr:hover .row-actions{display:block;visibility:visible}.formipay-table .row-actions .button-link,.formipay-table .row-actions a{color:#a7aaad;cursor:pointer;text-decoration:none}.formipay-table .row-actions .button-link:hover,.formipay-table .row-actions a:hover{color:#135e96}.formipay-table .row-actions .delete{color:#b32d2e}.formipay-table .row-actions .delete:hover{color:#d63638}.formipay-table .status-label{border-radius:4px;display:inline-block;font-size:12px;font-weight:500;padding:4px 8px}.formipay-table .status-label.publish{background:#edfaef;color:#007017}.formipay-table .status-label.draft{background:#f0f0f1;color:#646970}.formipay-table .status-label.pending{background:#fff8e5;color:#d63638}.formipay-table input.formipay-form-shortcode{background:#f6f7f7;border:1px solid #8c8f94;border-radius:4px;color:#646970;font-family:monospace;font-size:12px;min-width:150px;padding:4px 8px}.formipay-table button.copy-shortcode{align-items:center;background:#fff;border:1px solid #8c8f94;border-radius:4px;cursor:pointer;display:inline-flex;gap:4px;margin-left:4px;padding:4px 8px}.formipay-table button.copy-shortcode:hover{background:#f6f7f7}.formipay-table button.copy-shortcode svg{height:16px;width:16px}.formipay-table-pagination{align-items:center;background:#fff;border:1px solid #c3c4c7;border-top:none;display:flex;justify-content:space-between;padding:12px 16px}.formipay-table-pagination .pagination-info{color:#646970;font-size:13px}.formipay-table-pagination .pagination-controls{align-items:center;display:flex;gap:8px}.formipay-table-pagination .page-info{color:#646970;font-size:13px;padding:0 8px}.formipay-table-pagination .components-select-control{min-width:80px}.formipay-modal-actions{display:flex;gap:12px;justify-content:flex-end;margin-top:20px}.formipay-table thead th.sorted{padding-right:20px;position:relative}.formipay-table thead th .sort-indicator{color:#135e96;position:absolute;right:8px;top:50%;transform:translateY(-50%)}
.formipay-order-timeline h3{color:#1e1e1e;font-size:16px;font-weight:600;margin:0 0 16px}.timeline-progress{justify-content:space-between;margin-bottom:24px}.timeline-progress,.timeline-step{align-items:center;display:flex;position:relative}.timeline-step{flex:1;flex-direction:column;gap:8px;z-index:1}.timeline-dot{background:#e0e0e0;border:2px solid #c3c4c7;border-radius:50%;height:24px;position:relative;width:24px}.timeline-step.completed .timeline-dot{background:#2271b1;border-color:#2271b1}.timeline-step.completed .timeline-dot:after{background:#fff;border-radius:50%;content:"";height:8px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:8px}.timeline-line{background:#e0e0e0;height:2px;left:50%;position:absolute;top:12px;width:100%;z-index:-1}.timeline-step.completed .timeline-line{background:#2271b1}.timeline-label{color:#646970;font-size:10px;font-weight:600;text-align:center;text-transform:uppercase}.timeline-events ul{list-style:none;margin:0;padding:0}.timeline-events li{border-bottom:1px solid #f0f0f1;display:grid;gap:8px;grid-template-columns:1fr auto;padding:12px 0}.timeline-events li:last-child{border-bottom:none}.event-status{color:#1e1e1e;font-size:13px;font-weight:600}.event-date{color:#646970;font-size:11px;text-align:right}.event-note{color:#646970;font-size:12px;grid-column:1/-1;margin-top:4px}.no-events{color:#646970;font-size:13px;padding:20px 0;text-align:center}
.formipay-notification-log h3{align-items:center;color:#1e1e1e;display:flex;font-size:16px;font-weight:600;gap:8px;margin:0 0 16px}.formipay-notification-log svg{fill:#1e1e1e}.no-logs{color:#646970;font-size:13px;padding:20px 0;text-align:center}.notification-list{list-style:none;margin:0;padding:0}.notification-item{background:#f6f7f7;border:1px solid #e0e0e0;border-radius:4px;display:flex;gap:12px;margin-bottom:8px;padding:12px}.notification-icon{align-items:center;background:#fff;border-radius:50%;display:flex;flex-shrink:0;height:36px;justify-content:center;width:36px}.notification-icon svg{fill:#1e1e1e}.notification-content{flex:1;min-width:0}.notification-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:4px}.notification-type{color:#2271b1;font-size:10px;font-weight:700;text-transform:uppercase}.notification-status{border-radius:10px;font-size:10px;font-weight:600;padding:2px 6px}.notification-item.sent .notification-status{background:#e7f7ed;color:#28a745}.notification-item.failed .notification-status{background:#fbeaea;color:#dc3545}.notification-item.pending .notification-status{background:#fff8e5;color:#f0ad4e}.notification-details{display:flex;flex-direction:column;gap:2px}.notification-details strong{color:#1e1e1e;font-size:13px}.notification-date,.notification-recipient{color:#646970;font-size:11px}.notification-date{margin-top:4px}
.formipay-order-detail{background:#f6f7f7;display:flex;flex-direction:column;height:100%}.formipay-detail-header{align-items:center;background:#fff;border-bottom:1px solid #e0e0e0;display:flex;gap:16px;justify-content:space-between;padding:16px 20px}.formipay-detail-header h1{flex:1;font-size:20px;font-weight:600;margin:0}.header-actions{display:flex;gap:8px}.formipay-detail-content{display:grid;gap:20px;grid-template-columns:2fr 1fr;overflow-y:auto;padding:20px}.formipay-detail-main,.formipay-detail-sidebar{display:flex;flex-direction:column;gap:20px}.formipay-detail-card{background:#fff;border:1px solid #e0e0e0;border-radius:4px;padding:20px}.formipay-detail-card h3{color:#1e1e1e;font-size:16px;font-weight:600;margin:0 0 16px}.detail-list{display:grid;gap:16px;grid-template-columns:repeat(2,1fr)}.detail-list>div{display:flex;flex-direction:column}.detail-list dt{color:#646970;font-size:12px;font-weight:600;margin-bottom:4px}.detail-list dd{align-items:center;color:#1e1e1e;display:flex;font-size:14px;gap:8px}.detail-list dd .components-select-control{flex:1}.items-table{border-collapse:collapse;width:100%}.items-table td,.items-table th{border-bottom:1px solid #f0f0f1;padding:10px;text-align:left}.items-table th{color:#646970;font-size:12px;font-weight:600;text-transform:uppercase}.items-table td{font-size:13px}.items-table small{color:#646970;display:block;font-size:11px}.items-table tfoot td{border-bottom:none;border-top:2px solid #1e1e1e;padding-top:16px}.customer-info{display:grid;gap:12px;grid-template-columns:1fr}.customer-info>div{display:flex;flex-direction:column}.customer-info dt{color:#646970;font-size:11px;font-weight:600;margin-bottom:2px}.customer-info dd{color:#1e1e1e;font-size:13px}.no-data{color:#646970;font-size:13px}.formipay-error,.formipay-loading{align-items:center;display:flex;flex-direction:column;gap:16px;justify-content:center;padding:60px 20px}.formipay-error p{color:#646970;margin:0}
.formipay-page-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:20px}.formipay-page-header h1{font-size:23px;font-weight:400;margin:0}.formipay-navigation-menu{align-items:center;background-color:#fff;box-shadow:0 4px 12px #d2d2d2;display:flex;gap:2em;left:0;margin-left:-20px;margin-top:-10px;padding:12px 20px 12px 50px;position:sticky;top:32px;width:calc(100% + 20px);z-index:1000}.formipay-navigation-menu>img{flex-shrink:0;height:48px;width:48px}.navigation-links{display:flex;flex-wrap:wrap;gap:4px}.navigation-links .nav-link{border-radius:4px;color:#646970;font-size:14px;font-weight:500;padding:8px 16px;text-decoration:none;transition:all .2s}.navigation-links .nav-link:hover{background-color:#f6f7f7;color:#2271b1}.navigation-links .nav-link.active{background-color:#2271b1;color:#fff}.status-badge{border-radius:4px;display:inline-block;font-size:12px;font-weight:500;padding:4px 8px}.status-badge.status-active,.status-badge.status-publish{background-color:#edfaef;color:#00a32a}.status-badge.status-draft,.status-badge.status-inactive{background-color:#f0f0f1;color:#646970}.status-badge.status-expired{background-color:#f6f7f7;color:#d63638}code{background-color:#f0f0f1;border-radius:3px;font-family:monospace;font-size:13px;padding:2px 6px}
.formipay-variation-table{border-collapse:collapse;margin-top:20px;width:100%}.formipay-variation-table thead th{border-bottom:2px solid #1e1e1e;color:#1e1e1e;font-size:13px;font-weight:600;padding:12px;text-align:left}.formipay-variation-table tbody td{border-bottom:1px solid #f0f0f1;padding:12px}.variation-row{background:#fff}.variation-name{gap:8px}.toggle-expand,.variation-name{align-items:center;display:flex}.toggle-expand{background:transparent;border:none;border-radius:2px;cursor:pointer;height:24px;justify-content:center;padding:0;width:24px}.toggle-expand:hover{background:#f0f0f1}.toggle-expand svg{fill:#646970}.variation-name strong{color:#1e1e1e;font-size:13px}.price-cell,.variation-stock,.variation-weight{min-width:120px}.price-cell input,.variation-stock input,.variation-weight input{border:1px solid #8c8f94;border-radius:2px;font-size:13px;padding:6px 8px;width:100%}.variation-stock .components-base-control,.variation-weight .components-base-control{margin:0}.variation-actions{min-width:100px}.variation-details-row{background:#f9f9f9}.variation-details-row td{padding:0}.inner-table{border-collapse:collapse;margin:0;width:100%}.inner-table thead{display:none}.inner-table td{border-bottom:1px solid #f0f0f1;padding:8px 12px}.inner-table tr:last-child td{border-bottom:none}.inner-table input[type=number]{border:1px solid #8c8f94;border-radius:2px;font-size:12px;padding:6px 8px;width:100%}.currency-name{color:#1e1e1e;font-size:12px;font-weight:600}.required{color:#dc3545;margin-left:4px}

1
build/admin.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
/**
* Plugin Name: Formipay
* Description: -
* Version: 1.0.0
* Version: 2.0.0
* Plugin URI: https://formipay.com/
* Author: Formipay
* Text Domain: formipay
@@ -21,7 +21,7 @@
if ( ! defined( 'ABSPATH' ) ) exit;
define( 'FORMIPAY_NAME', 'Formipay' );
define( 'FORMIPAY_VERSION', '1.0.0' );
define( 'FORMIPAY_VERSION', '2.0.0' );
define( 'FORMIPAY_PATH', plugin_dir_path( __FILE__ ) );
define( 'FORMIPAY_URL', plugin_dir_url( __FILE__ ) );
define( 'FORMIPAY_MENU_SLUG', 'formipay' );
@@ -49,6 +49,8 @@ spl_autoload_register(function ($class) {
});
\Formipay\Init::get_instance();
\Formipay\Admin\ReactAdmin::get_instance();
\Formipay\Admin\FormBuilderAjax::get_instance();
register_activation_hook( __FILE__, 'formipay_activate' );
function formipay_activate() {

View File

@@ -84,12 +84,13 @@ class Access {
}
public function formipay_access_items() {
include_once FORMIPAY_PATH . 'admin/page-access-items.php';
// React admin
\Formipay\Admin\ReactAdmin::render_mount_point('access');
}
public function enqueue_admin() {
global $current_screen;
// Assets now handled by ReactAdmin class
return;
$screen = get_current_screen();
@@ -432,7 +433,7 @@ class Access {
public function formipay_tabledata_access_items() {
check_ajax_referer( 'formipay-admin-access-nonce', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -526,7 +527,7 @@ class Access {
public function formipay_access_items_get_products() {
check_ajax_referer( 'formipay-admin-access-nonce', 'nonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -560,7 +561,7 @@ class Access {
public function formipay_create_access_item_post() {
check_ajax_referer( 'formipay-admin-access-nonce', 'nonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -592,7 +593,7 @@ class Access {
public function formipay_delete_access_item() {
check_ajax_referer( 'formipay-admin-access-nonce', 'nonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -627,7 +628,7 @@ class Access {
public function formipay_bulk_delete_access_item() {
check_ajax_referer( 'formipay-admin-access-nonce', 'nonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -674,7 +675,7 @@ class Access {
public function formipay_duplicate_access_item() {
check_ajax_referer( 'formipay-admin-access-nonce', 'nonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );

View File

@@ -0,0 +1,127 @@
<?php
namespace Formipay\Admin;
use Formipay\Traits\SingletonTrait;
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* AJAX handlers for React Form Builder
*/
class FormBuilderAjax {
use SingletonTrait;
protected function __construct() {
add_action( 'wp_ajax_formipay_save_form_fields', [$this, 'save_form_fields'] );
add_action( 'wp_ajax_formipay_load_form_fields', [$this, 'load_form_fields'] );
}
/**
* Save form fields via AJAX
*/
public function save_form_fields() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
$post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
$fields_json = isset( $_POST['fields'] ) ? wp_unslash( $_POST['fields'] ) : '[]';
if ( $post_id === 0 ) {
wp_send_json_error( [ 'message' => 'Invalid post ID' ] );
}
$fields = json_decode( $fields_json, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
wp_send_json_error( [ 'message' => 'Invalid JSON data' ] );
}
// Sanitize fields
$sanitized_fields = [];
foreach ( $fields as $field ) {
$sanitized_fields[] = $this->sanitize_field( $field );
}
// Update post meta
$current_settings = get_post_meta( $post_id, 'formipay_settings', true );
$current_settings = is_array( $current_settings ) ? $current_settings : [];
$current_settings['fields'] = $sanitized_fields;
update_post_meta( $post_id, 'formipay_settings', $current_settings );
wp_send_json_success( [
'message' => 'Form fields saved successfully',
'fields' => $sanitized_fields
] );
}
/**
* Load form fields via AJAX
*/
public function load_form_fields() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
$post_id = isset( $_GET['post_id'] ) ? intval( $_GET['post_id'] ) : 0;
if ( $post_id === 0 ) {
wp_send_json_error( [ 'message' => 'Invalid post ID' ] );
}
$settings = get_post_meta( $post_id, 'formipay_settings', true );
$fields = isset( $settings['fields'] ) ? $settings['fields'] : [];
wp_send_json_success( [
'fields' => $fields
] );
}
/**
* Sanitize a single field
*/
private function sanitize_field( $field ) {
$sanitized = [
'field_type' => sanitize_text_field( $field['field_type'] ?? 'text' ),
'label' => sanitize_text_field( $field['label'] ?? '' ),
'field_id' => sanitize_title( str_replace( ' ', '_', $field['field_id'] ?? '' ) ),
'placeholder' => sanitize_text_field( $field['placeholder'] ?? '' ),
'default_value' => sanitize_text_field( $field['default_value'] ?? '' ),
'description' => sanitize_textarea_field( $field['description'] ?? '' ),
'is_required' => (bool) ( $field['is_required'] ?? false ),
'option_grid_columns' => absint( $field['option_grid_columns'] ?? 1 ),
'field_options' => [],
];
// Sanitize field options
if ( isset( $field['field_options'] ) && is_array( $field['field_options'] ) ) {
foreach ( $field['field_options'] as $option ) {
$sanitized['field_options'][] = [
'label' => sanitize_text_field( $option['label'] ?? '' ),
'value' => sanitize_text_field( $option['value'] ?? '' ),
'amount' => floatval( $option['amount'] ?? 0 ),
'weight' => floatval( $option['weight'] ?? 0 ),
'quantity' => (bool) ( $option['quantity'] ?? false ),
'thumbnail' => absint( $option['thumbnail'] ?? 0 ),
];
}
}
return $sanitized;
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace Formipay\Admin;
use Formipay\Traits\SingletonTrait;
if ( ! defined( 'ABSPATH' ) ) exit;
class ReactAdmin {
use SingletonTrait;
protected function __construct() {
add_action( 'admin_enqueue_scripts', [$this, 'enqueue_assets'] );
add_filter( 'formipay/admin/data', [$this, 'localize_data'] );
}
public function enqueue_assets() {
$screen = get_current_screen();
// Only load React assets on Formipay admin pages
if ( strpos( $screen->id, 'formipay' ) === false ) {
return;
}
// Enqueue React build assets
$build_dir = FORMIPAY_PATH . 'build';
$build_url = FORMIPAY_URL . 'build';
if ( ! file_exists( $build_dir . '/admin.asset.php' ) ) {
error_log('[Formipay] Build files not found at: ' . $build_dir . '/admin.asset.php');
return; // Build not generated yet
}
$assets_file = require $build_dir . '/admin.asset.php';
$dependencies = $assets_file['dependencies'] ?? [];
// Filter out icon build dependencies - they're bundled, not separate scripts
$original_count = count($dependencies);
$dependencies = array_values(array_filter($dependencies, function($dep) {
return strpos($dep, 'wp-icons/build/') === false;
}));
error_log('[Formipay] Filtered dependencies: ' . $original_count . ' -> ' . count($dependencies));
$version = $assets_file['version'] ?? FORMIPAY_VERSION;
wp_enqueue_style(
'formipay-admin-style',
$build_url . '/admin.css',
[],
$version
);
wp_enqueue_script(
'formipay-admin',
$build_url . '/admin.js',
$dependencies,
$version,
true
);
// Localize script with required data
$data = apply_filters( 'formipay/admin/data', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'restUrl' => rest_url( 'formipay/v1' ),
'nonce' => wp_create_nonce( 'formipay-admin' ),
'pluginUrl' => FORMIPAY_URL,
'siteUrl' => site_url(),
] );
// Debug logging
error_log('[Formipay] Enqueuing React assets on screen: ' . $screen->id);
error_log('[Formipay] Page data: ' . wp_json_encode($data));
wp_localize_script( 'formipay-admin', 'formipayAdmin', $data );
}
public function localize_data( $data ) {
$screen = get_current_screen();
$page = '';
// Determine current page based on screen ID
if ( $screen->id === 'formipay_page_formipay-orders' ) {
$page = 'orders';
} elseif ( $screen->id === 'formipay_page_formipay-customers' ) {
$page = 'customers';
} elseif ( $screen->id === 'formipay_page_formipay-products' ) {
$page = 'products';
} elseif ( $screen->id === 'toplevel_page_formipay' ) {
$page = 'forms';
} elseif ( $screen->id === 'formipay_page_formipay-coupons' ) {
$page = 'coupons';
} elseif ( $screen->id === 'formipay_page_formipay-access' ) {
$page = 'access';
} elseif ( $screen->id === 'formipay_page_formipay-licenses' ) {
$page = 'licenses';
}
if ( $page ) {
$data[$page] = $this->get_page_data( $page );
}
return $data;
}
private function get_page_data( $page ) {
$data = [];
switch ( $page ) {
case 'orders':
$data['statusOptions'] = formipay_order_status_list();
break;
case 'customers':
$data['columns'] = [
'id' => __( 'ID', 'formipay' ),
'name' => __( 'Name', 'formipay' ),
'email' => __( 'Email', 'formipay' ),
'phone' => __( 'Phone', 'formipay' ),
'total_order' => __( 'Total Orders', 'formipay' ),
];
break;
case 'products':
$data['currencies'] = formipay_global_currency_options();
break;
case 'forms':
case 'coupons':
case 'access':
case 'licenses':
// These pages fetch data via AJAX, no initial data needed
$data = [];
break;
}
return $data;
}
/**
* Render React mount point
*/
public static function render_mount_point( $page ) {
printf(
'<div class="wrap"><div id="formipay-admin-root" data-formipay-mount="%s">Loading %s...</div></div>',
esc_attr( $page ),
esc_html( ucfirst( $page ) )
);
}
}

View File

@@ -94,12 +94,13 @@ class Coupon {
}
public function formipay_coupon() {
include_once FORMIPAY_PATH . 'admin/page-coupons.php';
// React admin
\Formipay\Admin\ReactAdmin::render_mount_point('coupons');
}
public function enqueue_admin() {
global $current_screen;
// Assets now handled by ReactAdmin class
return;
if($current_screen->id == 'formipay_page_formipay-coupons') {
@@ -568,7 +569,7 @@ class Coupon {
public function formipay_tabledata_coupons() {
check_ajax_referer( 'formipay-admin-coupon-page', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -676,7 +677,7 @@ class Coupon {
public function formipay_coupon_get_products() {
check_ajax_referer( 'formipay-admin-coupon-page', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -710,7 +711,7 @@ class Coupon {
public function formipay_create_coupon_post() {
check_ajax_referer( 'formipay-admin-coupon-page', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -745,7 +746,7 @@ class Coupon {
public function formipay_delete_coupon() {
check_ajax_referer( 'formipay-admin-coupon-page', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -773,7 +774,7 @@ class Coupon {
public function formipay_bulk_delete_coupon() {
check_ajax_referer( 'formipay-admin-coupon-page', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -820,7 +821,7 @@ class Coupon {
public function formipay_duplicate_coupon() {
check_ajax_referer( 'formipay-admin-coupon-page', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );

View File

@@ -182,56 +182,48 @@ class Customer {
}
public function enqueue() {
// Assets now handled by ReactAdmin class
return;
global $current_screen;
// $customer_id = intval(filter_input(INPUT_GET, 'customer_id', FILTER_SANITIZE_STRING));
// if(empty($customer_id)){
// wp_enqueue_style( 'page-customers', FORMIPAY_URL . 'admin/assets/css/admin-customers.css', [], FORMIPAY_VERSION, 'all' );
// wp_enqueue_script( 'page-customers', FORMIPAY_URL . 'admin/assets/js/admin-customers.js', ['jquery', 'gridjs'], FORMIPAY_VERSION, true );
// }else{
// wp_enqueue_style( 'bootstrap-icon', FORMIPAY_URL . 'vendor/Bootstrap/bootstrap-icons.css', [], '1.11.1', 'all');
// wp_enqueue_style( 'bootstrap', FORMIPAY_URL . 'vendor/Bootstrap/bootstrap.min.css', [], '5.3.2' );
// wp_enqueue_style( 'page-customers', FORMIPAY_URL . 'admin/assets/css/admin-customer-details.css', [], FORMIPAY_VERSION, 'all' );
// wp_enqueue_script( 'handlebars', FORMIPAY_URL . 'vendor/HandleBars/handlebars.min.js', [], '4.7.7', true);
// wp_enqueue_script( 'bootstrap', FORMIPAY_URL . 'vendor/Bootstrap/bootstrap.bundle.min.js', ['jquery'], '5.3.2', true );
// wp_enqueue_script( 'page-customers', FORMIPAY_URL . 'admin/assets/js/admin-customer-details.js', ['jquery'], FORMIPAY_VERSION, true );
// }
if($current_screen->id == 'formipay_page_formipay-customers') {
// wp_localize_script( 'page-customers', 'formipay_customers_page', [
// 'ajax_url' => admin_url('admin-ajax.php'),
// 'site_url' => site_url(),
// 'customer_id' => $customer_id,
// 'nonce' => wp_create_nonce( 'formipay-admin-access-nonce' ),
// 'columns' => [
// 'id' => esc_html__( 'ID', 'formipay' ),
// 'name' => esc_html__( 'Name', 'formipay' ),
// 'email' => esc_html__( 'Email', 'formipay' ),
// 'phone' => esc_html__( 'Phone', 'formipay' ),
// 'total_order' => esc_html__( 'Total Order', 'formipay' ),
// ]
// ] );
$customer_id = intval(filter_input(INPUT_GET, 'customer_id', FILTER_SANITIZE_STRING));
if(empty($customer_id)){
wp_enqueue_style( 'page-customers', FORMIPAY_URL . 'admin/assets/css/admin-customers.css', [], FORMIPAY_VERSION, 'all' );
wp_enqueue_script( 'page-customers', FORMIPAY_URL . 'admin/assets/js/admin-customers.js', ['jquery', 'gridjs'], FORMIPAY_VERSION, true );
}else{
wp_enqueue_style( 'bootstrap-icon', FORMIPAY_URL . 'vendor/Bootstrap/bootstrap-icons.css', [], '1.11.1', 'all');
wp_enqueue_style( 'bootstrap', FORMIPAY_URL . 'vendor/Bootstrap/bootstrap.min.css', [], '5.3.2' );
wp_enqueue_style( 'page-customers', FORMIPAY_URL . 'admin/assets/css/admin-customer-details.css', [], FORMIPAY_VERSION, 'all' );
wp_enqueue_script( 'handlebars', FORMIPAY_URL . 'vendor/HandleBars/handlebars.min.js', [], '4.7.7', true);
wp_enqueue_script( 'bootstrap', FORMIPAY_URL . 'vendor/Bootstrap/bootstrap.bundle.min.js', ['jquery'], '5.3.2', true );
wp_enqueue_script( 'page-customers', FORMIPAY_URL . 'admin/assets/js/admin-customer-details.js', ['jquery'], FORMIPAY_VERSION, true );
}
wp_localize_script( 'page-customers', 'formipay_customers_page', [
'ajax_url' => admin_url('admin-ajax.php'),
'site_url' => site_url(),
'customer_id' => $customer_id,
'nonce' => wp_create_nonce( 'formipay-admin-access-nonce' ),
'columns' => [
'id' => esc_html__( 'ID', 'formipay' ),
'name' => esc_html__( 'Name', 'formipay' ),
'email' => esc_html__( 'Email', 'formipay' ),
'phone' => esc_html__( 'Phone', 'formipay' ),
'total_order' => esc_html__( 'Total Order', 'formipay' ),
]
] );
}
// }
}
public function customers_page() {
$customer_id = intval(filter_input(INPUT_GET, 'customer_id', FILTER_SANITIZE_STRING));
if(empty($customer_id)){
include FORMIPAY_PATH . 'admin/page-customers.php';
}else{
include FORMIPAY_PATH . 'admin/page-customer-details.php';
}
// React admin
\Formipay\Admin\ReactAdmin::render_mount_point('customers');
}
public function formipay_tabledata_customers() {
check_ajax_referer( 'formipay-admin-access-nonce', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );

View File

@@ -93,7 +93,8 @@ class Form {
}
public function formipay_form() {
include_once FORMIPAY_PATH . 'admin/page-forms.php';
// React admin
\Formipay\Admin\ReactAdmin::render_mount_point('forms');
}
public function metaboxes($post) {
@@ -1248,8 +1249,9 @@ class Form {
}
public function enqueue_admin() {
global $current_screen, $post;
// Assets now handled by ReactAdmin class
return;
// Check that we are on the 'Checker' post editor screen
if ( $current_screen->post_type === 'formipay-form' && $current_screen->base === 'post' ) {
@@ -1547,7 +1549,14 @@ class Form {
public function formipay_tabledata_forms() {
check_ajax_referer( 'formipay-admin-post', '_wpnonce' );
error_log('[Formipay] formipay_tabledata_forms called');
$nonce_check = check_ajax_referer( 'formipay-admin', '_wpnonce', false );
error_log('[Formipay] Nonce check result: ' . ($nonce_check ? 'valid' : 'invalid'));
if ( ! $nonce_check ) {
wp_send_json_error( [ 'message' => 'Invalid nonce' ] );
}
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -1629,7 +1638,7 @@ class Form {
public function formipay_create_form_post() {
check_ajax_referer( 'formipay-admin-post', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -1702,7 +1711,7 @@ class Form {
public function formipay_delete_form() {
check_ajax_referer( 'formipay-admin-post', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -1730,7 +1739,7 @@ class Form {
public function formipay_bulk_delete_form() {
check_ajax_referer( 'formipay-admin-post', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -1777,7 +1786,7 @@ class Form {
public function formipay_duplicate_form() {
check_ajax_referer( 'formipay-admin-post', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );

View File

@@ -70,58 +70,59 @@ class License {
}
public function page_licenses() {
include FORMIPAY_PATH . 'admin/page-licenses.php';
// React admin
\Formipay\Admin\ReactAdmin::render_mount_point('licenses');
}
/** Enqueue admin assets for Licenses page */
public function enqueue() {
global $current_screen; if (!$current_screen) return;
if ($current_screen->id === 'formipay_page_formipay-licenses') {
wp_enqueue_style('page-licenses', FORMIPAY_URL . 'admin/assets/css/admin-licenses.css', [], FORMIPAY_VERSION, 'all');
wp_enqueue_script('page-licenses', FORMIPAY_URL . 'admin/assets/js/admin-licenses.js', ['jquery', 'gridjs'], FORMIPAY_VERSION, true);
wp_localize_script('page-licenses', 'formipay_licenses_page', [
'ajax_url' => admin_url('admin-ajax.php'),
'site_url' => site_url(),
'columns' => [
'id' => __('ID','formipay'),
'product' => __('Product','formipay'),
'order' => __('Order','formipay'),
'email' => __('Email','formipay'),
'key' => __('Key','formipay'),
'status' => __('Status','formipay'),
'expiry' => __('Expiry','formipay'),
'date' => __('Date','formipay'),
],
'filter_form' => [
'products' => [
'placeholder' => __('Filter by Product','formipay'),
'noresult_text' => __('No results found','formipay')
],
'status' => [
'placeholder' => __('Filter by Status','formipay'),
'noresult_text' => __('No results found','formipay')
]
],
'modal' => [
'delete' => [
'question' => __('Do you want to delete the license?','formipay'),
'cancelButton' => __('Cancel','formipay'),
'confirmButton' => __('Delete Permanently','formipay')
],
'bulk_delete' => [
'question' => __('Do you want to delete the selected license(s)?','formipay'),
'cancelButton' => __('Cancel','formipay'),
'confirmButton' => __('Confirm','formipay')
],
],
'nonce' => wp_create_nonce('formipay-admin-licenses')
]);
}
// Assets now handled by ReactAdmin class
return;
// wp_enqueue_style('page-licenses', FORMIPAY_URL . 'admin/assets/css/admin-licenses.css', [], FORMIPAY_VERSION, 'all');
// wp_enqueue_script('page-licenses', FORMIPAY_URL . 'admin/assets/js/admin-licenses.js', ['jquery', 'gridjs'], FORMIPAY_VERSION, true);
// wp_localize_script('page-licenses', 'formipay_licenses_page', [
// 'ajax_url' => admin_url('admin-ajax.php'),
// 'site_url' => site_url(),
// 'columns' => [
// 'id' => __('ID','formipay'),
// 'product' => __('Product','formipay'),
// 'order' => __('Order','formipay'),
// 'email' => __('Email','formipay'),
// 'key' => __('Key','formipay'),
// 'status' => __('Status','formipay'),
// 'expiry' => __('Expiry','formipay'),
// 'date' => __('Date','formipay'),
// ],
// 'filter_form' => [
// 'products' => [
// 'placeholder' => __('Filter by Product','formipay'),
// 'noresult_text' => __('No results found','formipay')
// ],
// 'status' => [
// 'placeholder' => __('Filter by Status','formipay'),
// 'noresult_text' => __('No results found','formipay')
// ]
// ],
// 'modal' => [
// 'delete' => [
// 'question' => __('Do you want to delete the license?','formipay'),
// 'cancelButton' => __('Cancel','formipay'),
// 'confirmButton' => __('Delete Permanently','formipay')
// ],
// 'bulk_delete' => [
// 'question' => __('Do you want to delete the selected license(s)?','formipay'),
// 'cancelButton' => __('Cancel','formipay'),
// 'confirmButton' => __('Confirm','formipay')
// ],
// ],
// 'nonce' => wp_create_nonce('formipay-admin-licenses')
// ]);
// }
}
/** GridJS data source */
public function tabledata() {
check_ajax_referer('formipay-admin-licenses', '_wpnonce');
check_ajax_referer('formipay-admin', '_wpnonce');
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -202,7 +203,7 @@ class License {
/** Delete single license */
public function delete() {
check_ajax_referer('formipay-admin-licenses', '_wpnonce');
check_ajax_referer('formipay-admin', '_wpnonce');
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -238,7 +239,7 @@ class License {
/** Bulk delete */
public function bulk_delete() {
check_ajax_referer('formipay-admin-licenses', '_wpnonce');
check_ajax_referer('formipay-admin', '_wpnonce');
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );

View File

@@ -642,92 +642,19 @@ class Order {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$order_id = isset($_GET['order_id']) ? intval($_GET['order_id']) : 0;
if(empty($order_id)){
include FORMIPAY_PATH . 'admin/page-orders.php';
}else{
$order = formipay_get_order( $order_id );
include FORMIPAY_PATH . 'admin/page-order-details.php';
}
$page = $order_id ? 'order-detail' : 'orders';
// React admin
printf(
'<div id="formipay-admin-root" data-formipay-mount="%s"></div>',
esc_attr($page)
);
}
public function enqueue() {
global $current_screen;
if($current_screen->id == 'formipay_page_formipay-orders') {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$order_id = isset($_GET['order_id']) ? intval($_GET['order_id']) : 0;
if(empty($order_id)){
wp_enqueue_style( 'page-orders', FORMIPAY_URL . 'admin/assets/css/admin-orders.css', [], FORMIPAY_VERSION, 'all' );
wp_enqueue_script( 'page-orders', FORMIPAY_URL . 'admin/assets/js/admin-orders.js', ['jquery', 'gridjs'], FORMIPAY_VERSION, true );
wp_localize_script( 'page-orders', 'formipay_orders_page', [
'ajax_url' => admin_url('admin-ajax.php'),
'site_url' => site_url(),
'columns' => [
'id' => esc_html__( 'ID', 'formipay' ),
'form' => esc_html__( 'Form', 'formipay' ),
'total' => esc_html__( 'Total', 'formipay' ),
'date' => esc_html__( 'Date', 'formipay' ),
'payment_gateway' => esc_html__( 'Payment Gateway', 'formipay' ),
'status' => esc_html__( 'Status', 'formipay' ),
],
'filter_form' => [
'products' => [
'placeholder' => esc_html__( 'Filter by Product', 'formipay' ),
'noresult_text' => esc_html__( 'No results found', 'formipay' )
],
'currencies' => [
'placeholder' => esc_html__( 'Filter by Currency', 'formipay' ),
'noresult_text' => esc_html__( 'No results found', 'formipay' )
]
],
'nonce' => wp_create_nonce( 'formipay-order-details' )
] );
}else{
wp_enqueue_style( 'bootstrap-icon', FORMIPAY_URL . 'vendor/Bootstrap/bootstrap-icons.css', [], '1.11.1', 'all');
wp_enqueue_style( 'bootstrap', FORMIPAY_URL . 'vendor/Bootstrap/bootstrap.min.css', [], '5.3.2' );
wp_enqueue_style( 'page-orders', FORMIPAY_URL . 'admin/assets/css/admin-order-details.css', [], FORMIPAY_VERSION, 'all' );
wp_enqueue_script( 'handlebars', FORMIPAY_URL . 'vendor/HandleBars/handlebars.min.js', [], '4.7.7', true);
wp_enqueue_script( 'bootstrap', FORMIPAY_URL . 'vendor/Bootstrap/bootstrap.bundle.min.js', ['jquery'], '5.3.2', true );
wp_enqueue_script( 'page-orders', FORMIPAY_URL . 'admin/assets/js/admin-order-details.js', ['jquery'], FORMIPAY_VERSION, true );
wp_localize_script( 'page-orders', 'formipay_order_details_page', [
'ajax_url' => admin_url('admin-ajax.php'),
'site_url' => site_url(),
'order_id' => $order_id,
'order_detail' => [
'change_order_status_confirmation' => esc_html__( 'Are you sure to change status?', 'formipay' ),
'change_order_status_button_confirm' => esc_html__( 'Change', 'formipay' ),
'change_order_status_button_cancel' => esc_html__( 'Cancel', 'formipay' ),
'edit_button_loading_text' => esc_html__( 'Preparing...', 'formipay' ),
'update_button_loading_text' => esc_html__( 'Updating...', 'formipay' ),
'pass_method' => [
'magic_link' => esc_html__( 'Magic Link', 'formipay' ),
'static_password' => esc_html__( 'Static Password', 'formipay' )
]
],
'modal' => [
'delete' => [
'question' => esc_html__( 'Do you want to delete the order?', 'formipay' ),
'cancelButton' => esc_html__( 'Cancel', 'formipay' ),
'confirmButton' => esc_html__( 'Delete Permanently', 'formipay' )
],
'bulk_delete' => [
'question' => esc_html__( 'Do you want to delete the selected the order(s)?', 'formipay' ),
'cancelButton' => esc_html__( 'Cancel', 'formipay' ),
'confirmButton' => esc_html__( 'Confirm', 'formipay' )
],
],
'nonce' => wp_create_nonce( 'formipay-order-details' )
] );
}
}
// Assets now handled by ReactAdmin class
return;
}
public function formipay_get_all_forms() {
@@ -766,7 +693,7 @@ class Order {
public function formipay_orders_get_choices() {
check_ajax_referer( 'formipay-order-details', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -934,7 +861,7 @@ class Order {
public function formipay_tabledata_orders() {
check_ajax_referer( 'formipay-order-details', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -1037,7 +964,7 @@ class Order {
public function formipay_delete_order() {
check_ajax_referer( 'formipay-order-details', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -1065,7 +992,7 @@ class Order {
public function formipay_bulk_delete_order() {
check_ajax_referer( 'formipay-order-details', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -1112,7 +1039,7 @@ class Order {
public function formipay_load_order_data() {
check_ajax_referer( 'formipay-order-details', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -1126,7 +1053,7 @@ class Order {
public function formipay_change_order_status() {
check_ajax_referer( 'formipay-order-details', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -1164,7 +1091,7 @@ class Order {
public function formipay_check_editable_field() {
check_ajax_referer( 'formipay-order-details', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -1212,7 +1139,7 @@ class Order {
public function formipay_update_editable_field_data() {
check_ajax_referer( 'formipay-order-details', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
@@ -1267,7 +1194,7 @@ class Order {
public function formipay_update_digital_access() {
check_ajax_referer( 'formipay-order-details', '_wpnonce' );
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );

View File

@@ -42,6 +42,7 @@ class BankTransfer extends Payment {
`created_date` datetime DEFAULT CURRENT_TIMESTAMP,
`form_id` int DEFAULT 0,
`order_id` int DEFAULT 0,
`currency_code` text,
`channel` text NOT NULL,
`total` float(10, 2) DEFAULT 0,
`unique_code` int DEFAULT 0,
@@ -61,12 +62,17 @@ class BankTransfer extends Payment {
$formipay_settings = get_option('formipay_settings');
$timeout = isset($formipay_settings['bank_transfer_timeout']) ? (int) $formipay_settings['bank_transfer_timeout'] : 1440;
$table = $wpdb->prefix . 'formipay_bank_transfer_trx';
if ($order_data['payment_gateway'] == 'bank_transfer') {
$currency_data = explode(':::', get_post_meta($order_data['form_id'], 'product_currency', true));
$currency_code = !empty($currency_data[0]) ? $currency_data[0] : formipay_default_currency('code');
$submit_args = [
'created_date' => formipay_date('Y-m-d H:i:s', strtotime($order_data['created_date'])),
'form_id' => intval($order_data['form_id']),
'order_id' => intval($order_data['id']),
'currency_code' => sanitize_text_field($currency_code),
'channel' => sanitize_text_field(str_replace($order_data['payment_gateway'].':::', '', $order_data['form_data']['payment'])),
'total' => floatval($order_data['total']),
'unique_code' => sanitize_text_field($this->check_unique_code()),

View File

@@ -106,8 +106,8 @@ class Product {
}
public function enqueue_admin() {
global $current_screen;
// Assets now handled by ReactAdmin class
return;
if($current_screen->id == 'formipay_page_formipay-products') {
@@ -228,7 +228,8 @@ class Product {
}
public function formipay_products() {
include_once FORMIPAY_PATH . 'admin/page-products.php';
// React admin
\Formipay\Admin\ReactAdmin::render_mount_point('products');
}
public function cpt_post_fields_box($boxes) {

1
node_modules/.bin/acorn generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../acorn/bin/acorn

1
node_modules/.bin/ansi-html generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../ansi-html/bin/ansi-html

1
node_modules/.bin/autoprefixer generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../autoprefixer/bin/autoprefixer

1
node_modules/.bin/baseline-browser-mapping generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../baseline-browser-mapping/dist/cli.cjs

1
node_modules/.bin/browsers generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../@puppeteer/browsers/lib/cjs/main-cli.js

1
node_modules/.bin/browserslist generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../browserslist/cli.js

1
node_modules/.bin/check-node-version generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../check-node-version/bin.js

1
node_modules/.bin/chrome-debug generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../lighthouse/core/scripts/manual-chrome-launcher.js

1
node_modules/.bin/create-jest generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../create-jest/bin/create-jest.js

1
node_modules/.bin/cssesc generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../cssesc/bin/cssesc

1
node_modules/.bin/envinfo generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../envinfo/dist/cli.js

1
node_modules/.bin/escodegen generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../escodegen/bin/escodegen.js

1
node_modules/.bin/esgenerate generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../escodegen/bin/esgenerate.js

1
node_modules/.bin/eslint generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../eslint/bin/eslint.js

1
node_modules/.bin/eslint-config-prettier generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../eslint-config-prettier/bin/cli.js

1
node_modules/.bin/esparse generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../esprima/bin/esparse.js

1
node_modules/.bin/esvalidate generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../esprima/bin/esvalidate.js

1
node_modules/.bin/extract-zip generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../extract-zip/cli.js

1
node_modules/.bin/find-process generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../find-process/bin/find-process.js

1
node_modules/.bin/flat generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../flat/cli.js

1
node_modules/.bin/import-local-fixture generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../import-local/fixtures/cli.js

1
node_modules/.bin/is-docker generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../is-docker/cli.js

1
node_modules/.bin/jest generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../jest/bin/jest.js

1
node_modules/.bin/js-yaml generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../js-yaml/bin/js-yaml.js

1
node_modules/.bin/jsesc generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../jsesc/bin/jsesc

1
node_modules/.bin/json5 generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../json5/lib/cli.js

1
node_modules/.bin/lighthouse generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../lighthouse/cli/index.js

1
node_modules/.bin/loose-envify generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../loose-envify/cli.js

1
node_modules/.bin/markdown-it generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../markdown-it/bin/markdown-it.js

1
node_modules/.bin/markdownlint generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../markdownlint-cli/markdownlint.js

1
node_modules/.bin/mime generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../mime/cli.js

1
node_modules/.bin/multicast-dns generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../multicast-dns/cli.js

1
node_modules/.bin/nanoid generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../nanoid/bin/nanoid.cjs

1
node_modules/.bin/npm-packlist generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../npm-packlist/bin/index.js

1
node_modules/.bin/npmPkgJsonLint generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../npm-package-json-lint/dist/cli.js

1
node_modules/.bin/opener generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../opener/bin/opener-bin.js

1
node_modules/.bin/parser generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../@babel/parser/bin/babel-parser.js

1
node_modules/.bin/playwright generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../@playwright/test/cli.js

1
node_modules/.bin/playwright-core generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../playwright-core/cli.js

1
node_modules/.bin/pot-to-php generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../@wordpress/i18n/tools/pot-to-php.js

1
node_modules/.bin/prettier generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../prettier/bin/prettier.cjs

1
node_modules/.bin/print-chrome-path generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../chrome-launcher/bin/print-chrome-path.js

1
node_modules/.bin/regjsparser generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../regjsparser/bin/parser

1
node_modules/.bin/resolve generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../resolve/bin/resolve

1
node_modules/.bin/rimraf generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../rimraf/bin.js

1
node_modules/.bin/rtlcss generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../rtlcss/bin/rtlcss.js

1
node_modules/.bin/run-con generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../run-con/cli.js

1
node_modules/.bin/sass generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../sass/sass.js

1
node_modules/.bin/semver generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../semver/bin/semver.js

1
node_modules/.bin/smokehouse generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../lighthouse/cli/test/smokehouse/frontends/smokehouse-bin.js

1
node_modules/.bin/stylelint generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../stylelint/bin/stylelint.js

1
node_modules/.bin/svgo generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../svgo/bin/svgo

1
node_modules/.bin/terser generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../terser/bin/terser

1
node_modules/.bin/tree-kill generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../tree-kill/cli.js

1
node_modules/.bin/tsc generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../typescript/bin/tsc

1
node_modules/.bin/tsserver generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../typescript/bin/tsserver

1
node_modules/.bin/update-browserslist-db generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../update-browserslist-db/cli.js

1
node_modules/.bin/uuid generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../uuid/dist/bin/uuid

1
node_modules/.bin/wait-on generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../wait-on/bin/wait-on

1
node_modules/.bin/webpack generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../webpack/bin/webpack.js

1
node_modules/.bin/webpack-bundle-analyzer generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../webpack-bundle-analyzer/lib/bin/analyzer.js

1
node_modules/.bin/webpack-cli generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../webpack-cli/bin/cli.js

1
node_modules/.bin/webpack-dev-server generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../webpack-dev-server/bin/webpack-dev-server.js

1
node_modules/.bin/which generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../which/bin/which

1
node_modules/.bin/wp-scripts generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../@wordpress/scripts/bin/wp-scripts.js

Some files were not shown because too many files have changed in this diff Show More