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>
This commit is contained in:
dwindown
2026-04-19 05:58:44 +07:00
parent 96ea79600a
commit 057611ef40
19 changed files with 2158 additions and 214 deletions

View File

@@ -6,7 +6,59 @@ import { __ } from '@wordpress/i18n';
import DataTable from '../components/shared/DataTable';
import './AdminPages.css';
// SweetAlert2 is loaded via WordPress (global scope)
const Swal = window.Swal;
export default function AccessPage() {
const ajaxUrl = window.formipayAdmin?.ajaxUrl || '/wp-admin/admin-ajax.php';
const nonce = window.formipayAdmin?.nonce || '';
const handleDelete = async (id) => {
const result = await Swal.fire({
icon: 'info',
html: __('Do you want to delete this item?', 'formipay'),
showCancelButton: true,
confirmButtonText: __('Delete Permanently', 'formipay'),
cancelButtonText: __('Cancel', 'formipay'),
});
if (result.isConfirmed) {
await fetch(`${ajaxUrl}?action=formipay-delete-access-item`, {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
id,
_wpnonce: nonce,
}),
});
window.location.reload();
}
};
const handleDuplicate = async (id) => {
const result = await Swal.fire({
icon: 'info',
html: __('Do you want to duplicate this item?', 'formipay'),
showCancelButton: true,
confirmButtonText: __('Confirm', 'formipay'),
cancelButtonText: __('Cancel', 'formipay'),
});
if (result.isConfirmed) {
await fetch(`${ajaxUrl}?action=formipay-duplicate-access-item`, {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
id,
_wpnonce: nonce,
}),
});
window.location.reload();
}
};
const columns = [
{
key: 'ID',
@@ -16,6 +68,38 @@ export default function AccessPage() {
{
key: 'title',
label: __('Title', 'formipay'),
render: (row) => (
<>
<a href={`${window.formipayAdmin?.siteUrl || ''}/wp-admin/post.php?post=${row.ID}&action=edit`}>
<strong>{row.title || row.post_title || __('Untitled', 'formipay')}</strong>
</a>
<span className="row-actions">
<a href={`${window.formipayAdmin?.siteUrl || ''}/wp-admin/post.php?post=${row.ID}&action=edit`}>{__('edit', 'formipay')}</a>
{' | '}
<button
className="button-link delete"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleDelete(row.ID);
}}
>
{__('delete', 'formipay')}
</button>
{' | '}
<button
className="button-link duplicate"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleDuplicate(row.ID);
}}
>
{__('duplicate', 'formipay')}
</button>
</span>
</>
)
},
{
key: 'product_name',
@@ -50,10 +134,6 @@ export default function AccessPage() {
return (
<div className="formipay-page-access">
<div className="formipay-page-header">
<h1>{ __('Access Items', 'formipay') }</h1>
</div>
<DataTable
columns={columns}
ajaxUrl={window.formipayAdmin?.ajaxUrl || '/wp-admin/admin-ajax.php'}