feat: add React metabox island for coupon editor

- Create CouponMetabox React component with WPCFTO design system
- Add MetaboxLayout with vertical tabs (Rules, Restrictions)
- Implement Rules tab: active toggle, type radio, amount fields, multi-currency support
- Implement Restrictions tab: usage limit, date limit, autocomplete for forms/products/customers
- Add metabox registration in Coupon.php for formipay-coupon post type
- Update ReactAdmin to load assets on post.php edit screens
- Add autocomplete AJAX handler for relation fields
- Disable old WPCFTO metabox in favor of React island
This commit is contained in:
dwindown
2026-04-19 07:08:54 +07:00
parent bde43d8c66
commit d1de0015be
9 changed files with 854 additions and 77 deletions

View File

@@ -19,8 +19,11 @@ class ReactAdmin {
$screen = get_current_screen();
// Only load React assets on Formipay admin pages
if ( strpos( $screen->id, 'formipay' ) === false ) {
// Load React assets on Formipay admin pages OR on post edit screens for our CPTs
$is_formipay_admin = strpos($screen->id, 'formipay') !== false;
$is_formipay_cpt = $screen->base === 'post' && in_array($screen->post_type, ['formipay-coupon', 'formipay-product', 'formipay-form']);
if (!$is_formipay_admin && !$is_formipay_cpt) {
return;
}
@@ -28,7 +31,7 @@ class ReactAdmin {
$build_dir = FORMIPAY_PATH . 'build';
$build_url = FORMIPAY_URL . 'build';
if ( ! file_exists( $build_dir . '/admin.asset.php' ) ) {
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
}
@@ -61,19 +64,19 @@ class ReactAdmin {
);
// 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' ),
$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 );
wp_localize_script('formipay-admin', 'formipayAdmin', $data);
}

View File

@@ -37,6 +37,11 @@ class Coupon {
add_action( 'wp_ajax_formipay-duplicate-coupon', [$this, 'formipay_duplicate_coupon'] );
add_action( 'wp_ajax_formipay-get-coupon', [$this, 'formipay_get_coupon'] );
add_action( 'wp_ajax_formipay-save-coupon', [$this, 'formipay_save_coupon'] );
add_action( 'wp_ajax_formipay-autocomplete-search', [$this, 'formipay_autocomplete_search'] );
// React Metabox
add_action( 'add_meta_boxes', [$this, 'add_react_metabox'] );
add_action( 'admin_footer-post.php', [$this, 'render_react_metabox_template'] );
// Order
add_filter( 'formipay/order/order-details', [$this, 'order_details'], 99, 3 );
@@ -101,79 +106,29 @@ class Coupon {
}
public function enqueue_admin() {
// Assets now handled by ReactAdmin class
return;
if($current_screen->id == 'formipay_page_formipay-coupons') {
wp_enqueue_style( 'page-coupons', FORMIPAY_URL . 'admin/assets/css/page-coupons.css', [], FORMIPAY_VERSION, 'all' );
wp_enqueue_script( 'page-coupons', FORMIPAY_URL . 'admin/assets/js/page-coupons.js', ['jquery', 'gridjs'], FORMIPAY_VERSION, true );
wp_localize_script( 'page-coupons', 'formipay_coupons_page', [
'ajax_url' => admin_url('admin-ajax.php'),
'site_url' => site_url(),
'columns' => [
'id' => esc_html__( 'ID', 'formipay' ),
'code' => esc_html__( 'Coupon Code', 'formipay' ),
'usages' => esc_html__( 'Usages', 'formipay' ),
'date_limit' => esc_html__( 'Date Limit', 'formipay' ),
'status' => esc_html__( 'Status', 'formipay' ),
'type' => esc_html__( 'Type', 'formipay' ),
'amount' => esc_html__( 'Amount', 'formipay' )
],
'filter_form' => [
'products' => [
'placeholder' => esc_html__( 'Filter by Product', 'formipay' ),
'noresult_text' => esc_html__( 'No results found', 'formipay' )
]
],
'modal' => [
'add' => [
'title' => esc_html__( 'Your New Coupon Code', 'formipay' ),
'validation' => esc_html__( 'Coupon code is still empty. Please input the code before continue', 'formipay' ),
'cancelButton' => esc_html__( 'Cancel', 'formipay' ),
'confirmButton' => esc_html__( 'Create New Coupon', 'formipay' )
],
'delete' => [
'question' => esc_html__( 'Do you want to delete the coupon?', '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 coupon(s)?', 'formipay' ),
'cancelButton' => esc_html__( 'Cancel', 'formipay' ),
'confirmButton' => esc_html__( 'Confirm', 'formipay' )
],
'duplicate' => [
'question' => esc_html__( 'Do you want to duplicate the coupon?', 'formipay' ),
'cancelButton' => esc_html__( 'Cancel', 'formipay' ),
'confirmButton' => esc_html__( 'Confirm', 'formipay' )
],
],
'nonce' => wp_create_nonce('formipay-admin-coupon-page')
] );
}
$screen = get_current_screen();
if ( $screen->post_type === 'formipay-coupon' && $screen->base === 'post' ) {
wp_enqueue_style( 'sweetalert2', FORMIPAY_URL . 'vendor/SweetAlert2/sweetalert2.min.css', [], '11.14.4', 'all');
wp_enqueue_script( 'sweetalert2', FORMIPAY_URL . 'vendor/SweetAlert2/sweetalert2.min.js', ['jquery'], '11.14.4', true);
wp_localize_script( 'sweetalert2', 'formipay_admin', [
'ajax_url' => admin_url('admin-ajax.php'),
'site_url' => site_url(),
] );
// Enqueue SweetAlert2 for coupon post edit screen
if ($screen && $screen->post_type === 'formipay-coupon' && $screen->base === 'post') {
wp_enqueue_style('sweetalert2', FORMIPAY_URL . 'vendor/SweetAlert2/sweetalert2.min.css', [], '11.14.4', 'all');
wp_enqueue_script('sweetalert2', FORMIPAY_URL . 'vendor/SweetAlert2/sweetalert2.min.js', ['jquery'], '11.14.4', true);
// Localize admin data
wp_localize_script('formipay-admin', 'formipayAdmin', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'siteUrl' => site_url(),
'nonce' => wp_create_nonce('formipay-admin'),
]);
}
}
public function cpt_post_fields_box($boxes) {
$boxes['formipay_coupon_settings'] = array(
'post_type' => array('formipay-coupon'),
'label' => __('Details', 'formipay'),
);
// Disabled - using React metabox instead
// $boxes['formipay_coupon_settings'] = array(
// 'post_type' => array('formipay-coupon'),
// 'label' => __('Details', 'formipay'),
// );
return $boxes;
}
@@ -1046,4 +1001,101 @@ class Coupon {
] );
}
/**
* Add React metabox for coupon editor
*/
public function add_react_metabox() {
add_meta_box(
'formipay_coupon_reactor_metabox',
__( 'Coupon Settings', 'formipay' ),
[$this, 'render_react_metabox'],
'formipay-coupon',
'normal',
'high'
);
}
/**
* Render React metabox container
*/
public function render_react_metabox($post) {
?>
<div
data-formipay-metabox="coupon"
data-post-id="<?php echo esc_attr($post->ID); ?>"
class="formipay-react-metabox-container"
>
<div class="formipay-loading">
<div class="formipay-spinner"></div>
</div>
</div>
<?php
}
/**
* Render React metabox template (hidden)
*/
public function render_react_metabox_template() {
global $post;
if (!$post || $post->post_type !== 'formipay-coupon') {
return;
}
// Get global currencies
$global_currencies = get_global_currency_array();
?>
<script type="text/javascript">
window.formipayGlobalCurrencies = <?php echo wp_json_encode($global_currencies); ?>;
window.formipayGetFlag = function(currencyRaw) {
<?php
foreach ($global_currencies as $currency) {
echo 'if (currencyRaw === "' . esc_js($currency['currency']) . '") return "' . esc_url(formipay_get_flag_by_currency($currency['currency'])) . '";';
}
?>
return '';
};
</script>
<?php
}
/**
* Autocomplete search for relation fields
*/
public function formipay_autocomplete_search() {
check_ajax_referer( 'formipay-admin', '_wpnonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ] );
}
$post_type = isset($_REQUEST['post_type']) ? sanitize_text_field(wp_unslash($_REQUEST['post_type'])) : '';
$search = isset($_REQUEST['search']) ? sanitize_text_field(wp_unslash($_REQUEST['search'])) : '';
if (empty($post_type) || strlen($search) < 2) {
wp_send_json_error( [ 'message' => 'Invalid request' ] );
}
$query = get_posts([
'post_type' => $post_type,
's' => $search,
'posts_per_page' => 20,
'post_status' => ['publish', 'draft'],
]);
$results = [];
if (!empty($query)) {
foreach ($query as $post) {
$results[] = [
'value' => $post->ID,
'label' => $post->post_title,
];
}
}
wp_send_json_success($results);
}
}