Skip to content

feat: Dokan widthraw admin list page.#2882

Merged
MdAsifHossainNadim merged 42 commits intodevelopfrom
feat/dokan-widthraw-admin-list
Oct 22, 2025
Merged

feat: Dokan widthraw admin list page.#2882
MdAsifHossainNadim merged 42 commits intodevelopfrom
feat/dokan-widthraw-admin-list

Conversation

@MdAsifHossainNadim
Copy link
Copy Markdown
Contributor

@MdAsifHossainNadim MdAsifHossainNadim commented Aug 29, 2025

All Submissions:

  • My code follow the WordPress' coding standards
  • My code satisfies feature requirements
  • My code is tested
  • My code passes the PHPCS tests
  • My code has proper inline documentation
  • I've included related pull request(s) (optional)
  • I've included developer documentation (optional)
  • I've added proper labels to this pull request

Changes proposed in this Pull Request:

Related Pull Request(s)

  • Full PR Link

Closes

How to test the changes in this Pull Request:

  • Steps or issue link

Changelog entry

Title

Detailed Description of the pull request. What was previous behaviour
and what will be changed in this PR.

Before Changes

Describe the issue before changes with screenshots(s).

After Changes

Describe the issue after changes with screenshot(s).

Feature Video (optional)

Link of detailed video if this PR is for a feature.

PR Self Review Checklist:

  • Code is not following code style guidelines
  • Bad naming: make sure you would understand your code if you read it a few months from now.
  • KISS: Keep it simple, Sweetie (not stupid!).
  • DRY: Don't Repeat Yourself.
  • Code that is not readable: too many nested 'if's are a bad sign.
  • Performance issues
  • Complicated constructions that need refactoring or comments: code should almost always be self-explanatory.
  • Grammar errors.

FOR PR REVIEWER ONLY:

As a reviewer, your feedback should be focused on the idea, not the person. Seek to understand, be respectful, and focus on constructive dialog.

As a contributor, your responsibility is to learn from suggestions and iterate your pull request should it be needed based on feedback. Seek to collaborate and produce the best possible contribution to the greater whole.

  • Correct — Does the change do what it’s supposed to? ie: code 100% fulfilling the requirements?
  • Secure — Would a nefarious party find some way to exploit this change? ie: everything is sanitized/escaped appropriately for any SQL or XSS injection possibilities?
  • Readable — Will your future self be able to understand this change months down the road?
  • Elegant — Does the change fit aesthetically within the overall style and architecture?

Summary by CodeRabbit

  • New Features

    • Admin Withdraw and Vendors pages with dashboard routes, list views, filters, tabs, item/bulk actions, modals, export and PayPal mass-export flows; new export endpoints for reporting.
    • New admin UI components: tabs, filter selector, data-view table, and empty-state component.
  • Bug Fixes

    • Admin asset dependency corrected.
    • Currency formatting now falls back to dashboard-provided currency data.
  • Chores

    • TypeScript config, ambient type declarations, exports, styles, packaging and dependency updates.
  • Documentation

    • Guides for export controller usage and admin dataview table.

shohag121 and others added 12 commits August 27, 2025 18:29
…package-using-webpack' into feat/dokan-widthraw-admin-list
…package-using-webpack' into feat/dokan-widthraw-admin-list
…ing, and eligibility conditions

- Improved handling for `dokanAdminDashboard.currency` fallback in `Accounting.ts`.
- Implemented `processDetails` for detailed render of withdrawal methods.
- Replaced custom buttons with `DokanButton` for better consistency.
- Added eligibility conditions for bulk actions (approve, cancel, delete).
- Introduced `formatPrice` utility for consistent price formatting.
- Updated table styling with new borders and rounded corners in SCSS.
- Corrected TSPath alias in `tsconfig.json`.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Aug 29, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds Withdraw and Vendors admin pages and routes, new ExportController and WithdrawExportController REST endpoints, frontend Withdraw and Vendors React pages, dataview UI components/styles, TypeScript configs/types, accounting currency fallbacks, and registration of new dashboard services and assets.

Changes

Cohort / File(s) Summary
PHP: Admin dashboard core & pages
includes/Admin/Dashboard/Dashboard.php, includes/DependencyManagement/Providers/AdminDashboardServiceProvider.php, includes/Admin/Dashboard/Pages/Withdraw.php, includes/Admin/Dashboard/Pages/Vendors.php, includes/Admin/Dashboard/LegacySwitcher.php
Dashboard payload/asset localization changed (adds urls, currency, uses wp_add_inline_script); dokan-react-components added to dashboard deps; new Withdraw and Vendors page classes added and registered; legacy slug special-case removed.
PHP: REST export & manager
includes/REST/ExportController.php, includes/REST/WithdrawExportController.php, includes/REST/Manager.php, includes/REST/WithdrawControllerV2.php
New Dokan ExportController (namespace dokan/v1) and WithdrawExportController implementing export columns, filtering, pagination and permissions; Manager registers export controllers and rewrites export endpoints; WithdrawControllerV2 now calls parent register_routes().
Frontend: Admin routes & pages
src/admin/dashboard/components/Dashboard.tsx, src/admin/dashboard/pages/withdraw/index.tsx, src/admin/dashboard/pages/vendors.tsx, src/admin/panel-switcher/PanelSwitch.tsx
Adds /withdraw and /vendors admin routes; introduces WithdrawPage and VendorsPage React implementations with dataview-based UIs, filters, tabs, actions, modals, bulk operations and REST interactions; panel switcher extended to recognize new keys.
UI components & dataviews
src/components/AdminFilter.tsx, src/components/AdminTab.tsx, src/components/dataviews/AdminDataViewTable.tsx, src/components/dataviews/ListEmpty.tsx, src/components/index.tsx, src/components/dataviews/style.scss, docs/frontend/admin-dataview-table.md
Adds AdminFilter, AdminTab, AdminDataViewTable, ListEmpty components; exports them from components index; styles for dataviews/datatable added; docs for AdminDataViewTable included.
Styling
src/admin/dashboard/style.scss
Adds comprehensive SCSS for Dokan admin dashboard dataviews, table visuals, actions, no-results and loading states, and portal CSS variable adjustments.
Utilities & typescript infra
src/utilities/Accounting.ts, tsconfig.json, types/externals.d.ts, types/globals.d.ts, src/definitions/dokan-vendor.ts
Accounting utilities gain fallback to window.dokanAdminDashboard currency data; project tsconfig added; ambient externals and global types declared; vendor data TypeScript interfaces added.
Docs
docs/export-controller-usage.md
New guide describing Dokan export controllers, registration, usage, and troubleshooting with examples.
Build / deps
package.json
Dependency updates: WordPress packages, UI libs, Tailwind utilities, and related package adjustments.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Admin
  participant Router as Admin Router
  participant UI as WithdrawPage (React)
  participant WithdrawAPI as Withdraw REST (dokan/v2)
  participant ExportAPI as Export Controller (dokan/v1/reports/withdraws)

  Admin->>Router: Navigate /withdraw
  Router->>UI: Mount WithdrawPage
  UI->>WithdrawAPI: GET /dokan/v2/withdraws?per_page=&page=&status=
  WithdrawAPI-->>UI: 200 OK + X-WP-Total, X-WP-TotalPages
  UI->>Admin: Render tabs, filters, table

  rect rgba(200,230,255,0.18)
    Admin->>UI: Click Export
    UI->>ExportAPI: POST /dokan/v1/reports/withdraws/export
    ExportAPI-->>UI: 202 Accepted + { export_id }
    loop Polling
      UI->>ExportAPI: GET /dokan/v1/reports/withdraws/export/{export_id}/status
      ExportAPI-->>UI: 200 OK (pending|completed)
    end
    UI->>ExportAPI: GET /dokan/v1/reports/withdraws/export/{export_id}/download
    ExportAPI-->>UI: 200 OK (file download)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • mrabbani
  • Aunshon
  • shohag121

Poem

I hop through tabs of green and light,
Counting withdraws by day and night,
Filters pop, modals gleam so bright,
Exports polled until the file takes flight,
A rabbit dances — dashboard done just right 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description follows the provided template structure and includes several completed checklist items (WordPress coding standards, feature requirements, testing, PHPCS tests). However, most critical sections remain incomplete or contain only placeholder text. The "Changes proposed in this Pull Request" section is entirely empty, "How to test the changes" contains only a placeholder, "Changelog entry" shows only template headers without actual content, and sections for "Before Changes," "After Changes," and "Feature Video" are unfilled. The only substantive information provided is the linked issue reference (Closes #4666), which links to the enhancement issue but the description itself lacks concrete details about what was changed and how to test it. The PR description must be completed with: a detailed explanation of the changes proposed in the "Changes proposed" section; concrete testing steps in "How to test the changes"; a filled-out "Changelog entry" with title and detailed description; and optional but recommended "Before Changes" and "After Changes" sections with descriptions or screenshots to demonstrate the improvement. This information is essential for reviewers and future maintainers to understand the scope and impact of the changes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "feat: Dokan widthraw admin list page." (note: contains a typo "widthraw" instead of "withdraw") is clearly related to the main change in the changeset. The title directly summarizes the primary objective—adding a new admin interface for displaying a withdraw/withdrawal list page. The title is specific and concise enough that a reviewer scanning the history would understand the core change, even though it contains a minor spelling error that could be corrected.
Linked Issues Check ✅ Passed The linked issue #4666 specifies three main coding-related objectives: (1) update the Admin Withdraw Request page UI to a new layout/design following Figma design specifications, (2) present Withdraw Requests grouped or filtered by status (Pending, Approved, Cancelled), and (3) ensure UI conformity with the provided Figma design. The changeset comprehensively addresses these objectives: a new Withdraw.php admin dashboard page class is added; a full-featured WithdrawPage React component is implemented with multi-tab status filtering, vendor/payment-method filters, sorting, and pagination; comprehensive styling is added via admin dashboard CSS; supporting components (AdminDataViewTable, AdminFilter, AdminTab) are implemented for table management and filtering; and export functionality is integrated. The implementation demonstrates all required features and integrates properly into the admin dashboard infrastructure.
Out of Scope Changes Check ✅ Passed Reviewing the changeset against the linked issue scope, all modifications appear directly related to implementing the withdraw admin list page feature. Core changes include the Withdraw page class, WithdrawPage React component, admin dashboard integration, styling, and supporting UI component infrastructure (AdminDataViewTable, AdminFilter, AdminTab, ListEmpty). Infrastructure updates such as TypeScript configuration, type definitions, dependency additions, and the Accounting.ts currency fallback changes all support the new React-based implementation. The export functionality (ExportController, WithdrawExportController) appears to be supporting infrastructure enabling the withdraw list export feature referenced in the changeset. No changes appear unrelated to the stated objective of implementing a new admin withdraw list page with filtering, status grouping, and export capabilities.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bef76f0 and a48d46b.

📒 Files selected for processing (8)
  • includes/Admin/Dashboard/Dashboard.php (4 hunks)
  • includes/Admin/Dashboard/LegacySwitcher.php (1 hunks)
  • includes/Admin/Dashboard/Pages/Vendors.php (1 hunks)
  • includes/DependencyManagement/Providers/AdminDashboardServiceProvider.php (2 hunks)
  • src/admin/dashboard/components/Dashboard.tsx (2 hunks)
  • src/admin/dashboard/pages/vendors.tsx (1 hunks)
  • src/admin/panel-switcher/PanelSwitch.tsx (1 hunks)
  • src/definitions/dokan-vendor.ts (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (11)
src/admin/dashboard/style.scss (1)

18-87: Consider consolidating duplicate selectors and improving maintainability.

The styling is comprehensive, but there are several areas for improvement:

  1. Duplicate selectors: Lines 18-65 and 67-77 both target #dokan-admin-dashboard separately
  2. Redundant no-results styling: Lines 56-64 style .dataviews-no-results twice with different properties
  3. Missing organization: Related styles are scattered across multiple blocks

Apply this diff to consolidate and organize the styles:

-#dokan-admin-dashboard {
-    .dokan-dashboard-datatable {
-        .dataviews-wrapper {
-            .dataviews__view-actions {
-                display: none;
-            }
-        }
-    }
-
-    .dataviews-view-table__actions-column {
-        .components-button {
-            &:hover {
-                background: transparent;
-            }
-
-            &:focus {
-                box-shadow: none;
-            }
-        }
-    }
-
-    .dataviews-view-table__row {
-        td {
-            vertical-align: middle;
-        }
-    }
-
-    table.dataviews-view-table {
-        border: 1px solid #E9E9E9;
-        th {
-            background-color: #f9fafb;
-            border-bottom: 1px solid #E9E9E9;
-            border-top-left-radius: 3px;
-            border-top-right-radius: 3px;
-        }
-    }
-
-    .dataviews-wrapper {
-        .dataviews-no-results {
-            color: #6b7280;
-        }
-
-        .dataviews-no-results {
-            padding-top: 1rem;
-            padding-bottom: 1rem;
-        }
-    }
-}
-
-#dokan-admin-dashboard {
-    .dataviews-view-table {
-        .dataviews-view-table__actions-column {
-            .components-button {
-                &:first-child {
-                    margin-right: 16px;
-                }
-            }
-        }
-    }
-}
-
-#dokan-admin-dashboard {
-    .dataviews-wrapper {
-        background-color: #ffffff;
-        .dataviews-loading {
-            padding-top: 1rem;
-            padding-bottom: 1rem;
-        }
-    }
-}
+#dokan-admin-dashboard {
+    // DataTable wrapper
+    .dokan-dashboard-datatable {
+        .dataviews-wrapper {
+            .dataviews__view-actions {
+                display: none;
+            }
+        }
+    }
+
+    // DataViews wrapper
+    .dataviews-wrapper {
+        background-color: #ffffff;
+        
+        .dataviews-no-results {
+            color: #6b7280;
+            padding: 1rem 0;
+        }
+
+        .dataviews-loading {
+            padding: 1rem 0;
+        }
+    }
+
+    // Table styling
+    table.dataviews-view-table {
+        border: 1px solid #E9E9E9;
+        
+        th {
+            background-color: #f9fafb;
+            border-bottom: 1px solid #E9E9E9;
+            border-top-left-radius: 3px;
+            border-top-right-radius: 3px;
+        }
+    }
+
+    .dataviews-view-table__row {
+        td {
+            vertical-align: middle;
+        }
+    }
+
+    // Actions column styling
+    .dataviews-view-table__actions-column {
+        .components-button {
+            &:hover {
+                background: transparent;
+            }
+
+            &:focus {
+                box-shadow: none;
+            }
+
+            &:first-child {
+                margin-right: 16px;
+            }
+        }
+    }
+}
tsconfig.json (1)

36-36: Consider enabling noUncheckedIndexedAccess for better type safety.

Setting noUncheckedIndexedAccess to false disables an important TypeScript check that helps catch potential runtime errors when accessing array/object properties. Consider enabling this for better type safety.

-        "noUncheckedIndexedAccess": false,
+        "noUncheckedIndexedAccess": true,
src/utilities/Accounting.ts (2)

25-27: Consider extracting currency data resolution logic.

The fallback pattern is repeated for each currency property. Consider extracting this into a helper function to reduce code duplication and improve maintainability.

const getCurrencyData = () => {
    return window?.dokanFrontend?.currency || window?.dokanAdminDashboard?.currency;
};

export const formatPrice = (
    price: number | string = '',
    currencySymbol = '',
    precision = null,
    thousand = '',
    decimal = '',
    format = ''
): string | number => {
    if (!window.accounting) {
        console.warn('Woocommerce Accounting Library Not Found');
        return price;
    }

    const currency = getCurrencyData();
    if (!currency) {
        console.warn('Dokan Currency Data Not Found');
        return price;
    }

    return window.accounting.formatMoney(
        price,
        currencySymbol || currency.symbol,
        precision || currency.precision,
        thousand || currency.thousand,
        decimal || currency.decimal,
        format || currency.format
    );
};

Also applies to: 31-33, 37-39, 43-45, 49-51


84-91: Fix formatting and consider extracting currency data logic.

The parameters are inconsistently formatted, and the same currency data fallback pattern is repeated.

     return window.accounting.formatNumber(
         value,
-        // @ts-ignore
-        window?.dokanFrontend?.currency.precision ||
-            window?.dokanAdminDashboard?.currency.precision,
-        window?.dokanFrontend?.currency.thousand ||
-            window?.dokanAdminDashboard?.currency.thousand,
-        window?.dokanFrontend?.currency.decimal ||
-            window?.dokanAdminDashboard?.currency.decimal
+        currency.precision,
+        currency.thousand,
+        currency.decimal
     );
src/admin/dashboard/components/Dashboard.tsx (1)

7-7: Consider lazy-loading WithdrawPage to keep the admin bundle lean

Route-level code-splitting can reduce initial load.

-import WithdrawPage from '../pages/withdraw';
+import { lazy, Suspense } from 'react';
+const WithdrawPage = lazy(() => import('../pages/withdraw'));

Wrap render where used:

-            element: <WithdrawPage />,
+            element: (
+                <Suspense fallback={null}>
+                    <WithdrawPage />
+                </Suspense>
+            ),
includes/Admin/Dashboard/Pages/Withdraw.php (1)

21-29: Capability handling may be misleading

Returning 'capability' => $capability here doesn’t affect enforcement because Dashboard::register_menu() uses its own $capability when building submenu links. If Withdraw needs stricter/different caps, wire it through register_menu() (e.g., honor $menu_args['capability']).

Would you like a patch to make register_menu() respect per-page capabilities?

src/admin/dashboard/pages/withdraw/index.tsx (5)

560-565: Remove unused variable

updateData is set but never used.

-        try {
-            const updateData = {};
-            if ( action === 'approved' ) {
-                updateData.status = action;
-            } else if ( action === 'cancelled' ) {
-                updateData.status = action;
-            }
+        try {

504-512: Status filter: isNone operator unsupported in query building

UI exposes isNone, but fetch only handles isAny. Either implement exclusion logic or remove isNone to avoid a broken UX.

Example (if backend supports exclusion):

                 view.filters.forEach( ( filter ) => {
                     if (
                         filter.field === 'status' &&
                         filter.operator === 'isAny'
                     ) {
                         queryArgs.status = filter.value?.join( ',' );
+                    } else if (
+                        filter.field === 'status' &&
+                        filter.operator === 'isNone'
+                    ) {
+                        queryArgs.status__not_in = filter.value?.join( ',' );
                     }
                 } );

Or restrict operators:

-            filterBy: {
-                operators: [ 'isAny', 'isNone' ],
-            },
+            filterBy: { operators: [ 'isAny' ] },

Also applies to: 145-146


389-391: Unimplemented PayPal bulk action

Callback is empty; clicking will do nothing. Hide until implemented or wire to export/masspay endpoint.

Would you like me to add a CSV download for PayPal IDs (selected rows) behind a capability check, or remove the action for now?


112-118: Improve avatar img accessibility and performance

Provide a meaningful alt fallback and lazy-load the image.

-                        <img
+                        <img
                             src={ item.user.gravatar }
-                            alt={ item.user?.store_name || '' }
+                            alt={ item.user?.store_name || __( 'Vendor avatar', 'dokan-lite' ) }
+                            loading="lazy"
                             className="w-8 h-8 rounded-sm"
                         />

1008-1013: Modal primary action label ‘Update’ is misleading

The View modal does not update anything. Either implement update or change the confirm label to “Close” and hide the cancel button.

-                        onConfirm={ closeModal }
-                        dialogTitle={ <></> }
-                        confirmButtonText={ __( 'Update', 'dokan-lite' ) }
-                        hideCancelButton={ false }
-                        cancelButtonText={ __( 'Cancel', 'dokan-lite' ) }
+                        onConfirm={ closeModal }
+                        dialogTitle={ <></> }
+                        confirmButtonText={ __( 'Close', 'dokan-lite' ) }
+                        hideCancelButton={ true }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d1f1f01 and d5aea5a.

📒 Files selected for processing (11)
  • includes/Admin/Dashboard/Dashboard.php (2 hunks)
  • includes/Admin/Dashboard/Pages/Withdraw.php (1 hunks)
  • includes/DependencyManagement/Providers/AdminDashboardServiceProvider.php (2 hunks)
  • includes/REST/WithdrawControllerV2.php (1 hunks)
  • src/admin/dashboard/components/Dashboard.tsx (2 hunks)
  • src/admin/dashboard/pages/withdraw/index.tsx (1 hunks)
  • src/admin/dashboard/style.scss (1 hunks)
  • src/utilities/Accounting.ts (3 hunks)
  • tsconfig.json (1 hunks)
  • types/externals.d.ts (1 hunks)
  • types/globals.d.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
includes/REST/WithdrawControllerV2.php (1)
includes/REST/WithdrawController.php (1)
  • register_routes (42-176)
includes/Admin/Dashboard/Pages/Withdraw.php (3)
includes/Admin/Dashboard/Dashboard.php (4)
  • Dashboard (13-409)
  • settings (131-233)
  • scripts (242-249)
  • styles (258-265)
includes/Admin/Dashboard/Pages/AbstractPage.php (1)
  • AbstractPage (8-59)
includes/DependencyManagement/Providers/AdminDashboardServiceProvider.php (1)
  • register (29-36)
includes/DependencyManagement/Providers/AdminDashboardServiceProvider.php (2)
includes/Admin/Dashboard/Dashboard.php (1)
  • Dashboard (13-409)
includes/Admin/Dashboard/Pages/Withdraw.php (1)
  • Withdraw (5-66)
src/admin/dashboard/pages/withdraw/index.tsx (3)
src/utilities/Accounting.ts (1)
  • formatPrice (3-62)
types/externals.d.ts (5)
  • formatPrice (79-79)
  • DokanTab (61-61)
  • Filter (60-60)
  • DataViews (59-59)
  • DokanModal (65-65)
src/utilities/Truncate.ts (1)
  • truncate (1-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e tests (2, 3)
  • GitHub Check: e2e tests (1, 3)
  • GitHub Check: api tests (1, 1)
  • GitHub Check: e2e tests (3, 3)
🔇 Additional comments (10)
includes/REST/WithdrawControllerV2.php (1)

27-27: LGTM! Parent route registration ensures complete API availability.

Calling parent::register_routes() correctly ensures that all base class routes (from WithdrawController) are available alongside the V2-specific routes, providing a complete withdrawal API for admin functionality.

includes/Admin/Dashboard/Dashboard.php (2)

302-304: Good defensive programming for dependency management.

The safeguard to automatically include 'dokan-react-components' ensures the admin dashboard script loads correctly even if the dependency is missing from the asset file, preventing runtime errors in the React components.


317-317: Correct dependency declaration for CSS loading order.

Adding 'dokan-react-components' as a dependency for the admin dashboard stylesheet ensures proper loading sequence and prevents styling conflicts.

tsconfig.json (1)

14-20: Aliases in tsconfig.json match webpack.config.js. TSConfig paths align with the alias section in webpack.config.js; no further action required.

src/utilities/Accounting.ts (1)

16-22: LGTM! Robust fallback mechanism for currency data.

The dual-source approach correctly handles both frontend and admin dashboard contexts, ensuring consistent currency formatting across different parts of the application.

src/admin/dashboard/components/Dashboard.tsx (1)

29-33: Route registration looks consistent with PHP page id/route

id: 'withdraw' and path: '/withdraw' align with the PHP page. LGTM.

Manually hit wp-admin admin.php?page=dokan#/withdraw to confirm the page loads and nav highlights correctly.

includes/DependencyManagement/Providers/AdminDashboardServiceProvider.php (1)

9-24: Service registration is correct

Importing and adding Withdraw::class ensures hooks are registered and the page is enlisted. LGTM.

includes/Admin/Dashboard/Pages/Withdraw.php (1)

63-65: Confirm no page-specific assets/settings are required

register() is a no-op and settings() returns an empty array. Verify the React page does not expect dokanAdminDashboardSettings.withdraw values or extra assets.

If needed, we can add typed settings now and localize via Dashboard::settings().

src/admin/dashboard/pages/withdraw/index.tsx (2)

533-548: Verify header names for counts (‘Completed’ vs ‘Approved’)

You read X-Status-Completed and map to approved. Confirm backend headers; if it returns X-Status-Approved in some versions, consider reading both to harden.

Proposed hardening:

-            const completedCount = parseInt(
-                response.headers.get( 'X-Status-Completed' ) || 0
-            );
+            const completedCount = parseInt(
+                response.headers.get( 'X-Status-Approved' ) ??
+                response.headers.get( 'X-Status-Completed' ) ??
+                0
+            );

1212-1219: Use React className prop instead of class
React ignores the class attribute in JSX; replace it with className on <SimpleRadio> (and verify if per-option props should be optionClassName/selectedOptionClassName). (geeksforgeeks.org)

- <SimpleRadio
-     name="action"
-     options={ options }
-     class={ 'px-0' }
-     optionClass={ 'px-0' }
-     selectedOptionClass={ 'px-0' }
- />
+ <SimpleRadio
+     name="action"
+     options={ options }
+     className="px-0"
+     optionClass="px-0"
+     selectedOptionClass="px-0"
+ />

Confirm the exact prop names in @getdokan/dokan-ui; if they’re optionClassName/selectedOptionClassName, update accordingly.

Comment on lines +48 to +49
const price = ( amount ) => <RawHTML>{ formatPrice( amount ) }</RawHTML>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Stop using RawHTML for prices to prevent XSS; render as text

formatPrice returns plain text; RawHTML is unnecessary and risky.

-const price = ( amount ) => <RawHTML>{ formatPrice( amount ) }</RawHTML>;
+const price = ( amount ) => <span>{ formatPrice( amount ) }</span>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const price = ( amount ) => <RawHTML>{ formatPrice( amount ) }</RawHTML>;
const price = ( amount ) => <span>{ formatPrice( amount ) }</span>;
🤖 Prompt for AI Agents
In src/admin/dashboard/pages/withdraw/index.tsx around lines 48-49, the price
renderer uses RawHTML which is unnecessary and poses XSS risk; replace RawHTML
with plain text rendering. Change the price function to return formatted text
(e.g., a string or a React element like a span) containing formatPrice(amount)
without dangerously inserting HTML, ensuring the value is rendered as safe text.

Comment on lines +147 to +167
render: ( { item } ) => {
const statusColors = {
pending: 'bg-yellow-100 text-yellow-800',
approved: 'bg-green-100 text-green-800',
};
return (
<span
className={ `inline-flex items-center px-3.5 py-1.5 rounded-md text-xs font-medium ${
statusColors[ item.status ] ||
'bg-gray-100 text-gray-800'
}` }
>
{
WITHDRAW_STATUSES.find(
( status ) => status.value === item.status
)?.label
}
</span>
);
},
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Normalize backend status 'completed' to UI 'approved'

Prevents blank labels/colors when API returns completed while UI expects approved.

-            render: ( { item } ) => {
-                const statusColors = {
-                    pending: 'bg-yellow-100 text-yellow-800',
-                    approved: 'bg-green-100 text-green-800',
-                };
+            render: ( { item } ) => {
+                const normalized = item.status === 'completed' ? 'approved' : item.status;
+                const statusColors = {
+                    pending: 'bg-yellow-100 text-yellow-800',
+                    approved: 'bg-green-100 text-green-800',
+                    cancelled: 'bg-gray-100 text-gray-800',
+                };
                 return (
                     <span
                         className={ `inline-flex items-center px-3.5 py-1.5 rounded-md text-xs font-medium ${
-                            statusColors[ item.status ] ||
+                            statusColors[ normalized ] ||
                             'bg-gray-100 text-gray-800'
                         }` }
                     >
-                        {
-                            WITHDRAW_STATUSES.find(
-                                ( status ) => status.value === item.status
-                            )?.label
-                        }
+                        { WITHDRAW_STATUSES.find( (s) => s.value === normalized )?.label || normalized }
                     </span>
                 );
             },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
render: ( { item } ) => {
const statusColors = {
pending: 'bg-yellow-100 text-yellow-800',
approved: 'bg-green-100 text-green-800',
};
return (
<span
className={ `inline-flex items-center px-3.5 py-1.5 rounded-md text-xs font-medium ${
statusColors[ item.status ] ||
'bg-gray-100 text-gray-800'
}` }
>
{
WITHDRAW_STATUSES.find(
( status ) => status.value === item.status
)?.label
}
</span>
);
},
},
render: ({ item }) => {
// Normalize backend 'completed' to UI 'approved'
const normalized = item.status === 'completed' ? 'approved' : item.status;
const statusColors = {
pending: 'bg-yellow-100 text-yellow-800',
approved: 'bg-green-100 text-green-800',
cancelled:'bg-gray-100 text-gray-800',
};
return (
<span
className={`inline-flex items-center px-3.5 py-1.5 rounded-md text-xs font-medium ${
statusColors[normalized] ||
'bg-gray-100 text-gray-800'
}`}
>
{WITHDRAW_STATUSES.find(s => s.value === normalized)?.label || normalized}
</span>
);
},
🤖 Prompt for AI Agents
In src/admin/dashboard/pages/withdraw/index.tsx around lines 147 to 167, the UI
shows blank labels/colors when the backend returns status 'completed' because
the code only recognizes 'approved'; normalize 'completed' to 'approved' before
using statusColors and the WITHDRAW_STATUSES lookup. Update the render function
to map item.status === 'completed' to 'approved' (e.g., const uiStatus =
item.status === 'completed' ? 'approved' : item.status) and use uiStatus for
both statusColors[uiStatus] and the WITHDRAW_STATUSES.find(...) comparison so
labels and colors display correctly.

Comment on lines +212 to +217
<Tooltip content={ <RawHTML>{ full }</RawHTML> }>
<p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
<RawHTML>
{ truncate ? truncate( full, 120 ) : full }
</RawHTML>
</p>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Sanitize details tooltip and cell; avoid RawHTML

Render as text and preserve newlines with CSS. Current RawHTML risks XSS from user-supplied payment details.

-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
+                    <Tooltip content={ <span className="whitespace-pre-line">{ full }</span> }>
                         <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
                         </p>
                     </Tooltip>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Tooltip content={ <RawHTML>{ full }</RawHTML> }>
<p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
<RawHTML>
{ truncate ? truncate( full, 120 ) : full }
</RawHTML>
</p>
<Tooltip content={ <span className="whitespace-pre-line">{ full }</span> }>
<p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
<span>{ truncate ? truncate( full, 120 ) : full }</span>
</p>
</Tooltip>
🤖 Prompt for AI Agents
In src/admin/dashboard/pages/withdraw/index.tsx around lines 212 to 217, the
code uses RawHTML for both the Tooltip content and cell rendering which risks
XSS; replace RawHTML with plain text rendering and preserve newlines via CSS.
Specifically, pass the raw string (escaped by React when rendered as text) into
Tooltip and the <p> instead of RawHTML, apply a utility class like
"whitespace-pre-wrap break-words" (or equivalent) to preserve newlines and wrap
text, and keep using truncate(full, 120) when needed but ensure truncate returns
a plain string; remove any dangerouslySetInnerHTML/RawHTML usage. Ensure Tooltip
content receives the plain full string so no HTML is injected.

Comment on lines +4 to +7
declare module 'jquery' {
const jQuery: JQueryStatic;
export = jQuery;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Avoid hard dependency on JQueryStatic

JQueryStatic requires @types/jquery; without it, tsc will fail.

Run to verify presence:

If missing, either add it or relax the type:

 declare module 'jquery' {
-  const jQuery: JQueryStatic;
+  const jQuery: any;
   export = jQuery;
 }

🏁 Script executed:

#!/bin/bash
jq -r '.devDependencies["@types/jquery"] // .dependencies["@types/jquery"] // "MISSING"' package.json

Length of output: 111


Install @types/jquery or relax jQuery type

In types/externals.d.ts (lines 4–7), you’re using JQueryStatic but @types/jquery is not in package.json, causing tsc failures. Either add @types/jquery to dependencies/devDependencies or update the declaration:

declare module 'jquery' {
-  const jQuery: JQueryStatic;
+  const jQuery: any;
   export = jQuery;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
declare module 'jquery' {
const jQuery: JQueryStatic;
export = jQuery;
}
// types/externals.d.ts
declare module 'jquery' {
const jQuery: any;
export = jQuery;
}
🤖 Prompt for AI Agents
In types/externals.d.ts around lines 4 to 7, the ambient declaration references
JQueryStatic which requires @types/jquery to be installed; either add
"@types/jquery" to your project (preferably as a devDependency) and run your
package manager to install it, or change the declaration to remove the
JQueryStatic dependency (e.g., declare module 'jquery' { const jQuery: any;
export = jQuery; }) so TypeScript no longer requires the missing types; update
package.json and reinstall if adding the types, or replace the type with any in
the file to relax the type reference.

Comment on lines +10 to +19
declare module 'moment' {
import moment from 'moment';
export = moment;
}

// Chart.js - mapped to global Chart
declare module 'chart.js' {
import Chart from 'chart.js';
export = Chart;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not import from a module inside its own ambient declaration (moment, chart.js)

import ... from 'moment' within declare module 'moment' (same for chart.js) can cause declaration merging/duplication issues.

Use simple externs:

 declare module 'moment' {
-  import moment from 'moment';
-  export = moment;
+  const moment: any;
+  export = moment;
 }
 
 declare module 'chart.js' {
-  import Chart from 'chart.js';
-  export = Chart;
+  const Chart: any;
+  export = Chart;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
declare module 'moment' {
import moment from 'moment';
export = moment;
}
// Chart.js - mapped to global Chart
declare module 'chart.js' {
import Chart from 'chart.js';
export = Chart;
}
declare module 'moment' {
const moment: any;
export = moment;
}
// Chart.js - mapped to global Chart
declare module 'chart.js' {
const Chart: any;
export = Chart;
}
🤖 Prompt for AI Agents
In types/externals.d.ts around lines 10 to 19, the ambient module declarations
import from the same module (e.g., "import moment from 'moment'") which can
cause declaration merging/duplication; replace those import-based declarations
with simple extern declarations that don't import the module itself — for
example declare module 'moment' { const moment: any; export = moment; } and
declare module 'chart.js' { const Chart: any; export = Chart; } so the files
provide plain extern types without internal imports.

Comment on lines +21 to +56
// WooCommerce packages - mapped to global wc object
declare module '@woocommerce/blocks-registry' {
const registry: any;
export = registry;
}

declare module '@woocommerce/settings' {
const settings: any;
export = settings;
}

declare module '@woocommerce/block-data' {
const blockData: any;
export = blockData;
}

declare module '@woocommerce/shared-context' {
const sharedContext: any;
export = sharedContext;
}

declare module '@woocommerce/shared-hocs' {
const sharedHocs: any;
export = sharedHocs;
}

declare module '@woocommerce/price-format' {
const priceFormat: any;
export = priceFormat;
}

declare module '@woocommerce/blocks-checkout' {
const blocksCheckout: any;
export = blocksCheckout;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify WooCommerce externals match actual imports

Ensure these module IDs reflect what the code imports to avoid unresolved modules at build time.


🏁 Script executed:

#!/bin/bash
# Used modules in src
used=$(rg -nP --type=ts --type=tsx -o "'@woocommerce/[^']+'" src | sed "s/.*'//;s/'.*//" | sort -u)
# Declared externals here
declared=$(sed -n "s/^declare module '\([^']*\)'.*/\1/p" types/externals.d.ts | grep '^@woocommerce/' | sort -u)
echo "Used-only (not declared):"
comm -23 <(echo "$used") <(echo "$declared") || true
echo "Declared-only (not used):"
comm -13 <(echo "$used") <(echo "$declared") || true

Length of output: 1156


🏁 Script executed:

#!/bin/bash
set -e
# Collect used WooCommerce imports in TS/TSX files
used=$(grep -RhoP "@woocommerce/[^'\" ]+" src --include "*.ts" --include "*.tsx" | sort -u)

# Collect declared externals in types/externals.d.ts
declared=$(grep -Po "(?<=declare module ')[^']+" types/externals.d.ts | grep '^@woocommerce/' | sort -u)

echo "Used-only (not declared):"
comm -23 <(echo "$used") <(echo "$declared") || true

echo "Declared-only (not used):"
comm -13 <(echo "$used") <(echo "$declared") || true

Length of output: 1511


Sync WooCommerce externals with actual imports

  • In types/externals.d.ts, add missing declarations for:
    • @woocommerce/admin-layout
    • @woocommerce/components
    • @woocommerce/data
    • @woocommerce/date
    • @woocommerce/experimental
    • @woocommerce/navigation
  • Remove stale declarations for:
    • @woocommerce/block-data
    • @woocommerce/blocks-checkout
    • @woocommerce/blocks-registry
    • @woocommerce/price-format
    • @woocommerce/settings
    • @woocommerce/shared-context
    • @woocommerce/shared-hocs

Comment on lines +86 to +91
declare module '@dokan/hooks' {
export const useWindowDimensions: any;
export const useCurrentUser: any;
export const useCustomerSearch: any;
export const ViewportDimensions: any;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

ViewportDimensions should be a type, not a value

Avoid declaring it as a const; declare a type to enable import type usage.

 declare module '@dokan/hooks' {
-  export const useWindowDimensions: any;
+  export function useWindowDimensions(): ViewportDimensions;
   export const useCurrentUser: any;
   export const useCustomerSearch: any;
-  export const ViewportDimensions: any;
+  export type ViewportDimensions = any;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
declare module '@dokan/hooks' {
export const useWindowDimensions: any;
export const useCurrentUser: any;
export const useCustomerSearch: any;
export const ViewportDimensions: any;
}
declare module '@dokan/hooks' {
export function useWindowDimensions(): ViewportDimensions;
export const useCurrentUser: any;
export const useCustomerSearch: any;
export type ViewportDimensions = any;
}
🤖 Prompt for AI Agents
In types/externals.d.ts around lines 86 to 91, the declaration exports
ViewportDimensions as a value (export const ViewportDimensions: any;) but it
should be a type so consumers can use `import type`; change the export to a type
declaration (export type ViewportDimensions = any;) while leaving the other hook
declarations unchanged.

Comment on lines +39 to +41
var jQuery: JQueryStatic;
var $: JQueryStatic;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

jQuery globals rely on JQueryStatic; prefer safer fallback or ensure typings installed

If @types/jquery isn’t installed, these declarations will fail.

Same check as suggested for externals.d.ts.

If needed, relax:

-  var jQuery: JQueryStatic;
-  var $: JQueryStatic;
+  var jQuery: any;
+  var $: any;

🏁 Script executed:

#!/bin/bash
# Check for @types/jquery in package.json
if [ -f package.json ]; then
  grep -R '"@types/jquery"' -n package.json || echo "@types/jquery not found in package.json"
else
  echo "package.json not found"
fi

# Check tsconfig.json for jquery in compilerOptions.types
if [ -f tsconfig.json ]; then
  grep -R '"jquery"' -n tsconfig.json || echo "jquery not found in tsconfig.json"
else
  echo "tsconfig.json not found"
fi

Length of output: 300


Install jQuery typings or fall back to any
Declarations in types/globals.d.ts (lines 39–41) reference JQueryStatic, but @types/jquery isn’t in package.json and jquery isn’t in tsconfig.json types—this will break builds. Either add @types/jquery to your devDependencies and include "jquery" under compilerOptions.types in tsconfig, or relax the globals:

- var jQuery: JQueryStatic;
- var $: JQueryStatic;
+ var jQuery: any;
+ var $: any;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var jQuery: JQueryStatic;
var $: JQueryStatic;
var jQuery: any;
var $: any;
🤖 Prompt for AI Agents
In types/globals.d.ts around lines 39 to 41, the file references the
JQueryStatic type but the repo doesn't include @types/jquery or a tsconfig types
entry, which will break builds; fix by either adding @types/jquery to
devDependencies and adding "jquery" to compilerOptions.types in tsconfig.json so
JQueryStatic is available, or replace the strict declarations with a safe
fallback (declare var jQuery: any; declare var $: any;) to avoid the
missing-typing error.

# Conflicts:
#	includes/Admin/Dashboard/Dashboard.php
- Added `VendorAsyncSelect` and `DateRangePicker` components for improved filtering.
- Updated SCSS to fix z-index and add relative positioning.
- Refactored filter handling logic for better clarity and usability.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (11)
src/admin/dashboard/pages/withdraw/index.tsx (11)

161-169: Normalize backend 'completed' → UI 'approved' and add 'cancelled' color

Prevents blank label/color and keeps filters consistent.

-            getValue: ( { item } ) => item.status,
+            getValue: ( { item } ) => ( item.status === 'completed' ? 'approved' : item.status ),
@@
-            render: ( { item } ) => {
-                const statusColors = {
-                    pending: 'bg-yellow-100 text-yellow-800',
-                    approved: 'bg-green-100 text-green-800',
-                };
+            render: ( { item } ) => {
+                const normalized = item.status === 'completed' ? 'approved' : item.status;
+                const statusColors = {
+                    pending: 'bg-yellow-100 text-yellow-800',
+                    approved: 'bg-green-100 text-green-800',
+                    cancelled: 'bg-gray-100 text-gray-800',
+                };
                 return (
                     <span
-                        className={ `inline-flex items-center px-3.5 py-1.5 rounded-md text-xs font-medium ${ statusColors[ item.status ] || 'bg-gray-100 text-gray-800' }` }
+                        className={ `inline-flex items-center px-3.5 py-1.5 rounded-md text-xs font-medium ${ statusColors[ normalized ] || 'bg-gray-100 text-gray-800' }` }
                     >
-                        { WITHDRAW_STATUSES.find( ( status ) => status.value === item.status )?.label }
+                        { WITHDRAW_STATUSES.find( (s) => s.value === normalized )?.label || normalized }
                     </span>
                 );
             },

Also applies to: 169-189


271-285: Guard unknown icons from lucide-react

Avoid runtime crash when icon name is invalid.

-        const Icon = LucideIcons[ iconName ];
+        const Icon = LucideIcons[ iconName as keyof typeof LucideIcons ] as any;
@@
-                    <Icon size={ 16 } className="!fill-none" />
+                    { Icon ? <Icon size={ 16 } className="!fill-none" /> : null }

371-372: Treat 'completed' as approved in action eligibility

Keeps actions disabled for both approved/completed.

-            isEligible: ( item ) => item?.status !== 'approved',
+            isEligible: ( item ) => ! [ 'approved', 'completed' ].includes( item?.status ),

Also applies to: 392-393


515-586: Stabilize fetching; avoid stale filters/duplicates

Memoize fetchWithdraws and drive it via effect; let handlers only update state.

-    const fetchWithdraws = async () => {
+    const fetchWithdraws = useCallback( async () => {
         setIsLoading( true );
         try {
             const queryArgs = {
                 per_page: view?.perPage ?? 10,
                 page: view?.page ?? 1,
                 search: view?.search ?? '',
                 status: view?.status === 'all' ? '' : view?.status,
                 ...filterArgs,
             };
@@
         } finally {
             setIsLoading( false );
         }
-    };
+    }, [ view, filterArgs ] );
-    const handleFilter = () => {
-        setView( ( prevView ) => ( {
-            ...prevView,
-            page: 1, // Reset to first page when applying filters
-        } ) );
-        // fetchWithdraws();
-    };
+    const handleFilter = () => {
+        setView( ( prevView ) => ( { ...prevView, page: 1 } ) );
+    };
-    const clearFilter = () => {
+    const clearFilter = () => {
         setVendorFilter( null );
         setAfter( '' );
         setAfterText( '' );
         setBefore( '' );
         setBeforeText( '' );
         setFilterArgs( {} );
-
-        setView( ( prevView ) => ( {
-            ...prevView,
-            page: 1, // Reset to first page when applying filters
-        } ) );
-        // fetchWithdraws();
+        setView( ( prevView ) => ( { ...prevView, page: 1 } ) );
     };
-    useEffect( () => {
-        fetchWithdraws();
-    }, [ view ] );
+    useEffect( () => {
+        fetchWithdraws();
+    }, [ fetchWithdraws ] );

Also applies to: 639-647, 650-663, 665-669


56-57: Stop using RawHTML for prices; render as text

RawHTML invites XSS and isn’t needed for plain strings.

-const price = ( amount ) => <RawHTML>{ formatPrice( amount ) }</RawHTML>;
+const price = ( amount ) => <span>{ formatPrice( amount ) }</span>;

234-241: Sanitize “details” rendering; replace RawHTML

User-provided details must not be injected as HTML.

-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
-                        <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
-                        </p>
-                    </Tooltip>
+                    <Tooltip content={ <span className="whitespace-pre-line break-words">{ full }</span> }>
+                        <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600 whitespace-pre-line break-words">
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
+                        </p>
+                    </Tooltip>

250-257: Sanitize “note” rendering; replace RawHTML

Same XSS risk for notes.

-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
-                        <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
-                        </p>
-                    </Tooltip>
+                    <Tooltip content={ <span className="whitespace-pre-line break-words">{ full }</span> }>
+                        <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600 whitespace-pre-line break-words">
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
+                        </p>
+                    </Tooltip>

489-492: Don’t inject empty field id into DataViews

Empty ids can break column rendering; filter vendor out explicitly.

-        fields: fields.map( ( field ) =>
-            field.id !== 'vendor' ? field.id : ''
-        ),
+        fields: fields.filter( ( f ) => f.id !== 'vendor' ).map( ( f ) => f.id ),

547-554: Fix REST path: add leading slash

Prevents broken requests on some installs.

-                path: addQueryArgs( 'dokan/v2/withdraw', queryArgs ),
+                path: addQueryArgs( '/dokan/v2/withdraw', queryArgs ),

692-701: Export: whitelist params and add leading slash

Avoid leaking internal view keys and fix REST path.

-                                const path = addQueryArgs(
-                                    'dokan/v2/withdraw',
-                                    { ...view, is_export: true }
-                                );
-                                const res = await apiFetch( { path } );
+                                const exportArgs: any = {
+                                    per_page: view?.perPage ?? 10,
+                                    page: view?.page ?? 1,
+                                    search: view?.search ?? '',
+                                    status: view?.status === 'all' ? '' : view?.status,
+                                    ...filterArgs,
+                                    is_export: true,
+                                    ...( view?.sort?.field ? { orderby: view.sort.field } : {} ),
+                                    ...( view?.sort?.direction ? { order: view.sort.direction } : {} ),
+                                };
+                                const path = addQueryArgs( '/dokan/v2/withdraw', exportArgs );
+                                const res = await apiFetch( { path } );

1186-1193: Sanitize details in modal; remove RawHTML

Render as text and preserve newlines via CSS.

-                                                <RawHTML>
-                                                    { processDetails(
-                                                        item.details,
-                                                        item.method
-                                                    )
-                                                        .split( '\n' )
-                                                        .join( '<br />' ) }
-                                                </RawHTML>
+                                                <span className="whitespace-pre-line">
+                                                    { processDetails( item.details, item.method ) }
+                                                </span>
🧹 Nitpick comments (3)
src/admin/dashboard/pages/withdraw/index.tsx (3)

1068-1124: Normalize status in modal and use UI label

Keep modal consistent with table/status tabs.

-                                    const statusColors = {
+                                    const normalized = item.status === 'completed' ? 'approved' : item.status;
+                                    const statusColors = {
                                         pending:
                                             'bg-yellow-100 text-yellow-800',
                                         approved: 'bg-green-100 text-green-800',
                                         cancelled: 'bg-gray-100 text-gray-800',
                                     };
@@
-                                                            className={ `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${ statusColors[ item.status ] || 'bg-gray-100 text-gray-800' }` }
+                                                            className={ `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${ statusColors[ normalized ] || 'bg-gray-100 text-gray-800' }` }
                                                         >
-                                                            { item.status.charAt( 0 ).toUpperCase() + item.status.slice( 1 ) }
+                                                            { WITHDRAW_STATUSES.find( (s) => s.value === normalized )?.label || normalized }
                                                         </span>

819-831: Localize the placeholder

Ensure i18n consistency.

-                                    placeholder: 'Select date range',
+                                    placeholder: __( 'Select date range', 'dokan-lite' ),

591-596: Remove unused variable

updateData is unused; drop it to reduce noise.

-            const updateData = {};
-            if ( action === 'approved' ) {
-                updateData.status = action;
-            } else if ( action === 'cancelled' ) {
-                updateData.status = action;
-            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5aea5a and b00f548.

📒 Files selected for processing (3)
  • includes/Admin/Dashboard/Dashboard.php (1 hunks)
  • src/admin/dashboard/pages/withdraw/index.tsx (1 hunks)
  • src/admin/dashboard/style.scss (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/admin/dashboard/style.scss
🧰 Additional context used
🧬 Code graph analysis (1)
src/admin/dashboard/pages/withdraw/index.tsx (3)
src/utilities/Accounting.ts (1)
  • formatPrice (3-62)
types/externals.d.ts (5)
  • formatPrice (79-79)
  • DokanTab (61-61)
  • Filter (60-60)
  • DataViews (59-59)
  • DokanModal (65-65)
src/utilities/Truncate.ts (1)
  • truncate (1-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e tests (2, 3)
  • GitHub Check: e2e tests (1, 3)
  • GitHub Check: e2e tests (3, 3)
  • GitHub Check: api tests (1, 1)
🔇 Additional comments (2)
includes/Admin/Dashboard/Dashboard.php (1)

310-315: CSS dependency order change looks good

Registering the dashboard CSS after Dokan React Components and WC components should prevent style overrides/regressions.

src/admin/dashboard/pages/withdraw/index.tsx (1)

421-422: Empty PayPal mass payment callback

Confirm whether this is intentionally stubbed; otherwise disable the action until implemented.

Would you like me to wire this to an export endpoint based on payment method and current filters?

Comment on lines 311 to 315
$this->script_key,
DOKAN_PLUGIN_ASSEST . '/css/dokan-admin-dashboard.css',
[ 'wc-components' ],
[ 'dokan-react-components', 'wc-components' ],
$version
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify dependency handle is a registered STYLE, not just a script

Ensure a style with handle 'dokan-react-components' is registered before this, otherwise the dependency won’t resolve.

Run:


🏁 Script executed:

#!/bin/bash
# Check that a STYLE handle named 'dokan-react-components' is registered anywhere.
rg -nP "wp_register_style\\(\\s*'dokan-react-components'" -S
rg -nP "wp_enqueue_style\\(\\s*'dokan-react-components'" -S
# Sanity: also list where dokan-admin-dashboard.css is referenced.
rg -nP "dokan-admin-dashboard\\.css" -n

Length of output: 114


Register missing style dependency 'dokan-react-components'
No style handle named ‘dokan-react-components’ is registered before being used as a dependency for dokan-admin-dashboard.css (includes/Admin/Dashboard/Dashboard.php:312). Add a wp_register_style or wp_enqueue_style for this handle prior to this enqueue.

🤖 Prompt for AI Agents
In includes/Admin/Dashboard/Dashboard.php around lines 311 to 315, the style
handle 'dokan-react-components' is referenced as a dependency for
dokan-admin-dashboard.css but is never registered; register the
'dokan-react-components' style before enqueuing dokan-admin-dashboard.css by
adding a wp_register_style (or wp_enqueue_style) call that provides the correct
handle, file path (e.g., DOKAN_PLUGIN_ASSEST .
'/css/dokan-react-components.css'), version and optional dependencies, then
proceed to enqueue dokan-admin-dashboard.css as before so the dependency exists
and loads correctly.

Comment on lines +211 to +214
<div className="font-medium text-gray-900">
{ price( item.receivable || item.amount ) }
</div>
),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use nullish coalescing to avoid wrong fallback when receivable is 0

If receivable is 0, current code incorrectly falls back to amount.

-                    { price( item.receivable || item.amount ) }
+                    { price( item.receivable ?? item.amount ) }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="font-medium text-gray-900">
{ price( item.receivable || item.amount ) }
</div>
),
<div className="font-medium text-gray-900">
{ price( item.receivable ?? item.amount ) }
</div>
),
🤖 Prompt for AI Agents
In src/admin/dashboard/pages/withdraw/index.tsx around lines 211 to 214, the
current expression uses the || operator so when item.receivable is 0 it
incorrectly falls back to item.amount; replace the fallback logic to use the
nullish coalescing operator (??) so that item.receivable is used when it is 0
but falls back only when it is null or undefined (i.e., call
price(item.receivable ?? item.amount)).

- Introduced `AsyncSelect` with payment method filter support.
- Enhanced UI components for filtering, including updated placeholders and button styles.
- Added `loadPaymentMethods` function to dynamically fetch payment methods.
- Improved filter reset functionality with additional cleanup actions.
@shohag121 shohag121 added Needs: Testing This requires further testing Needs: Dev Review It requires a developer review and approval labels Sep 12, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (13)
src/admin/dashboard/pages/withdraw/index.tsx (13)

176-196: Normalize 'completed' → 'approved' in table status and add cancelled color

-            render: ( { item } ) => {
-                const statusColors = {
-                    pending: 'bg-yellow-100 text-yellow-800',
-                    approved: 'bg-green-100 text-green-800',
-                };
+            render: ( { item } ) => {
+                const normalized = item.status === 'completed' ? 'approved' : item.status;
+                const statusColors = {
+                    pending: 'bg-yellow-100 text-yellow-800',
+                    approved: 'bg-green-100 text-green-800',
+                    cancelled: 'bg-gray-100 text-gray-800',
+                };
                 return (
                     <span
                         className={ `inline-flex items-center px-3.5 py-1.5 rounded-md text-xs font-medium ${
-                            statusColors[ item.status ] ||
+                            statusColors[ normalized ] ||
                             'bg-gray-100 text-gray-800'
                         }` }
                     >
-                        {
-                            WITHDRAW_STATUSES.find(
-                                ( status ) => status.value === item.status
-                            )?.label
-                        }
+                        { WITHDRAW_STATUSES.find( (s) => s.value === normalized )?.label || normalized }
                     </span>
                 );
             },

378-378: Account for 'completed' in eligibility checks

-            isEligible: ( item ) => item?.status !== 'approved',
+            isEligible: ( item ) => ! [ 'approved', 'completed' ].includes( item?.status ),
@@
-            isEligible: ( item ) => item?.status !== 'approved',
+            isEligible: ( item ) => ! [ 'approved', 'completed' ].includes( item?.status ),

Also applies to: 399-399


1113-1167: Normalize status in modal pill/label (treat 'completed' as 'approved')

                                     const statusColors = {
                                         pending:
                                             'bg-yellow-100 text-yellow-800',
                                         approved: 'bg-green-100 text-green-800',
                                         cancelled: 'bg-gray-100 text-gray-800',
                                     };
+                                    const normalized = item.status === 'completed' ? 'approved' : item.status;
@@
-                                                        <span
+                                                        <span
                                                             className={ `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
-                                                                statusColors[
-                                                                    item.status
-                                                                ] ||
+                                                                statusColors[ normalized ] ||
                                                                 'bg-gray-100 text-gray-800'
                                                             }` }
                                                         >
-                                                            { item.status
+                                                            { normalized
                                                                 .charAt( 0 )
                                                                 .toUpperCase() +
-                                                                item.status.slice(
+                                                                normalized.slice(
                                                                     1
                                                                 ) }

218-221: Use nullish coalescing for payable fallback (handles 0 correctly)

-                    { price( item.receivable || item.amount ) }
+                    { price( item.receivable ?? item.amount ) }
@@
-                                                            { price(
-                                                                item.receivable ||
-                                                                    item.amount
-                                                            ) }
+                                                            { price( item.receivable ?? item.amount ) }

Also applies to: 1206-1210


279-289: Guard unknown icon names in getActionLabel

         const Icon = LucideIcons[ iconName ];
         return (
             <div className="dokan-layout">
                 <span className="inline-flex items-center gap-2.5">
-                    <Icon size={ 16 } className="!fill-none" />
+                    { Icon ? <Icon size={ 16 } className="!fill-none" /> : null }
                     { label }
                 </span>
             </div>
         );

521-593: Stabilize data fetching: memoize fetchWithdraws and update effect deps

-    const fetchWithdraws = async () => {
+    const fetchWithdraws = useCallback( async () => {
         setIsLoading( true );
         try {
             const queryArgs = {
                 per_page: view?.perPage ?? 10,
                 page: view?.page ?? 1,
                 search: view?.search ?? '',
                 status: view?.status === 'all' ? '' : view?.status,
                 ...filterArgs,
             };
@@
         } finally {
             setIsLoading( false );
         }
-    };
+    }, [ view, filterArgs ] );
-    useEffect( () => {
-        fetchWithdraws();
-    }, [ view ] );
+    useEffect( () => {
+        fetchWithdraws();
+    }, [ fetchWithdraws ] );

Also applies to: 684-687


3-3: Stop using RawHTML for prices; render as text to avoid XSS

formatPrice returns text. Drop RawHTML import and render safely.

-import { RawHTML, useEffect, useState, useCallback } from '@wordpress/element';
+import { useEffect, useState, useCallback } from '@wordpress/element';
@@
-const price = ( amount ) => <RawHTML>{ formatPrice( amount ) }</RawHTML>;
+const price = ( amount: number | string ) => <span>{ formatPrice( amount ) }</span>;

Also applies to: 58-58


241-246: Sanitize Details tooltip/cell; remove RawHTML

Render plain text and preserve newlines via CSS.

-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
+                    <Tooltip content={ <span className="whitespace-pre-line break-words">{ full }</span> }>
                         <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
                         </p>
                     </Tooltip>

257-262: Sanitize Note tooltip/cell; remove RawHTML

-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
+                    <Tooltip content={ <span className="whitespace-pre-line break-words">{ full }</span> }>
                         <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
                         </p>
                     </Tooltip>

1229-1236: Avoid RawHTML in modal; render details safely

-                                                <RawHTML>
-                                                    { processDetails(
-                                                        item.details,
-                                                        item.method
-                                                    )
-                                                        .split( '\n' )
-                                                        .join( '<br />' ) }
-                                                </RawHTML>
+                                                <span className="whitespace-pre-line">
+                                                    { processDetails( item.details, item.method ) }
+                                                </span>

496-499: Fix DataViews fields: avoid empty field id ('')

-        fields: fields.map( ( field ) =>
-            field.id !== 'vendor' ? field.id : ''
-        ),
+        fields: fields.filter( ( f ) => f.id !== 'vendor' ).map( ( f ) => f.id ),

555-555: Fix REST path: add leading slash

-                path: addQueryArgs( 'dokan/v2/withdraw', queryArgs ),
+                path: addQueryArgs( '/dokan/v2/withdraw', queryArgs ),

711-719: Export: whitelist params and fix REST path

-                                const path = addQueryArgs(
-                                    'dokan/v2/withdraw',
-                                    { ...view, is_export: true }
-                                );
-                                const res = await apiFetch( { path } );
+                                const exportArgs: any = {
+                                    per_page: view?.perPage ?? 10,
+                                    page: view?.page ?? 1,
+                                    search: view?.search ?? '',
+                                    status: view?.status === 'all' ? '' : view?.status,
+                                    ...filterArgs,
+                                    is_export: true,
+                                    ...(view?.sort?.field ? { orderby: view.sort.field } : {}),
+                                    ...(view?.sort?.direction ? { order: view.sort.direction } : {}),
+                                };
+                                const path = addQueryArgs( '/dokan/v2/withdraw', exportArgs );
+                                const res = await apiFetch( { path } );
🧹 Nitpick comments (5)
src/admin/dashboard/pages/withdraw/index.tsx (5)

595-604: Remove dead code in handleBulkAction

-        try {
-            const updateData = {};
-            if ( action === 'approved' ) {
-                updateData.status = action;
-            } else if ( action === 'cancelled' ) {
-                updateData.status = action;
-            }
+        try {

60-67: Harden processDetails against missing/undefined fields

-const processDetails = ( data, method: string ) => {
+const processDetails = ( data, method: string ) => {
+    if ( ! data || typeof data !== 'object' ) {
+        return __( '-', 'dokan-lite' );
+    }
     // get method key details from data then return the values of the key from the data
     const methodKey = Object.keys( data ).find(
         ( key ) => key.toLowerCase() === method.toLowerCase()
     );
     if ( methodKey ) {
-        const methodObject = data[ methodKey ];
+        const methodObject = data?.[ methodKey ] || {};
         let details = '';
@@
-            case 'bank':
+            case 'bank':
                 details = sprintf(
                     __(
                         'Account Name: %s, \nAccount Number: %s\nBank Name: %s\nRouting Number: %s\nSwift Code: %s\nIBAN: %s',
                         'dokan-lite'
                     ),
-                    methodObject.ac_name,
-                    methodObject.ac_number,
-                    methodObject.bank_name,
-                    methodObject.routing_number,
-                    methodObject.swift,
-                    methodObject.iban
+                    methodObject?.ac_name || '-',
+                    methodObject?.ac_number || '-',
+                    methodObject?.bank_name || '-',
+                    methodObject?.routing_number || '-',
+                    methodObject?.swift || '-',
+                    methodObject?.iban || '-'
                 );
                 break;
@@
-            case 'paypal':
-                details = methodObject.email;
+            case 'paypal':
+                details = methodObject?.email || '';
                 break;

Also applies to: 74-86


799-799: Localize placeholders

-                            placeholder="Payment Method"
+                            placeholder={ __( 'Payment Method', 'dokan-lite' ) }
@@
-                                    placeholder: 'Date',
+                                    placeholder: __( 'Date', 'dokan-lite' ),

Also applies to: 871-871


427-429: TODO: Implement PayPal mass payment callback

Currently a no-op; wire up download or disable until available.


141-147: Optional: add width/height and fallback for avatar

Improves CLS and avoids broken images.

-                        <img
-                            src={ item.user.gravatar }
-                            alt={ item.user?.store_name || '' }
-                            className="w-8 h-8 rounded-sm"
-                        />
+                        <img
+                            src={ item.user.gravatar }
+                            alt={ item.user?.store_name || '' }
+                            width={ 32 }
+                            height={ 32 }
+                            className="w-8 h-8 rounded-sm"
+                            onError={ (e) => (e.currentTarget.style.visibility = 'hidden') }
+                        />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b00f548 and 6a56293.

📒 Files selected for processing (1)
  • src/admin/dashboard/pages/withdraw/index.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/admin/dashboard/pages/withdraw/index.tsx (3)
src/utilities/Accounting.ts (1)
  • formatPrice (3-62)
types/externals.d.ts (5)
  • formatPrice (79-79)
  • DokanTab (61-61)
  • Filter (60-60)
  • DataViews (59-59)
  • DokanModal (65-65)
src/utilities/Truncate.ts (1)
  • truncate (1-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e tests (1, 3)
  • GitHub Check: e2e tests (2, 3)
  • GitHub Check: e2e tests (3, 3)
  • GitHub Check: api tests (1, 1)
🔇 Additional comments (1)
src/admin/dashboard/pages/withdraw/index.tsx (1)

1304-1314: Verify SimpleRadio prop names

Component props use class/optionClass; confirm API doesn’t expect className variants to avoid styling issues.

…ollers

- Added `ExportController` and `WithdrawExportController` to manage export functionality.
- Registered `WithdrawExportController` with WooCommerce under `withdraws` type.
- Created comprehensive usage documentation for the export controller.
- Enhanced `REST/Manager` to support export controllers seamlessly.
…integration

- Added support for withdraw data export via WooCommerce export system.
- Introduced `register_data_endpoint` method to map withdraw export endpoint.
- Updated `ExportController` and `WithdrawExportController` for endpoint structure alignment.
- Enhanced admin withdraw page with bulk export options and improved pagination.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (12)
src/admin/dashboard/pages/withdraw/index.tsx (12)

58-58: Remove RawHTML in price renderer; render plain text

formatPrice returns text; RawHTML is unnecessary and risky.

-const price = ( amount ) => <RawHTML>{ formatPrice( amount ) }</RawHTML>;
+const price = ( amount ) => <span>{ formatPrice( amount ) }</span>;

218-221: Use nullish coalescing for payable

If receivable is 0, || falls back incorrectly to amount.

-                    { price( item.receivable || item.amount ) }
+                    { price( item.receivable ?? item.amount ) }
-                                                            { price(
-                                                                item.receivable ||
-                                                                    item.amount
-                                                            ) }
+                                                            { price(
+                                                                item.receivable ?? item.amount
+                                                            ) }

Also applies to: 1301-1305


283-289: Guard unknown icons to prevent crashes

LucideIcons[iconName] may be undefined.

-        const Icon = LucideIcons[ iconName ];
+        const Icon = LucideIcons[ iconName ];
         return (
             <div className="dokan-layout">
                 <span className="inline-flex items-center gap-2.5">
-                    <Icon size={ 16 } className="!fill-none" />
+                    { Icon ? <Icon size={ 16 } className="!fill-none" /> : null }
                     { label }
                 </span>
             </div>
         );

498-501: Fix DataViews view.fields: avoid empty string field id

Empty ids can break column rendering; filter out vendor.

-        fields: fields.map( ( field ) =>
-            field.id !== 'vendor' ? field.id : ''
-        ),
+        fields: fields.filter( ( f ) => f.id !== 'vendor' ).map( ( f ) => f.id ),

523-596: Stabilize fetching; avoid stale filters/duplicate calls

Memoize fetchWithdraws and depend on it in useEffect.

-    const fetchWithdraws = async () => {
+    const fetchWithdraws = useCallback( async () => {
         setIsLoading( true );
         try {
             const queryArgs = {
                 per_page: view?.perPage ?? 20,
                 page: view?.page ?? 1,
                 search: view?.search ?? '',
                 status: view?.status === 'all' ? '' : view?.status,
                 ...filterArgs,
             };
@@
         } finally {
             setIsLoading( false );
         }
-    };
+    }, [ view, filterArgs ] );
-    useEffect( () => {
-        fetchWithdraws();
-    }, [ view ] );
+    useEffect( () => {
+        fetchWithdraws();
+    }, [ fetchWithdraws ] );

Also applies to: 779-783


806-814: Export: whitelist query params and fix REST path

Don’t leak internal view keys; use server-supported params only and add /.

-                                const path = addQueryArgs(
-                                    'dokan/v2/withdraw',
-                                    { ...view, is_export: true }
-                                );
-                                const res = await apiFetch( { path } );
+                                const exportArgs: any = {
+                                    per_page: view?.perPage ?? 10,
+                                    page: view?.page ?? 1,
+                                    search: view?.search ?? '',
+                                    status: view?.status === 'all' ? '' : view?.status,
+                                    ...filterArgs,
+                                    is_export: true,
+                                    ...( view?.sort?.field ? { orderby: view.sort.field } : {} ),
+                                    ...( view?.sort?.direction ? { order: view.sort.direction } : {} ),
+                                };
+                                const path = addQueryArgs( '/dokan/v2/withdraw', exportArgs );
+                                const res = await apiFetch( { path } );

378-378: Eligibility: treat ‘completed’ as approved

Blocks actions on already-completed items.

-            isEligible: ( item ) => item?.status !== 'approved',
+            isEligible: ( item ) => ! [ 'approved', 'completed' ].includes( item?.status ),
-            isEligible: ( item ) => item?.status !== 'approved',
+            isEligible: ( item ) => ! [ 'approved', 'completed' ].includes( item?.status ),

Also applies to: 399-399


241-247: XSS: stop injecting Details with RawHTML

Render plain text and preserve newlines with CSS.

-                return (
-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
-                        <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
-                        </p>
-                    </Tooltip>
-                );
+                return (
+                    <Tooltip content={ <span className="whitespace-pre-line">{ full }</span> }>
+                        <p className="m-0 space-x-2 flex flex-wrap max-w-40 leading-6 text-sm text-gray-600 whitespace-pre-line break-words">
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
+                        </p>
+                    </Tooltip>
+                );

257-263: XSS: stop injecting Note with RawHTML

Same risk as Details; render as text.

-                return (
-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
-                        <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
-                        </p>
-                    </Tooltip>
-                );
+                return (
+                    <Tooltip content={ <span className="whitespace-pre-line">{ full }</span> }>
+                        <p className="m-0 space-x-2 flex flex-wrap max-w-40 leading-6 text-sm text-gray-600 whitespace-pre-line break-words">
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
+                        </p>
+                    </Tooltip>
+                );

176-195: Normalize status and handle ‘cancelled’; show correct color/label

Treat backend completed as UI approved and add a style for cancelled.

-            render: ( { item } ) => {
-                const statusColors = {
-                    pending: 'bg-yellow-100 text-yellow-800',
-                    approved: 'bg-green-100 text-green-800',
-                };
+            render: ( { item } ) => {
+                const normalized = item.status === 'completed' ? 'approved' : item.status;
+                const statusColors = {
+                    pending: 'bg-yellow-100 text-yellow-800',
+                    approved: 'bg-green-100 text-green-800',
+                    cancelled: 'bg-gray-100 text-gray-800',
+                };
                 return (
                     <span
                         className={ `inline-flex items-center px-3.5 py-1.5 rounded-md text-xs font-medium ${
-                            statusColors[ item.status ] ||
+                            statusColors[ normalized ] ||
                             'bg-gray-100 text-gray-800'
                         }` }
                     >
-                        {
-                            WITHDRAW_STATUSES.find(
-                                ( status ) => status.value === item.status
-                            )?.label
-                        }
+                        { WITHDRAW_STATUSES.find( (s) => s.value === normalized )?.label || normalized }
                     </span>
                 );
             },

556-563: Fix REST path: add leading slash

Missing / can break requests on some installs.

-            const response = await apiFetch( {
-                path: addQueryArgs( 'dokan/v2/withdraw', queryArgs ),
+            const response = await apiFetch( {
+                path: addQueryArgs( '/dokan/v2/withdraw', queryArgs ),
                 headers: {
                     'Content-Type': 'application/json',
                 },
                 parse: false, // Get raw response to access headers
             } );

1324-1332: XSS in modal: stop using RawHTML

Render details safely; preserve line breaks via CSS.

-                                                <RawHTML>
-                                                    { processDetails(
-                                                        item.details,
-                                                        item.method
-                                                    )
-                                                        .split( '\n' )
-                                                        .join( '<br />' ) }
-                                                </RawHTML>
+                                                <span className="whitespace-pre-line">
+                                                    { processDetails( item.details, item.method ) }
+                                                </span>
🧹 Nitpick comments (13)
src/admin/dashboard/pages/withdraw/index.tsx (6)

894-894: Localize placeholder

Wrap with __() for i18n consistency.

-                            placeholder="Payment Method"
+                            placeholder={ __( 'Payment Method', 'dokan-lite' ) }

687-694: Remove dead code: unused updateData

updateData is built but never used.

-        try {
-            const updateData = {};
-            if ( action === 'approved' ) {
-                updateData.status = action;
-            } else if ( action === 'cancelled' ) {
-                updateData.status = action;
-            }
+        try {

1199-1203: Modal UX: confirm button says “Update” but does nothing

Either change label to “Close” or hide confirm and keep Cancel.

-                        onConfirm={ closeModal }
-                        dialogTitle={ <></> }
-                        confirmButtonText={ __( 'Update', 'dokan-lite' ) }
-                        hideCancelButton={ false }
+                        onConfirm={ closeModal }
+                        dialogTitle={ <></> }
+                        confirmButtonText={ __( 'Close', 'dokan-lite' ) }
+                        hideCancelButton={ true }

208-211: Nit: prefer nullish coalescing for charge

Avoid falling back when charge is 0.

-                    { price( item.charge || 0 ) }
+                    { price( item.charge ?? 0 ) }

639-683: Optional: clear polling timer on unmount

Prevent orphaned timers if the component unmounts during export.

You can keep a ref to the timeout id and clear it in a cleanup:

// at top-level
const exportTimerRef = useRef<number | null>( null );

// inside checkStatus setTimeout
exportTimerRef.current = window.setTimeout( checkStatus, 5000 );

// on unmount
useEffect(() => () => {
  if ( exportTimerRef.current ) {
    clearTimeout( exportTimerRef.current );
  }
}, []);

3-3: Drop RawHTML import after replacing usages

Keep imports minimal.

-import { RawHTML, useEffect, useState, useCallback } from '@wordpress/element';
+import { useEffect, useState, useCallback } from '@wordpress/element';
includes/REST/WithdrawExportController.php (3)

104-107: Sanitize and bound pagination params.

per_page/page should be absint and bounded to avoid huge offsets or negatives.

Apply:

-        $args['limit'] = $request['per_page'] ?? 20;
-        $args['offset'] = ( ( $request['page'] ?? 1 ) - 1 ) * $args['limit'];
+        $limit = isset( $request['per_page'] ) ? max( 1, min( 100, absint( $request['per_page'] ) ) ) : 20;
+        $page  = isset( $request['page'] ) ? max( 1, absint( $request['page'] ) ) : 1;
+        $args['limit']  = $limit;
+        $args['offset'] = ( $page - 1 ) * $limit;

130-135: Resilient pagination headers.

If max_num_pages isn’t provided by all(), fall back to a calculated value.

Apply:

-        $response->header( 'X-WP-TotalPages', $withdraws_obj->max_num_pages );
+        $max_pages = isset( $withdraws_obj->max_num_pages ) ? (int) $withdraws_obj->max_num_pages : (int) ceil( $total_withdraws / max( 1, (int) $args['limit'] ) );
+        $response->header( 'X-WP-TotalPages', $max_pages );

192-198: PHPMD “unused parameter” on $request.

Harmless here. Consider adding a short inline ignore or underscore prefix to quiet linters.

Example:

/** @noinspection PhpUnusedParameterInspection */
// phpcs:ignore WordPressVIPMinimum.Hooks.StrictAvoidUnhookedFunctions
public function get_items_permissions_check( $request ) {
docs/export-controller-usage.md (3)

31-37: Fix rest_base example and declare namespace separately.

rest_base shouldn’t include the namespace; keep it relative. This mirrors the controllers in code.

Apply:

 class YourCustomExportController extends GenericController implements ExportableInterface {
@@
-    /**
-     * Route base for this controller
-     */
-    protected $rest_base = 'dokan/v1/reports/your-report-type';
+    /**
+     * Route base and namespace for this controller
+     */
+    protected $namespace = 'dokan/v1';
+    protected $rest_base = 'reports/your-report-type';

175-177: Add language to fenced block (MD040).

Apply:

-```
+```http
 POST /wp-json/dokan/v1/reports/your-report-type/export
-```
+```

193-195: Add language to fenced block (MD040).

Apply:

-```
+```http
 GET /wp-json/dokan/v1/reports/your-report-type/export/{export_id}/status
-```
+```
includes/REST/ExportController.php (1)

68-74: PHPMD “unused parameter” warning.

Benign. Optionally prefix $request with underscore or add an inline ignore to appease linters.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a56293 and 08fadb0.

📒 Files selected for processing (5)
  • docs/export-controller-usage.md (1 hunks)
  • includes/REST/ExportController.php (1 hunks)
  • includes/REST/Manager.php (3 hunks)
  • includes/REST/WithdrawExportController.php (1 hunks)
  • src/admin/dashboard/pages/withdraw/index.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
includes/REST/ExportController.php (1)
includes/REST/WithdrawExportController.php (1)
  • get_items_permissions_check (192-198)
includes/REST/WithdrawExportController.php (3)
includes/Vendor/Vendor.php (1)
  • get_name (321-325)
includes/Withdraw/Withdraw.php (6)
  • get_user_id (93-95)
  • get_amount (104-106)
  • get_date (115-117)
  • get_method (137-139)
  • get_details (159-161)
  • get_note (148-150)
includes/REST/ExportController.php (1)
  • get_items_permissions_check (68-74)
src/admin/dashboard/pages/withdraw/index.tsx (3)
src/utilities/Accounting.ts (1)
  • formatPrice (3-62)
types/externals.d.ts (5)
  • formatPrice (79-79)
  • DokanTab (61-61)
  • Filter (60-60)
  • DataViews (59-59)
  • DokanModal (65-65)
src/utilities/Truncate.ts (1)
  • truncate (1-6)
🪛 PHPMD (2.15.0)
includes/REST/ExportController.php

68-68: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)

includes/REST/WithdrawExportController.php

192-192: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)

🪛 markdownlint-cli2 (0.17.2)
docs/export-controller-usage.md

175-175: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


193-193: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (5)
includes/REST/WithdrawExportController.php (1)

154-160: Good: constrained status enum.

The fixed enum reduces invalid queries and simplifies validation.

includes/REST/Manager.php (2)

188-197: Class load in filter callback is fine; keep require_once guarded.

The guard avoids autoload assumptions. Looks good.


257-259: Registration in class map looks correct.

ExportController and WithdrawExportController are discoverable for route registration.

docs/export-controller-usage.md (1)

246-253: Keep leading slash in custom endpoint example (matches Manager fix).

Example already shows leading slash—good. Ensure code follows the same convention.

includes/REST/ExportController.php (1)

33-60: Route registration aligns with Woo export controller.

Once rest_base is fixed, both routes look correct with proper permissions and schemas.

*
* @var string
*/
protected $rest_base = '/reports/(?P<type>[a-z]+)/export';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove leading slash from $rest_base to prevent double slashes in registered routes.

register_rest_route prepends '/', so rest_base should be relative.

Apply:

-    protected $rest_base = '/reports/(?P<type>[a-z]+)/export';
+    protected $rest_base = 'reports/(?P<type>[a-z]+)/export';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected $rest_base = '/reports/(?P<type>[a-z]+)/export';
protected $rest_base = 'reports/(?P<type>[a-z]+)/export';
🤖 Prompt for AI Agents
In includes/REST/ExportController.php around line 26, the $rest_base currently
starts with a leading slash which causes register_rest_route to produce double
slashes; remove the leading slash so $rest_base is relative (e.g. change
"/reports/(?P<type>[a-z]+)/export" to "reports/(?P<type>[a-z]+)/export") and
verify any concatenation when registering the route expects a relative base.

Comment on lines +209 to +215
public function register_data_endpoint( string $endpoint, string $type ): string {
if ( 'withdraws' === $type ) {
return 'dokan/v1/reports/withdraws';
}

return $endpoint;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure data endpoint starts with a leading slash.

WooCommerce’s filter typically works with a leading-slash path (docs below also show this). Returning a bare path risks malformed URLs.

Apply:

-        if ( 'withdraws' === $type ) {
-            return 'dokan/v1/reports/withdraws';
-        }
+        if ( 'withdraws' === $type ) {
+            return '/dokan/v1/reports/withdraws';
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function register_data_endpoint( string $endpoint, string $type ): string {
if ( 'withdraws' === $type ) {
return 'dokan/v1/reports/withdraws';
}
return $endpoint;
}
public function register_data_endpoint( string $endpoint, string $type ): string {
if ( 'withdraws' === $type ) {
return '/dokan/v1/reports/withdraws';
}
return $endpoint;
}
🤖 Prompt for AI Agents
In includes/REST/Manager.php around lines 209-215, the register_data_endpoint
function returns paths without a leading slash which can produce malformed URLs;
update the function so the special-case path is returned as
'/dokan/v1/reports/withdraws' (with a leading slash) and normalize the general
return by ensuring $endpoint begins with a leading slash (prepend one if
missing) before returning it.

Comment on lines +23 to +30
protected $namespace = 'dokan/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = '/reports/withdraws';

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove leading slash from $rest_base to avoid route normalization quirks.

Using a leading slash in rest_base combined with register_rest_route patterns elsewhere can generate double slashes and rely on normalization. Keep rest_base relative.

Apply:

-    protected $rest_base = '/reports/withdraws';
+    protected $rest_base = 'reports/withdraws';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected $namespace = 'dokan/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = '/reports/withdraws';
protected $namespace = 'dokan/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'reports/withdraws';
🤖 Prompt for AI Agents
In includes/REST/WithdrawExportController.php around lines 23 to 30, the
protected $rest_base currently starts with a leading slash which can cause
double-slash route issues; change it to be relative by removing the leading
slash (e.g. 'reports/withdraws') so register_rest_route constructs routes
consistently and avoid normalization quirks; update any usages or
documentation/comments accordingly.

Copy link
Copy Markdown
Member

@mrabbani mrabbani Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls check this feedback to remove the first the slash(/).

CC: @shohag121 via.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +56 to +69
public function prepare_item_for_export( $item ) {
$vendor = dokan()->vendor->get( $item['user_id'] );

return array(
'id' => $item['id'],
'user_id' => $item['user_id'],
'vendor_name' => $vendor->get_name(),
'amount' => $item['amount'],
'date' => $item['date'],
'status' => $item['status'],
'payment_method' => $item['method'],
'details' => $item['details'],
'note' => $item['note'],
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Null-safe vendor name resolution (avoid fatal on missing vendor).

dokan()->vendor->get(...) can be null/invalid. Calling get_name() unguarded risks fatals during export.

Apply:

-        $vendor = dokan()->vendor->get( $item['user_id'] );
+        $vendor = dokan()->vendor->get( $item['user_id'] );
+        $vendor_name = ( $vendor && method_exists( $vendor, 'get_name' ) ) ? $vendor->get_name() : '';
...
-            'vendor_name'    => $vendor->get_name(),
+            'vendor_name'    => $vendor_name,

Optional: micro-cache vendor names to cut repeated lookups during large exports.

// e.g., static $vendor_names = []; $vendor_names[$id] ??= ...
🤖 Prompt for AI Agents
In includes/REST/WithdrawExportController.php around lines 56-69, calling
dokan()->vendor->get($item['user_id'])->get_name() is unsafe because get(...)
can return null; guard against null before calling get_name() and fall back to
an empty string or placeholder (e.g., '') for vendor_name. Update the code to
fetch the vendor, check if it's truthy before calling get_name(), and assign a
default when absent; optionally implement a simple static micro-cache keyed by
user_id to avoid repeated get() calls during large exports.

# Conflicts:
#	src/admin/dashboard/style.scss
…t/dokan-widthraw-admin-list

# Conflicts:
#	includes/DependencyManagement/Providers/AdminDashboardServiceProvider.php
#	includes/REST/Manager.php
…raw admin UI

- Created `AdminDataViewTable` component for reusable, filterable, and responsive data tables.
- Integrated new data table in withdraw admin page for better UX.
- Added new SCSS styles to support component styling.
- Enhanced withdraw page functionality with improved tabs, export options, and filtering.
- Updated `handleBulkAction` to process only items with the `paypal` method before mapping their IDs.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (11)
src/admin/dashboard/pages/withdraw/index.tsx (9)

34-34: Remove RawHTML from price helper to prevent XSS risk.

The formatPrice utility returns plain text, making RawHTML unnecessary and dangerous. Render the formatted price as safe text instead.

Apply this diff:

-const price = ( amount ) => <RawHTML>{ formatPrice( amount ) }</RawHTML>;
+const price = ( amount ) => <span>{ formatPrice( amount ) }</span>;

147-167: Normalize backend 'completed' status to 'approved' for consistent UI.

The backend may return completed while your UI expects approved. Without normalization, items with completed status will display with incorrect colors and labels.

Apply this diff:

             render: ( { item } ) => {
+                const normalized = item.status === 'completed' ? 'approved' : item.status;
                 const statusColors = {
                     pending: 'bg-[#FDF2F8] text-[#9D174D]',
                     approved: 'bg-[#D4FBEF] text-[#00563F]',
                 };
                 return (
                     <span
                         className={ `inline-flex items-center px-3.5 py-1.5 rounded-full text-xs font-medium ${
-                            statusColors[ item.status ] ||
+                            statusColors[ normalized ] ||
                             'bg-[#F1F1F4] text-[#393939]'
                         }` }
                     >
                         {
                             WITHDRAW_STATUSES.find(
-                                ( status ) => status.value === item.status
+                                ( status ) => status.value === normalized
                             )?.label
                         }
                     </span>
                 );
             },

189-192: Use nullish coalescing to handle receivable value of 0 correctly.

When item.receivable is 0, the current || operator incorrectly falls back to item.amount. Use ?? instead to only fall back when the value is null or undefined.

Apply this diff:

                 <div className="font-medium text-gray-900">
-                    { price( item.receivable || item.amount ) }
+                    { price( item.receivable ?? item.amount ) }
                 </div>

212-219: Replace RawHTML with safe text rendering to prevent XSS.

Using RawHTML for both tooltip and cell content creates an XSS vulnerability. Render the details as plain text and use CSS to preserve formatting.

Apply this diff:

-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
+                    <Tooltip content={ <span className="whitespace-pre-line">{ full }</span> }>
                         <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
                         </p>
                     </Tooltip>

370-370: Account for 'completed' status in delete eligibility check.

The backend may return completed instead of approved. The delete action should treat both as ineligible.

Apply this diff:

-            isEligible: ( item ) => item?.status !== 'approved',
+            isEligible: ( item ) => ! [ 'approved', 'completed' ].includes( item?.status ),

562-568: Add leading slash to REST path for consistent endpoint resolution.

The path parameter to apiFetch should start with / to ensure correct absolute endpoint resolution across different WordPress installations.

Apply this diff:

             const response = await apiFetch( {
-                path: addQueryArgs( 'dokan/v2/withdraw', queryArgs ),
+                path: addQueryArgs( '/dokan/v2/withdraw', queryArgs ),
                 headers: {
                     'Content-Type': 'application/json',
                 },
                 parse: false,
             } );

1321-1328: Replace RawHTML with safe rendering to prevent XSS in modal.

Injecting raw HTML to preserve line breaks is unsafe. Use CSS whitespace-pre-line or render line-separated elements instead.

Apply this diff:

-                                                <RawHTML>
-                                                    { processDetails(
-                                                        item.details,
-                                                        item.method
-                                                    )
-                                                        .split( '\n' )
-                                                        .join( '<br />' ) }
-                                                </RawHTML>
+                                                <span className="whitespace-pre-line">
+                                                    { processDetails( item.details, item.method ) }
+                                                </span>

1373-1382: Account for 'completed' status when showing delete option.

The backend may return completed instead of approved. The delete option should be hidden for both statuses.

Apply this diff:

                                                 if (
-                                                    item.status !== 'approved'
+                                                    ! [ 'approved', 'completed' ].includes( item.status )
                                                 ) {
                                                     options.push( {
                                                         label: __(
                                                             'Delete',
                                                             'dokan-lite'
                                                         ),
                                                         value: 'delete',
                                                     } );
                                                 }

509-520: Fix REST path and avoid leaking internal view state in export.

Two issues remain:

  1. The REST path lacks a leading slash, which can break requests on some installations
  2. Spreading the entire view object sends internal keys (type, layout, titleField, etc.) as query params

Whitelist only server-supported parameters before calling addQueryArgs.

Apply this diff:

             onClick={ async () => {
                 try {
-                    const path = addQueryArgs( 'dokan/v2/withdraw', {
-                        ...view,
-                        is_export: true,
-                    } );
+                    const exportArgs = {
+                        per_page: view?.perPage ?? 10,
+                        page: view?.page ?? 1,
+                        search: view?.search ?? '',
+                        status: view?.status === 'all' ? '' : view?.status,
+                        ...filterArgs,
+                        is_export: true,
+                        ...(view?.sort?.field ? { orderby: view.sort.field } : {}),
+                        ...(view?.sort?.direction ? { order: view.sort.direction } : {}),
+                    };
+                    const path = addQueryArgs( '/dokan/v2/withdraw', exportArgs );
                     const res = await apiFetch( { path } );
includes/REST/WithdrawExportController.php (2)

29-29: Remove leading slash from rest_base to avoid route normalization issues.

Using a leading slash in rest_base can cause double-slash issues when combined with register_rest_route patterns. Keep it relative for consistent route construction.

Apply this diff:

-    protected $rest_base = '/reports/withdraws';
+    protected $rest_base = 'reports/withdraws';

57-71: Add null-safe vendor name resolution to prevent fatal errors.

dokan()->vendor->get() can return null or an invalid object. Calling get_name() without a guard will cause a fatal error during export.

Apply this diff:

     public function prepare_item_for_export( $item ) {
         $vendor = dokan()->vendor->get( $item['user_id'] );
+        $vendor_name = ( $vendor && method_exists( $vendor, 'get_name' ) ) 
+            ? $vendor->get_name() 
+            : '';

         return array(
             'id'             => $item['id'],
             'user_id'        => $item['user_id'],
-            'vendor_name'    => $vendor->get_name(),
+            'vendor_name'    => $vendor_name,
             'amount'         => $item['amount'],
             'payable'        => $item['payable'],
             'date'           => $item['date'],
             'status'         => $item['status'],
             'payment_method' => $item['method'],
             'details'        => $item['details'],
             'note'           => $item['note'],
         );
     }
🧹 Nitpick comments (2)
package.json (2)

68-68: Pin GitHub dependency to a commit SHA for reproducible builds.

"@getdokan/dokan-ui": "github:getdokan/dokan-ui#dokan-plugin" is mutable. Pin to a specific commit/tag.


87-88: Heavy deps; consider size controls.

d3 and dompurify can bloat bundles. Prefer modular imports or dynamic import where feasible; ensure DOMPurify usage is constrained to trusted contexts.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9bb51a1 and c9389c7.

📒 Files selected for processing (4)
  • includes/REST/WithdrawExportController.php (1 hunks)
  • package.json (3 hunks)
  • src/admin/dashboard/pages/withdraw/index.tsx (1 hunks)
  • src/components/dataviews/AdminDataViewTable.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/dataviews/AdminDataViewTable.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
includes/REST/WithdrawExportController.php (3)
includes/Vendor/Vendor.php (1)
  • get_name (321-325)
includes/Withdraw/Withdraw.php (7)
  • get_user_id (93-95)
  • get_amount (104-106)
  • get_receivable_amount (192-194)
  • get_date (115-117)
  • get_method (137-139)
  • get_details (159-161)
  • get_note (148-150)
includes/REST/ExportController.php (1)
  • get_items_permissions_check (68-74)
src/admin/dashboard/pages/withdraw/index.tsx (3)
src/utilities/Accounting.ts (1)
  • formatPrice (3-62)
types/externals.d.ts (3)
  • formatPrice (79-79)
  • DataViews (59-59)
  • DokanModal (65-65)
src/utilities/Truncate.ts (1)
  • truncate (1-6)
🪛 GitHub Check: Run PHPCS inspection
includes/REST/WithdrawExportController.php

[warning] 200-200:
Found unknown capability "dokan_view_reports" in function call to current_user_can(). Please check the spelling of the capability. If this is a custom capability, please verify the capability is registered with WordPress via a call to WP_Role(s)->add_cap().
Custom capabilities can be made known to this sniff by setting the "custom_capabilities" property in the PHPCS ruleset.

🪛 PHPMD (2.15.0)
includes/REST/WithdrawExportController.php

199-199: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e tests (3, 3)
  • GitHub Check: api tests (1, 1)
  • GitHub Check: e2e tests (1, 3)
  • GitHub Check: e2e tests (2, 3)
🔇 Additional comments (3)
package.json (3)

28-29: Tooling bump looks good; confirm environment compatibility.

Updates to @wordpress/scripts and dependency-extraction are fine. Please confirm Node version and builds locally/CI with the new toolchain.


59-59: Dev tool addition is fine.

wp-readme-to-markdown as a devDependency is appropriate.


70-86: Verify WordPress Core compatibility and extraction.

These @WordPress packages are fairly recent. Ensure:

  • Site minimum WP Core provides matching script handles (esp. @wordpress/dataviews, components, data).
  • Dependency extraction is enabled and enqueues correct core assets.

Based on learnings

Comment on lines +42 to +43
"tailwind-merge": "^2.6.0",
"tailwind-scrollbar-hide": "^4.0.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Duplicate tailwind-merge (dev vs prod) with mismatched versions.

Present in devDependencies (^2.6.0) and dependencies (^2.5.5 at line 96). Keep only one (prefer dependencies if used at runtime) and align versions.

Apply:

   "devDependencies": {
-    "tailwind-merge": "^2.6.0",
     "tailwind-scrollbar-hide": "^4.0.0",
   },

And bump the runtime one for consistency:

-    "tailwind-merge": "^2.5.5",
+    "tailwind-merge": "^2.6.0",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"tailwind-merge": "^2.6.0",
"tailwind-scrollbar-hide": "^4.0.0",
"devDependencies": {
- "tailwind-merge": "^2.6.0",
"tailwind-scrollbar-hide": "^4.0.0",
},
"dependencies": {
"tailwind-merge": "^2.6.0",
// …other dependencies
},
🤖 Prompt for AI Agents
In package.json around lines 42-43 and 96, there is a duplicate tailwind-merge
entry with mismatched versions; remove the duplicate and align versions. If
tailwind-merge is required at runtime keep it in "dependencies" and delete it
from "devDependencies"; set the remaining dependency version to "^2.6.0" (bump
the runtime entry to ^2.6.0 if it's the one kept) so both places are consistent,
then run npm/yarn install to update lockfile.

Comment on lines +87 to +88
"d3": "^7.9.0",
"dompurify": "^3.2.6",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

React peer deps missing for added React-based libs.

lucide-react, react-router-dom, qrcode.react, and usehooks-ts require react (and react-dom). Neither is listed. Builds may fail or bundle a duplicate React.

Options:

  • Prefer WordPress-native alternatives (e.g., @wordpress/icons instead of lucide-react; avoid react-router-dom if not bundling React), or
  • Add and align React deps with Core, then configure externals/alias to avoid duplicates.

If proceeding, add:

   "dependencies": {
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
     "react-router-dom": "^6.27.0",
     "usehooks-ts": "^3.1.0"
   }

And ensure webpack/dependency-extraction maps React correctly (or you’ll ship a second React).

Also applies to: 89-93, 97-97


🏁 Script executed:

#!/bin/bash
# Check if react/react-dom are declared
jq '.dependencies.react, .dependencies["react-dom"]' package.json

# Locate imports that require React presence
rg -nP --type=ts --type=tsx --type=js --type=jsx -C2 \
  -e 'from\s+[\"\']react[\"\']' \
  -e 'from\s+[\"\']react-router-dom[\"\']' \
  -e 'from\s+[\"\']lucide-react[\"\']' \
  -e 'from\s+[\"\']qrcode\.react[\"\']' \
  -e 'usehooks-ts'

Length of output: 79


🏁 Script executed:

#!/bin/bash
echo "Dependencies:"
jq '.dependencies.react, .dependencies["react-dom"]' package.json
echo "DevDependencies:"
jq '.devDependencies.react, .devDependencies["react-dom"]' package.json

echo "Searching for React-related imports:"
for pkg in react react-router-dom lucide-react qrcode.react usehooks-ts; do
  echo "=== $pkg ==="
  rg -nP --type=js --type=jsx --type=ts --type=tsx -C2 "from\s+['\"]$pkg['\"]" || true
done

Length of output: 328


🏁 Script executed:

#!/bin/bash
echo "Fallback search for React-related imports:"
for pkg in react react-router-dom lucide-react "qrcode.react" usehooks-ts; do
  echo "=== $pkg ==="
  rg -n -e "import .* from ['\"]$pkg['\"]" -C2 --glob '*.{js,jsx,ts,tsx}' || true
  rg -n -e "require\(['\"]$pkg['\"]\)" -C2 --glob '*.{js,jsx,ts,tsx}' || true
done

Length of output: 16052


Add React and React-DOM dependencies and configure externals

  • Add "react": "^18.2.0" and "react-dom": "^18.2.0" to your package.json dependencies.
  • Configure your bundler (webpack dependency-extraction or externals/alias) to avoid shipping a duplicate React instance.
🤖 Prompt for AI Agents
In package.json around lines 87-88 add "react": "^18.2.0" and "react-dom":
"^18.2.0" to dependencies; then update your bundler config to prevent shipping a
duplicate React instance by marking react and react-dom as externals (or using
the dependency-extraction plugin) or by setting resolve.alias to a single path
to your installed react (ensuring both consumers use the same version), and
confirm any peerDependencies or host app also use the same 18.2.0 version to
avoid multiple React copies.

Comment on lines +529 to +600
const fetchWithdraws = async () => {
setIsLoading( true );
try {
const queryArgs = {
per_page: view?.perPage ?? 20,
page: view?.page ?? 1,
search: view?.search ?? '',
status: view?.status === 'all' ? '' : view?.status,
...filterArgs,
};

// Handle sorting
if ( view?.sort?.field ) {
queryArgs.orderby = view.sort.field;
}
if ( view?.sort?.direction ) {
queryArgs.order = view.sort.direction;
}

// Handle filters
if ( view?.filters ) {
view.filters.forEach( ( filter ) => {
if (
filter.field === 'status' &&
filter.operator === 'isAny'
) {
queryArgs.status = filter.value?.join( ',' );
}
} );
}

// Fetch data from the REST API
const response = await apiFetch( {
path: addQueryArgs( 'dokan/v2/withdraw', queryArgs ),
headers: {
'Content-Type': 'application/json',
},
parse: false, // Get raw response to access headers
} );

const data = await response.json();
const totalItems = parseInt(
response.headers.get( 'X-WP-Total' ) || 0
);

setTotalItems( totalItems );
setData( data );

// Extract status counts from response headers
const pendingCount = parseInt(
response.headers.get( 'X-Status-Pending' ) || 0
);
const completedCount = parseInt(
response.headers.get( 'X-Status-Completed' ) || 0
);
const cancelledCount = parseInt(
response.headers.get( 'X-Status-Cancelled' ) || 0
);

const counts = {
all: pendingCount + completedCount + cancelledCount,
pending: pendingCount,
approved: completedCount, // 'approved' maps to 'completed' in backend
cancelled: cancelledCount,
};
setStatusCounts( counts );
} catch ( error ) {
console.error( 'Error fetching withdraws:', error );
} finally {
setIsLoading( false );
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Memoize fetchWithdraws to prevent stale closures and duplicate requests.

The fetchWithdraws function is recreated on every render and can capture stale values of filterArgs. Wrap it in useCallback and update the useEffect dependency to use the memoized version.

Apply this diff:

-    const fetchWithdraws = async () => {
+    const fetchWithdraws = useCallback( async () => {
         setIsLoading( true );
         try {
             const queryArgs = {
                 per_page: view?.perPage ?? 20,
                 page: view?.page ?? 1,
                 search: view?.search ?? '',
                 status: view?.status === 'all' ? '' : view?.status,
                 ...filterArgs,
             };
             // ... rest of function
         } finally {
             setIsLoading( false );
         }
-    };
+    }, [ view, filterArgs ] );

     // Fetch withdraws when view changes
     useEffect( () => {
         fetchWithdraws();
-    }, [ view ] );
+    }, [ fetchWithdraws ] );

Also applies to: 822-824

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (13)
src/admin/dashboard/pages/withdraw/index.tsx (7)

34-34: Remove unnecessary RawHTML wrapper from price function.

formatPrice returns plain text; wrapping it in RawHTML is both unnecessary and poses an XSS risk if the function output ever includes user-supplied data.

Apply this diff:

-const price = ( amount ) => <RawHTML>{ formatPrice( amount ) }</RawHTML>;
+const price = ( amount ) => formatPrice( amount );

190-190: Use nullish coalescing to handle zero receivable correctly.

When receivable is 0, the current || operator incorrectly falls back to amount. Use ?? instead.

Apply this diff:

-                    { price( item.receivable || item.amount ) }
+                    { price( item.receivable ?? item.amount ) }

303-303: Account for 'completed' status in action eligibility checks.

The backend may return completed instead of approved. Treat both as equivalent in eligibility logic.

Apply this diff at line 303:

-            isEligible: ( item ) => item?.status === 'pending',
+            isEligible: ( item ) => item?.status === 'pending',

Apply this diff at line 325:

-            isEligible: ( item ) => item?.status === 'pending',
+            isEligible: ( item ) => item?.status === 'pending',

Apply this diff at line 370:

-            isEligible: ( item ) => item?.status !== 'approved',
+            isEligible: ( item ) => ! [ 'approved', 'completed' ].includes( item?.status ),

Also applies to: 325-325, 370-370


564-564: Add missing leading slash to REST path.

The REST path must start with / for correct endpoint resolution on all WordPress installations.

Apply this diff:

             const response = await apiFetch( {
-                path: addQueryArgs( 'dokan/v2/withdraw', queryArgs ),
+                path: addQueryArgs( '/dokan/v2/withdraw', queryArgs ),
                 headers: {

1323-1330: Remove RawHTML from modal; render details safely.

Injecting HTML via RawHTML for payment details is unsafe. Render as text with CSS line-break preservation.

Apply this diff:

-                                                <RawHTML>
-                                                    { processDetails(
-                                                        item.details,
-                                                        item.method
-                                                    )
-                                                        .split( '\n' )
-                                                        .join( '<br />' ) }
-                                                </RawHTML>
+                                                <span className="whitespace-pre-line">
+                                                    { processDetails( item.details, item.method ) }
+                                                </span>

1374-1376: Account for 'completed' in modal delete eligibility.

The backend may return completed instead of approved. Hide delete for both statuses.

Apply this diff:

                                                 if (
-                                                    item.status !== 'approved'
+                                                    ! [ 'approved', 'completed' ].includes( item.status )
                                                 ) {

147-166: Normalize backend 'completed' status to 'approved' for UI display.

The backend may return completed instead of approved, causing blank labels and missing color styling.

Apply this diff:

             render: ( { item } ) => {
+                const normalized = item.status === 'completed' ? 'approved' : item.status;
                 const statusColors = {
                     pending: 'bg-[#FDF2F8] text-[#9D174D]',
                     approved: 'bg-[#D4FBEF] text-[#00563F]',
+                    cancelled: 'bg-[#F1F1F4] text-[#393939]',
                 };
                 return (
                     <span
                         className={ `inline-flex items-center px-3.5 py-1.5 rounded-full text-xs font-medium ${
-                            statusColors[ item.status ] ||
+                            statusColors[ normalized ] ||
                             'bg-[#F1F1F4] text-[#393939]'
                         }` }
                     >
                         {
-                            WITHDRAW_STATUSES.find(
-                                ( status ) => status.value === item.status
-                            )?.label
+                            WITHDRAW_STATUSES.find( ( s ) => s.value === normalized )?.label || normalized
                         }
                     </span>
                 );
             },
src/components/dataviews/AdminDataViewTable.tsx (4)

19-19: Avoid importing from internal build-types paths.

Importing from build-types/ couples code to internal implementation details that may change without notice across package versions.

Apply this diff:

-import { Item } from '@wordpress/components/build-types/navigation/types';
+// Define Item type locally or import from a stable public API
+type Item = { id: string; [key: string]: any };

123-133: Fix Rules of Hooks violation: hoist useEffect unconditionally.

Calling useEffect inside if (responsive) violates React's Rules of Hooks. The effect also uses view/onChangeView without proper dependencies, causing stale closures.

Apply this diff:

-    if ( responsive ) {
-        // Set view type `list` for mobile device.
-        useEffect(
-            () =>
-                onChangeView( {
-                    ...view,
-                    type: windowWidth <= 768 ? 'list' : 'table',
-                } ),
-            [ windowWidth ]
-        );
-    }
+    useEffect( () => {
+        if ( ! responsive ) {
+            return;
+        }
+        const nextType = windowWidth <= 768 ? 'list' : 'table';
+        onChangeView( ( prevView ) =>
+            prevView.type === nextType
+                ? prevView
+                : { ...prevView, type: nextType }
+        );
+    }, [ responsive, windowWidth, onChangeView ] );

149-152: Use namespace-aware key for filter button uniqueness.

The hardcoded key 'dokan_admin_dashboard_filter_button' will cause collisions if multiple AdminDataViewTable instances with different namespaces render on the same page.

Apply this diff:

                  const hasButton = existing.some(
                      ( comp: any ) =>
                          comp &&
-                         comp.key === 'dokan_admin_dashboard_filter_button'
+                         comp.key === `dokan_admin_${ tableNameSpace }_filter_button`
                  );

And update the button key:

                  const newButton = (
                      <button
                          type="button"
                          ref={ setButtonRef }
-                         key="dokan_admin_dashboard_filter_button"
+                         key={ `dokan_admin_${ tableNameSpace }_filter_button` }
                          title={ __( 'Filter', 'dokan-lite' ) }

Also applies to: 164-164


193-195: Remove or replace literal {item_name} placeholder in data-filter-id.

The data-filter-id contains a literal {item_name} placeholder that will render verbatim instead of being substituted.

Apply this diff to remove it:

             data-filter-id={ `dokan_admin_${ snakeCase(
                 namespace
-            ) }_dataviews_{item_name}` }
+            ) }_dataviews` }
includes/REST/WithdrawExportController.php (2)

29-29: Remove leading slash from $rest_base to avoid route normalization issues.

Using a leading slash in rest_base combined with register_rest_route can generate double slashes. Keep it relative.

Apply this diff:

-    protected $rest_base = '/reports/withdraws';
+    protected $rest_base = 'reports/withdraws';

57-63: Add null-safety check for vendor before calling get_name().

dokan()->vendor->get( $item['user_id'] ) can return null or an invalid vendor object. Calling get_name() unguarded risks fatal errors during export.

Apply this diff:

     public function prepare_item_for_export( $item ) {
         $vendor = dokan()->vendor->get( $item['user_id'] );
+        $vendor_name = ( $vendor && method_exists( $vendor, 'get_name' ) ) ? $vendor->get_name() : '';

         return array(
             'id'             => $item['id'],
             'user_id'        => $item['user_id'],
-            'vendor_name'    => $vendor->get_name(),
+            'vendor_name'    => $vendor_name,
             'amount'         => $item['amount'],
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9bb51a1 and 815e358.

📒 Files selected for processing (4)
  • includes/REST/WithdrawExportController.php (1 hunks)
  • package.json (3 hunks)
  • src/admin/dashboard/pages/withdraw/index.tsx (1 hunks)
  • src/components/dataviews/AdminDataViewTable.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
includes/REST/WithdrawExportController.php (3)
includes/Vendor/Vendor.php (1)
  • get_name (321-325)
includes/Withdraw/Withdraw.php (7)
  • get_user_id (93-95)
  • get_amount (104-106)
  • get_receivable_amount (192-194)
  • get_date (115-117)
  • get_method (137-139)
  • get_details (159-161)
  • get_note (148-150)
includes/REST/ExportController.php (1)
  • get_items_permissions_check (68-74)
src/admin/dashboard/pages/withdraw/index.tsx (3)
src/utilities/Accounting.ts (1)
  • formatPrice (3-62)
types/externals.d.ts (3)
  • formatPrice (79-79)
  • DataViews (59-59)
  • DokanModal (65-65)
src/utilities/Truncate.ts (1)
  • truncate (1-6)
src/components/dataviews/AdminDataViewTable.tsx (3)
src/components/AdminFilter.tsx (2)
  • Field (12-16)
  • AdminFilterProps (18-39)
types/externals.d.ts (3)
  • ViewportDimensions (90-90)
  • kebabCase (81-81)
  • DataViews (59-59)
src/components/AdminTab.tsx (1)
  • AdminTabProps (17-29)
🪛 GitHub Check: Run PHPCS inspection
includes/REST/WithdrawExportController.php

[warning] 200-200:
Found unknown capability "dokan_view_reports" in function call to current_user_can(). Please check the spelling of the capability. If this is a custom capability, please verify the capability is registered with WordPress via a call to WP_Role(s)->add_cap().
Custom capabilities can be made known to this sniff by setting the "custom_capabilities" property in the PHPCS ruleset.

🪛 PHPMD (2.15.0)
includes/REST/WithdrawExportController.php

199-199: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e tests (1, 3)
  • GitHub Check: e2e tests (2, 3)
  • GitHub Check: e2e tests (3, 3)
  • GitHub Check: api tests (1, 1)

Comment on lines +28 to +29
"@wordpress/dependency-extraction-webpack-plugin": "^6.32.0",
"@wordpress/scripts": "^30.25.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify WordPress package versions exist and are compatible.

Several WordPress packages specify versions like ^6.32.0, ^7.32.0, ^30.5.0 which don't match official WordPress release patterns. WordPress 6.x releases are typically 6.5–6.8.

Please verify these package versions are valid:

Also applies to: 71-86


🌐 Web query:

What are the latest stable versions of @wordpress/dependency-extraction-webpack-plugin, @wordpress/scripts, @wordpress/api-fetch, @wordpress/components, and @wordpress/dataviews packages on npm?

💡 Result:

Here are the latest stable npm versions I found:

  • @wordpress/dependency-extraction-webpack-plugin — 6.30.0. [1]
  • @wordpress/scripts — 30.23.0 (tagged "latest"). [2]
  • @wordpress/api-fetch — 7.30.0. [3]
  • @wordpress/components — 30.3.0. [4]
  • @wordpress/dataviews — 7.0.0. [5]

Sources:

  1. npm package page for @wordpress/dependency-extraction-webpack-plugin.
  2. npm versions/tags listing for @wordpress/scripts.
  3. npm package page for @wordpress/api-fetch.
  4. npm package page for @wordpress/components.
  5. npm package page for @wordpress/dataviews.

Align WordPress package versions to published npm releases

  • @wordpress/dependency-extraction-webpack-plugin: ^6.32.0 → ^6.30.0
  • @wordpress/scripts: ^30.25.0 → ^30.23.0
  • @wordpress/api-fetch: ^7.32.0 → ^7.30.0
  • @wordpress/components: ^30.5.0 → ^30.3.0
  • @wordpress/dataviews: ^7.32.0 → ^7.0.0

Comment on lines +42 to +43
"tailwind-merge": "^2.6.0",
"tailwind-scrollbar-hide": "^4.0.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Remove duplicate tailwind-merge from devDependencies.

tailwind-merge is listed in both devDependencies (v2.6.0) and dependencies (v2.5.5), creating version conflicts. Keep it only in dependencies since it's used at runtime.

Apply this diff:

     "@tailwindcss/forms": "^0.5.9",
     "@tailwindcss/typography": "^0.5.15",
     "@wordpress/dependency-extraction-webpack-plugin": "^6.32.0",
     "@wordpress/scripts": "^30.25.0",
     "chartjs-adapter-moment": "^1.0.1",
     "debounce": "^1.2.1",
     "eslint-import-resolver-typescript": "^3.7.0",
     "eslint-plugin-import": "^2.31.0",
     "fs-extra": "^10.1.0",
     "jed": "^1.1.1",
     "less": "^4.1.3",
     "less-loader": "^6.0.0",
     "lodash": "^4.17.21",
     "mini-css-extract-plugin": "^2.7.6",
     "papaparse": "^5.4.1",
     "replace-in-file": "^6.3.5",
-    "tailwind-merge": "^2.6.0",
-    "tailwind-scrollbar-hide": "^4.0.0",
     "tailwindcss": "^3.3.3",

Also applies to: 96-96

🤖 Prompt for AI Agents
In package.json around lines 42-43 (and also line 96), there is a duplicate
"tailwind-merge" entry in devDependencies (v2.6.0) while dependencies already
contains v2.5.5; remove the "tailwind-merge" entry from devDependencies so it
exists only under dependencies (keep the runtime dependency as-is), then update
package-lock.json/yarn.lock by running the package manager install to ensure the
lockfile reflects the single entry.

Comment on lines +212 to +218
<Tooltip content={ <RawHTML>{ full }</RawHTML> }>
<p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
<RawHTML>
{ truncate ? truncate( full, 120 ) : full }
</RawHTML>
</p>
</Tooltip>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Replace RawHTML with safe text rendering in tooltips.

Using RawHTML for user-supplied payment details and notes creates XSS vulnerabilities. Render as plain text and preserve line breaks with CSS.

Apply this diff for details (lines 212-218):

-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
+                    <Tooltip content={ <span className="whitespace-pre-line">{ full }</span> }>
                         <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
                         </p>
                     </Tooltip>

Apply this diff for notes (lines 228-235):

-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
+                    <Tooltip content={ <span className="whitespace-pre-line">{ full }</span> }>
                         <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
                         </p>
                     </Tooltip>

Also applies to: 228-235

🤖 Prompt for AI Agents
In src/admin/dashboard/pages/withdraw/index.tsx around lines 212-218 and
228-235, the Tooltip currently uses RawHTML to render user-supplied payment
details and notes which causes XSS risk; replace RawHTML with plain text
rendering and<codegen_instructions>
In src/admin/dashboard/pages/withdraw/index.tsx around lines 212-218 and
228-235, the Tooltip currently uses RawHTML to render user-supplied payment
details and notes which causes XSS risk; replace RawHTML with plain text
rendering (no dangerously-set HTML), render the possibly truncated text
directly, and preserve line breaks using CSS (e.g. a class that applies
white-space: pre-wrap or tailwind’s whitespace-pre-wrap) so content is escaped
and line breaks are kept in both the tooltip content and the <p> display.

@shohag121 shohag121 self-assigned this Oct 15, 2025
Comment on lines +23 to +30
protected $namespace = 'dokan/v1';
/**
* Route base.
*
* @var string
*/
protected $rest_base = '/reports/withdraws';

Copy link
Copy Markdown
Member

@mrabbani mrabbani Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls check this feedback to remove the first the slash(/).

CC: @shohag121 via.

@mrabbani mrabbani added Dev Review Done and removed Needs: Dev Review It requires a developer review and approval labels Oct 17, 2025
@dev-shahed
Copy link
Copy Markdown
Member

Note: This PR has multiple PR dependencies, so make sure this is marged now.

@shohan0120

@dev-shahed
Copy link
Copy Markdown
Member

dev-shahed commented Oct 17, 2025

@shohag121 This PR trigger a fatal error: `

[17-Oct-2025 08:44:18 UTC] PHP Fatal error:  Uncaught Error: Class "WeDevs\Dokan\Intelligence\Services\Provider" not found in /Users/wedevs/Documents/Sites/dokantesting/wp-content/plugins/dokan-pro/includes/Intelligence/Provider/BriaAi.php:16
Stack trace:
#0 phar:///Applications/Herd.app/Contents/Resources/valet/dump.phar/vendor/composer/ClassLoader.php(576): include()
#1 phar:///Applications/Herd.app/Contents/Resources/valet/dump.phar/vendor/composer/ClassLoader.php(427): {closure:Composer\Autoload\ClassLoader::initializeIncludeClosure():575}('/Users/wedevs/D...')
#2 /Users/wedevs/Documents/Sites/dokantesting/wp-content/plugins/dokan-pro/includes/Intelligence/Manager.php(23): Composer\Autoload\ClassLoader->loadClass('WeDevs\\DokanPro...')
#3 /Users/wedevs/Documents/Sites/dokantesting/wp-content/plugins/dokan-pro/dokan-pro.php(452): WeDevs\DokanPro\Intelligence\Manager->__construct()
#4 /Users/wedevs/Documents/Sites/dokantesting/wp-includes/class-wp-hook.php(324): Dokan_Pro->init_classes('')
#5 /Users/wedevs/Documents/Sites/dokantesting/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#6 /Users/wedevs/Documents/Sites/dokantesting/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#7 /Users/wedevs/Documents/Sites/dokantesting/wp-settings.php(727): do_action('init')
#8 /Users/wedevs/Documents/Sites/dokantesting/wp-config.php(129): require_once('/Users/wedevs/D...')
#9 /Users/wedevs/Documents/Sites/dokantesting/wp-load.php(50): require_once('/Users/wedevs/D...')
#10 /Users/wedevs/Documents/Sites/dokantesting/wp-admin/admin.php(35): require_once('/Users/wedevs/D...')
#11 /Applications/Herd.app/Contents/Resources/valet/server.php(167): require('/Users/wedevs/D...')
#12 {main}
  thrown in /Users/wedevs/Documents/Sites/dokantesting/wp-content/plugins/dokan-pro/includes/Intelligence/Provider/BriaAi.php on line 16

@dev-shahed
Copy link
Copy Markdown
Member

dev-shahed commented Oct 17, 2025

@shohag121

Issue 1: These actions are not working:

image

Issue 2: Would you please adjust the modal with screensize? Modal taking some gap would be great..

Issue 3: Please disable the future date section within calendar:

image

Issue 4: There are buttons within the Approved withdraw> view modal which doesn't have any action.

image

- Added `selectedAction` state to manage action selections in the withdraw modal.
- Improved action initialization for the "view" modal based on item status.
- Simplified conditional rendering and cleaned up modal structure for better readability.
- Refined button handling, ensuring appropriate action execution and modal behavior.
@shohag121
Copy link
Copy Markdown
Member

@dev-shahed Bhai, Updated the PR resolving all the above concerns.

@dev-shahed dev-shahed added 🎉 QA Approved This PR is approved by the QA team and removed Needs: Testing This requires further testing QA In Progress labels Oct 21, 2025
shohag121 and others added 2 commits October 21, 2025 11:46
- Extended `supportedKeys` filter to include 'withdraw' for better panel switch handling.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (14)
src/admin/dashboard/pages/withdraw/index.tsx (14)

34-34: Replace RawHTML with plain text rendering for prices to prevent XSS.

formatPrice returns plain text (string or number), so RawHTML is unnecessary and introduces XSS risk if the price value is ever user-controlled or manipulated.

Apply this diff:

-const price = ( amount ) => <RawHTML>{ formatPrice( amount ) }</RawHTML>;
+const price = ( amount ) => <span>{ formatPrice( amount ) }</span>;

147-167: Normalize 'completed' status to 'approved' for consistent UI rendering.

The backend may return status: 'completed', but the UI only defines colors and labels for 'pending', 'approved', and 'cancelled'. When 'completed' is returned, the status badge will show blank or default styling.

Apply this diff:

         render: ( { item } ) => {
+            const normalizedStatus = item.status === 'completed' ? 'approved' : item.status;
             const statusColors = {
                 pending: 'bg-[#FDF2F8] text-[#9D174D]',
                 approved: 'bg-[#D4FBEF] text-[#00563F]',
+                cancelled: 'bg-[#F1F1F4] text-[#393939]',
             };
             return (
                 <span
                     className={ `inline-flex items-center px-3.5 py-1.5 rounded-full text-xs font-medium ${
-                        statusColors[ item.status ] ||
+                        statusColors[ normalizedStatus ] ||
                         'bg-[#F1F1F4] text-[#393939]'
                     }` }
                 >
                     {
                         WITHDRAW_STATUSES.find(
-                            ( status ) => status.value === item.status
-                        )?.label
+                            ( status ) => status.value === normalizedStatus
+                        )?.label || normalizedStatus
                     }
                 </span>
             );
         },

189-191: Use nullish coalescing to avoid incorrect fallback when receivable is 0.

When item.receivable is 0, the || operator will incorrectly fall back to item.amount. Use ?? to only fall back when the value is null or undefined.

Apply this diff:

-                    { price( item.receivable || item.amount ) }
+                    { price( item.receivable ?? item.amount ) }

212-218: Replace RawHTML with safe text rendering to prevent XSS in payment details.

User-supplied payment details are rendered using RawHTML, which creates an XSS vulnerability. Render as plain text and use CSS to preserve line breaks.

Apply this diff:

-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
+                    <Tooltip content={ <span className="whitespace-pre-line">{ full }</span> }>
                         <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
                         </p>
                     </Tooltip>

228-235: Replace RawHTML with safe text rendering to prevent XSS in notes.

User-supplied notes are rendered using RawHTML, creating an XSS vulnerability. Render as plain text and use CSS to preserve line breaks.

Apply this diff:

-                    <Tooltip content={ <RawHTML>{ full }</RawHTML> }>
+                    <Tooltip content={ <span className="whitespace-pre-line">{ full }</span> }>
                         <p className="m-0 space-x-2 flex flex-wrap max-w-40 text-wrap leading-6 text-sm text-gray-600">
-                            <RawHTML>
-                                { truncate ? truncate( full, 120 ) : full }
-                            </RawHTML>
+                            <span>{ truncate ? truncate( full, 120 ) : full }</span>
                         </p>
                     </Tooltip>

249-263: Guard against undefined icon to prevent crashes.

LucideIcons[iconName] can return undefined for unknown icon names, which will crash when attempting to render <Icon />.

Apply this diff:

     const Icon = LucideIcons[ iconName ];
+    if ( ! Icon ) {
+        return <span>{ label }</span>;
+    }
     return (
         <div className="dokan-layout">
             <span className="inline-flex items-center gap-2.5">
                 <Icon size={ 16 } className="!fill-none" />
                 { label }
             </span>
         </div>
     );

349-349: Account for 'completed' status in action eligibility checks.

The backend may return status: 'completed' instead of 'approved'. Actions that should be disabled for approved withdrawals must also exclude 'completed' status.

Apply this diff at line 349:

-            isEligible: ( item ) => item?.status !== 'approved',
+            isEligible: ( item ) => ! [ 'approved', 'completed' ].includes( item?.status ),

Apply this diff at line 370:

-            isEligible: ( item ) => item?.status !== 'approved',
+            isEligible: ( item ) => ! [ 'approved', 'completed' ].includes( item?.status ),

Also applies to: 370-370


493-495: Fix fields mapping: filter out vendor field instead of using empty string.

Passing an empty string '' as a field id can break DataViews column rendering. Filter out the 'vendor' field explicitly.

Apply this diff:

-        fields: fields.map( ( field ) =>
-            field.id !== 'vendor' ? field.id : ''
-        ),
+        fields: fields
+            .filter( ( field ) => field.id !== 'vendor' )
+            .map( ( field ) => field.id ),

526-542: Fix REST path and avoid leaking internal view state in export query.

The REST path is missing a leading slash (which can break requests), and the entire view object is being spread into query params, including internal keys like type, layout, titleField, and fields that the backend doesn't expect.

Apply this diff:

             onClick={ async () => {
                 try {
-                    // Minimal placeholder; backend export flow may vary.
-                    // Attempt to hit export endpoint via same query params.
-                    const path = addQueryArgs( 'dokan/v2/withdraw', {
-                        ...view,
-                        is_export: true,
-                    } );
+                    const exportArgs = {
+                        per_page: view?.perPage ?? 10,
+                        page: view?.page ?? 1,
+                        search: view?.search ?? '',
+                        status: view?.status === 'all' ? '' : view?.status,
+                        ...filterArgs,
+                        is_export: true,
+                        ...(view?.sort?.field ? { orderby: view.sort.field } : {}),
+                        ...(view?.sort?.direction ? { order: view.sort.direction } : {}),
+                    };
+                    const path = addQueryArgs( '/dokan/v2/withdraw', exportArgs );
                     const res = await apiFetch( { path } );

583-583: Fix REST path: add leading slash to prevent broken requests.

apiFetch expects the path to start with /. Missing the leading slash can cause requests to fail on some WordPress installations.

Apply this diff:

-                path: addQueryArgs( 'dokan/v2/withdraw', queryArgs ),
+                path: addQueryArgs( '/dokan/v2/withdraw', queryArgs ),

550-621: Memoize fetchWithdraws to prevent stale closures and unnecessary re-renders.

The fetchWithdraws function is recreated on every render and can capture stale values of filterArgs. Wrap it in useCallback and update the useEffect dependency.

Apply this diff:

-    const fetchWithdraws = async () => {
+    const fetchWithdraws = useCallback( async () => {
         setIsLoading( true );
         try {
             const queryArgs = {
                 per_page: view?.perPage ?? 20,
                 page: view?.page ?? 1,
                 search: view?.search ?? '',
                 status: view?.status === 'all' ? '' : view?.status,
                 ...filterArgs,
             };
             // ... rest of function body
         } finally {
             setIsLoading( false );
         }
-    };
+    }, [ view, filterArgs ] );

     // Fetch withdraws when view changes
     useEffect( () => {
         fetchWithdraws();
-    }, [ view ] );
+    }, [ fetchWithdraws ] );

Also applies to: 843-845


1230-1232: Account for 'completed' status when showing delete action in view modal.

The backend may return status: 'completed' instead of 'approved'. The delete option should be hidden for both statuses.

Apply this diff:

-                    if ( item.status !== 'approved' ) {
+                    if ( ! [ 'approved', 'completed' ].includes( item.status ) ) {
                         options.push( { label: __( 'Delete', 'dokan-lite' ), value: 'delete' } );
                     }

1360-1364: Replace RawHTML with safe text rendering in modal to prevent XSS.

Using RawHTML to render user-supplied payment details in the modal creates an XSS vulnerability. Render as plain text and preserve line breaks with CSS.

Apply this diff:

-                                            <RawHTML>
-                                                { processDetails( item.details, item.method )
-                                                    .split( '\n' )
-                                                    .join( '<br />' ) }
-                                            </RawHTML>
+                                            <span className="whitespace-pre-line">
+                                                { processDetails( item.details, item.method ) }
+                                            </span>

1344-1344: Use nullish coalescing in modal to avoid incorrect fallback when receivable is 0.

When item.receivable is 0, the || operator will incorrectly fall back to item.amount. Use ?? to only fall back when the value is null or undefined.

Apply this diff:

-                                                        { price( item.receivable || item.amount ) }
+                                                        { price( item.receivable ?? item.amount ) }
🧹 Nitpick comments (2)
src/admin/dashboard/pages/withdraw/index.tsx (2)

655-660: Consider using a toast notification instead of alert() for better UX.

Using alert() blocks the UI and provides a poor user experience. Consider using a toast notification system for error messages.

Also applies to: 710-710


944-944: Consider disabling future dates in the date range picker.

The isInvalidDate prop always returns false, allowing users to select future dates. For withdraw history filtering, future dates are typically not valid.

Apply this diff:

-                    isInvalidDate={ () => false }
+                    isInvalidDate={ ( date ) => {
+                        const today = new Date();
+                        today.setHours( 23, 59, 59, 999 );
+                        return date > today;
+                    } }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12560cb and bef76f0.

📒 Files selected for processing (1)
  • src/admin/dashboard/pages/withdraw/index.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/admin/dashboard/pages/withdraw/index.tsx (3)
src/utilities/Accounting.ts (1)
  • formatPrice (3-62)
types/externals.d.ts (3)
  • formatPrice (79-79)
  • DataViews (59-59)
  • DokanModal (65-65)
src/utilities/Truncate.ts (1)
  • truncate (1-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: e2e tests (1, 3)
  • GitHub Check: e2e tests (3, 3)
  • GitHub Check: e2e tests (2, 3)
🔇 Additional comments (1)
src/admin/dashboard/pages/withdraw/index.tsx (1)

36-73: LGTM!

The processDetails function correctly handles different payment methods and returns plain text strings.

* feat: add DokanSelect component with customizable styles and indicators

* enhance: update DokanSelect component to improve cursor styles for indicators and options

* enhance: update indicator separator visibility in DokanSelect component

* enhance: improve component composition and style precedence in DokanSelect

* feat: add DokanAsyncSelect and VendorAsyncSelect components for enhanced vendor selection

* feat: add ProductAsyncSelect component for enhanced product selection

* feat: add WcDateRangePicker component for date range selection

* feat: add WpDateTimePicker component for date and time selection

* feat: refactor ProductAsyncSelect for improved loading and option handling

* feat: add OrderAsyncSelect component for enhanced order selection

* feat: add SearchInput component for enhanced search functionality

* feat: add CouponAsyncSelect component for coupon selection

* feat: add Test component with various Dokan UI elements and functionality

* feat: remove DokanSelect export from index.tsx

* feat: remove DokanSelect component from Test.tsx

* feat: enhance Test component with ProductAsyncSelect and VendorAsyncSelect

* feat: remove unused imports from Test component

* feat: rename DokanAsyncSelect to AsyncSelect and update imports

* feat: rename DokanAsyncSelect to AsyncSelect and update imports

* feat: add Select component and update AsyncSelect usage in Test component

* feat: refactor AsyncSelect and related components to use BaseSelectProps for improved type safety

* feat: enable multi-select functionality in Test component

* feat: integrate SimpleInput for date input in WcDateRangePicker, WpDatePicker, and WpDateTimePicker components

* feat: add documentation for AsyncSelect, CouponAsyncSelect, OrderAsyncSelect, ProductAsyncSelect, VendorAsyncSelect, WpDateTimePicker, SearchInput, and Select components

* feat: add prefetch and strict validation options to AsyncSelect components

* feat: update AsyncSelect components to improve fetching logic and add unique instance IDs

* feat: enhance AsyncSelect components with unique merging logic for prefetched options

* feat: implement skip merging logic for AsyncSelect components on dependency changes

* feat: replace Test component with Status component in routing

* feat: add Vendors management page and related components

* feat: enhance vendor admin list with avatar display and action buttons

* feat: update vendor admin list with improved URL handling and status display

* feat: add confirmation modal for vendor approval and disable actions

* feat: add selection state management for bulk actions in vendor admin list

* feat: rename WcDateRangePicker and WpDateTimePicker components to DateRangePicker and DateTimePicker

* feat: enhance AsyncSelect components with Tailwind CSS styling and improve option label formatting

* feat: update documentation for Props API and enhance WpDatePicker with unique instance IDs

* feat: enhance AsyncSelect component with icon support and update documentation

* feat: update ProductAsyncSelect endpoint and enhance SearchInput with stable debouncing

* feat: remove onClose handler from DateRangePicker component

* feat: replace Test component with Status in Dokan routes

* Finish select components

* feat: enhance AsyncSelect components with onMenuOpen handling and loading optimization

* feat: add filter button and toggle functionality to Vendors page

* feat: add seller badge and subscription filters to Vendors page

* feat: improve loading states and optimize rendering for vendor fields

* feat: add SellerBadgeSelect component and update vendor list layout

* feat: enhance vendor filtering with dynamic fields and improved query handling

* refactor: enhance vendor admin list with dynamic filters and improved UI elements

- Updated filters to allow dynamic field injection and better customization.
- Improved UI for status tabs and filters with consistent theming.
- Refactored filter logic and replaced redundant methods for better clarity.
- Integrated search and tab selection into the main table component for enhanced usability.

* refactor: remove unused components and redundant state logic in Vendors page

- Removed `DokanTab` and `Filter` components.
- Deleted `showFilters` state and unnecessary `useEffect` for filter visibility.
- Simplified and cleaned up markup and filter methods for improved readability.

* feat: add 'vendors' to supported keys in PanelSwitch filter

* refactor: Remove unused `shouldRender` state and legacy redirect logic from PanelSwitch and LegacySwitcher

* fix: update vendor navigation path in Vendors page

* refactor: replace `wp.hooks.applyFilters` with `applyFilters` and update `wp_localize_script` usage

- Used `@wordpress/hooks`'s `applyFilters` for consistency.
- Replaced `wp_localize_script` with `wp_add_inline_script` for better script handling.

---------

Co-authored-by: Md Asif Hossain Nadim <devianadim@gmail.com>
@MdAsifHossainNadim MdAsifHossainNadim merged commit 059c2ed into develop Oct 22, 2025
0 of 5 checks passed
@MdAsifHossainNadim MdAsifHossainNadim deleted the feat/dokan-widthraw-admin-list branch October 22, 2025 06:11
@coderabbitai coderabbitai bot mentioned this pull request Nov 14, 2025
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants