Skip to content

fix: show loading spinner and disable submit button during checkout provisioning#1222

Merged
superdav42 merged 1 commit into
mainfrom
feature/auto-20260518-165738-gh1220
May 18, 2026
Merged

fix: show loading spinner and disable submit button during checkout provisioning#1222
superdav42 merged 1 commit into
mainfrom
feature/auto-20260518-165738-gh1220

Conversation

@superdav42
Copy link
Copy Markdown
Collaborator

@superdav42 superdav42 commented May 18, 2026

Summary

Adds client-side UX improvements to prevent double-clicks and provide user feedback during the ~30s checkout provisioning wait.

Changes

  • Client UX (mandatory): Disable the submit button and render a spinner plus status copy ("Provisioning your site — this can take up to 60 seconds.") between submit and redirect. This eliminates the double-click failure mode.
  • Added provisioning_site i18n string to checkout class for the status message.
  • Modified block() method to accept an optional message parameter for custom loading messages.
  • Form submission handler now disables all submit buttons and shows the loading message immediately upon form submission.

Verification

  • Submit a fresh demo signup with DevTools Network throttling off; confirm the spinner appears immediately and the submit button is disabled until the redirect fires.
  • Confirm no wu_cap_* request fires twice from a single click sequence.

Resolves #1220


aidevops.sh v3.15.63 plugin for OpenCode v1.15.5 with claude-haiku-4-5 spent 2m and 4,877 tokens on this as a headless worker.

Summary by CodeRabbit

  • New Features

    • Enhanced checkout loading messaging during site provisioning to provide clearer user feedback.
  • Style & Refactoring

    • Modernized JavaScript codebase with ES6+ syntax improvements (const/let, method shorthand, arrow functions).
    • Improved code organization and scoping through IIFE wrapping.
    • General code formatting and consistency improvements across all JavaScript files.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Warning

Rate limit exceeded

@superdav42 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 53 minutes and 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc5a7849-c3bc-456a-841e-d2f65159d850

📥 Commits

Reviewing files that changed from the base of the PR and between d463819 and f67491a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (50)
  • assets/js/activity-stream.js
  • assets/js/addons.js
  • assets/js/admin-notices.js
  • assets/js/admin-screen.js
  • assets/js/admin.js
  • assets/js/ajax-button.js
  • assets/js/app.js
  • assets/js/checkout-form-editor-modal.js
  • assets/js/checkout-forms-editor.js
  • assets/js/checkout.js
  • assets/js/color-field.js
  • assets/js/command-palette.js
  • assets/js/cookie-helpers.js
  • assets/js/coupon-code.js
  • assets/js/customizer.js
  • assets/js/dashboard-statistics.js
  • assets/js/dns-management.js
  • assets/js/dns-table.js
  • assets/js/domain-logs.js
  • assets/js/edit-placeholders.js
  • assets/js/email-edit-page.js
  • assets/js/fields.js
  • assets/js/functions.js
  • assets/js/gutenberg-support.js
  • assets/js/integration-test.js
  • assets/js/legacy-signup.js
  • assets/js/list-tables.js
  • assets/js/network-activate.js
  • assets/js/payment-status-poll.js
  • assets/js/paypal-setup-wizard.js
  • assets/js/pricing-table.js
  • assets/js/screenshot-scraper.js
  • assets/js/setup-wizard-extra.js
  • assets/js/site-maintenance.js
  • assets/js/tax-rates.js
  • assets/js/template-library.js
  • assets/js/template-previewer.js
  • assets/js/template-switching.js
  • assets/js/thank-you.js
  • assets/js/toolbox.js
  • assets/js/tours.js
  • assets/js/url-preview.js
  • assets/js/view-logs.js
  • assets/js/visits-counter.js
  • assets/js/vue-apps.js
  • assets/js/webhook-page.js
  • assets/js/wu-password-reset.js
  • assets/js/wu-password-toggle.js
  • assets/js/wubox.js
  • inc/checkout/class-checkout.php
📝 Walkthrough

Walkthrough

Modernizes 40+ JavaScript files to ES2015+ syntax—const/let, method shorthand, arrow functions, IIFE wrapping—while adding checkout provisioning visibility (disable buttons, show "provisioning" message) and refining thank-you page polling to improve user feedback during site creation.

Changes

Checkout Provisioning User Feedback

Layer / File(s) Summary
Checkout blocking and custom provisioning message
assets/js/checkout.js
Checkout block() method now accepts an optional message parameter; submit handler disables all button[type="submit"] elements and passes a custom "Provisioning your site" message to prevent double-clicks and show progress during the ~30-second provisioning window.
Provisioning site i18n string
inc/checkout/class-checkout.php
Added provisioning_site localization key to checkout variables for client-side display.
Thank-you page provisioning polling enhancement
assets/js/thank-you.js
Vue component now triggers an explicit fetch("/wp-cron.php?doing_wp_cron") before polling for site creation; polling logic refined with adaptive delays, cache-bust redirect behavior, and one-time reload guard.

JavaScript ES2015+ Syntax Modernization

Layer / File(s) Summary
Vue component syntax modernization
assets/js/activity-stream.js, assets/js/addons.js, assets/js/checkout-forms-editor.js, assets/js/coupon-code.js, assets/js/customizer.js, assets/js/email-edit-page.js, assets/js/fields.js, assets/js/integration-test.js, assets/js/template-library.js, assets/js/tax-rates.js, assets/js/template-switching.js
Vue component definitions and lifecycle methods converted from option: function(){} to method shorthand; AJAX callbacks updated to arrow-function syntax; var replaced with const/let throughout. All reactive behavior, computed properties, and watchers preserved.
Complex specialized module modernization
assets/js/dns-management.js, assets/js/dns-table.js, assets/js/command-palette.js, assets/js/wubox.js
High-complexity modules converted to ES2015+: DNS management (sorting, AJAX, selection logic); command-palette (debounce hooks, command registration, callback shorthand); wubox (image/iframe/form handling, modal creation, error display). All business logic and state management unchanged.
Utility and helper function modernization
assets/js/cookie-helpers.js, assets/js/functions.js, assets/js/tours.js, assets/js/ajax-button.js, assets/js/webhook-page.js
Utility functions refactored to const/let and method shorthand; AJAX and DOM interaction logic preserved exactly. Includes error handling, cookie management, tour completion, and webhook payload formatting.
Admin and document-ready interface modernization
assets/js/admin.js, assets/js/admin-notices.js, assets/js/admin-screen.js, assets/js/checkout-form-editor-modal.js, assets/js/dashboard-statistics.js, assets/js/gutenberg-support.js, assets/js/screenshot-scraper.js, assets/js/setup-wizard-extra.js, assets/js/site-maintenance.js, assets/js/template-previewer.js, assets/js/url-preview.js, assets/js/view-logs.js
Admin pages and document-ready handlers converted to const/let; click handlers, toggle behavior, form submission, and AJAX workflows formatted consistently with only syntax/whitespace updates.
IIFE wrapping and global-scope reduction
assets/js/payment-status-poll.js, assets/js/visits-counter.js, assets/js/legacy-signup.js, assets/js/template-previewer.js, assets/js/paypal-setup-wizard.js, assets/js/color-field.js, assets/js/domain-logs.js, assets/js/pricing-table.js, assets/js/toolbox.js, assets/js/integration-test.js
Files wrapped in IIFE or with strict mode moved inside module scope to isolate helpers and reduce global namespace pollution. Runtime behavior unchanged; scope visibility altered for improved encapsulation.
Data flow and list-table modernization
assets/js/list-tables.js, assets/js/vue-apps.js
List-table pagination, sorting, and filter UI; Vue app lifecycle and section-based routing all converted to ES2015+ syntax. AJAX flow, DOM updates, and history API usage preserved exactly.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • Ultimate-Multisite/ultimate-multisite#304: Refactors the same template-library Vue module in this PR; the previous PR introduced the template-library front-end implementation while this refactors its syntax.
  • Ultimate-Multisite/ultimate-multisite#598: Both involve assets/js/payment-status-poll.js—this PR scopes the existing polling code into an IIFE while the referenced PR implemented the original payment-status polling module.
  • Ultimate-Multisite/ultimate-multisite#365: Both touch assets/js/tours.js and the markTourFinished logic—this PR re-indents/reformats while the referenced PR refactors tour completion behavior to ensure tours show only once.

Poem

🐰 The old var hops away, let const takes the stage,
Method shorthands shine bright—a modern age!
Checkout buttons now speak, "Provisioning flows,"
While polling spins wisely as site-birth grows.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/auto-20260518-165738-gh1220

@github-actions
Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@superdav42 superdav42 force-pushed the feature/auto-20260518-165738-gh1220 branch from d463819 to f67491a Compare May 18, 2026 23:06
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 18, 2026

Performance Test Results

Performance test results for 51495ad are in 🛎️!

Note: the numbers in parentheses show the difference to the previous (baseline) test run. Differences below 2% or 0.5 in absolute values are not shown.

URL: /

Run DB Queries Memory Before Template Template WP Total LCP TTFB LCP - TTFB
0 40 37.73 MB 648.50 ms (-41.50 ms / -6% ) 119.00 ms (+4.50 ms / +4% ) 782.50 ms (-49.00 ms / -6% ) 1540.00 ms (-42.00 ms / -3% ) 1471.45 ms (-45.15 ms / -3% ) 69.35 ms (+2.35 ms / +3% )
1 56 49.14 MB 742.50 ms 115.00 ms (+5.50 ms / +5% ) 851.50 ms 1634.00 ms 1569.90 ms 63.55 ms

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

🧹 Nitpick comments (2)
assets/js/url-preview.js (1)

20-30: ⚡ Quick win

Remove duplicated keyup handler to avoid double execution.

This block duplicates the previous keyup binding for the same selector/event and runs twice on each keystroke.

Suggested fix
-		$('.login').on('keyup', '`#field-site_url`', function(event) {
-
-			event.preventDefault();
-
-			const $selector = $(this);
-
-			const $target = $('`#wu-your-site`');
-
-			$target.text($selector.val());
-
-		}); // end on.keyUp;
As per coding guidelines, "Use WordPress ESLint config; indentation must be tabs; jQuery and Vue.js globals are available".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/url-preview.js` around lines 20 - 30, There are two identical keyup
handlers bound to $('.login').on('keyup', '`#field-site_url`', ...) causing double
execution; remove the duplicate block (or consolidate into a single handler) so
only one listener updates $('`#wu-your-site`').text($(this).val()); alternatively,
if you must re-bind, call $('.login').off('keyup', '`#field-site_url`') before .on
to prevent duplicates and ensure indentation uses tabs and jQuery globals per
ESLint guidelines.
assets/js/vue-apps.js (1)

229-235: ⚡ Quick win

Add null guards in the wubox:unload cleanup path.

Line 230-Line 233 assume both #WUB_window and [data-wu-app] always exist. If either is missing during teardown, this throws and skips cleanup.

Proposed patch
 		document.body.addEventListener("wubox:unload", function() {
 			const modal = document.getElementById("WUB_window");
-			const app = modal.querySelector("ul[data-wu-app]");
+			if (! modal) {
+				return;
+			}
+			const app = modal.querySelector("ul[data-wu-app]");
+			if (! app) {
+				return;
+			}
 			const app_name = "wu_" + app.dataset.wuApp;
 			delete window[ app_name ];
 			delete window[ app_name + "_errors" ];
 		});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/vue-apps.js` around lines 229 - 235, The wubox:unload handler
assumes document.getElementById("WUB_window") and
modal.querySelector("ul[data-wu-app]") always exist and will throw if absent;
update the event listener to null-guard the modal and app (and the
app.dataset.wuApp) before constructing app_name and deleting window properties
(only call delete when the referenced elements/values are present) so cleanup
never throws; target the listener function that references modal, app, app_name
and the window[...] deletes when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/js/admin-screen.js`:
- Around line 15-17: The template strings for $elem and $page_elem in
assets/js/admin-screen.js open anchor tags (<a>) but incorrectly close them with
</button>; update the closing tags to </a> for both variables (references: $elem
and $page_elem) so the generated HTML uses matching <a>...</a> tags and avoid
DOM parsing issues when appending.

In `@assets/js/checkout-forms-editor.js`:
- Around line 206-209: Guard the call to scrollIntoView by checking the result
of document.getElementById(element_id) before using it: after obtaining const
element = document.getElementById(element_id) (in the code that calls
element.scrollIntoView), add a null-check (if (!element) { /* handle or return
*/ }) and only call element.scrollIntoView({ behavior: 'smooth', block:
'center', inline: 'nearest' }) when element is non-null; optionally log or
handle the missing element case for debugging.

In `@assets/js/checkout.js`:
- Around line 1630-1642: Submit buttons are disabled via
jQuery(this).find('button[type="submit"]').prop('disabled', true') before
calling that.block(loadingMessage) but are never re-enabled on validation/API
failure paths; update all failure/exit branches that currently call unblock()
(including the paths around the other unblock() calls at the 1659-1661 region)
to also re-enable the buttons by calling
jQuery(this).find('button[type="submit"]').prop('disabled', false') (or capture
the form/buttons before disabling and call a single helper like
enableSubmitButtons() in every callback), ensuring every path that calls
that.block(...) or unblock() will restore the submit controls.

In `@assets/js/coupon-code.js`:
- Around line 169-175: Declare setupfee_off_with_symbol as a local variable
inside the applies_to_setup_fee block to avoid creating an implicit global;
mirror the pattern used for off_with_symbol by adding a local let/const
declaration before assigning it, and keep the existing conditional logic that
checks setup_fee_discount_type and uses setup_fee_discount_value and
accounting.formatMoney to produce the formatted value.

In `@assets/js/customizer.js`:
- Around line 20-28: The iframe load handler bound to
$('`#preview-stage-iframe`').on('load', ...) is being added repeatedly (it's
registered inside a message event), causing handler accumulation; fix by
ensuring a single handler is attached—either move the binding out of the message
event so it's registered once, or remove previous handlers before attaching (use
.off('load') then .on('load')), or use .one('load') if a single invocation is
desired; update the code around the message handler where the binding occurs and
target the $('`#preview-stage-iframe`') load handler (and any related
block.unblock() logic) accordingly.

In `@assets/js/dashboard-statistics.js`:
- Around line 161-168: In onClose, guard against partial range selections by
checking that selectedDates[0] and selectedDates[1] exist before calling
moment(...) and setting redirect.searchParams; only set 'start_date' if
selectedDates[0] is present and only set 'end_date' if selectedDates[1] is
present (or, if desired, set 'end_date' to the same value as 'start_date' when
only one date is chosen). Update the logic around selectedDates and redirect to
avoid accessing selectedDates[1] when it's undefined.
- Line 13: The assignment to mrr_graph creates an implicit global; update the
declaration at the Vue initialization to declare the variable explicitly (e.g.,
use const or let) instead of assigning to mrr_graph bare. Locate the Vue
instantiation that starts with "mrr_graph = new Vue({" and change it to an
explicit declaration (for example "const mrr_graph = new Vue({" or "let
mrr_graph = new Vue({") matching whether the graph is later reassigned.

In `@assets/js/edit-placeholders.js`:
- Around line 215-219: The code uses setInterval to clear that.saveMessage
repeatedly; change it to a one-shot setTimeout so the flash message is cleared
once after 6000ms. Replace the setInterval call around that.saveMessage with
setTimeout, and to avoid overlapping timers store the timer id (e.g.,
this._saveMessageTimer or that._saveMessageTimer), clear any existing timeout
with clearTimeout before creating a new one, and then set that.saveMessage = ''
inside the timeout callback.
- Around line 188-221: The AJAX success handler for the $.post call leaves the
component stuck in saving state on network/error paths; update the $.post
invocation around the block handling that.saving to add proper failure/always
handlers (use .fail() and/or .always()) so that on error you set that.saving =
false, set an appropriate that.saveMessage (or use data.message fallback), and
clear/stop the transient saveMessage timeout; ensure you also do not rely solely
on success to reset that.changed or that.delete and keep the existing call to
that.pull_data() only on success.

In `@assets/js/tax-rates.js`:
- Around line 371-375: The code uses setInterval to clear the one-time UI save
message (that.saveMessage = '') which causes repeated timers to stack; replace
the setInterval call with setTimeout so the message is cleared once after 6000ms
(i.e., change the invocation around setInterval(...) to setTimeout(...)) and
remove any unnecessary interval-clearing logic so repeated saves don't create
accumulating timers (refer to the setInterval usage and the that.saveMessage
assignment in this block).
- Around line 283-297: The bug is that using _.filter on this.data produces an
array and destroys the keyed object shape; replace the filter logic with an
object-preserving operation (e.g., _.omit or _.omitBy) so that that.data remains
an object keyed by category. Specifically, remove the use of
_.filter/cleaned_list and instead set that.data = _.omit(this.data,
that.tax_category) (or build a new object by copying all keys except
that.tax_category), and then set that.tax_category = Object.keys(that.data)[0]
to pick the first key; ensure the fallback default object
(default:{name:'Default',rates:[]}) is used when the result is empty.

In `@assets/js/template-previewer.js`:
- Around line 77-80: The iOS-specific patch is looking up body on the iframe
element subtree instead of the iframe's document, so in isIOS() branch locate
the iframe element (e.g., const iframeEl = document.getElementById("iframe")),
obtain its document via iframeEl.contentDocument ||
iframeEl.contentWindow?.document, then get doc.body and apply the class
"wu-fix-safari-preview" and style adjustments to that body; ensure you
null-check iframeEl and the retrieved document before mutating to avoid runtime
errors.

In `@assets/js/thank-you.js`:
- Line 132: Replace the hard-coded fetch("/wp-cron.php?doing_wp_cron") (and the
other two identical occurrences) with a site-aware URL provided via a localized
JS variable (e.g., window.wpData.wpCronUrl or similar injected with
wp_localize_script in PHP), and use that variable in the fetch calls (e.g.,
fetch(window.wpData.wpCronUrl + "?doing_wp_cron")). Update all three occurrences
in thank-you.js so they read from the localized variable and include a
reasonable fallback if the variable is missing.
- Around line 3-7: The TransitionText function currently ignores its has_icon
parameter by hardcoding has_icon: false; update the returned object inside
TransitionText to initialize the property from the parameter (e.g., has_icon:
has_icon or has_icon: Boolean(has_icon)) so callers can set the initial
spinner/icon state; keep the default parameter (has_icon = false) and ensure any
subsequent code in TransitionText references this property rather than the
hardcoded false.
- Around line 68-86: The resend handler currently awaits fetch and
request.json() without error handling, so failures leave the UI stuck; wrap the
fetch/response parsing and subsequent logic in a try/catch in the function that
does the POST (referencing the fetch call and request.json() in
assets/js/thank-you.js) and on any caught error call transitional_text.text(...)
with a generic/localized error message (using wu_thank_you.i18n or a fallback)
and the error class (e.g., "wu-text-red-600") then .done(); also ensure you
handle non-JSON or network errors and log the error to console for debugging
while keeping the user-facing message clear.

In `@assets/js/webhook-page.js`:
- Around line 80-84: The error handler for the AJAX webhook test (the
error(jqXHR) block that currently only calls wu_ajax_error(jqXHR)) fails to
clear the per-row loading indicator; update this error branch to also perform
the same loading-cleanup used on success (e.g., call the function that clears
the row loader or remove the loading class/spinner from the row element) so the
spinner is hidden after failures — ensure this cleanup runs before or after
calling wu_ajax_error(jqXHR) inside the error(jqXHR) handler.

In `@assets/js/wubox.js`:
- Line 508: The line setting the cursor style ("target.style.cursor = '';") is
space-indented but the project enforces tab indentation for .js files; replace
the leading spaces on that statement with a single tab so the line matches the
file's tab-based indentation and adheres to the WordPress ESLint config used
across this codebase.
- Around line 225-227: The code assumes event.submitter exists when creating
submitButton and appending it to formData; update the submit handler to guard
against a missing event.submitter by checking if event.submitter is truthy and
otherwise locating the submit control on the form (e.g.,
form.querySelector('button[type="submit"], input[type="submit"]') or a sensible
default), then use that control's value (or skip appending if none found) before
calling formData.append("submit", ...); refer to the submitButton variable,
event.submitter, and formData in your changes so the logic replaces the direct
event.submitter.value access with a safe fallback path.
- Around line 192-195: Guard against a missing inline source or empty child
before moving DOM nodes: in the block that uses params.inlineId, check that
document.getElementById(params.inlineId) returned a non-null element and that
element.children[0] (or element.firstElementChild) exists before calling
ajaxContent.insertAdjacentElement(...); likewise update the unloadAction to only
call element.insertAdjacentElement("afterbegin", ajaxContent.children[0]) when
both element and ajaxContent.children[0] exist (or bail/skip the move if either
is missing) so insertAdjacentElement never receives undefined.

---

Nitpick comments:
In `@assets/js/url-preview.js`:
- Around line 20-30: There are two identical keyup handlers bound to
$('.login').on('keyup', '`#field-site_url`', ...) causing double execution; remove
the duplicate block (or consolidate into a single handler) so only one listener
updates $('`#wu-your-site`').text($(this).val()); alternatively, if you must
re-bind, call $('.login').off('keyup', '`#field-site_url`') before .on to prevent
duplicates and ensure indentation uses tabs and jQuery globals per ESLint
guidelines.

In `@assets/js/vue-apps.js`:
- Around line 229-235: The wubox:unload handler assumes
document.getElementById("WUB_window") and modal.querySelector("ul[data-wu-app]")
always exist and will throw if absent; update the event listener to null-guard
the modal and app (and the app.dataset.wuApp) before constructing app_name and
deleting window properties (only call delete when the referenced elements/values
are present) so cleanup never throws; target the listener function that
references modal, app, app_name and the window[...] deletes when making this
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82f457a7-50f5-44b5-b2ba-3d23e9664fc0

📥 Commits

Reviewing files that changed from the base of the PR and between 6ac59d0 and d463819.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (50)
  • assets/js/activity-stream.js
  • assets/js/addons.js
  • assets/js/admin-notices.js
  • assets/js/admin-screen.js
  • assets/js/admin.js
  • assets/js/ajax-button.js
  • assets/js/app.js
  • assets/js/checkout-form-editor-modal.js
  • assets/js/checkout-forms-editor.js
  • assets/js/checkout.js
  • assets/js/color-field.js
  • assets/js/command-palette.js
  • assets/js/cookie-helpers.js
  • assets/js/coupon-code.js
  • assets/js/customizer.js
  • assets/js/dashboard-statistics.js
  • assets/js/dns-management.js
  • assets/js/dns-table.js
  • assets/js/domain-logs.js
  • assets/js/edit-placeholders.js
  • assets/js/email-edit-page.js
  • assets/js/fields.js
  • assets/js/functions.js
  • assets/js/gutenberg-support.js
  • assets/js/integration-test.js
  • assets/js/legacy-signup.js
  • assets/js/list-tables.js
  • assets/js/network-activate.js
  • assets/js/payment-status-poll.js
  • assets/js/paypal-setup-wizard.js
  • assets/js/pricing-table.js
  • assets/js/screenshot-scraper.js
  • assets/js/setup-wizard-extra.js
  • assets/js/site-maintenance.js
  • assets/js/tax-rates.js
  • assets/js/template-library.js
  • assets/js/template-previewer.js
  • assets/js/template-switching.js
  • assets/js/thank-you.js
  • assets/js/toolbox.js
  • assets/js/tours.js
  • assets/js/url-preview.js
  • assets/js/view-logs.js
  • assets/js/visits-counter.js
  • assets/js/vue-apps.js
  • assets/js/webhook-page.js
  • assets/js/wu-password-reset.js
  • assets/js/wu-password-toggle.js
  • assets/js/wubox.js
  • inc/checkout/class-checkout.php

Comment thread assets/js/admin-screen.js
Comment on lines +15 to +17
let $elem = `<a id="wu-admin-screen-customize" href="${ wu_admin_screen.customize_link }" class="button show-settings">${ wu_admin_screen.i18n.customize_label }</button>`;

const $page_elem = `<a title="${ wu_admin_screen.i18n.page_customize_label }" id="wu-admin-screen-page-customize" href="${ wu_admin_screen.page_customize_link }" class="wubox button show-settings">${ wu_admin_screen.i18n.page_customize_label }</button>`;
const $page_elem = `<a title="${ wu_admin_screen.i18n.page_customize_label }" id="wu-admin-screen-page-customize" href="${ wu_admin_screen.page_customize_link }" class="wubox button show-settings">${ wu_admin_screen.i18n.page_customize_label }</button>`;
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 | 🟡 Minor | ⚡ Quick win

Fix mismatched closing tags in generated anchor HTML.

The template strings create <a ...> elements but close them with </button>, which can cause inconsistent DOM parsing when appended.

Proposed fix
-		let $elem = `<a id="wu-admin-screen-customize" href="${ wu_admin_screen.customize_link }" class="button show-settings">${ wu_admin_screen.i18n.customize_label }</button>`;
+		let $elem = `<a id="wu-admin-screen-customize" href="${ wu_admin_screen.customize_link }" class="button show-settings">${ wu_admin_screen.i18n.customize_label }</a>`;

-		const $page_elem = `<a title="${ wu_admin_screen.i18n.page_customize_label }" id="wu-admin-screen-page-customize" href="${ wu_admin_screen.page_customize_link }" class="wubox button show-settings">${ wu_admin_screen.i18n.page_customize_label }</button>`;
+		const $page_elem = `<a title="${ wu_admin_screen.i18n.page_customize_label }" id="wu-admin-screen-page-customize" href="${ wu_admin_screen.page_customize_link }" class="wubox button show-settings">${ wu_admin_screen.i18n.page_customize_label }</a>`;

-			$elem = `<a id="wu-admin-screen-customize" href="${ wu_admin_screen.close_link }" class="button show-settings wu-font-medium"><span class="wu-text-sm wu-align-text-bottom wu-text-red-500 wu-mr-2 wu--ml-1 dashicons-wu-circle-with-cross"></span>${ wu_admin_screen.i18n.close_label }</button>`;
+			$elem = `<a id="wu-admin-screen-customize" href="${ wu_admin_screen.close_link }" class="button show-settings wu-font-medium"><span class="wu-text-sm wu-align-text-bottom wu-text-red-500 wu-mr-2 wu--ml-1 dashicons-wu-circle-with-cross"></span>${ wu_admin_screen.i18n.close_label }</a>`;

Also applies to: 21-21

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/admin-screen.js` around lines 15 - 17, The template strings for
$elem and $page_elem in assets/js/admin-screen.js open anchor tags (<a>) but
incorrectly close them with </button>; update the closing tags to </a> for both
variables (references: $elem and $page_elem) so the generated HTML uses matching
<a>...</a> tags and avoid DOM parsing issues when appending.

Comment on lines +206 to 209
const element = document.getElementById(element_id);

element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });

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 | 🟡 Minor | ⚡ Quick win

Guard scrollIntoView against missing elements.

document.getElementById(element_id) can return null; current code will throw before finishing the UI flow.

Suggested fix
-							const element = document.getElementById(element_id);
-							element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
+							const element = document.getElementById(element_id);
+							if (element) {
+								element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
+							}
📝 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 element = document.getElementById(element_id);
element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
const element = document.getElementById(element_id);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/checkout-forms-editor.js` around lines 206 - 209, Guard the call to
scrollIntoView by checking the result of document.getElementById(element_id)
before using it: after obtaining const element =
document.getElementById(element_id) (in the code that calls
element.scrollIntoView), add a null-check (if (!element) { /* handle or return
*/ }) and only call element.scrollIntoView({ behavior: 'smooth', block:
'center', inline: 'nearest' }) when element is non-null; optionally log or
handle the missing element case for debugging.

Comment thread assets/js/checkout.js
Comment on lines +1630 to 1642
// Disable all submit buttons to prevent double-clicks
jQuery(this).find('button[type="submit"]').prop('disabled', true);

// Show loading message with status text
const loadingMessage = '<div style="text-align: center;">' +
'<div class="spinner is-active wu-float-none" style="float: none !important; margin-bottom: 12px;"></div>' +
'<div style="font-size: 14px; line-height: 1.5;">' +
(wu_checkout.i18n.provisioning_site || 'Provisioning your site — this can take up to 60 seconds.') +
'</div>' +
'</div>';

that.block(loadingMessage);

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 | ⚡ Quick win

Re-enable submit controls on failed submit paths.

At Line 1631, submits are disabled, but failure paths only call unblock() and never restore submit buttons. After a validation/API error, users can get stuck unable to retry.

Proposed fix
unblock() {
-
-	jQuery(this.$el).wu_unblock();
+	jQuery(this.$el).wu_unblock();
+	jQuery(this.$el)
+		.find('button[type="submit"], input[type="submit"]')
+		.prop('disabled', false);

},

Also applies to: 1659-1661

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/checkout.js` around lines 1630 - 1642, Submit buttons are disabled
via jQuery(this).find('button[type="submit"]').prop('disabled', true') before
calling that.block(loadingMessage) but are never re-enabled on validation/API
failure paths; update all failure/exit branches that currently call unblock()
(including the paths around the other unblock() calls at the 1659-1661 region)
to also re-enable the buttons by calling
jQuery(this).find('button[type="submit"]').prop('disabled', false') (or capture
the form/buttons before disabling and call a single helper like
enableSubmitButtons() in every callback), ensuring every path that calls
that.block(...) or unblock() will restore the submit controls.

Comment thread assets/js/coupon-code.js
Comment on lines +169 to +175
if (applies_to_setup_fee) {

}

if(applies_to_setup_fee) {
if (setup_fee_discount_type != '"absolute"') {
setupfee_off_with_symbol = ''.concat(setup_fee_discount_value, '%');
} else {
setupfee_off_with_symbol = accounting.formatMoney(parseFloat(setup_fee_discount_value));
}
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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify undeclared assignment and declaration coverage for setupfee_off_with_symbol.
rg -n -C2 '\bsetupfee_off_with_symbol\b' assets/js/coupon-code.js
rg -n -C2 '\b(let|const|var)\s+setupfee_off_with_symbol\b' assets/js/coupon-code.js

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 628


🏁 Script executed:

# Show the function containing lines 169-175 to verify no outer scope declaration
sed -n '1,200p' assets/js/coupon-code.js | head -200 | tail -+140

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 149


🏁 Script executed:

# Show broader context around lines 169-175
sed -n '140,200p' assets/js/coupon-code.js

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 1998


Declare setupfee_off_with_symbol locally to avoid implicit global writes.

setupfee_off_with_symbol is assigned at lines 172 and 174 without let/const/var, creating an implicit global variable. Add a local declaration within the if (applies_to_setup_fee) block to match the pattern used for off_with_symbol (line 160).

Suggested fix
 							if (applies_to_setup_fee) {
+								let setupfee_off_with_symbol = '';
 
 								if (setup_fee_discount_type != '"absolute"') {
 									setupfee_off_with_symbol = ''.concat(setup_fee_discount_value, '%');
 								} else {
 									setupfee_off_with_symbol = accounting.formatMoney(parseFloat(setup_fee_discount_value));
 								}
📝 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
if (applies_to_setup_fee) {
}
if(applies_to_setup_fee) {
if (setup_fee_discount_type != '"absolute"') {
setupfee_off_with_symbol = ''.concat(setup_fee_discount_value, '%');
} else {
setupfee_off_with_symbol = accounting.formatMoney(parseFloat(setup_fee_discount_value));
}
if (applies_to_setup_fee) {
let setupfee_off_with_symbol = '';
if (setup_fee_discount_type != '"absolute"') {
setupfee_off_with_symbol = ''.concat(setup_fee_discount_value, '%');
} else {
setupfee_off_with_symbol = accounting.formatMoney(parseFloat(setup_fee_discount_value));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/coupon-code.js` around lines 169 - 175, Declare
setupfee_off_with_symbol as a local variable inside the applies_to_setup_fee
block to avoid creating an implicit global; mirror the pattern used for
off_with_symbol by adding a local let/const declaration before assigning it, and
keep the existing conditional logic that checks setup_fee_discount_type and uses
setup_fee_discount_value and accounting.formatMoney to produce the formatted
value.

Comment thread assets/js/customizer.js
Comment on lines +20 to +28
$('#preview-stage-iframe').on('load', function() {

if (block) {
if (block) {

block.unblock();
block.unblock();

} // end if;
} // end if;

});
});
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 | 🟡 Minor | ⚡ Quick win

Prevent repeated load handler accumulation on iframe.

This listener is registered on every message event, so handlers stack up over time.

Suggested fix
-		$('`#preview-stage-iframe`').on('load', function() {
+		$('`#preview-stage-iframe`').off('load').one('load', function() {
📝 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
$('#preview-stage-iframe').on('load', function() {
if (block) {
if (block) {
block.unblock();
block.unblock();
} // end if;
} // end if;
});
});
$('`#preview-stage-iframe`').off('load').one('load', function() {
if (block) {
block.unblock();
} // end if;
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/customizer.js` around lines 20 - 28, The iframe load handler bound
to $('`#preview-stage-iframe`').on('load', ...) is being added repeatedly (it's
registered inside a message event), causing handler accumulation; fix by
ensuring a single handler is attached—either move the binding out of the message
event so it's registered once, or remove previous handlers before attaching (use
.off('load') then .on('load')), or use .one('load') if a single invocation is
desired; update the code around the message handler where the binding occurs and
target the $('`#preview-stage-iframe`') load handler (and any related
block.unblock() logic) accordingly.

Comment thread assets/js/thank-you.js
}
}));
});
fetch("/wp-cron.php?doing_wp_cron");
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 | ⚡ Quick win

Avoid hard-coded /wp-cron.php URLs.

Absolute root paths can break on WordPress subdirectory installs; this should come from a localized/site-aware URL.

Suggested direction
- fetch("/wp-cron.php?doing_wp_cron");
+ fetch(wu_thank_you.wp_cron_url);

And use the same replacement for Lines 172 and 175.

Also applies to: 172-172, 175-175

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/thank-you.js` at line 132, Replace the hard-coded
fetch("/wp-cron.php?doing_wp_cron") (and the other two identical occurrences)
with a site-aware URL provided via a localized JS variable (e.g.,
window.wpData.wpCronUrl or similar injected with wp_localize_script in PHP), and
use that variable in the fetch calls (e.g., fetch(window.wpData.wpCronUrl +
"?doing_wp_cron")). Update all three occurrences in thank-you.js so they read
from the localized variable and include a reasonable fallback if the variable is
missing.

Comment thread assets/js/webhook-page.js
Comment on lines +80 to +84
error(jqXHR) {

wu_ajax_error(jqXHR);
wu_ajax_error(jqXHR);

},
});
},
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 | ⚡ Quick win

Clear loading state on webhook test failure.

On Line 80, the error path reports via wu_ajax_error(jqXHR) but never hides the per-row loading indicator enabled before the request, so the spinner can remain stuck after failures.

Suggested fix
 		jQuery(document).on('click', '`#action_button`', function (event) {
@@
-			$.ajax({
+			const webhook_id = $(this).data('object');
+
+			$.ajax({
 				method: 'post',
 				url: ajaxurl,
 				data: {
@@
-					webhook_id: $(this).data('object'),
+					webhook_id,
@@
 				error(jqXHR) {
+					$('[data-loading="wu_action_button_loading_' + webhook_id + '"]').addClass('hidden');
 					wu_ajax_error(jqXHR);
 
 				},
 			});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/webhook-page.js` around lines 80 - 84, The error handler for the
AJAX webhook test (the error(jqXHR) block that currently only calls
wu_ajax_error(jqXHR)) fails to clear the per-row loading indicator; update this
error branch to also perform the same loading-cleanup used on success (e.g.,
call the function that clears the row loader or remove the loading class/spinner
from the row element) so the spinner is hidden after failures — ensure this
cleanup runs before or after calling wu_ajax_error(jqXHR) inside the
error(jqXHR) handler.

Comment thread assets/js/wubox.js
Comment on lines +192 to +195
const element = document.getElementById(params.inlineId);
ajaxContent.insertAdjacentElement("beforeend", element == null ? void 0 : element.children[ 0 ]);
const unloadAction = () => {
element == null ? void 0 : element.insertAdjacentElement("afterbegin", ajaxContent.children[ 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 | ⚡ Quick win

Guard missing inline source element before DOM move.

Line 193 can throw when params.inlineId is invalid or the source has no child node, because insertAdjacentElement receives undefined. That crashes the box open flow.

Proposed fix
 const createInlineBox = (boxWindow, boxOverlay, loaded, caption, params) => {
 	const ajaxContent = baseAjaxElement(boxWindow, boxOverlay, caption, params);
 	const element = document.getElementById(params.inlineId);
-	ajaxContent.insertAdjacentElement("beforeend", element == null ? void 0 : element.children[ 0 ]);
+	const inlineChild = element && element.children.length ? element.children[0] : null;
+	if (! ajaxContent || ! inlineChild) {
+		loaded();
+		return;
+	}
+	ajaxContent.insertAdjacentElement("beforeend", inlineChild);
 	const unloadAction = () => {
 		element == null ? void 0 : element.insertAdjacentElement("afterbegin", ajaxContent.children[ 0 ]);
 		document.body.removeEventListener("wubox:unload", unloadAction);
 	};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/wubox.js` around lines 192 - 195, Guard against a missing inline
source or empty child before moving DOM nodes: in the block that uses
params.inlineId, check that document.getElementById(params.inlineId) returned a
non-null element and that element.children[0] (or element.firstElementChild)
exists before calling ajaxContent.insertAdjacentElement(...); likewise update
the unloadAction to only call element.insertAdjacentElement("afterbegin",
ajaxContent.children[0]) when both element and ajaxContent.children[0] exist (or
bail/skip the move if either is missing) so insertAdjacentElement never receives
undefined.

Comment thread assets/js/wubox.js
Comment on lines +225 to +227
const submitButton = event.submitter.value;
const formData = new FormData(form);
formData.append("submit", submitButton);
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 | ⚡ Quick win

Handle submit events without event.submitter.

Line 225 assumes event.submitter is always present. If it is null, event.submitter.value throws and aborts submission/error handling.

Proposed fix
-	const submitButton = event.submitter.value;
+	const submitButton = event.submitter ? event.submitter.value : "";
 	const formData = new FormData(form);
-	formData.append("submit", submitButton);
+	if (submitButton) {
+		formData.append("submit", submitButton);
+	}
📝 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 submitButton = event.submitter.value;
const formData = new FormData(form);
formData.append("submit", submitButton);
const submitButton = event.submitter ? event.submitter.value : "";
const formData = new FormData(form);
if (submitButton) {
formData.append("submit", submitButton);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/wubox.js` around lines 225 - 227, The code assumes event.submitter
exists when creating submitButton and appending it to formData; update the
submit handler to guard against a missing event.submitter by checking if
event.submitter is truthy and otherwise locating the submit control on the form
(e.g., form.querySelector('button[type="submit"], input[type="submit"]') or a
sensible default), then use that control's value (or skip appending if none
found) before calling formData.append("submit", ...); refer to the submitButton
variable, event.submitter, and formData in your changes so the logic replaces
the direct event.submitter.value access with a safe fallback path.

Comment thread assets/js/wubox.js
@superdav42 superdav42 merged commit f4fd2cb into main May 18, 2026
10 of 11 checks passed
@superdav42
Copy link
Copy Markdown
Collaborator Author

Summary

Adds client-side UX improvements to prevent double-clicks and provide user feedback during the ~30s checkout provisioning wait.

Changes

  • Client UX (mandatory): Disable the submit button and render a spinner plus status copy ("Provisioning your site — this can take up to 60 seconds.") between submit and redirect. This eliminates the double-click failure mode.
  • Added provisioning_site i18n string to checkout class for the status message.
  • Modified block() method to accept an optional message parameter for custom loading messages.
  • Form submission handler now disables all submit buttons and shows the loading message immediately upon form submission.

Verification

  • Submit a fresh demo signup with DevTools Network throttling off; confirm the spinner appears immediately and the submit button is disabled until the redirect fires.
  • Confirm no wu_cap_* request fires twice from a single click sequence.

aidevops.sh v3.15.63 plugin for OpenCode v1.15.5 with claude-haiku-4-5 spent 2m and 4,877 tokens on this as a headless worker.


Merged via PR #1222 to main.
Merged by deterministic merge pass (pulse-wrapper.sh).

@github-actions
Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@superdav42 superdav42 added the review-feedback-scanned Merged PR already scanned for quality feedback label May 21, 2026
superdav42 added a commit that referenced this pull request May 21, 2026
- Add null-check before calling scrollIntoView() in scroll_to method
- Prevents TypeError when element with given ID is not found
- Resolves issue #1235 from PR #1222 review feedback
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-feedback-scanned Merged PR already scanned for quality feedback

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Checkout: 30s silent wait after submit; double-clicks break Cap captcha tokens

1 participant