Skip to content

enhance/admin-store-category-react#2980

Merged
mrabbani merged 2 commits intodevelopfrom
enhance/admin-store-category-react
Nov 18, 2025
Merged

enhance/admin-store-category-react#2980
mrabbani merged 2 commits intodevelopfrom
enhance/admin-store-category-react

Conversation

@Aunshon
Copy link
Collaborator

@Aunshon Aunshon commented Nov 13, 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

Related Pull Request(s)

Closes

How to test the changes in this Pull Request:

  • Test if the feature is working as existing.

Changelog entry

No needed

Summary by CodeRabbit

  • New Features
    • Vendor list columns and view configuration are now customizable by extensions
    • Extension UI slots added above/below the Add Vendor button
    • Plugin area added to the bottom of the Vendors page for extension widgets

@Aunshon Aunshon self-assigned this Nov 13, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 13, 2025

Walkthrough

The vendors list page now supports extensibility through WordPress filters and plugin areas. Fields array and view configuration can be dynamically modified by extensions. Slot components enable plugin UI injections around the Add Vendor button, and a PluginArea component allows extension widgets at the bottom of the page.

Changes

Cohort / File(s) Summary
Vendors List Extensibility
src/admin/dashboard/pages/vendors.tsx
Replaced static fields array with dynamic filtering via applyFilters('dokan-admin-vendors-list-column-fields', [...]). Wrapped DataViews view prop with applyFilters('dokan-admin-vendors-list-view', view). Added Slot components before and after Add Vendor button for UI extensions. Added PluginArea at page bottom for extension widgets. Imported Slot from @wordpress/components and PluginArea from @wordpress/plugins.

Sequence Diagram(s)

sequenceDiagram
    participant VendorsList as Vendors List<br/>Component
    participant FilterHook as applyFilters<br/>Hook System
    participant Extension as WordPress<br/>Extension/Plugin
    participant UI as Rendered UI

    VendorsList->>FilterHook: applyFilters('dokan-admin-vendors-list-column-fields', fields)
    Extension->>FilterHook: Modify/Add fields
    FilterHook->>VendorsList: Return augmented fields
    VendorsList->>UI: Render columns

    VendorsList->>FilterHook: applyFilters('dokan-admin-vendors-list-view', view)
    Extension->>FilterHook: Modify view config
    FilterHook->>VendorsList: Return modified view
    VendorsList->>UI: Render with updated view

    VendorsList->>UI: Render Slot (before Add Vendor)
    Extension->>UI: Inject UI via Slot
    VendorsList->>UI: Render PluginArea (page bottom)
    Extension->>UI: Register widgets in PluginArea
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • Single file modification with localized, structural changes (no complex logic)
  • Straightforward additions: filter calls follow existing patterns, standard WP component imports
  • Review focus: Verify filter hook names align with extension expectations, confirm Slot/PluginArea placement is intentional for UX

Possibly related PRs

Suggested labels

Needs: Dev Review, Dependency With Pro

Suggested reviewers

  • mrabbani

Poem

🐰 Extensions bloom where filters grow,
Slots and plugins steal the show!
Vendors list now bends its way,
For plugins grand to have their say. 🌱

Pre-merge checks and finishing touches

❌ Failed checks (2 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The PR title 'enhance/admin-store-category-react' describes a feature branch naming pattern but does not clearly convey the primary changes in the PR, which are adding extension points and dynamic filters to the vendors list page. Revise the title to clearly describe the main changes, such as 'Add extension points and dynamic filters to vendors list' or 'Enable plugin extensions for vendors list customization'.
Linked Issues check ❓ Inconclusive The PR adds extension points and dynamic filters to the vendors list page to enable plugin customization, partially supporting issue #5078 and #1151 by providing infrastructure for adding store category functionality via extensions. Verify that the extension points are sufficient for adding the Store Category button and column as required by issues #5078 and #1151, or clarify if additional implementation is needed.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description follows the template structure with checklist items marked complete, linked issues and closes statements provided, but lacks detailed explanation of actual changes made beyond the checklist.
Out of Scope Changes check ✅ Passed All changes directly support the objective of enabling extensibility for the vendors list, specifically introducing hooks and UI injection points to allow plugins to add store category functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch enhance/admin-store-category-react

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
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

🧹 Nitpick comments (2)
src/admin/dashboard/pages/vendors.tsx (2)

263-366: Consider adding runtime validation for the filtered fields array.

The applyFilters call returns an untyped value that's directly used as the fields configuration. If an extension returns an invalid structure, it could cause runtime errors.

Apply this pattern to validate the filtered result:

-    const fields = applyFilters(
+    let fields = applyFilters(
         'dokan-admin-vendors-list-column-fields',
         [
             {
                 id: 'vendor',
                 label: __( 'Vendor', 'dokan-lite' ),
                 // ... rest of fields
             },
         ],
         loadingClass,
         isLoading
     );
+    
+    // Validate filtered result
+    if (!Array.isArray(fields)) {
+        console.error('dokan-admin-vendors-list-column-fields filter must return an array');
+        fields = []; // fallback to safe default or original array
+    }

729-732: Consider adding runtime validation for the filtered view object.

Similar to the fields filter, the view filter returns an untyped value. If an extension returns an invalid view configuration, it could cause the DataViews component to malfunction.

Apply this pattern to validate the filtered result:

-                    view={ applyFilters(
+                    view={ (() => {
+                        const filteredView = applyFilters(
                             'dokan-admin-vendors-list-view',
                             view
-                        ) }
+                        );
+                        
+                        // Validate filtered result
+                        if (!filteredView || typeof filteredView !== 'object') {
+                            console.error('dokan-admin-vendors-list-view filter must return a valid view object');
+                            return view; // fallback to original
+                        }
+                        
+                        return filteredView;
+                    })() }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03a7afb and e1d4a6c.

📒 Files selected for processing (1)
  • src/admin/dashboard/pages/vendors.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/admin/dashboard/pages/vendors.tsx (2)
src/definitions/dokan-vendor.ts (1)
  • Vendor (101-160)
src/stores/vendors/selectors.ts (1)
  • isLoading (19-21)
⏰ 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 (4)
src/admin/dashboard/pages/vendors.tsx (4)

16-19: LGTM!

The new imports for Slot and PluginArea enable the extensibility features added in this PR, allowing the Pro version to inject store category functionality.


467-470: LGTM!

The Slot component is properly configured to allow extensions to inject UI elements before the Add Vendor button, with appropriate props passed via fillProps.


481-484: LGTM!

The Slot component provides a symmetric injection point after the Add Vendor button, maintaining consistency with the before-slot implementation.


753-754: LGTM!

The PluginArea component is properly configured to allow extensions to inject widgets at the bottom of the vendors page. This provides a flexible extension point for the Pro version to add features like store category management.

Comment on lines +263 to +366
const fields = applyFilters(
'dokan-admin-vendors-list-column-fields',
[
{
id: 'vendor',
label: __( 'Vendor', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const name = item?.store_name || '';
const avatar = item?.gravatar || '';
return (
<UserCard
name={ name }
avatar={ avatar }
isLoading={ isLoading }
loadingClass={ loadingClass }
onClick={ () => {
navigate( `/vendors/${ item.id }` );
} }
subTitle={ item?.email || '' }
/>
);
},
},
},
{
id: 'phone',
label: __( 'Phone', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const phone = item?.phone || '';
return (
<div className="flex items-center gap-3">
<span className="flex flex-col">
{ phone ? (
<span
className={ twMerge(
'text-[14px] font-[400] text-[#575757]',
isLoading ? loadingClass : ''
) }
>
{ phone }
</span>
) : (
<span className="text-gray-400">—</span>
) }
</span>
</div>
);
{
id: 'phone',
label: __( 'Phone', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const phone = item?.phone || '';
return (
<div className="flex items-center gap-3">
<span className="flex flex-col">
{ phone ? (
<span
className={ twMerge(
'text-[14px] font-[400] text-[#575757]',
isLoading ? loadingClass : ''
) }
>
{ phone }
</span>
) : (
<span className="text-gray-400">—</span>
) }
</span>
</div>
);
},
},
},
{
id: 'registered',
label: __( 'Registered', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const registered = item?.registered || '';
return (
<div className="flex items-center gap-3">
<div className="flex flex-col">
{ registered ? (
<div
className={ twMerge(
'text-[14px] font-[400] text-[#575757]',
isLoading ? loadingClass : ''
) }
>
<DateTimeHtml.Date date={ registered } />
</div>
) : (
<span className="text-gray-400">—</span>
) }
{
id: 'registered',
label: __( 'Registered', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const registered = item?.registered || '';
return (
<div className="flex items-center gap-3">
<div className="flex flex-col">
{ registered ? (
<div
className={ twMerge(
'text-[14px] font-[400] text-[#575757]',
isLoading ? loadingClass : ''
) }
>
<DateTimeHtml.Date
date={ registered }
/>
</div>
) : (
<span className="text-gray-400">—</span>
) }
</div>
</div>
</div>
);
);
},
},
},
{
id: 'status',
label: __( 'Status', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
return (
<span
className={ twMerge(
'inline-flex items-center px-3.5 py-1.5 rounded-full text-xs font-medium',
item?.enabled
? 'bg-[#D4FBEF] text-[#00563F]'
: 'bg-[#F1F1F4] text-[#393939]',
isLoading ? loadingClass : ''
) }
>
{ item?.enabled
? __( 'Enabled', 'dokan-lite' )
: __( 'Disabled', 'dokan-lite' ) }
</span>
);
{
id: 'status',
label: __( 'Status', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
return (
<span
className={ twMerge(
'inline-flex items-center px-3.5 py-1.5 rounded-full text-xs font-medium',
item?.enabled
? 'bg-[#D4FBEF] text-[#00563F]'
: 'bg-[#F1F1F4] text-[#393939]',
isLoading ? loadingClass : ''
) }
>
{ item?.enabled
? __( 'Enabled', 'dokan-lite' )
: __( 'Disabled', 'dokan-lite' ) }
</span>
);
},
},
},
];
],
loadingClass,
isLoading
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent filter naming convention.

The new filter name dokan-admin-vendors-list-column-fields uses kebab-case, while existing filters in this file use snake_case (e.g., dokan_admin_vendors_list_filters at Line 48, dokan_admin_vendors_before_request at Line 98). This inconsistency may confuse extension developers.

Consider using snake_case for consistency:

-    const fields = applyFilters(
-        'dokan-admin-vendors-list-column-fields',
+    const fields = applyFilters(
+        'dokan_admin_vendors_list_column_fields',
         [
📝 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 fields = applyFilters(
'dokan-admin-vendors-list-column-fields',
[
{
id: 'vendor',
label: __( 'Vendor', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const name = item?.store_name || '';
const avatar = item?.gravatar || '';
return (
<UserCard
name={ name }
avatar={ avatar }
isLoading={ isLoading }
loadingClass={ loadingClass }
onClick={ () => {
navigate( `/vendors/${ item.id }` );
} }
subTitle={ item?.email || '' }
/>
);
},
},
},
{
id: 'phone',
label: __( 'Phone', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const phone = item?.phone || '';
return (
<div className="flex items-center gap-3">
<span className="flex flex-col">
{ phone ? (
<span
className={ twMerge(
'text-[14px] font-[400] text-[#575757]',
isLoading ? loadingClass : ''
) }
>
{ phone }
</span>
) : (
<span className="text-gray-400"></span>
) }
</span>
</div>
);
{
id: 'phone',
label: __( 'Phone', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const phone = item?.phone || '';
return (
<div className="flex items-center gap-3">
<span className="flex flex-col">
{ phone ? (
<span
className={ twMerge(
'text-[14px] font-[400] text-[#575757]',
isLoading ? loadingClass : ''
) }
>
{ phone }
</span>
) : (
<span className="text-gray-400"></span>
) }
</span>
</div>
);
},
},
},
{
id: 'registered',
label: __( 'Registered', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const registered = item?.registered || '';
return (
<div className="flex items-center gap-3">
<div className="flex flex-col">
{ registered ? (
<div
className={ twMerge(
'text-[14px] font-[400] text-[#575757]',
isLoading ? loadingClass : ''
) }
>
<DateTimeHtml.Date date={ registered } />
</div>
) : (
<span className="text-gray-400"></span>
) }
{
id: 'registered',
label: __( 'Registered', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const registered = item?.registered || '';
return (
<div className="flex items-center gap-3">
<div className="flex flex-col">
{ registered ? (
<div
className={ twMerge(
'text-[14px] font-[400] text-[#575757]',
isLoading ? loadingClass : ''
) }
>
<DateTimeHtml.Date
date={ registered }
/>
</div>
) : (
<span className="text-gray-400"></span>
) }
</div>
</div>
</div>
);
);
},
},
},
{
id: 'status',
label: __( 'Status', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
return (
<span
className={ twMerge(
'inline-flex items-center px-3.5 py-1.5 rounded-full text-xs font-medium',
item?.enabled
? 'bg-[#D4FBEF] text-[#00563F]'
: 'bg-[#F1F1F4] text-[#393939]',
isLoading ? loadingClass : ''
) }
>
{ item?.enabled
? __( 'Enabled', 'dokan-lite' )
: __( 'Disabled', 'dokan-lite' ) }
</span>
);
{
id: 'status',
label: __( 'Status', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
return (
<span
className={ twMerge(
'inline-flex items-center px-3.5 py-1.5 rounded-full text-xs font-medium',
item?.enabled
? 'bg-[#D4FBEF] text-[#00563F]'
: 'bg-[#F1F1F4] text-[#393939]',
isLoading ? loadingClass : ''
) }
>
{ item?.enabled
? __( 'Enabled', 'dokan-lite' )
: __( 'Disabled', 'dokan-lite' ) }
</span>
);
},
},
},
];
],
loadingClass,
isLoading
);
const fields = applyFilters(
'dokan_admin_vendors_list_column_fields',
[
{
id: 'vendor',
label: __( 'Vendor', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const name = item?.store_name || '';
const avatar = item?.gravatar || '';
return (
<UserCard
name={ name }
avatar={ avatar }
isLoading={ isLoading }
loadingClass={ loadingClass }
onClick={ () => {
navigate( `/vendors/${ item.id }` );
} }
subTitle={ item?.email || '' }
/>
);
},
},
{
id: 'phone',
label: __( 'Phone', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const phone = item?.phone || '';
return (
<div className="flex items-center gap-3">
<span className="flex flex-col">
{ phone ? (
<span
className={ twMerge(
'text-[14px] font-[400] text-[#575757]',
isLoading ? loadingClass : ''
) }
>
{ phone }
</span>
) : (
<span className="text-gray-400"></span>
) }
</span>
</div>
);
},
},
{
id: 'registered',
label: __( 'Registered', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
const registered = item?.registered || '';
return (
<div className="flex items-center gap-3">
<div className="flex flex-col">
{ registered ? (
<div
className={ twMerge(
'text-[14px] font-[400] text-[#575757]',
isLoading ? loadingClass : ''
) }
>
<DateTimeHtml.Date
date={ registered }
/>
</div>
) : (
<span className="text-gray-400"></span>
) }
</div>
</div>
);
},
},
{
id: 'status',
label: __( 'Status', 'dokan-lite' ),
enableSorting: false,
render: ( { item }: { item: Vendor } ) => {
return (
<span
className={ twMerge(
'inline-flex items-center px-3.5 py-1.5 rounded-full text-xs font-medium',
item?.enabled
? 'bg-[#D4FBEF] text-[#00563F]'
: 'bg-[#F1F1F4] text-[#393939]',
isLoading ? loadingClass : ''
) }
>
{ item?.enabled
? __( 'Enabled', 'dokan-lite' )
: __( 'Disabled', 'dokan-lite' ) }
</span>
);
},
},
],
loadingClass,
isLoading
);
🤖 Prompt for AI Agents
In src/admin/dashboard/pages/vendors.tsx around lines 263 to 366, the filter
name 'dokan-admin-vendors-list-column-fields' uses kebab-case while the rest of
this file uses snake_case; change the filter name to
'dokan_admin_vendors_list_column_fields' in the applyFilters call and update any
corresponding external usages/listeners to the new snake_case name so naming is
consistent with other filters in the file.

Comment on lines +729 to +732
view={ applyFilters(
'dokan-admin-vendors-list-view',
view
) }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent filter naming convention.

The filter name dokan-admin-vendors-list-view uses kebab-case, which is inconsistent with existing filters in this file that use snake_case. Consider using dokan_admin_vendors_list_view for consistency.

🤖 Prompt for AI Agents
In src/admin/dashboard/pages/vendors.tsx around lines 729 to 732, the filter
name uses kebab-case ('dokan-admin-vendors-list-view') which is inconsistent
with the file's snake_case convention; change the filter key to
dokan_admin_vendors_list_view and update any corresponding add_filter/useFilter
calls or tests to use the new snake_case name so all filter registration and
consumption remain consistent.

@Aunshon Aunshon added Needs: Testing This requires further testing Needs: Dev Review It requires a developer review and approval Dependency With Pro labels Nov 13, 2025
@mrabbani mrabbani added Dev Review Done and removed Needs: Dev Review It requires a developer review and approval labels Nov 13, 2025
@dev-shahed dev-shahed added 🎉 QA Approved This PR is approved by the QA team and removed Needs: Testing This requires further testing labels Nov 14, 2025
@mrabbani mrabbani merged commit c089288 into develop Nov 18, 2025
2 of 6 checks passed
@mrabbani mrabbani deleted the enhance/admin-store-category-react branch November 18, 2025 05:21
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.

3 participants