From 6b481d22a7363c9cf8adae27211da14228d85a3f 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 --- .../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 dbabcef9aa..00e236bc7e 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 7fe3f813b6..2b3a1fb0f5 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt @@ -1254,6 +1254,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 cc59617307..f9dfb34df7 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt @@ -837,6 +837,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 %]