Skip to content

feat(vendor-settings): flat-array Store settings backend (schema, mapper, writer, REST) - #3310

Open
MdAsifHossainNadim wants to merge 30 commits into
refactor/simplify-settings-to-flat-arrayfrom
feat/vendor-store-settings-backend
Open

feat(vendor-settings): flat-array Store settings backend (schema, mapper, writer, REST)#3310
MdAsifHossainNadim wants to merge 30 commits into
refactor/simplify-settings-to-flat-arrayfrom
feat/vendor-store-settings-backend

Conversation

@MdAsifHossainNadim

@MdAsifHossainNadim MdAsifHossainNadim commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

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)

Changes proposed in this Pull Request:

PR 1 of 3 for the vendor Store Settings → React migration (spec: getdokan/plugin-internal-tasks#2115, parent: getdokan/plugin-internal-tasks#1981). This is the pure PHP backend — no UI change, no behavior change for the legacy page.

Serves the plugin-ui flat-array schema with values from dokan_profile_settings and persists through the exact legacy save pipeline, so storage keys and value shapes stay byte-identical and every Pro consumer on the two seams keeps working.

  • Vendor/Settings/Schema/StoreSettingsSchema.php — flat SettingsElement[] (page → subpage → collapsible section cards → fields) with values populated inline. Conditional blocks use the same server-side gates as templates/settings/store-form.php: map only with dokan_has_map_api_key(), ToC only when seller_enable_terms_and_conditions is on, schedule only when appearance store_open_close is on, email visibility hidden per dokan_is_vendor_info_hidden('email'), address handed over when Pro delivery-time is active, catalog-mode per its admin gates. Pro appends via the new dokan_get_vendor_settings_schema filter (mirrors dokan_get_admin_settings_schema).
  • Vendor/Settings/ValueMapper.php — flat field ids → legacy keys. Composites handled bespoke: store_maplocation + find_address; address merges over previous subkeys (preserves store-pickup's location_name); catalog-mode ids → nested catalog_mode array preserving RFQ's request_a_quote_enabled + the legacy forced-off rule. Any Pro-injected field persists via its string legacy_key (defaults to the field id).
  • Vendor/Settings/StoreSettingsWriter.php — the one write path: shallow array_merge, dokan_store_name mirror meta, dokan_store_profile_settings_args filter, canonical meta write, dokan_store_profile_saved( $id, $merged, $prev ). Deliberately never re-applies the legacy dokan_store_time POST-parsing filter (Pro's parser returns null without the legacy nonce and would wipe the schedule on REST saves).
  • REST/VendorStoreSettingsController.phpGET/PUT /dokan/v1/vendor-settings/store on DokanBaseVendorController (vendor-staff aware via get_vendor_id_for_user()), permission dokandar + dokan_view_store_settings_menu. Validation failures return 400 { errors: { field_id: [messages] } } (plugin-ui merges these into per-field errors). Validations use the plugin-ui client contract ([{ rules: 'not_empty', message }]) so one declaration drives both sides; the schedule validator enforces matching g:i a pairs with opening < closing (single Lite slot and Pro multi-slot both accepted). Pro extension filters: dokan_rest_vendor_settings_sanitize_field / dokan_rest_vendor_settings_validate_field.

Related Pull Request(s)

Closes

How to test the changes in this Pull Request:

  1. As a logged-in vendor, GET /wp-json/dokan/v1/vendor-settings/store — returns the flat schema with current values; sections respect the admin gates (disable the map API key → store_map_section disappears, etc.).
  2. PUT /wp-json/dokan/v1/vendor-settings/store with { "values": { "store_name": "" } }400 with errors.store_name.
  3. PUT a valid payload → dokan_profile_settings keys/shapes are byte-identical to a legacy form save ('yes'/'no', 'on'/'off', attachment-ID ints, per-day {status, opening_time[], closing_time[]} with g:i a strings); untouched keys (payment, vendor_biography, …) survive; dokan_store_name mirror meta syncs; both legacy seams fire (verify a Seam-B consumer like store-support still persists).
  4. Smoke-tested on this branch: schema builds for a real seller (20 elements, delivery-time active correctly omits the address section) and the route registers.

Changelog entry

Vendor Store Settings flat-array backend

Adds the schema/REST backend for the React vendor Store Settings page. No user-facing change yet: the legacy page keeps rendering and saving as before; storage stays in dokan_profile_settings with unchanged keys and value shapes.

🤖 Generated with Claude Code

…per, writer, REST)

Backend for the vendor Store Settings React migration (PR 1 of 3, spec:
getdokan/plugin-internal-tasks#2115). Serves the plugin-ui flat-array schema
with values from dokan_profile_settings and persists through the exact legacy
pipeline - storage keys and value shapes stay byte-identical.

- StoreSettingsSchema: sections/fields with legacy server-side gating; Pro
  extends via the new dokan_get_vendor_settings_schema filter.
- ValueMapper: flat ids -> legacy keys (composite store_map/address, nested
  catalog_mode preserving RFQ's key + the legacy forced-off rule).
- StoreSettingsWriter: shallow merge, dokan_store_name mirror, both legacy
  seams; never re-applies the legacy dokan_store_time POST filter.
- VendorStoreSettingsController: GET/PUT /dokan/v1/vendor-settings/store,
  400 {errors:{field_id}} contract, g:i a schedule validation, Pro
  sanitize/validate extension filters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4c2c8bce-6391-45f6-94c0-8d88f0d295ff

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/vendor-store-settings-backend

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.

… variants

Wiring + components for the vendor Store Settings React page (PR 2 of 3,
spec: getdokan/plugin-internal-tasks#2115), delivered through the existing
VendorNavMenuChecker takeover - no new flag or template interception.

- SPA route /settings/store (dokan_view_store_settings_menu) mounting the
  plugin-ui <Settings> engine with hookPrefix dokan_vendor against
  GET/PUT /dokan/v1/vendor-settings/store; 400 field errors re-thrown in
  the {errors} shape plugin-ui merges into per-field state.
- react_route on the Settings > Store nav item + template dependencies
  (store-form, store-time, address-form, maps-with-search) so any theme
  override automatically falls back to the legacy page.
- 4 custom variants: vendor_image (wp.media via @dokan/components
  MediaUploader, attachment-ID fidelity), vendor_address (WC country/state
  cascade from dokan/v1/data/countries), vendor_map (Google canvas +
  places autocomplete, provider-agnostic composite value), and
  vendor_store_schedule (shared WeeklyTimeSlots bound to the legacy
  dokan_store_time struct per dokan-pro#5915; Lite single-range, Pro flips
  multiple via the schema filter).
- wp_enqueue_media() on the new-dashboard asset path for the media fields.

Verified live on dokan-core.test: route renders schema cards, banner
preview + values populate, schedule/ToC dependency toggles hide fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completion pass for the vendor Store Settings React page (PR 3 of 3, spec:
getdokan/plugin-internal-tasks#2115).

- vendor_map: full Mapbox support (mapbox-gl on demand, draggable marker,
  Enter-to-geocode via the Mapbox places API) alongside Google; Google
  auth failures (gm_authFailure) and loader errors now degrade to plain
  coordinate inputs instead of a dead canvas.
- vendor_image: Change action under the preview (Figma parity) in addition
  to remove; still persists attachment IDs only.
- Schema: subpage titled Store so the content header no longer duplicates
  the page title.

Verified end-to-end on dokan-core.test: PUT with empty store name returns
400 {errors:{store_name}}; a real save flips dokan_store_time_enabled,
keeps all 47 profile keys byte-identical (vendor_biography/payment
untouched), syncs the dokan_store_name mirror, and the LEGACY settings
form immediately renders the change (schedule checkbox on, day rows
visible). Snapshot restored after testing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MdAsifHossainNadim and others added 19 commits July 8, 2026 22:00
…ST saves

Verifier finding (plan-aligner #7): RFQ's Seam-A handler is nonce-guarded
and silent on REST saves, so a React save turning the cart button back on
left request_a_quote_enabled='on' where the identical legacy save writes
'off'. The mapper now applies the same forced-off rule when the key exists,
keeping REST and AJAX saves byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reworks the page presentation to the Figma Store frames using only
plugin-ui's native schema capabilities (no engine forks):

- Every section is its own card with title + helper description
  (Branding, Store Information, Store Locations, Store Map, Store
  Schedule, Catalog Mode, Terms & Conditions) ordered via priority so
  Pro slots its cards (Store Biography lands before T&C).
- Fields use layout: full-width (stacked label above a full-width
  control); subpage hide_heading kills the duplicated inner title;
  the route header reads Store.
- Store schedule renders the delivery-time treatment: WeeklyTimeSlots
  inside a bordered box with divided weekday rows (dokan-pro#5915).
- vendor_image matches the Figma media pattern: spec line, preview
  with pencil-Change and trash beside it, circular logo via the new
  shape attribute.
- Footer gains Cancel (refetch + engine remount discards edits)
  alongside Save Changes.
- Map card: on Google auth failure the search input is handed back to
  the vendor (Google disables it and injects an error string after
  gm_authFailure), and a bare ',' stored location displays empty.

Field ids, storage keys, and value shapes are untouched - this is
presentation only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… Cancel style

Figma re-verification findings (design agent, 21 -> 8 mismatches after the
rework; these close the two real defects and one style delta):

- vendor_image remove button: the SVG lost the fight with the theme's
  button rules (same battle WeeklyTimeSlots' icon buttons solve) - inline
  lucide size + important-flagged classes make the trash glyph render.
- vendor_store_schedule: override the shared component's delivery-domain
  validation copy ('%s delivery time can not be empty') with store-schedule
  wording via the messages prop.
- Cancel is a ghost (borderless) button per the Figma footer.

Verified live: banner row renders [pencil Change] [trash] per the design;
map search input restores after Google auth failure with the proper
placeholder, and a bare ',' stored location displays empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation and page chrome

- Header: "Store Settings" title with Cancel/Update Settings portaled beside it;
  the engine save area stays as the Figma footer, stripped of its bar chrome
- Cards sit flush with the page title (dokan-tailwind ships !important utilities,
  so the gutter/save-bar overrides carry the flag too)
- Store Title gains the red (Required) marker via a vendor_text variant
- Banner/Logo reworked to the admin vendor-edit pattern: title+spec with inline
  Change/delete controls, full-width banner frame, default banner/logo placeholders
- New vendor_multiselect renderer (SmartMultiSelect) that Pro drives for Store
  Category; non_meta fields skip the profile slice and persist on the new
  dokan_after_saving_vendor_settings seam
- Store Information: email toggle (with helper copy) ahead of Phone
- Page-shaped loading skeleton replacing the engine's sidebar-panel skeleton
- Terms & Conditions card expanded by default

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… editor

- Day rows render full-bleed in the card (horizontal rules only) instead of a
  second bordered/padded box
- Days with no stored times show one dimmed empty pair, so disabled rows read
  like the delivery-time page instead of bare labels
- Toggling a day off keeps its times in the form (dimmed, delivery behaviour);
  the server still zeroes closed days at save so storage stays legacy-identical
- Errors surface on touch like the delivery page (no validate-on-mount)
- Helper descriptions for the schedule toggle and open/close notices

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A Full Day slot previously hid its + button, leaving a gap beside the trash so
the row didn't line up with dated rows. Render it visible-but-disabled (a full
day already spans 24h, matching the disabled trash), so every row aligns in both
the store schedule and the delivery-time editor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… cross-field validation seam

- New vendor_store_locations variant: DataViews locations table with
  add/edit/delete modals; row 0 is the pinned Default location and extra
  rows are gated by the sibling multiple-locations switch (Pro injects
  the section and owns persistence).
- New vendor_rich_text variant so store_tnc can render the red (Required)
  marker; the schema marks it required and adds T&C field descriptions.
- update_item() now applies dokan_rest_vendor_settings_validate before the
  400 return so Pro modules can enforce cross-field rules (e.g. min <= max).
- style.scss: seat the DataViews grid flush inside the settings card, lift
  the location popovers above the WP modal overlay, and pin the light input
  theme inside the modal (it renders outside plugin-ui's theme provider).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…add-slot end-of-day gate

- Add `vendor_number` variant with an optional live cross-field guard so the
  cart Min-Max "max ≥ min" error surfaces the instant either value changes,
  not only after Save (mirrors the server rule client-side).
- Store Map: render the search box with the shared Input component and geocode
  the map on the fly as the vendor types (Google + Mapbox), keeping Places
  autocomplete and pin drag on the same composite value.
- WeeklyTimeSlots: retire the add-slot "+" once a slot closes at the last
  selectable preset (11:30 pm), matching the legacy "Add hours" behaviour.
- Register the new field variants (number, plus the pending company-bank,
  vacation-history, date-range, radio and textarea renderers).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Emit `type => tab` elements (General/Location/Schedule/Business/Policies)
  with inline `tab_id` on each Lite section; group_into_tabs() runs after
  the schema filter so extensions place cards by declaring `tab_id` on
  their section (or injecting their own tab element) — undeclared cards
  fall back to General and empty tabs are dropped.
- TabKeeper keeps the active tab across the save/cancel refresh-remounts,
  and a failed save activates the first tab carrying a validation error.
- Header Save/Cancel pair removed — the footer pair is the save UI.
- Tab strip styling: flush-left, 32px gaps, 15px/600 labels, near-black
  resting color, 1px active underline flush with the strip hairline
  (active color already follows the customizer via the base --primary map).
- Store Map card: "Find Address" title and spacing above the canvas.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The `dokan-before-header` Slot wrapper always renders, and when nothing fills
the slot (every page by default) the empty div still occupies a slot in the
header's `flex flex-col gap-4` column — a phantom 16px of space above the page
title on every vendor-dashboard SPA page. `empty:hidden` removes it from the
flow until a plugin actually fills the slot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tabs with little content (e.g. Policies' single Terms & Conditions card) let
the Cancel/Save row hug the card with a sea of empty page below. The content
region now keeps a 400px minimum, and the engine's inner wrapper is stretched
into a full-height column so the cards container's existing flex-1 pushes the
save row to the bottom of the region — the footer holds a steady, breathable
position across all tabs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The address variant hand-rolled bare <input> elements, so the dashboard
theme's generic input chrome (grey fill, heavier border) painted over
Street/Street 2/City/ZIP while sibling fields (Store Name, etc.) rendered
through plugin-ui's <Input>. The text subfields — and the country/state
free-text fallbacks — now use the same <Input> component, and the native
country/state selects pin a white background so the whole grid reads as one
family of controls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Make VendorStoreSettingsController variant-agnostic, mirroring
AdminSettingsController: a field declares its own `sanitize_callback`
(tried before the variant switch) and `validation_func` (run after the
declarative rules), so the controller no longer special-cases phone,
store_tnc or the store schedule.

- update_item now sanitizes every field first, then validates, so a
  field's validation_func can compare siblings (returns true|false|
  string|string[]).
- Move the schedule sanitize/validate into a new StoreScheduleValidator
  and wire it as the field's callbacks; phone + store_tnc collapse move
  onto their fields; the enable_tnc→content rule becomes store_tnc's
  validation_func.
- Keep the whole-payload `dokan_rest_vendor_settings_validate` filter for
  section-level rules that belong to no single field (seller vacation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A merge concatenated both sides of the class-map conflict, registering
every controller twice (48 entries). Collapse it to the 34-entry union:
each controller once, keeping both the settings controllers this branch
adds (AdminSettingsController, VendorStoreSettingsController) and the
base's newer ones (ProductControllerV3, AdminDashboardStatsController,
WithdrawExportController, ExportController, AdminExtensionsController).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MdAsifHossainNadim

Copy link
Copy Markdown
Contributor Author

Pro counterpart: getdokan/dokan-pro#5919 — extends this vendor Store settings implementation (module sections, tab placements, min-max validation) and should ship together with it.

MdAsifHossainNadim and others added 3 commits July 15, 2026 14:43
Align Manager.php with the parent flat-array settings branch and add just
the one line this PR owns — the VendorStoreSettingsController route. The
class-map de-duplication and the AdminSettingsController registration are
the parent PR's (refactor/simplify-settings-to-flat-array) concern, not
this one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend:
- dedupe the vendor-resolution 404, extract normalize_field_key() and
  validate_rule() to flatten the sanitize/validate nesting
- StoreScheduleValidator: split validate_open_day() out of the day loop
- ValueMapper: drop the empty catalog switch cases for a single skip-guard
  + legacy_entry() mapper
- schema: visible_when() helper replaces three inlined show/hide pairs
- keep __() (not esc_html__) for schema strings: plugin-ui renders labels as
  React text with no entity decode, so esc_html__ would double-escape

Frontend:
- reuse plugin-ui built-ins: store_name -> text, store_tnc -> rich_text,
  vacation message -> textarea; delete the pure-wrapper variants
- register-fields is now a data-driven variant->component map
- new shared.tsx: useCountries() (cached + decodeEntities, fixes AddressFields
  not decoding), fieldKeyOf(), RequiredMark, shared row-action button classes
- ImageField trash + Vacation textarea use plugin-ui Button/Textarea
- rename dead vendor_date_range variant file to RangeInput.tsx

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…to frontend

- validate_rule: full-ternary message fallbacks; keep the strict blank check
  (empty() would reject '0'/0 and diverge from the client's not_empty, matching
  AdminSettingsController)
- split group_into_tabs into base_tabs()/known_tab_ids()/is_tab_member()
- add reusable vendor_note field variant (info icon + muted text) so schemas
  declare note text instead of building raw HTML/SVG in PHP

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MdAsifHossainNadim and others added 4 commits July 16, 2026 13:57
…-ui v2 markup

plugin-ui v2 dropped the [data-slot='popover-portal'] wrapper around the
SmartSelect portal, so the old selector matched nothing and the country/state
dropdown in the Edit Location Details modal opened invisibly behind the WP
modal overlay (z-50 vs 100000). Anchor the lift one level shallower at
.pui-root so it survives either markup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…at-array' into feat/vendor-store-settings-backend
…der again

plugin-ui v2 evaluates `values[dep.key]` literally against a values map keyed
by dot paths, where the old engine resolved a sibling id relative to the field.
Schema conventions like `key: 'dokan_store_time_enabled'` therefore matched
nothing and every same-section dependent field was silently hidden — the
schedule day rows, open/close notices, vacation fields and the terms editor all
disappeared behind their toggles with no error.

qualifyDependencyKeys() rewrites each dep.key through the engine's own
formatSettingsData() map before the schema reaches <Settings>. The same map
re-keys REST validation errors, which the engine reads as errors[dependency_key]
— bare-id keys meant inline errors never rendered either.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant