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

@@ -5,74 +5,94 @@ var async_task_check_url = null;
var async_task_old_url = null;
var async_task_is_download = false;
var async_task_is_long = false;
var async_task_dont_redirect = false;
function async_task_check() {
var async_task_status_messages = {
long_task_started: gettext(
'Your request is currently being processed. Depending on the size of your event, this might take up to ' +
'a few minutes.'
),
long_task_pending: gettext(
'Your request has been queued on the server and will soon be ' +
'processed.'
),
short_task: gettext(
'Your request arrived on the server but we still wait for it to be ' +
'processed. If this takes longer than two minutes, please contact us or go ' +
'back in your browser and try again.'
)
};
function async_task_schedule_check(context, timeout) {
"use strict";
$.ajax(
{
'type': 'GET',
'url': async_task_check_url,
'success': async_task_check_callback,
'error': async_task_check_error,
'context': this,
'dataType': 'json'
async_task_timeout = window.setTimeout(function() {
$.ajax(
{
'type': 'GET',
'url': async_task_check_url,
'success': async_task_check_callback,
'error': async_task_check_error,
'context': context,
'dataType': 'json'
}
);
}, timeout);
}
function async_task_on_success(data) {
"use strict";
if ((async_task_is_download && data.success) || async_task_dont_redirect) {
waitingDialog.hide();
if (location.href.indexOf("async_id") !== -1) {
history.replaceState({}, "pretix", async_task_old_url);
}
);
}
if (!async_task_dont_redirect)
location.href = data.redirect;
$(this).trigger('pretix:async-task-success', data);
}
function async_task_check_callback(data, textStatus, jqXHR) {
"use strict";
if (data.ready && data.redirect) {
if (async_task_is_download && data.success) {
waitingDialog.hide();
if (location.href.indexOf("async_id") !== -1) {
history.replaceState({}, "pretix", async_task_old_url);
}
}
location.href = data.redirect;
async_task_on_success.call(this, data);
return;
} else if (typeof data.percentage === "number") {
$("#loadingmodal .progress").show();
$("#loadingmodal .progress .progress-bar").css("width", data.percentage + "%");
if (typeof data.steps === "object" && Array.isArray(data.steps)) {
var $steps = $("#loadingmodal .steps");
$steps.html("").show()
for (var step of data.steps) {
$steps.append(
$("<span>").addClass("fa fa-fw")
.toggleClass("fa-check text-success", step.done)
.toggleClass("fa-cog fa-spin text-muted", !step.done)
).append(
$("<span>").text(step.label)
).append(
$("<br>")
)
}
}
}
async_task_timeout = window.setTimeout(async_task_check, 250);
if (typeof data.percentage === "number") {
waitingDialog.setProgress(data.percentage);
}
if (typeof data.steps === "object" && Array.isArray(data.steps)) {
waitingDialog.setSteps(data.steps);
}
async_task_schedule_check(this, 250);
async_task_update_status(data);
}
function async_task_update_status(data) {
if (async_task_is_long) {
if (data.started) {
$("#loadingmodal p.status").text(gettext(
'Your request is currently being processed. Depending on the size of your event, this might take up to ' +
'a few minutes.'
));
waitingDialog.setStatus(async_task_status_messages.long_task_started);
} else {
$("#loadingmodal p.status").text(gettext(
'Your request has been queued on the server and will soon be ' +
'processed.'
));
waitingDialog.setStatus(async_task_status_messages.long_task_pending);
}
} else {
$("#loadingmodal p.status").text(gettext(
'Your request arrived on the server but we still wait for it to be ' +
'processed. If this takes longer than two minutes, please contact us or go ' +
'back in your browser and try again.'
));
waitingDialog.setStatus(async_task_status_messages.short_task);
}
}
function async_task_replace_page(target, new_html) {
"use strict";
waitingDialog.hide();
$(target).html(new_html);
setup_basics($(target));
form_handlers($(target));
setup_collapsible_details($(target));
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
$(document).trigger("pretix:bind-forms");
}
function async_task_check_error(jqXHR, textStatus, errorThrown) {
"use strict";
var respdom = $(jqXHR.responseText);
@@ -80,16 +100,10 @@ function async_task_check_error(jqXHR, textStatus, errorThrown) {
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
// This is a failed form validation, let's just use it
$("body").data('ajaxing', false);
waitingDialog.hide();
$("body").html(jqXHR.responseText.substring(
async_task_replace_page("body", jqXHR.responseText.substring(
jqXHR.responseText.indexOf("<body"),
jqXHR.responseText.indexOf("</body")
));
setup_basics($("body"));
form_handlers($("body"));
setup_collapsible_details($("body"));
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
$(document).trigger("pretix:bind-forms");
} else if (c.length > 0) {
// This is some kind of 500/404/403 page, show it in an overlay
$("body").data('ajaxing', false);
@@ -105,9 +119,9 @@ function async_task_check_error(jqXHR, textStatus, errorThrown) {
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
} else {
// 500 can be an application error or overload in some cases :(
$("#loadingmodal p.status").text(gettext('We currently cannot reach the server, but we keep trying.' +
' Last error code: {code}').replace(/\{code\}/, jqXHR.status));
async_task_timeout = window.setTimeout(async_task_check, 5000);
waitingDialog.setStatus(gettext('We currently cannot reach the server, but we keep trying.' +
' Last error code: {code}').replace(/\{code\}/, jqXHR.status));
async_task_schedule_check(this, 5000);
}
}
}
@@ -116,38 +130,19 @@ function async_task_callback(data, jqXHR, status) {
"use strict";
$("body").data('ajaxing', false);
if (data.redirect) {
if (async_task_is_download && data.success) {
waitingDialog.hide();
if (location.href.indexOf("async_id") !== -1) {
history.replaceState({}, "pretix", async_task_old_url);
}
}
location.href = data.redirect;
async_task_on_success.call(this, data);
return;
}
async_task_id = data.async_id;
async_task_check_url = data.check_url;
async_task_timeout = window.setTimeout(async_task_check, 100);
if (async_task_is_long) {
if (data.started) {
$("#loadingmodal p.status").text(gettext(
'Your request is currently being processed. Depending on the size of your event, this might take up to ' +
'a few minutes.'
));
} else {
$("#loadingmodal p.status").text(gettext(
'Your request has been queued on the server and will soon be ' +
'processed.'
));
}
} else {
$("#loadingmodal p.status").text(gettext(
'Your request arrived on the server but we still wait for it to be ' +
'processed. If this takes longer than two minutes, please contact us or go ' +
'back in your browser and try again.'
));
var check_url = new URL(data.check_url);
if (async_task_dont_redirect) {
check_url.searchParams.set('ajax_dont_redirect', '1');
}
async_task_id = data.async_id;
async_task_check_url = check_url.toString();
async_task_schedule_check(this, 100);
async_task_update_status(data);
if (location.href.indexOf("async_id") === -1) {
history.pushState({}, "Waiting", async_task_check_url.replace(/ajax=1/, ''));
}
@@ -156,48 +151,34 @@ function async_task_callback(data, jqXHR, status) {
function async_task_error(jqXHR, textStatus, errorThrown) {
"use strict";
$("body").data('ajaxing', false);
waitingDialog.hide();
if (textStatus === "timeout") {
alert(gettext("The request took too long. Please try again."));
waitingDialog.hide();
} else if (jqXHR.responseText.indexOf('<html') > 0) {
var respdom = $(jqXHR.responseText);
var c = respdom.filter('.container');
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
// This is a failed form validation, let's just use it
waitingDialog.hide();
if (respdom.filter('#page-wrapper') && $('#page-wrapper').length) {
$("#page-wrapper").html(respdom.find("#page-wrapper").html());
setup_basics($("#page-wrapper"));
form_handlers($("#page-wrapper"));
setup_collapsible_details($("#page-wrapper"));
$(document).trigger("pretix:bind-forms");
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
async_task_replace_page("#page-wrapper", respdom.find("#page-wrapper").html());
} else {
$("body").html(jqXHR.responseText.substring(
async_task_replace_page("body", jqXHR.responseText.substring(
jqXHR.responseText.indexOf("<body"),
jqXHR.responseText.indexOf("</body")
));
setup_basics($("body"));
form_handlers($("body"));
setup_collapsible_details($("body"));
$(document).trigger("pretix:bind-forms");
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
}
} else if (c.length > 0) {
waitingDialog.hide();
// This is some kind of 500/404/403 page, show it in an overlay
ajaxErrDialog.show(c.first().html());
} else {
waitingDialog.hide();
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
}
} else {
if (jqXHR.status >= 400 && jqXHR.status < 500) {
waitingDialog.hide();
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
} else {
waitingDialog.hide();
alert(gettext('We currently cannot reach the server. Please try again. ' +
'Error code: {code}').replace(/\{code\}/, jqXHR.status));
}
@@ -221,28 +202,26 @@ $(function () {
}
async_task_id = null;
async_task_is_download = $(this).is("[data-asynctask-download]");
async_task_dont_redirect = $(this).is("[data-asynctask-no-redirect]");
async_task_is_long = $(this).is("[data-asynctask-long]");
async_task_old_url = location.href;
$("body").data('ajaxing', true);
if ($(this).is("[data-asynctask-headline]")) {
waitingDialog.show($(this).attr("data-asynctask-headline"));
} else {
waitingDialog.show(gettext('We are processing your request …'));
}
if ($(this).is("[data-asynctask-text]")) {
$("#loadingmodal p.text").text($(this).attr("data-asynctask-text")).show();
} else {
$("#loadingmodal p.text").hide();
}
$("#loadingmodal p.status").text(gettext(
'We are currently sending your request to the server. If this takes longer ' +
'than one minute, please check your internet connection and then reload ' +
'this page and try again.'
));
waitingDialog.show(
$(this).attr("data-asynctask-headline") || gettext('We are processing your request …'),
$(this).attr("data-asynctask-text") || '',
gettext(
'We are currently sending your request to the server. If this takes longer ' +
'than one minute, please check your internet connection and then reload ' +
'this page and try again.'
)
);
var action = this.action;
var formData = new FormData(this);
formData.append('ajax', '1');
if (async_task_dont_redirect) {
formData.append('ajax_dont_redirect', '1');
}
if (submitter && submitter.name) {
formData.append(submitter.name, submitter.value);
}
@@ -275,21 +254,66 @@ $(function () {
}, 10);
}
}, false);
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
$("#loadingmodal").on("cancel", function() {
return false;
});
$("#loadingmodal").prop("closedBy", "none");
});
var waitingDialog = {
show: function (message) {
show: function (title, text, status) {
"use strict";
$("#loadingmodal h3").html(message);
$("#loadingmodal .progress").hide();
$("#loadingmodal .steps").hide();
$("body").addClass("loading");
$("#loadingmodal").removeAttr("hidden");
this.setTitle(title);
this.setText(text);
this.setStatus(status || gettext('If this takes longer than a few minutes, please contact us.'));
this.setProgress(null);
this.setSteps(null);
document.getElementById("loadingmodal").showModal();
},
hide: function () {
"use strict";
$("body").removeClass("loading");
$("#loadingmodal").attr("hidden", true);
document.getElementById("loadingmodal").close();
},
setTitle: function(title) {
$("#loadingmodal .modal-card-title").text(title);
},
setStatus: function(statusText) {
$("#loadingmodal p.status").text(statusText);
},
setText: function(text) {
if (text)
$("#loadingmodal .modal-card-description").text(text).show();
else
$("#loadingmodal .modal-card-description").hide();
},
setProgress: function(percentage) {
if (typeof percentage === 'number') {
$("#loadingmodal .progress").show();
$("#loadingmodal .progress .progress-bar").css("width", percentage + "%");
} else {
$("#loadingmodal .progress").hide();
}
},
setSteps: function(steps) {
var $steps = $("#loadingmodal .steps");
if (steps) {
$steps.html("").show()
for (var step of steps) {
$steps.append(
$("<span>").addClass("fa fa-fw")
.toggleClass("fa-check text-success", step.done)
.toggleClass("fa-cog fa-spin text-muted", !step.done)
).append(
$("<span>").text(step.label)
).append(
$("<br>")
)
}
} else {
$steps.hide();
}
}
};
@@ -299,10 +323,10 @@ var ajaxErrDialog = {
$("#ajaxerr").html(c);
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
+ gettext("Close message") + "</a>");
$("body").addClass("ajaxerr");
$("body").addClass("ajaxerr has-modal-dialog");
},
hide: function () {
"use strict";
$("body").removeClass("ajaxerr");
}
$("body").removeClass("ajaxerr has-modal-dialog");
},
};

View File

@@ -0,0 +1,174 @@
/* Modal dialogs using HTML5 dialog tags for accessibility */
dialog.modal-card {
border: none;
width: 80%;
max-width: 43em;
padding: 0;
margin-top: 60px;
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07);
background: white;
border-radius: $border-radius-large;
opacity: 0;
transition: opacity .5s allow-discrete;
}
.modal-card-inner {
display: flex;
flex-direction: column;
align-content: stretch;
}
dialog.modal-card .modal-card-icon {
background: $brand-primary;
font-size: 2em;
color: white;
text-align: center;
padding: 3px;
.rotating {
-webkit-animation: fa-spin 8s infinite linear;
animation: fa-spin 8s infinite linear;
}
}
dialog.modal-card .modal-card-content {
padding: 1.5em;
flex-grow: 1;
}
.modal-card-content>*:last-child {
margin-bottom: 0;
}
.modal-card-content>*:first-child {
margin-top: 0;
}
.modal-card-confirm {
margin-top: 2em;
display: flex;
justify-content: flex-end;
gap: 1em;
align-items: center;
}
.modal-card-confirm-spread {
justify-content: space-between;
}
dialog::backdrop {
background-color: rgba(255, 255, 255, .5);
opacity: 0;
transition: opacity .5s allow-discrete;
backdrop-filter: blur(2px);
}
dialog[open], dialog[open]::backdrop {
opacity: 1;
}
@starting-style {
dialog[open], dialog[open]::backdrop {
opacity: 0;
}
}
@media screen and (min-width: $screen-sm-min) {
dialog.modal-card:has(.modal-card-icon) .modal-card-inner {
flex-direction: row;
}
dialog.modal-card .modal-card-content {
padding: 2em;
}
dialog.modal-card .modal-card-icon {
font-size: 4em;
padding: 6px 16px;
}
}
.shake-once {
animation: shake .2s;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
}
@keyframes shake {
0% { transform: skewX(0deg); }
20% { transform: skewX(-5deg); }
40% { transform: skewX(5deg); }
60% { transform: skewX(-5deg); }
80% { transform: skewX(5deg); }
100% { transform: skewX(0deg); }
}
/* Legacy dialogs (still used for #ajaxerr and #popupmodal) */
body.has-modal-dialog .container, body.has-modal-dialog #wrapper {
-webkit-filter: blur(2px);
-moz-filter: blur(2px);
-ms-filter: blur(2px);
-o-filter: blur(2px);
filter: blur(2px);
}
.modal-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(255, 255, 255, .7);
z-index: 900000;
padding: 10px;
.modal-card {
margin: 50px auto 0;
width: 90%;
max-width: 600px;
max-height: calc(100vh - 100px);
overflow-y: auto;
background: white;
border-radius: $border-radius-large;
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07);
padding: 20px;
min-height: 160px;
border: 0;
.modal-card-icon {
float: left;
width: 150px;
text-align: center;
.big-icon {
margin-top: 10px;
font-size: 100px;
color: $brand-primary;
}
}
.modal-card-content {
margin-left: 160px;
text-align: left;
h3 {
margin-top: 0;
}
}
}
}
@media (max-width: 700px) {
.modal-wrapper .modal-card {
margin: 25px auto 0;
max-height: calc(100vh - 50px - 20px);
.modal-card-icon {
float: none;
width: 100%;
}
.modal-card-content {
text-align: center;
margin-left: 0;
margin-right: 0;
margin-top: 10px;
}
}
}
#ajaxerr {
background: rgba(236, 236, 236, .9);
.big-icon {
margin-top: 50px;
font-size: 200px;
color: $brand-primary;
}
}

View File

@@ -263,117 +263,3 @@ svg.svg-icon {
@include table-row-variant('warning', var(--pretix-brand-warning-lighten-40), var(--pretix-brand-warning-lighten-35));
@include table-row-variant('danger', var(--pretix-brand-danger-lighten-30), var(--pretix-brand-danger-lighten-25));
dialog {
border: none;
width: 80%;
max-width: 43em;
padding: 0;
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07);
background: white;
border-radius: $border-radius-large;
opacity: 0;
transition: opacity .5s allow-discrete;
.modal-card {
display: flex;
flex-direction: column;
align-content: stretch;
}
.modal-card-icon {
background: $brand-primary;
font-size: 2em;
color: white;
text-align: center;
padding: 3px;
}
.modal-card-content {
padding: 1.5em;
}
.modal-card-content>*:last-child {
margin-bottom: 0;
}
.modal-card-content>*:first-child {
margin-top: 0;
}
.modal-card-confirm {
margin-top: 2em;
display: flex;
justify-content: flex-end;
gap: 1em;
align-items: center;
}
.modal-card-confirm-spread {
justify-content: space-between;
}
}
dialog::backdrop {
background-color: rgba(255, 255, 255, .5);
opacity: 0;
transition: opacity .5s allow-discrete;
}
dialog[open], dialog[open]::backdrop {
opacity: 1;
}
@starting-style {
dialog[open], dialog[open]::backdrop {
opacity: 0;
}
}
@media screen and (min-width: $screen-sm-min) {
dialog {
.modal-card:has(.modal-card-icon) {
flex-direction: row;
}
.modal-card-content {
padding: 2em;
}
.modal-card-icon {
font-size: 4em;
padding: 6px 16px;
}
}
}
.shake-once {
animation: shake .2s;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
}
@keyframes shake {
0% { transform: skewX(0deg); }
20% { transform: skewX(-5deg); }
40% { transform: skewX(5deg); }
60% { transform: skewX(-5deg); }
80% { transform: skewX(5deg); }
100% { transform: skewX(0deg); }
}
#lightbox-dialog {
width: fit-content;
max-width: 80%;
min-width: 24em;
.modal-card-content {
padding: 2.5em;
}
img {
max-width: 100%;
}
button {
position: absolute;
top: 1em;
right: 1em;
}
figcaption {
margin-top: 1em;
}
}

View File

@@ -51,32 +51,6 @@ function formatPrice(price, currency, locale) {
}
}
var waitingDialog = {
show: function (message) {
"use strict";
$("#loadingmodal").find("h1").html(message);
$("body").addClass("loading");
},
hide: function () {
"use strict";
$("body").removeClass("loading");
}
};
var ajaxErrDialog = {
show: function (c) {
"use strict";
$("#ajaxerr").html(c);
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
+ gettext("Close message") + "</a>");
$("body").addClass("ajaxerr");
},
hide: function () {
"use strict";
$("body").removeClass("ajaxerr");
}
};
var apiGET = function (url, callback) {
$.getJSON(url, function (data) {
callback(data);

View File

@@ -3,6 +3,7 @@
@import "../../bootstrap/scss/_bootstrap.scss";
@import "../../fontawesome/scss/font-awesome.scss";
@import "../../pretixbase/scss/_theme.scss";
@import "../../pretixbase/scss/_dialogs.scss";
@import "../../typeahead/typeahead.scss";
@import "../../charts/morris.scss";
@import "../../cropper/cropper.scss";
@@ -234,91 +235,6 @@ p.bigger {
}
}
body.loading #wrapper {
-webkit-filter: blur(2px);
-moz-filter: blur(2px);
-ms-filter: blur(2px);
-o-filter: blur(2px);
filter: blur(2px);
}
#loadingmodal, #ajaxerr {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(255, 255, 255, .7);
opacity: 0;
z-index: 900000;
visibility: hidden;
padding: 10px;
.big-icon {
margin-top: 50px;
font-size: 200px;
color: $brand-primary;
}
.modal-card {
margin: 50px auto 0;
top: 50px;
width: 90%;
max-width: 600px;
background: white;
border-radius: $border-radius-large;
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07);
padding: 20px;
min-height: 160px;
.modal-card-icon {
float: left;
width: 150px;
text-align: center;
.big-rotating-icon {
font-size: 120px;
margin: 0;
}
}
.modal-card-content {
margin-left: 160px;
text-align: left;
h3 {
margin-top: 0;
}
}
}
}
@media (max-width: 700px) {
#loadingmodal, #ajaxerr {
.modal-card {
.modal-card-icon {
float: none;
width: 100%;
}
.modal-card-content {
text-align: center;
margin-left: 0;
margin-right: 0;
margin-top: 10px;
}
}
}
}
#ajaxerr {
background: rgba(236, 236, 236, .9);
}
.loading #loadingmodal, .ajaxerr #ajaxerr {
opacity: 1;
visibility: visible;
transition: opacity .5s ease-in-out;
-moz-transition: opacity .5s ease-in-out;
-webkit-transition: opacity .5s ease-in-out;
}
.big-rotating-icon {
margin-top: 50px;
-webkit-animation: fa-spin 8s infinite linear;

View File

@@ -1,9 +1,11 @@
/*global $,gettext,ngettext */
var cart = {
_deadline: null,
_deadline_interval: null,
_deadline_timeout: null,
_deadline_call: 0,
_time_offset: 0,
_prev_diff_minutes: 0,
_renewed_message: "",
_get_now: function () {
return moment().add(cart._time_offset, 'ms');
@@ -20,7 +22,12 @@ var cart = {
cart._time_offset = server_time - client_time;
},
draw_deadline: function () {
show_expiry_notification: function () {
document.getElementById("dialog-cart-extend").showModal();
cart._expiry_notified = true;
},
draw_deadline: function (renewed="") {
function pad(n, width, z) {
z = z || '0';
n = n + '';
@@ -33,36 +40,73 @@ var cart = {
return;
}
var now = cart._get_now();
var diff_minutes = Math.floor(cart._deadline.diff(now) / 1000 / 60);
var diff_seconds = Math.floor(cart._deadline.diff(now) / 1000 % 60);
if (diff_minutes < 2 || diff_minutes == 5) $("#cart-deadline").get(0).setAttribute("aria-live", "polite");
else $("#cart-deadline").get(0).removeAttribute("aria-live");
var diff_total_seconds = cart._deadline.diff(now) / 1000;
var diff_minutes = Math.floor(diff_total_seconds / 60);
var diff_seconds = Math.floor(diff_total_seconds % 60);
if (diff_minutes < 0) {
$("#cart-deadline").text(gettext("The items in your cart are no longer reserved for you. You can still complete your order as long as theyre available."));
$("#cart-deadline-short").text(
gettext("Cart expired")
);
window.clearInterval(cart._deadline_interval);
if (!cart._deadline_timeout) {
// no timeout => first time draw_deadline is invoked, but cart already expired => do not show dialog
cart._expiry_notified = true;
}
} else {
$("#cart-deadline").text(ngettext(
"The items in your cart are reserved for you for one minute.",
"The items in your cart are reserved for you for {num} minutes.",
diff_minutes
).replace(/\{num\}/g, diff_minutes));
if (diff_minutes !== cart._prev_diff_minutes) {
if (diff_minutes == 0) {
$("#cart-deadline").text(gettext("Your cart is about to expire. If you want to continue, please click here:"))
} else {
$("#cart-deadline").text(
cart._renewed_message + " " +
ngettext(
"The items in your cart are reserved for you for one minute.",
"The items in your cart are reserved for you for {num} minutes.",
diff_minutes
).replace(/\{num\}/g, diff_minutes)
);
}
cart._prev_diff_minutes = diff_minutes;
}
$("#cart-deadline-short").text(
pad(diff_minutes.toString(), 2) + ':' + pad(diff_seconds.toString(), 2)
);
cart._renewed_message = "";
cart._deadline_timeout = window.setTimeout(cart.draw_deadline, 500);
}
var already_expired = diff_total_seconds <= 0;
var can_extend_cart = diff_minutes < 3 && (already_expired || cart._deadline < cart._max_extend);
$("#cart-extend-button").toggle(can_extend_cart);
if (can_extend_cart && diff_total_seconds < 45) {
if (!cart._expiry_notified) cart.show_expiry_notification();
$("#dialog-cart-extend-description").text(already_expired
? gettext("Your cart has expired. If you want to continue, please click here:")
: gettext("Your cart is about to expire. If you want to continue, please click here:"));
}
$("#cart-extend-button").toggle(diff_minutes < 3);
},
init: function () {
"use strict";
cart._deadline = moment($("#cart-deadline").attr("data-expires"));
cart._deadline_interval = window.setInterval(cart.draw_deadline, 500);
cart._calc_offset();
cart.set_deadline(
$("#cart-deadline").attr("data-expires"),
$("#cart-deadline").attr("data-max-expiry-extend")
);
},
set_deadline: function (expiry, max_extend, renewed_message) {
"use strict";
cart._expiry_notified = false;
cart._deadline = moment(expiry);
if (cart._deadline_timeout) {
window.clearTimeout(cart._deadline_timeout);
}
cart._deadline_timeout = null;
cart._max_extend = moment(max_extend);
cart._renewed_message = renewed_message || "";
cart.draw_deadline();
}
};
@@ -74,6 +118,22 @@ $(function () {
cart.init();
}
$("#cart-extend-form").on("pretix:async-task-success", function(e, data) {
if (data.success) {
cart.set_deadline(data.expiry, data.max_expiry_extend, data.message);
var cart_panel_heading = $(this).closest(".panel").find(".panel-heading").get(0);
if (cart_panel_heading) {
cart_panel_heading.focus();
}
} else {
alert(data.message);
}
});
$("#dialog-cart-extend form").submit(function() {
$("#cart-extend-form").submit();
});
$(".toggle-container").each(function() {
var summary = $(".toggle-summary", this);
var content = $("> :not(.toggle-summary)", this);

View File

@@ -407,8 +407,6 @@ $(function () {
$("#voucher-toggle").slideUp();
});
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
// Handlers for "Copy answers from above" buttons
$(".js-copy-answers").click(function (e) {
e.preventDefault();

View File

@@ -4,8 +4,6 @@ $(function () {
var popup_window = null
var popup_check_interval = null
$("#popupmodal").removeAttr("hidden");
$("a[data-open-in-popup-window]").on("click", function (e) {
e.preventDefault()
@@ -22,11 +20,13 @@ $(function () {
"presale-popup",
"scrollbars=yes,resizable=yes,status=yes,location=yes,toolbar=no,menubar=no,width=940,height=620,left=50,top=50"
)
$("body").addClass("has-popup")
$("body").addClass("has-popup has-modal-dialog")
$("#popupmodal").removeAttr("hidden");
popup_check_interval = window.setInterval(function () {
if (popup_window.closed) {
$("body").removeClass("has-popup")
$("body").removeClass("has-popup has-modal-dialog")
$("#popupmodal").attr("hidden", true);
window.clearInterval(popup_check_interval)
}
}, 250)

View File

@@ -44,6 +44,7 @@ $headings-small-color: $text-muted;
@import "../../bootstrap/scss/_bootstrap_reduced.scss";
@import "../../pretixbase/scss/_theme.scss";
@import "../../pretixbase/scss/_dialogs.scss";
@import "../../lightbox/css/lightbox.scss";
@import "../../cropper/cropper.scss";
@import "../../datetimepicker/_bootstrap-datetimepicker.scss";
@@ -113,7 +114,7 @@ pre {
h1, h2, h3, h4, h5, h6, p, li {
a:not(.btn) {
text-decoration: underline;
}
}
}
a.no-underline:link, nav li a:link {
@@ -277,119 +278,12 @@ a:hover .panel-primary > .panel-heading {
margin-right: auto;
}
body.loading .container {
-webkit-filter: blur(2px);
-moz-filter: blur(2px);
-ms-filter: blur(2px);
-o-filter: blur(2px);
filter: blur(2px);
}
.big-rotating-icon {
-webkit-animation: fa-spin 8s infinite linear;
animation: fa-spin 8s infinite linear;
font-size: 120px;
color: $brand-primary;
}
#loadingmodal, #ajaxerr, #popupmodal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(255, 255, 255, .7);
opacity: 0;
z-index: 900000;
visibility: hidden;
padding: 10px;
.big-icon {
margin-top: 50px;
font-size: 200px;
color: $brand-primary;
}
&#popupmodal .big-icon {
margin-top: 10px;
font-size: 100px;
color: $brand-primary;
}
.modal-card {
margin: 50px auto 0;
width: 90%;
max-width: 600px;
max-height: calc(100vh - 100px);
overflow-y: auto;
background: white;
border-radius: $border-radius-large;
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07);
padding: 20px;
min-height: 160px;
.modal-card-icon {
float: left;
width: 150px;
text-align: center;
}
.modal-card-content {
margin-left: 160px;
text-align: left;
h3 {
margin-top: 0;
}
}
}
&#cookie-consent-modal {
background: rgba(255, 255, 255, .5);
opacity: 1;
visibility: visible;
display: none;
.modal-card-content {
margin-left: 0;
}
details {
& > summary {
list-style: none;
}
& > summary::-webkit-details-marker,
& > summary::marker {
display: none;
}
margin-bottom: 10px;
}
}
}
@media (max-width: 700px) {
#loadingmodal, #ajaxerr, #popupmodal {
.modal-card {
margin: 25px auto 0;
max-height: calc(100vh - 50px - 20px);
.modal-card-icon {
float: none;
width: 100%;
}
.modal-card-content {
text-align: center;
margin-left: 0;
margin-right: 0;
margin-top: 10px;
}
}
}
}
#ajaxerr {
background: rgba(236, 236, 236, .9);
}
.loading #loadingmodal, .ajaxerr #ajaxerr, .has-popup #popupmodal {
opacity: 1;
visibility: visible;
transition: opacity .5s ease-in-out;
-moz-transition: opacity .5s ease-in-out;
-webkit-transition: opacity .5s ease-in-out;
}
.typo-alert span[data-typosuggest] {
text-decoration: underline;