Skip to content

~11.2 - IS-11032 Change submit button to have spinners and disabled behavior …#75

Open
urre wants to merge 2 commits intodevfrom
feat/dev/IS-11032-when-using-chrome-with-a-slow-network-if-the-user-submits-the-authentication-many-times-they-will-get-a-bad-request-response
Open

~11.2 - IS-11032 Change submit button to have spinners and disabled behavior …#75
urre wants to merge 2 commits intodevfrom
feat/dev/IS-11032-when-using-chrome-with-a-slow-network-if-the-user-submits-the-authentication-many-times-they-will-get-a-bad-request-response

Conversation

@urre
Copy link
Collaborator

@urre urre commented Mar 11, 2026

Summary

  • Adds a new button-loading fragment that shows a spinner and disables submit buttons on form submission, preventing duplicate submissions when using Chrome on slow networks
  • Updates all 47 Velocity templates across authentication flows (authenticators, authentication actions, OAuth views) to use the new button-loading CSS class and wrap button text in <span> elements for the spinner animation
  • Converts several <input type="submit"> elements to <button type="submit"> for consistent loading behavior
  • Includes a MutationObserver that reverts the loading state if validation errors appear, and a pageshow listener to handle browser back-button navigation

How it works

The button-loading.vm fragment injects a script that:

  1. Listens for submit events on all forms
  2. Checks HTML5 form validity before activating the loading state
  3. Adds button-loading-active class and sets disabled on submit buttons
  4. Uses a MutationObserver to detect error elements and restore buttons if submission fails
  5. Clears loading state on pageshow (back/forward cache navigation)

Demo

This gif shows that when button is clicked, it becomes disabled and has a spinner inside. This way it cannot be clicked again.

test

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a reusable “loading/disabled submit button” behavior across Identity Server UI templates to prevent duplicate submissions (notably on slow Chrome networks) and to show a spinner while requests are in flight.

Changes:

  • Introduces a new fragments/button-loading.vm script to toggle .button-loading-active + disabled on submit buttons and to reset state on pageshow / detected errors.
  • Updates OAuth, authenticator, and authentication-action Velocity templates to add the button-loading class and wrap labels in <span>.
  • Converts some <input type="submit"> controls to <button type="submit"> for consistent spinner behavior.

Reviewed changes

Copilot reviewed 47 out of 47 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/identity-server/templates/core/views/oauth/enter-usercode.vm Converts submit to button with loading markup; includes button-loading fragment.
src/identity-server/templates/core/views/oauth/consent.vm Converts consent submit/cancel to buttons with loading markup; includes fragment.
src/identity-server/templates/core/views/oauth/confirm-usercode.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/fragments/set-alias.vm Adds loading markup and includes fragment for alias form.
src/identity-server/templates/core/fragments/button-loading.vm New shared JS for submit-button loading/disable + error/back-nav reset behavior.
src/identity-server/templates/core/authenticator/webauthn/register/get.vm Adds loading markup to registration buttons; includes fragment.
src/identity-server/templates/core/authenticator/webauthn/authenticate-device/get.vm Adds loading markup to submit/authenticate buttons; includes fragment.
src/identity-server/templates/core/authenticator/totp/set-alias/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/totp/register/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/totp/authenticate/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/totp/authenticate/enter-totp.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/sms/register/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/sms/enter-username/get.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/sms/enter-otp/get.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/sms/confirm/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/pingfederate/authenticate/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/passkeys/register/get.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/passkeys/authenticate-device/get.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/html-form/set-password/get.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/html-form/reset-password/get.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/html-form/forgot-account-id/get.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/html-form/create-account/post.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/html-form/create-account/get.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/html-form/authenticate/get.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/html-form/account-activation/set-password.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/html-form/account-activation/request-new-activation.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/html-form/account-activation/otp.vm Adds loading markup and includes fragment for activate/resend OTP buttons.
src/identity-server/templates/core/authenticator/email/enter-username/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/email/enter-otp/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/duo/register/register-device.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/duo/enter-username/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/bankid/wait/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authenticator/bankid/launch/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authentication-action/signup/get.vm Includes fragment (to enable loading behavior for submit buttons on this page).
src/identity-server/templates/core/authentication-action/reset-password/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authentication-action/require-active-account/get.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authentication-action/request-acknowledgement/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authentication-action/opt-in-mfa/setup.vm Adds loading markup (incl. spans) and includes fragment.
src/identity-server/templates/core/authentication-action/opt-in-mfa/setup-confirm.vm Converts submit inputs to buttons w/ loading markup; includes fragment.
src/identity-server/templates/core/authentication-action/opt-in-mfa/select.vm Adds loading markup (incl. spans) and includes fragment.
src/identity-server/templates/core/authentication-action/opt-in-mfa/select-to-manage.vm Adds loading markup (incl. spans) and includes fragment.
src/identity-server/templates/core/authentication-action/opt-in-mfa/register-confirm.vm Converts submit inputs to buttons w/ loading markup; includes fragment.
src/identity-server/templates/core/authentication-action/opt-in-mfa/recovery-codes-reset.vm Converts submit inputs to buttons w/ loading markup; includes fragment.
src/identity-server/templates/core/authentication-action/opt-in-mfa/recovery-code.vm Converts submit input and cancel button to loading markup; includes fragment.
src/identity-server/templates/core/authentication-action/opt-in-mfa/manage.vm Adds loading markup (incl. spans) and includes fragment.
src/identity-server/templates/core/authentication-action/auto-link-account/index.vm Adds loading markup and includes fragment.
src/identity-server/templates/core/authentication-action/attribute-prompt/index.vm Adds loading markup and includes fragment.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +25 to +28
// Function to check if error elements exist
function hasErrorElements() {
return document.querySelector('.alert-danger, .is-error, .error, [class*="error"]') !== null;
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

hasErrorElements() uses a very broad selector ([class*="error"]) and doesn’t account for errors that already exist in the DOM before a new submit attempt. If an error container is always present (or a previous error remains visible), the observer will clear the loading state on the next unrelated DOM mutation, re-enabling buttons and undermining duplicate-submit protection. Consider narrowing the selector to known error containers and/or tracking the pre-submit error state and only clearing when the error state transitions from “no errors” to “errors shown”.

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +61
// Stop observing after 5 seconds (successful navigation would unload the page)
setTimeout(function() {
if (errorObserver) {
errorObserver.disconnect();
errorObserver = null;
}
}, 5000);
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The error observer is forcibly disconnected after 5 seconds. If a submit triggers an async flow (eg WebAuthn) and the failure UI appears after this timeout, buttons can stay disabled/spinning with no way for the user to retry. Consider removing the timeout (navigation will unload the page anyway) or increasing it substantially and/or tying cleanup to a more reliable lifecycle event.

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +88
// Add loading state to all submit buttons in this form
var submitButtons = form.querySelectorAll('button[type="submit"], input[type="submit"]');

submitButtons.forEach(function(btn) {
if (!btn.classList.contains('button-loading-active')) {
btn.classList.add('button-loading-active');
btn.disabled = true;
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Loading state is applied to every submit control in the form (button[type=submit], input[type=submit]) regardless of whether it has the .button-loading base class. Since the spinner CSS relies on .button-loading (eg position: relative and span styling), applying only .button-loading-active can result in misplaced/unstyled spinners (and inputs won’t render ::after in many browsers). Consider limiting this to .button-loading buttons, or adding the base class programmatically when activating loading, and explicitly handling <button> elements that omit a type attribute (default submit).

Copilot uses AI. Check for mistakes.
@urre urre self-assigned this Mar 11, 2026
@urre urre changed the title IS-11032 Change submit button to have spinners and disabled behavior … ~11.1 - IS-11032 Change submit button to have spinners and disabled behavior … Mar 16, 2026
@renatoathaydes
Copy link
Collaborator

@urre did you have a look at the AI review? Should we merge this or you want to fix anything else?

@urre urre changed the title ~11.1 - IS-11032 Change submit button to have spinners and disabled behavior … ~11.2 - IS-11032 Change submit button to have spinners and disabled behavior … Mar 17, 2026
attributeFilter: ['class']
});

// Stop observing after 5 seconds (successful navigation would unload the page)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why observing for errors is stopped after 5 seconds ? If we have a slow connection, a response with errors can arrive after the 5 seconds have elapsed and we would not reset the buttons.

</div>
</form>

#parse("fragments/button-loading")
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't this be added into the default layout, to avoid having to add it in all templates ?

}

// Create observer to watch for error elements being added to the DOM
errorObserver = new MutationObserver(function(mutations) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is MutationObserver compatible with all the Browser versions we support ? I checked that it should be ok, but asking just in case.

}

// Create observer to watch for error elements being added to the DOM
errorObserver = new MutationObserver(function(mutations) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this observer on the document body, based on css selectors, can have a performance impact on slow end devices ? Or is it ok, knowing that the size of our templates is rather small ?

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.

5 participants