Dialog for cart renewal, async task without page refresh (#5148)

* async_task: deduplicate response handling code

* extend cart without full page reload

* update dialog markup

* fix error response from CartExtend

* refactor asynctask, make sure waitingDialog.show() re-initializes dialog contents

* add cart expiry notification

* add aria references to other dialogs

* improve error handling

* fix error if max_extend=None

* different message for expiring soon and expired carts

* refactor dialog css

* add classes to further dialog elements

* switch extend-cart-dialog and loadingmodal to <dialog>

* Backport simple_block_tag from Django 5.2

* Use simple_block_tag for {% dialog %} tag

* add alertdialog role

* Update src/pretix/static/pretixbase/scss/_dialogs.scss

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* fix mobile dialog styles not being overwritten

* asynctask dialog: prevent close by escape on chrome

* remove dynamic aria-live from #cart-deadline

dynamic aria-live is generally not well supported and as we have the dialog now anyways, we can remove it

* move continue-button to right

* Update src/pretix/static/pretixpresale/js/ui/cart.js

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Fix CSS for old-style dialog

* fix heading display/level

* align dialogs at the top as they originally were

* fix </div> from merge-conflict

* fix missing grow for dialog-content

* improve cart-extend-button ui

* do not show cart-extend-dialog onload

* improve message if 0 minutes

* do not save messae in session if ajax_dont_redirect

* add ajax_dont_redirect to async_task_check_url

* improve draw_deadline to only update #cart-deadline if necessary

* add renew-confirmation-message

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
luelista
2025-05-27 07:17:50 +02:00
committed by GitHub
parent fdbcffd5fd
commit 5962536a11
19 changed files with 621 additions and 556 deletions

View File

@@ -495,7 +495,7 @@
<form class="text-muted" id="cart-extend-form" data-asynctask data-asynctask-no-redirect
method="post" action="{% eventurl request.event "presale:event.cart.extend" cart_namespace=cart_namespace %}">
{% csrf_token %}
<span id="cart-deadline" data-expires="{{ cart.first_expiry|date:"Y-m-d H:i:sO" }}" data-max-expiry-extend="{{ cart.max_expiry_extend|date:"Y-m-d H:i:sO" }}">
<p id="cart-deadline" data-expires="{{ cart.first_expiry|date:"Y-m-d H:i:sO" }}" data-max-expiry-extend="{{ cart.max_expiry_extend|date:"Y-m-d H:i:sO" }}">
{% if cart.minutes_left > 0 or cart.seconds_left > 0 %}
{% blocktrans trimmed with minutes=cart.minutes_left %}
The items in your cart are reserved for you for {{ minutes }} minutes.
@@ -503,9 +503,12 @@
{% else %}
{% trans "The items in your cart are no longer reserved for you. You can still complete your order as long as theyre available." %}
{% endif %}
</span>
<button class="btn btn-link" type="submit" id="cart-extend-button">
<i class="fa fa-refresh" aria-hidden="true"></i> {% trans "Extend" %}</button>
</p>
<p>
<button class="btn btn-default" type="submit" id="cart-extend-button" aria-describedby="cart-deadline">
<i class="fa fa-refresh" aria-hidden="true"></i> {% trans "Extend reservation" %}
</button>
</p>
</form>
{% else %}
<p class="sr-only" id="cart-description">{% trans "Overview of your ordered products." %}</p>

View File

@@ -4,7 +4,7 @@
{% load rich_text %}
{% load money %}
<details class="panel {% if open %}panel-primary{% else %}panel-default{% endif %} cart" {% if open %}open{% endif %}>
<summary class="panel-heading">
<summary class="panel-heading" aria-describedby="cart-deadline">
<h2 class="panel-title">
<span>
<i class="fa fa-shopping-cart" aria-hidden="true"></i>

View File

@@ -4,18 +4,19 @@
{% load escapejson %}
{% load icon %}
{% load dialog %}
<div id="ajaxerr">
<div id="ajaxerr" class="modal-wrapper" hidden>
</div>
<div id="popupmodal" hidden aria-live="polite">
<div id="popupmodal" class="modal-wrapper" hidden aria-live="polite" role="dialog"
aria-labelledby="popupmodal-title">
<div class="modal-card">
<div class="modal-card-icon">
<i class="fa fa-window-restore big-icon" aria-hidden="true"></i>
</div>
<div class="modal-card-content">
<div>
<h3>
<h2 id="popupmodal-title" class="h3">
{% trans "We've started the requested process in a new window." %}
</h3>
</h2>
<p class="text">
{% trans "If you do not see the new window, we can help you launch it again." %}
</p>
@@ -32,20 +33,21 @@
</div>
</div>
</div>
<div id="loadingmodal" hidden aria-live="polite">
<div class="modal-card">
<div class="modal-card-icon">
<i class="fa fa-cog big-rotating-icon" aria-hidden="true"></i>
</div>
<div class="modal-card-content">
<h3 id="loadingmodal-label"></h3>
<div id="loadingmodal-description">
<p class="text"></p>
<p class="status">{% trans "If this takes longer than a few minutes, please contact us." %}</p>
</div>
{% dialog "loadingmodal" "" "" icon="cog rotating" %}
<p class="status">{% trans "If this takes longer than a few minutes, please contact us." %}</p>
<div class="progress">
<div class="progress-bar progress-bar-success">
</div>
</div>
</div>
<div class="steps">
</div>
{% enddialog %}
{% trans "Please let us know you're still there." as label_cart_extend_dialog %}
{% dialog "dialog-cart-extend" label_cart_extend_dialog "" icon="clock-o" alert=true %}
<p class="modal-card-confirm"><button class="btn btn-lg btn-primary">{% trans "Continue" %}</button></p>
{% enddialog %}
<dialog id="lightbox-dialog" role="alertdialog" aria-labelledby="lightbox-label">
<form method="dialog" class="modal-card">

View File

@@ -229,11 +229,13 @@ class CartMixin:
try:
first_expiry = min(p.expires for p in positions) if positions else now()
max_expiry_extend = min((p.max_extend for p in positions if p.max_extend), default=None)
total_seconds_left = max(first_expiry - now(), timedelta()).total_seconds()
minutes_left = int(total_seconds_left // 60)
seconds_left = int(total_seconds_left % 60)
except AttributeError:
first_expiry = None
max_expiry_extend = None
minutes_left = None
seconds_left = None
@@ -250,6 +252,7 @@ class CartMixin:
'minutes_left': minutes_left,
'seconds_left': seconds_left,
'first_expiry': first_expiry,
'max_expiry_extend': max_expiry_extend,
'is_ordered': bool(order),
'itemcount': sum(c.count for c in positions if not c.addon_to),
'current_selected_payments': [p for p in self.current_selected_payments(total) if p.get('multi_use_supported')]

View File

@@ -542,8 +542,14 @@ class CartExtendReservation(EventViewMixin, CartActionMixin, AsyncAction, View):
task = extend_cart_reservation
known_errortypes = ['CartError']
def _ajax_response_data(self, value):
if isinstance(value, dict):
return value
else:
return {}
def get_success_message(self, value):
if value > 0:
if value['success'] > 0:
return _('Your cart timeout was extended.')
def post(self, request, *args, **kwargs):
@@ -561,7 +567,7 @@ class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
def get_success_message(self, value):
return _('The products have been successfully added to your cart.')
def _ajax_response_data(self):
def _ajax_response_data(self, value):
cart_id = get_or_create_cart_id(self.request)
return {
'cart_id': cart_id,