fix: confirm prompt on terminal offer actions (accept / decline / withdraw)

These end the negotiation, so the forms now carry onsubmit="return
confirm(...)". offer.js bails when the prompt is cancelled — it checks
event.defaultPrevented before firing the AJAX request — so the JS path
respects the confirmation too, and the no-JS path gets the native dialog
before the POST+redirect.
This commit is contained in:
russell@unturf.com 2026-05-12 20:10:13 -04:00
parent c43336925c
commit 87ac9d90ca
No known key found for this signature in database
3 changed files with 27 additions and 3 deletions

View file

@ -28,6 +28,10 @@
}
function handleSubmit(e) {
// An inline onsubmit="return confirm(...)" on terminal-action forms
// runs before this listener. If the user cancelled, the event is
// already defaultPrevented — bail so we don't fire the AJAX request.
if (e.defaultPrevented) return;
var form = e.currentTarget;
e.preventDefault();
var submitBtn = form.querySelector('[type="submit"]');

View file

@ -62,7 +62,11 @@
<section class="offer-actions well">
<h3 class="type-title">Your turn</h3>
<form method="post" action="/o/{{ id }}/accept" class="offer-action-form">
{# Terminal actions (accept / decline / withdraw) carry a confirm
prompt. offer.js bails if the prompt was cancelled (it checks
event.defaultPrevented), so the AJAX path respects it too. #}
<form method="post" action="/o/{{ id }}/accept" class="offer-action-form"
onsubmit="return confirm('Accept this offer for ${{ "%.2f"|format(current_amount) }} and continue to checkout? This is final.');">
<input type="text" name="message" placeholder="Optional message" />
<input type="submit" class="mps-submit" value="Accept ${{ "%.2f"|format(current_amount) }}" />
</form>
@ -74,13 +78,15 @@
<input type="submit" class="mps-button" value="Counter" />
</form>
<form method="post" action="/o/{{ id }}/decline" class="offer-action-form" style="margin-top: var(--space-3);">
<form method="post" action="/o/{{ id }}/decline" class="offer-action-form" style="margin-top: var(--space-3);"
onsubmit="return confirm('Decline this offer? This ends the negotiation and cannot be undone.');">
<input type="text" name="message" placeholder="Reason (optional)" />
<input type="submit" class="mps-button" value="Decline" />
</form>
{% if actor_party == 0 %}
<form method="post" action="/o/{{ id }}/withdraw" class="offer-action-form" style="margin-top: var(--space-3);">
<form method="post" action="/o/{{ id }}/withdraw" class="offer-action-form" style="margin-top: var(--space-3);"
onsubmit="return confirm('Withdraw your offer? This ends the negotiation.');">
<input type="submit" class="mps-button" value="Withdraw offer" />
</form>
{% endif %}

View file

@ -5971,6 +5971,20 @@ class TestOfferRoutes(_AuthenticatedBase):
)
self.assertEqual(res2.status_int, 403)
def test_terminal_action_forms_have_confirm_prompt(self):
product_id = self._make_offer_product(list_price=10000)
self.testapp.get("/log-out")
self.log_in_user(self.user2_creds)
res = self._ajax_post(f"/p/{product_id}/offer", {"amount": "70.00"})
offer_id = res.json["offer_id"]
# Seller views the pending offer — accept/decline forms must carry
# an "are you sure" confirm.
self.testapp.get("/log-out")
self.log_in_user(self.user1_creds)
body = self.testapp.get(f"/o/{offer_id}", status=200).body.decode()
self.assertIn(f"/o/{offer_id}/accept", body)
self.assertIn('onsubmit="return confirm(', body)
def test_offers_disabled_on_fixed_price_product(self):
from ..models.product import Product
shop = self._create_shop_helper()