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>
This commit is contained in:
dwindown
2026-04-18 17:02:14 +07:00
parent bd9cdac02e
commit e8fbfb14c1
74973 changed files with 6658406 additions and 71 deletions

View File

@@ -24,6 +24,8 @@ export async function ajaxRequest(action, data = {}) {
}
});
console.log(`[AJAX] Sending request: ${action}`, { nonce: NONCE, data });
try {
const response = await fetch(API_BASE, {
method: 'POST',
@@ -31,10 +33,14 @@ export async function ajaxRequest(action, data = {}) {
body: formData,
});
console.log(`[AJAX] Response status: ${action}`, response.status);
const result = await response.json();
console.log(`[AJAX] Response data: ${action}`, result);
if (!response.ok || result.success === false) {
throw new Error(result.message || 'Request failed');
const errorMsg = result.data?.message || result.message || 'Request failed';
throw new Error(errorMsg);
}
return result;
@@ -76,21 +82,21 @@ export async function apiRequest(endpoint, options = {}) {
* Orders API
*/
export const ordersApi = {
list: (params = {}) => ajaxRequest('formipay_tabledata_orders', params),
get: (orderId) => ajaxRequest('formipay_get_order', { order_id: orderId }),
updateStatus: (orderId, status) => ajaxRequest('formipay_update_order_status', {
list: (params = {}) => ajaxRequest('formipay-tabledata-orders', params),
get: (orderId) => ajaxRequest('formipay-get-order', { order_id: orderId }),
updateStatus: (orderId, status) => ajaxRequest('formipay-update-order-status', {
order_id: orderId,
status,
}),
delete: (orderIds) => ajaxRequest('formipay_bulk_delete_order', { ids: orderIds }),
delete: (orderIds) => ajaxRequest('formipay-bulk-delete-order', { ids: orderIds }),
};
/**
* Customers API
*/
export const customersApi = {
list: (params = {}) => ajaxRequest('formipay_tabledata_customers', params),
get: (customerId) => ajaxRequest('formipay_get_customer', { customer_id: customerId }),
list: (params = {}) => ajaxRequest('formipay-tabledata-customers', params),
get: (customerId) => ajaxRequest('formipay-get-customer', { customer_id: customerId }),
};
/**
@@ -106,16 +112,28 @@ export const productsApi = {
* Forms API
*/
export const formsApi = {
list: (params = {}) => ajaxRequest('formipay_tabledata_forms', params),
get: (formId) => ajaxRequest('formipay_get_form', { post_id: formId }),
list: (params = {}) => ajaxRequest('formipay-tabledata-forms', params),
get: (formId) => ajaxRequest('formipay-get-form', { post_id: formId }),
};
/**
* Coupons API
*/
export const couponsApi = {
list: (params = {}) => ajaxRequest('formipay_tabledata_coupons', params),
check: (code, formId) => ajaxRequest('formipay_check_coupon', { code, form_id: formId }),
list: (params = {}) => ajaxRequest('formipay-tabledata-coupons', params),
check: (code, formId) => ajaxRequest('formipay-check-coupon', { code, form_id: formId }),
create: (data) => ajaxRequest('formipay-create-coupon-post', data),
delete: (ids) => ajaxRequest('formipay-bulk-delete-coupon', { ids }),
};
/**
* Access Items API
*/
export const accessApi = {
list: (params = {}) => ajaxRequest('formipay-tabledata-access-items', params),
getProducts: () => ajaxRequest('formipay_access_items_get_products', {}),
create: (data) => ajaxRequest('formipay-create-access-item-post', data),
delete: (ids) => ajaxRequest('formipay-bulk-delete-access-item', { ids }),
};
/**

View File

@@ -2,6 +2,7 @@
* App Component - Main admin application shell
*/
import { useEffect } from '@wordpress/element';
import OrdersPage from '../pages/Orders';
import CustomersPage from '../pages/Customers';
import ProductsPage from '../pages/Products';
@@ -21,6 +22,10 @@ const pageComponents = {
};
export default function App({ page, initialData }) {
useEffect(() => {
console.log('[Formipay App] Rendering page:', page, 'with data:', initialData);
}, [page, initialData]);
const PageComponent = pageComponents[page];
if (!PageComponent) {

View File

@@ -9,14 +9,24 @@ import App from './components/App';
const mountApps = () => {
const mountPoints = document.querySelectorAll('[data-formipay-mount]');
console.log('[Formipay] Mount points found:', mountPoints.length);
console.log('[Formipay] formipayAdmin data:', window.formipayAdmin);
mountPoints.forEach((mountPoint) => {
const page = mountPoint.dataset.formipayMount;
const initialData = window.formipayAdmin?.[page] || {};
render(
<App page={page} initialData={initialData} />,
mountPoint
);
console.log('[Formipay] Mounting page:', page, 'with data:', initialData);
try {
render(
<App page={page} initialData={initialData} />,
mountPoint
);
console.log('[Formipay] Successfully mounted:', page);
} catch (error) {
console.error('[Formipay] Failed to mount:', page, error);
}
});
};

View File

@@ -1,14 +1,94 @@
/**
* Access Page - Placeholder
* Access Page - Access items management
*/
import { __ } from '@wordpress/i18n';
import { useState, useCallback, useEffect } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { accessApi } from '../api/client';
import DataTable from '../components/shared/DataTable';
import './AdminPages.css';
export default function AccessPage({ initialData }) {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const loadItems = useCallback(() => {
setLoading(true);
accessApi.list()
.then(result => {
// Handle both WordPress format and direct format
const items = result.data?.results || result.results || result.data || [];
setItems(items);
})
.catch(error => {
console.error('Load access items error:', error);
})
.finally(() => {
setLoading(false);
});
}, []);
useEffect(() => {
loadItems();
}, [loadItems]);
const columns = [
{
key: 'id',
label: __('ID', 'formipay'),
render: (row) => <strong>#{row.id}</strong>
},
{
key: 'title',
label: __('Title', 'formipay'),
},
{
key: 'product_name',
label: __('Product', 'formipay'),
},
{
key: 'status',
label: __('Status', 'formipay'),
render: (row) => {
const status = row.post_status || row.status || 'unknown';
const statusLabel = {
publish: __('Published', 'formipay'),
draft: __('Draft', 'formipay'),
}[status] || status;
return (
<span className={`status-badge status-${status}`}>
{statusLabel}
</span>
);
}
},
{
key: 'date',
label: __('Date', 'formipay'),
render: (row) => {
const date = row.post_date || row.date;
if (!date) return '-';
return new Date(date).toLocaleDateString();
}
},
];
return (
<div className="formipay-page-access">
<h1>{ __('Access Items', 'formipay') }</h1>
<p>{ __('Access items management coming soon. Use the classic editor for now.', 'formipay') }</p>
<div className="formipay-page-header">
<h1>{ __('Access Items', 'formipay') }</h1>
<Button variant="primary" href={window.formipayAdmin?.siteUrl + '/wp-admin/post-new.php?post_type=formipay-access'}>
{ __('+ Add New Item', 'formipay') }
</Button>
</div>
<DataTable
columns={columns}
data={items}
loading={loading}
emptyMessage={__('No access items found', 'formipay')}
/>
</div>
);
}

View File

@@ -1,14 +1,102 @@
/**
* Coupons Page - Placeholder
* Coupons Page - Coupon management
*/
import { __ } from '@wordpress/i18n';
import { useState, useCallback, useEffect } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { couponsApi } from '../api/client';
import DataTable from '../components/shared/DataTable';
import './AdminPages.css';
export default function CouponsPage({ initialData }) {
const [coupons, setCoupons] = useState([]);
const [loading, setLoading] = useState(true);
const loadCoupons = useCallback(() => {
setLoading(true);
couponsApi.list()
.then(result => {
// Handle both WordPress format and direct format
const coupons = result.data?.results || result.results || result.data || [];
setCoupons(coupons);
})
.catch(error => {
console.error('Load coupons error:', error);
})
.finally(() => {
setLoading(false);
});
}, []);
useEffect(() => {
loadCoupons();
}, [loadCoupons]);
const columns = [
{
key: 'id',
label: __('ID', 'formipay'),
render: (row) => <strong>#{row.id}</strong>
},
{
key: 'code',
label: __('Coupon Code', 'formipay'),
render: (row) => <strong>{row.code || row.post_title}</strong>
},
{
key: 'type',
label: __('Type', 'formipay'),
render: (row) => {
const type = row.coupon_type || row.type || 'percentage';
return type === 'percentage' ? __('%', 'formipay') : __('Fixed', 'formipay');
}
},
{
key: 'amount',
label: __('Amount', 'formipay'),
},
{
key: 'usages',
label: __('Usages', 'formipay'),
render: (row) => row.usage_count || row.usages || 0
},
{
key: 'status',
label: __('Status', 'formipay'),
render: (row) => {
const status = row.post_status || row.status || 'unknown';
return status === 'publish' ? __('Active', 'formipay') : __('Inactive', 'formipay');
}
},
];
return (
<div className="formipay-page-coupons">
<h1>{ __('Coupons', 'formipay') }</h1>
<p>{ __('Coupon management coming soon. Use the classic editor for now.', 'formipay') }</p>
<div className="formipay-page-header">
<h1>{ __('Coupons', 'formipay') }</h1>
<Button
variant="primary"
onClick={() => {
// Open create modal - for now just prompt
const code = prompt(__('Enter coupon code:', 'formipay'));
if (code) {
couponsApi.create({ code })
.then(() => loadCoupons())
.catch(console.error);
}
}}
>
{ __('+ Add New Coupon', 'formipay') }
</Button>
</div>
<DataTable
columns={columns}
data={coupons}
loading={loading}
emptyMessage={__('No coupons found', 'formipay')}
/>
</div>
);
}

View File

@@ -1,14 +1,85 @@
/**
* Customers Page - Placeholder
* Customers Page - Customer list
*/
import { __ } from '@wordpress/i18n';
import { useState, useCallback, useEffect } from '@wordpress/element';
import { customersApi } from '../api/client';
import DataTable from '../components/shared/DataTable';
import './AdminPages.css';
export default function CustomersPage({ initialData }) {
const [customers, setCustomers] = useState([]);
const [loading, setLoading] = useState(true);
const loadCustomers = useCallback(() => {
setLoading(true);
customersApi.list()
.then(result => {
// Handle both WordPress format and direct format
const customers = result.data?.results || result.results || result.data || [];
setCustomers(customers);
})
.catch(error => {
console.error('Load customers error:', error);
})
.finally(() => {
setLoading(false);
});
}, []);
useEffect(() => {
loadCustomers();
}, [loadCustomers]);
const columns = [
{
key: 'id',
label: __('ID', 'formipay'),
render: (row) => <strong>#{row.id}</strong>
},
{
key: 'name',
label: __('Name', 'formipay'),
render: (row) => row.name || row.full_name || '-'
},
{
key: 'email',
label: __('Email', 'formipay'),
},
{
key: 'phone',
label: __('Phone', 'formipay'),
render: (row) => row.phone || row.whatsapp || '-'
},
{
key: 'total_order',
label: __('Total Orders', 'formipay'),
render: (row) => row.total_order || row.total_orders || 0
},
{
key: 'created_date',
label: __('Date', 'formipay'),
render: (row) => {
const date = row.created_date || row.date;
if (!date) return '-';
return new Date(date).toLocaleDateString();
}
},
];
return (
<div className="formipay-page-formipay-page">
<h1>{ __('Customers', 'formipay') }</h1>
<p>{ __('Page content coming soon...', 'formipay') }</p>
<div className="formipay-page-customers">
<div className="formipay-page-header">
<h1>{ __('Customers', 'formipay') }</h1>
</div>
<DataTable
columns={columns}
data={customers}
loading={loading}
emptyMessage={__('No customers found', 'formipay')}
/>
</div>
);
}

View File

@@ -3,30 +3,120 @@
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import FormBuilder from '../components/formBuilder/FormBuilder';
import { useState, useCallback, useEffect } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { formsApi } from '../api/client';
import DataTable from '../components/shared/DataTable';
import './AdminPages.css';
export default function FormsPage({ initialData }) {
const [isBuilder, setIsBuilder] = useState(false);
const [selectedFormId, setSelectedFormId] = useState(null);
const [forms, setForms] = useState([]);
const [loading, setLoading] = useState(true);
if (isBuilder) {
return (
<div className="formipay-page-forms">
<FormBuilder
formId={selectedFormId}
initialData={initialData}
/>
</div>
);
const loadForms = useCallback(() => {
setLoading(true);
formsApi.list()
.then(result => {
console.log('Forms API result:', result);
// Handle both WordPress format and direct format
const formsData = result.data?.results || result.results || result.data || [];
console.log('Forms data extracted:', formsData);
console.log('Forms data length:', formsData.length);
setForms(formsData);
})
.catch(error => {
console.error('Load forms error:', error);
})
.finally(() => {
setLoading(false);
});
}, []);
useEffect(() => {
loadForms();
}, [loadForms]);
if (isBuilder && selectedFormId) {
window.location.href = `${window.formipayAdmin?.siteUrl || ''}/wp-admin/post.php?post=${selectedFormId}&action=edit`;
return null;
}
const columns = [
{
key: 'id',
label: __('ID', 'formipay'),
render: (row) => <strong>#{row.ID || row.id}</strong>
},
{
key: 'title',
label: __('Title', 'formipay'),
render: (row) => (
<a
href={`${window.formipayAdmin?.siteUrl || ''}/wp-admin/post.php?post=${row.ID || row.id}&action=edit`}
onClick={(e) => {
e.preventDefault();
setIsBuilder(true);
setSelectedFormId(row.ID || row.id);
}}
>
{row.post_title || row.title || __('Untitled', 'formipay')}
</a>
)
},
{
key: 'shortcode',
label: __('Shortcode', 'formipay'),
render: (row) => <code>[formipay id="{row.ID || row.id}"]</code>
},
{
key: 'status',
label: __('Status', 'formipay'),
render: (row) => {
const status = row.post_status || row.status || 'unknown';
const statusLabels = {
publish: __('Published', 'formipay'),
draft: __('Draft', 'formipay'),
pending: __('Pending', 'formipay'),
};
const statusLabel = statusLabels[status] || status;
return (
<span className={`status-badge status-${status === 'publish' ? 'active' : 'draft'}`}>
{statusLabel}
</span>
);
}
},
{
key: 'date',
label: __('Date', 'formipay'),
render: (row) => {
const date = row.post_date || row.date;
if (!date) return '-';
return new Date(date).toLocaleDateString();
}
},
];
return (
<div className="formipay-page-forms">
<div className="formipay-forms-list">
<div className="formipay-page-header">
<h1>{ __('Forms', 'formipay') }</h1>
<p>{ __('Forms list coming soon. Use the classic editor for now.', 'formipay') }</p>
<Button
variant="primary"
href={`${window.formipayAdmin?.siteUrl || ''}/wp-admin/post-new.php?post_type=formipay-form`}
>
{ __('+ Add New Form', 'formipay') }
</Button>
</div>
<DataTable
columns={columns}
data={forms}
loading={loading}
emptyMessage={__('No forms found', 'formipay')}
/>
</div>
);
}

View File

@@ -1,14 +1,100 @@
/**
* Licenses Page - Placeholder
* Licenses Page - License management
*/
import { __ } from '@wordpress/i18n';
import { useState, useCallback, useEffect } from '@wordpress/element';
import DataTable from '../components/shared/DataTable';
import './AdminPages.css';
export default function LicensesPage({ initialData }) {
const [licenses, setLicenses] = useState([]);
const [loading, setLoading] = useState(true);
const loadLicenses = useCallback(() => {
setLoading(true);
// Licenses API might use a different endpoint
fetch(window.formipayAdmin?.ajaxUrl || '/wp-admin/admin-ajax.php', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'formipay-tabledata-licenses',
_wpnonce: window.formipayAdmin?.nonce || '',
}),
})
.then(response => response.json())
.then(result => {
// Handle both WordPress format and direct format
const licenses = result.data?.results || result.results || result.data || [];
setLicenses(licenses);
})
.catch(error => {
console.error('Load licenses error:', error);
})
.finally(() => {
setLoading(false);
});
}, []);
useEffect(() => {
loadLicenses();
}, [loadLicenses]);
const columns = [
{
key: 'id',
label: __('ID', 'formipay'),
render: (row) => <strong>#{row.id}</strong>
},
{
key: 'license_key',
label: __('License Key', 'formipay'),
render: (row) => <code>{row.license_key || '-'}</code>
},
{
key: 'product',
label: __('Product', 'formipay'),
render: (row) => row.product_name || row.product || '-'
},
{
key: 'order',
label: __('Order', 'formipay'),
render: (row) => row.order_id || `#${row.order}` || '-'
},
{
key: 'email',
label: __('Email', 'formipay'),
},
{
key: 'status',
label: __('Status', 'formipay'),
render: (row) => {
const status = row.status || 'unknown';
const statusLabels = {
active: __('Active', 'formipay'),
inactive: __('Inactive', 'formipay'),
expired: __('Expired', 'formipay'),
};
return statusLabels[status] || status;
}
},
];
return (
<div className="formipay-page-licenses">
<h1>{ __('Licenses', 'formipay') }</h1>
<p>{ __('License management coming soon. Use the classic editor for now.', 'formipay') }</p>
<div className="formipay-page-licenses">
<div className="formipay-page-header">
<h1>{ __('Licenses', 'formipay') }</h1>
</div>
<DataTable
columns={columns}
data={licenses}
loading={loading}
emptyMessage={__('No licenses found', 'formipay')}
/>
</div>
</div>
);
}