From 3a6a79a607ef87b08e71140a6acc68dbdfdf2e15 Mon Sep 17 00:00:00 2001 From: Paul Derscheid Date: Tue, 3 Mar 2026 10:08:05 +0100 Subject: [PATCH] Bug 15792: Throttle patron renewal links to prevent double-clicks - Replace plain renewal links in patron_messages.inc with the reusable throttled-button component introduced in Bug 40896 - Covers all four renewal links: warndeparture and expired states, in both circulation and patron detail contexts - Include throttledButton.js in circulation.tt and moremember.tt - Add persist mode and done icon state to throttled-button component so the button stays disabled on bfcache restore with a check icon instead of a misleading spinner Test plan: 1) Create or update a patron category with an enrollment fee and an enrollment period in months (e.g. 24 months) 2) Create a patron of that type and write off any initial enrollment fee 3) Set the patron's dateexpiry to the past so the account is expired 4) Go to /cgi-bin/koha/circ/circulation.pl and scan the patron 5) Observe the "Renew" link in the expiration warning now shows as a small button with a rotate icon 6) Click "Renew" and confirm the button becomes disabled with a spinner, preventing a second click 7) Verify the patron is renewed correctly with only one enrollment fee charged and the correct expiry date 8) Press the browser back button to return via bfcache 9) Verify the button remains disabled and shows a check icon instead of the spinner, indicating the action was already completed 10) Repeat from the patron detail page (moremember.pl) to verify the same throttle and done-state behavior there 11) Test the warndeparture case by setting dateexpiry to a near-future date and confirming the same behavior Signed-off-by: David Nind Signed-off-by: Andrew Fuerste Henry Signed-off-by: Lucas Gass --- .../prog/en/includes/patron_messages.inc | 12 ++++++++---- .../prog/en/includes/throttled-button.inc | 6 +++++- .../prog/en/modules/circ/circulation.tt | 1 + .../prog/en/modules/members/moremember.tt | 1 + .../intranet-tmpl/prog/js/throttledButton.js | 17 ++++++++++++++++- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patron_messages.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patron_messages.inc index c0fcc0a575..b93f9f5b39 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron_messages.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron_messages.inc @@ -24,10 +24,12 @@ Expiration: Patron's card expires on [% patron.dateexpiry | $KohaDates %]. [% IF ( moremember ) %] - Renew or + [% INCLUDE 'throttled-button.inc' element_type='link' href='/cgi-bin/koha/members/setstatus.pl?borrowernumber=' _ patron.borrowernumber _ '&destination=member&reregistration=y' classes='btn btn-default btn-xs' ready_icon='fa-solid fa-rotate' label=t('Renew') persist=1 done_icon='fa-solid fa-check' %] + or Edit details [% ELSE %] - Renew or + [% INCLUDE 'throttled-button.inc' element_type='link' href='/cgi-bin/koha/members/setstatus.pl?borrowernumber=' _ patron.borrowernumber _ '&destination=circ&reregistration=y' classes='btn btn-default btn-xs' ready_icon='fa-solid fa-rotate' label=t('Renew') persist=1 done_icon='fa-solid fa-check' %] + or Edit details [% END %] @@ -47,10 +49,12 @@ Patron's card expired on [% expiry | $KohaDates %] [% END %] [% IF ( moremember ) %] - Renew or + [% INCLUDE 'throttled-button.inc' element_type='link' href='/cgi-bin/koha/members/setstatus.pl?borrowernumber=' _ patron.borrowernumber _ '&destination=member&reregistration=y' classes='btn btn-default btn-xs' ready_icon='fa-solid fa-rotate' label=t('Renew') persist=1 done_icon='fa-solid fa-check' %] + or Edit details [% ELSE %] - Renew or + [% INCLUDE 'throttled-button.inc' element_type='link' href='/cgi-bin/koha/members/setstatus.pl?borrowernumber=' _ patron.borrowernumber _ '&destination=circ&reregistration=y' classes='btn btn-default btn-xs' ready_icon='fa-solid fa-rotate' label=t('Renew') persist=1 done_icon='fa-solid fa-check' %] + or Edit details [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/throttled-button.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/throttled-button.inc index f48e5aa8c6..8cf32dd866 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/throttled-button.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/throttled-button.inc @@ -7,12 +7,16 @@ [% DEFAULT href = '#' %] [%# Common attributes shared by both button and link %] -[% BLOCK common_attrs %]class="[% classes | html %]"[% IF id %]id="[% id | html %]"[% END %] data-state="ready" data-throttled-button [% IF attrs %][% FOREACH key IN attrs.keys %][% key | html %]="[% attrs.$key | html %]"[% END %][% END %][% END %] +[% BLOCK common_attrs %] + class="[% classes | html %]"[% IF id %]id="[% id | html %]"[% END %] data-state="ready" data-throttled-button [% IF persist %]data-throttle-persist[% END %] + [% IF attrs %][% FOREACH key IN attrs.keys %][% key | html %]="[% attrs.$key | html %]"[% END %][% END %] +[% END %] [%# Inner content shared by both button and link %] [% BLOCK inner_content %] + [% IF done_icon %][% END %] [% UNLESS icon_only %][% label | html %][% END %] [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt index 0d7757e441..a066911e65 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt @@ -1260,6 +1260,7 @@ [% Asset.js("js/checkouts.js") | $raw %] [% Asset.js("js/tables/bookings.js") | $raw %] [% Asset.js("js/recalls.js") | $raw %] + [% Asset.js("js/throttledButton.js") | $raw %] [% END %] [% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt index bdf2acc854..5313d42e50 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt @@ -849,6 +849,7 @@ [% INCLUDE 'str/members-menu.inc' %] [% Asset.js("js/members-menu.js") | $raw %] [% Asset.js("js/recalls.js") | $raw %] + [% Asset.js("js/throttledButton.js") | $raw %] [% Asset.js("js/hold-group.js") | $raw %]