forked from CGM_Public/pretix_original
Merge branch 'master' into questions-vue3
# Conflicts: # .github/workflows/tests.yml # .gitignore # Dockerfile # package-lock.json # package.json # src/pretix/base/management/commands/runserver.py # src/pretix/base/middleware.py # src/pretix/base/templatetags/vite.py # src/pretix/control/forms/global_settings.py # src/pretix/control/templates/pretixcontrol/checkin/list_edit.html # src/pretix/control/views/item.py # src/pretix/presale/views/widget.py # src/pretix/settings.py # src/pretix/static/pretixcontrol/js/ui/checkinrules/App.vue # src/pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts # src/pretix/static/pretixcontrol/js/ui/checkinrules/django-interop.ts # src/pretix/static/pretixcontrol/js/ui/checkinrules/lookup-select2.vue # src/pretix/static/pretixcontrol/js/ui/checkinrules/timefield.vue # src/pretix/static/pretixcontrol/js/ui/checkinrules/viz-node.vue # src/pretix/static/pretixpresale/widget/index.html # src/pretix/static/pretixpresale/widget/src/api.ts # src/pretix/static/pretixpresale/widget/src/button.ts # src/pretix/static/pretixpresale/widget/src/components/PriceBox.vue # src/pretix/static/pretixpresale/widget/src/main.ts # src/pretix/static/pretixpresale/widget/src/sharedStore.ts # src/pretix/static/pretixpresale/widget/src/utils.ts # src/pretix/static/pretixpresale/widget/src/widget.ts # src/tests/e2e/conftest.py # vite.config.ts
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
|
||||
|
||||
'use strict';
|
||||
{
|
||||
const globals = this;
|
||||
|
||||
@@ -53,7 +53,17 @@ function async_task_on_success(data) {
|
||||
// hide waitingDialog when using browser's history back
|
||||
waitingDialog.hide();
|
||||
});
|
||||
location.href = data.redirect;
|
||||
if (async_task_is_download && window.self !== window.top) {
|
||||
// if in an iframe, force to download an async_task_is_download
|
||||
// e.g. pretix-reseller embeds order-page in iframe, which would cause ticket-PDFs to be displayed inline
|
||||
var a = document.createElement("a");
|
||||
a.href = data.redirect;
|
||||
a.download = "";
|
||||
a.target = "_blank";
|
||||
a.click();
|
||||
} else {
|
||||
location.href = data.redirect;
|
||||
}
|
||||
}
|
||||
$(this).trigger('pretix:async-task-success', data);
|
||||
}
|
||||
@@ -164,15 +174,16 @@ function async_task_error(jqXHR, textStatus, errorThrown) {
|
||||
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
|
||||
|
||||
if (respdom.filter('#page-wrapper') && $('#page-wrapper').length) {
|
||||
// This is a failed form validation, let's just use it
|
||||
async_task_replace_page("#page-wrapper", respdom.find("#page-wrapper").html());
|
||||
} else {
|
||||
async_task_replace_page("body", jqXHR.responseText.substring(
|
||||
jqXHR.responseText.indexOf("<body"),
|
||||
jqXHR.responseText.indexOf("</body")
|
||||
));
|
||||
document.dispatchEvent(new Event("pretix:async-task-error"))
|
||||
|
||||
}
|
||||
|
||||
} else if (c.length > 0) {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
document.getElementById('goback').onclick =
|
||||
function() {window.history.back()};
|
||||
|
||||
document.getElementById('reload').onclick =
|
||||
function() {window.location.reload(true)};
|
||||
['DOMContentLoaded', 'pretix:async-task-error'].forEach(function (ev) {
|
||||
document.addEventListener(ev, function () {
|
||||
document.querySelectorAll('#goback, #reload').forEach(function (element) {
|
||||
const regularLoad = ev === 'DOMContentLoaded' && element.id === 'goback';
|
||||
element.addEventListener('click', regularLoad
|
||||
? () => window.history.back()
|
||||
: () => window.location.reload()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { rules as rawRules, items, allProducts, limitProducts } from './django-interop'
|
||||
import { rules as rawRules, allItems, activeItems, allProducts, limitProducts } from './django-interop'
|
||||
import { convertToDNF } from './jsonlogic-boolalg'
|
||||
|
||||
import RulesEditor from './checkin-rules-editor.vue'
|
||||
@@ -53,7 +53,7 @@ const missingItems = computed(() => {
|
||||
}
|
||||
|
||||
let missing = []
|
||||
for (const item of items.value) {
|
||||
for (const item of activeItems.value) {
|
||||
if (productsSeen[item.id]) continue
|
||||
if (!allProducts.value && !limitProducts.value.includes(item.id)) continue
|
||||
if (item.variations.length > 0) {
|
||||
@@ -75,15 +75,19 @@ const missingItems = computed(() => {
|
||||
li.active(role="presentation")
|
||||
a(href="#rules-edit", role="tab", data-toggle="tab")
|
||||
span.fa.fa-edit
|
||||
// space between icon and string
|
||||
|
|
||||
| {{ gettext("Edit") }}
|
||||
li(role="presentation")
|
||||
a(href="#rules-viz", role="tab", data-toggle="tab")
|
||||
span.fa.fa-eye
|
||||
// space between icon and string
|
||||
|
|
||||
| {{ gettext("Visualize") }}
|
||||
|
||||
//- Tab panes
|
||||
.tab-content
|
||||
#rules-edit.tab-pane.active(v-if="items", role="tabpanel")
|
||||
#rules-edit.tab-pane.active(v-if="allItems", role="tabpanel")
|
||||
RulesEditor
|
||||
#rules-viz.tab-pane(role="tabpanel")
|
||||
RulesVisualization
|
||||
|
||||
@@ -191,3 +191,8 @@ export const DATETIME_OPTIONS = {
|
||||
close: 'fa fa-remove'
|
||||
}
|
||||
}
|
||||
|
||||
export const TIME_OPTIONS = {
|
||||
...DATETIME_OPTIONS,
|
||||
format: document.body.dataset.timeformat,
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export const rules = ref<any>({})
|
||||
// grab rules from hidden input
|
||||
const rulesInput = document.querySelector<HTMLInputElement>('#id_rules')
|
||||
if (rulesInput?.value) {
|
||||
rules.value = JSON.parse(rulesInput.value)
|
||||
rules.value = JSON.parse(rulesInput.value) ?? {}
|
||||
}
|
||||
|
||||
// sync back to hidden input
|
||||
@@ -26,11 +26,13 @@ watch(rules, (newVal) => {
|
||||
rulesInput.value = JSON.stringify(newVal)
|
||||
}, { deep: true })
|
||||
|
||||
export const items = ref<any[]>([])
|
||||
export const activeItems = ref<any[]>([])
|
||||
export const allItems = ref<any[]>([])
|
||||
|
||||
const itemsEl = document.querySelector('#items')
|
||||
if (itemsEl?.textContent) {
|
||||
items.value = JSON.parse(itemsEl.textContent || '[]')
|
||||
allItems.value = JSON.parse(itemsEl.textContent || '[]')
|
||||
activeItems.value = allItems.value.filter(item => item.active)
|
||||
|
||||
function checkForInvalidIds (validProducts: Record<string, string>, validVariations: Record<string, string>, rule: any) {
|
||||
if (rule['and']) {
|
||||
@@ -57,12 +59,12 @@ if (itemsEl?.textContent) {
|
||||
}
|
||||
|
||||
checkForInvalidIds(
|
||||
Object.fromEntries(items.value.map(p => [p.id, p.name])),
|
||||
Object.fromEntries(items.value.flatMap(p => p.variations?.map(v => [v.id, p.name + ' – ' + v.name]) ?? [])),
|
||||
Object.fromEntries(allItems.value.map(p => [p.id, p.name])),
|
||||
Object.fromEntries(allItems.value.flatMap(p => p.variations?.map(v => [v.id, p.name + ' – ' + v.name]) ?? [])),
|
||||
rules.value
|
||||
)
|
||||
}
|
||||
|
||||
export const productSelectURL = ref(document.querySelector('#product-select2')?.textContent)
|
||||
export const variationSelectURL = ref(document.querySelector('#variations-select2')?.textContent)
|
||||
export const gateSelectURL = ref(document.querySelector('#gate-select2')?.textContent)
|
||||
export const gateSelectURL = ref(document.querySelector('#gates-select2')?.textContent)
|
||||
|
||||
@@ -100,14 +100,19 @@ watch(() => props.value, (newval, oldval) => {
|
||||
}
|
||||
})
|
||||
|
||||
let rawSelectEl: HTMLSelectElement | null = null
|
||||
|
||||
onMounted(() => {
|
||||
rawSelectEl = select.value
|
||||
build()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
$(select.value)
|
||||
if (!rawSelectEl) return
|
||||
$(rawSelectEl)
|
||||
.off()
|
||||
.select2('destroy')
|
||||
rawSelectEl = null
|
||||
})
|
||||
</script>
|
||||
<template lang="pug">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { DATETIME_OPTIONS } from './constants'
|
||||
import { TIME_OPTIONS } from './constants'
|
||||
|
||||
const props = defineProps<{
|
||||
required?: boolean
|
||||
@@ -20,7 +20,7 @@ watch(() => props.value, (val) => {
|
||||
onMounted(() => {
|
||||
$(input.value)
|
||||
.datetimepicker({
|
||||
...DATETIME_OPTIONS,
|
||||
...TIME_OPTIONS,
|
||||
showClear: props.required,
|
||||
})
|
||||
.trigger('change')
|
||||
|
||||
@@ -180,7 +180,7 @@ g
|
||||
br
|
||||
span(v-if="varresult !== null") {{ varresult }}
|
||||
strong
|
||||
| {{ op.label }} {{ rightoperand }}
|
||||
| {{ op?.label}} {{ rightoperand }}
|
||||
span(v-else-if="vardata && vardata.type === 'int_by_datetime'")
|
||||
span.fa.fa-sign-in(v-if="variable.startsWith('entries_')")
|
||||
| {{ vardata.label }}
|
||||
@@ -193,21 +193,21 @@ g
|
||||
br
|
||||
span(v-if="varresult !== null") {{ varresult }}
|
||||
strong
|
||||
| {{ op.label }} {{ rightoperand }}
|
||||
| {{ op?.label }} {{ rightoperand }}
|
||||
span(v-else-if="vardata && variable === 'now'")
|
||||
span.fa.fa-clock-o
|
||||
| {{ vardata.label }}
|
||||
br
|
||||
span(v-if="varresult !== null") {{ varresult }}
|
||||
strong
|
||||
| {{ op.label }}
|
||||
| {{ op?.label }}
|
||||
br
|
||||
span(v-if="rightoperand.buildTime[0] === 'custom'")
|
||||
| {{ df(rightoperand.buildTime[1]) }}
|
||||
span(v-else-if="rightoperand.buildTime[0] === 'customtime'")
|
||||
| {{ tf(rightoperand.buildTime[1]) }}
|
||||
span(v-if="rightoperand?.buildTime[0] === 'custom'")
|
||||
| {{ df(rightoperand?.buildTime[1]) }}
|
||||
span(v-else-if="rightoperand?.buildTime[0] === 'customtime'")
|
||||
| {{ tf(rightoperand?.buildTime[1]) }}
|
||||
span(v-else)
|
||||
| {{ TEXTS[rightoperand.buildTime[0]] }}
|
||||
| {{ TEXTS[rightoperand?.buildTime[0]] }}
|
||||
span(v-if="operands[2]")
|
||||
span(v-if="operator === 'isBefore'") +
|
||||
span(v-else) -
|
||||
@@ -220,14 +220,14 @@ g
|
||||
span(v-if="varresult !== null") ({{ varresult }})
|
||||
br
|
||||
strong
|
||||
| {{ rightoperand.objectList.map((o: any) => o.lookup[2]).join(", ") }}
|
||||
| {{ rightoperand?.objectList.map((o: any) => o.lookup[2]).join(", ") }}
|
||||
span(v-else-if="vardata && vardata.type === 'enum_entry_status'")
|
||||
span.fa.fa-check-circle-o
|
||||
| {{ vardata.label }}
|
||||
span(v-if="varresult !== null") ({{ varresult }})
|
||||
br
|
||||
strong
|
||||
| {{ op.label }} {{ rightoperand }}
|
||||
| {{ op?.label }} {{ rightoperand }}
|
||||
|
||||
g(v-if="result === false", :transform="`translate(${x + boxWidth - 15}, ${y - 10})`")
|
||||
ellipse(fill="#fff", cx="14.685823", cy="14.318233", rx="12.140151", ry="11.55523")
|
||||
|
||||
@@ -71,6 +71,7 @@ $(document).ajaxError(function (event, jqXHR, settings, thrownError) {
|
||||
});
|
||||
|
||||
var form_handlers = function (el) {
|
||||
el.trigger("rescan.areYouSure");
|
||||
el.find("[data-formset]").formset(
|
||||
{
|
||||
animateForms: true,
|
||||
@@ -638,11 +639,13 @@ var form_handlers = function (el) {
|
||||
).append(" ").append($("<div>").text(res.organizer).html())
|
||||
);
|
||||
}
|
||||
$ret.append(
|
||||
$("<span>").addClass("event-daterange").append(
|
||||
$("<span>").addClass("fa fa-calendar fa-fw")
|
||||
).append(" ").append(res.date_range)
|
||||
);
|
||||
if (res.date_range) {
|
||||
$ret.append(
|
||||
$("<span>").addClass("event-daterange").append(
|
||||
$("<span>").addClass("fa fa-calendar fa-fw")
|
||||
).append(" ").append(res.date_range)
|
||||
);
|
||||
}
|
||||
return $ret;
|
||||
},
|
||||
}).on("select2:select", function () {
|
||||
|
||||
@@ -864,6 +864,9 @@ tbody th {
|
||||
.checkin-sim-result-status-incomplete {
|
||||
background: $brand-primary;
|
||||
}
|
||||
.checkin-sim-result-status-exchange {
|
||||
background: $brand-primary;
|
||||
}
|
||||
.checkin-sim-result-status-error {
|
||||
background: $brand-danger;
|
||||
}
|
||||
|
||||
@@ -110,8 +110,17 @@ var setCookie = function (cname, cvalue, exdays) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
|
||||
var expires = "expires=" + d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
if (!cvalue) {
|
||||
var expires = "expires=Thu, 01 Jan 1970 00:00:00 GMT";
|
||||
cvalue = "";
|
||||
}
|
||||
var same_site = "";
|
||||
if (site_is_secure()) {
|
||||
same_site = ";SameSite=None;Secure"
|
||||
}
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + same_site + ";path=/";
|
||||
};
|
||||
|
||||
var getCookie = function (name) {
|
||||
var value = "; " + document.cookie;
|
||||
var parts = value.split("; " + name + "=");
|
||||
@@ -726,17 +735,16 @@ var shared_methods = {
|
||||
buy_callback: function (data) {
|
||||
if (data.redirect) {
|
||||
if (data.cart_id) {
|
||||
this.$root.cart_id = data.cart_id;
|
||||
setCookie(this.$root.cookieName, data.cart_id, 30);
|
||||
this.$root.set_cart_id(data.cart_id);
|
||||
}
|
||||
if (data.redirect.substr(0, 1) === '/') {
|
||||
data.redirect = this.$root.target_url.replace(/^([^\/]+:\/\/[^\/]+)\/.*$/, "$1") + data.redirect;
|
||||
}
|
||||
var url = data.redirect;
|
||||
if (url.indexOf('?')) {
|
||||
url = url + '&iframe=1&locale=' + lang + '&take_cart_id=' + this.$root.cart_id;
|
||||
url = url + '&iframe=1&locale=' + lang + '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id());
|
||||
} else {
|
||||
url = url + '?iframe=1&locale=' + lang + '&take_cart_id=' + this.$root.cart_id;
|
||||
url = url + '?iframe=1&locale=' + lang + '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id());
|
||||
}
|
||||
url += this.$root.consent_parameter;
|
||||
if (this.$root.additionalURLParams) {
|
||||
@@ -779,15 +787,24 @@ var shared_methods = {
|
||||
}
|
||||
},
|
||||
resume: function () {
|
||||
if (!this.$root.get_cart_id() && this.$root.keep_cart) {
|
||||
// create an empty cart whose id we can persist
|
||||
this.$root.create_cart(this.resume)
|
||||
return;
|
||||
}
|
||||
var redirect_url;
|
||||
redirect_url = this.$root.target_url + 'w/' + widget_id + '/';
|
||||
if (this.$root.subevent && !this.$root.cart_id) {
|
||||
if (this.$root.subevent && this.$root.is_button && this.$root.items.length === 0) {
|
||||
// button with subevent but no items
|
||||
redirect_url += this.$root.subevent + '/';
|
||||
}
|
||||
redirect_url += '?iframe=1&locale=' + lang;
|
||||
if (this.$root.cart_id) {
|
||||
redirect_url += '&take_cart_id=' + this.$root.cart_id;
|
||||
if (this.$root.get_cart_id()) {
|
||||
redirect_url += '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id());
|
||||
if (this.$root.keep_cart) {
|
||||
// make sure the cart-id is used, even if the cart is currently empty
|
||||
redirect_url += '&ajax=1'
|
||||
}
|
||||
}
|
||||
if (this.$root.widget_data) {
|
||||
redirect_url += '&widget_data=' + encodeURIComponent(this.$root.widget_data_json);
|
||||
@@ -1864,12 +1881,11 @@ var shared_root_methods = {
|
||||
if (this.$root.variation_filter) {
|
||||
url += '&variations=' + encodeURIComponent(this.$root.variation_filter);
|
||||
}
|
||||
var cart_id = getCookie(this.cookieName);
|
||||
if (this.$root.voucher_code) {
|
||||
url += '&voucher=' + encodeURIComponent(this.$root.voucher_code);
|
||||
}
|
||||
if (cart_id) {
|
||||
url += "&cart_id=" + encodeURIComponent(cart_id);
|
||||
if (this.$root.get_cart_id()) {
|
||||
url += "&cart_id=" + encodeURIComponent(this.$root.get_cart_id());
|
||||
}
|
||||
if (this.$root.date !== null) {
|
||||
url += "&date=" + this.$root.date.substr(0, 7);
|
||||
@@ -1939,7 +1955,6 @@ var shared_root_methods = {
|
||||
root.display_add_to_cart = data.display_add_to_cart;
|
||||
root.waiting_list_enabled = data.waiting_list_enabled;
|
||||
root.show_variations_expanded = data.show_variations_expanded || !!root.variation_filter;
|
||||
root.cart_id = cart_id;
|
||||
root.cart_exists = data.cart_exists;
|
||||
root.vouchers_exist = data.vouchers_exist;
|
||||
root.has_seating_plan = data.has_seating_plan;
|
||||
@@ -2004,8 +2019,8 @@ var shared_root_methods = {
|
||||
if (this.$root.voucher_code) {
|
||||
redirect_url += '&voucher=' + encodeURIComponent(this.$root.voucher_code);
|
||||
}
|
||||
if (this.$root.cart_id) {
|
||||
redirect_url += '&take_cart_id=' + this.$root.cart_id;
|
||||
if (this.$root.get_cart_id()) {
|
||||
redirect_url += '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id());
|
||||
}
|
||||
if (this.$root.widget_data) {
|
||||
redirect_url += '&widget_data=' + encodeURIComponent(this.$root.widget_data_json);
|
||||
@@ -2027,7 +2042,40 @@ var shared_root_methods = {
|
||||
this.$root.subevent = event.subevent;
|
||||
this.$root.loading++;
|
||||
this.$root.reload();
|
||||
}
|
||||
},
|
||||
create_cart: function(callback) {
|
||||
var url = this.$root.target_url + 'w/' + widget_id + '/cart/create?ajax=1';
|
||||
|
||||
this.$root.overlay.frame_loading = true;
|
||||
api._getJSON(url, (data) => {
|
||||
this.$root.set_cart_id(data.cart_id);
|
||||
this.$root.overlay.frame_loading = false;
|
||||
callback()
|
||||
}, (xhr, data) => {
|
||||
if (xhr.status === 429 && typeof xhr.responseURL !== "undefined") {
|
||||
this.$root.overlay.error_message = strings['cart_error_429'];
|
||||
this.$root.overlay.frame_loading = false;
|
||||
this.$root.overlay.error_url_after = this.$root.newTabTarget;
|
||||
this.$root.overlay.error_url_after_new_tab = true;
|
||||
} else {
|
||||
this.$root.overlay.error_message = strings['cart_error'];
|
||||
this.$root.overlay.frame_loading = false;
|
||||
}
|
||||
})
|
||||
},
|
||||
get_cart_id: function() {
|
||||
if (!this.$root.keep_cart) {
|
||||
return null
|
||||
}
|
||||
if (this.$root.cart_id) {
|
||||
return this.$root.cart_id
|
||||
}
|
||||
return getCookie(this.$root.cookieName);
|
||||
},
|
||||
set_cart_id: function(newValue) {
|
||||
this.$root.cart_id = newValue
|
||||
setCookie(this.$root.cookieName, newValue, 30);
|
||||
},
|
||||
};
|
||||
|
||||
var shared_root_computed = {
|
||||
@@ -2049,9 +2097,8 @@ var shared_root_computed = {
|
||||
},
|
||||
voucherFormTarget: function () {
|
||||
var form_target = this.target_url + 'w/' + widget_id + '/redeem?iframe=1&locale=' + lang;
|
||||
var cookie = getCookie(this.cookieName);
|
||||
if (cookie) {
|
||||
form_target += "&take_cart_id=" + cookie;
|
||||
if (this.get_cart_id()) {
|
||||
form_target += "&take_cart_id=" + encodeURIComponent(this.get_cart_id());
|
||||
}
|
||||
if (this.subevent) {
|
||||
form_target += "&subevent=" + this.subevent;
|
||||
@@ -2091,9 +2138,8 @@ var shared_root_computed = {
|
||||
checkout_url += '?' + this.$root.additionalURLParams;
|
||||
}
|
||||
var form_target = this.target_url + 'w/' + widget_id + '/cart/add?iframe=1&next=' + encodeURIComponent(checkout_url);
|
||||
var cookie = getCookie(this.cookieName);
|
||||
if (cookie) {
|
||||
form_target += "&take_cart_id=" + cookie;
|
||||
if (this.get_cart_id()) {
|
||||
form_target += "&take_cart_id=" + encodeURIComponent(this.get_cart_id());
|
||||
}
|
||||
form_target += this.$root.consent_parameter
|
||||
return form_target
|
||||
@@ -2103,7 +2149,14 @@ var shared_root_computed = {
|
||||
if (this.subevent) {
|
||||
target = this.target_url + this.subevent + '/';
|
||||
}
|
||||
return target;
|
||||
var parameters = this.$root.consent_parameter
|
||||
if (this.$root.additionalURLParams) {
|
||||
parameters += `&${this.$root.additionalURLParams}`
|
||||
}
|
||||
if (parameters) {
|
||||
target += '?' + parameters.replace(/^&/, '')
|
||||
}
|
||||
return target
|
||||
},
|
||||
useIframe: function () {
|
||||
if (window.crossOriginIsolated === true) {
|
||||
@@ -2329,6 +2382,8 @@ var create_widget = function (element, html_id=null) {
|
||||
has_seating_plan: false,
|
||||
has_seating_plan_waitinglist: false,
|
||||
meta_filter_fields: [],
|
||||
keep_cart: true,
|
||||
cart_id: null
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
@@ -2366,6 +2421,7 @@ var create_button = function (element, html_id=null) {
|
||||
var raw_items = element.attributes.items ? element.attributes.items.value : "";
|
||||
var skip_ssl = element.attributes["skip-ssl-check"] ? true : false;
|
||||
var disable_iframe = element.attributes["disable-iframe"] ? true : false;
|
||||
var keep_cart = element.attributes["keep-cart"] ? true : false;
|
||||
var button_text = element.innerHTML;
|
||||
var widget_data = JSON.parse(JSON.stringify(window.PretixWidget.widget_data));
|
||||
for (var i = 0; i < element.attributes.length; i++) {
|
||||
@@ -2417,7 +2473,9 @@ var create_button = function (element, html_id=null) {
|
||||
widget_data: widget_data,
|
||||
widget_id: 'pretix-widget-' + widget_id,
|
||||
html_id: html_id,
|
||||
button_text: button_text
|
||||
button_text: button_text,
|
||||
keep_cart: keep_cart || items.length > 0,
|
||||
cart_id: null
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
@@ -2426,7 +2484,7 @@ var create_button = function (element, html_id=null) {
|
||||
observer.observe(this.$el, observerOptions);
|
||||
},
|
||||
computed: shared_root_computed,
|
||||
methods: shared_root_methods
|
||||
methods: shared_root_methods,
|
||||
});
|
||||
create_overlay(app);
|
||||
return app;
|
||||
@@ -2492,13 +2550,15 @@ window.PretixWidget.open = function (target_url, voucher, subevent, items, widge
|
||||
frame_dismissed: false,
|
||||
widget_data: all_widget_data,
|
||||
widget_id: 'pretix-widget-' + widget_id,
|
||||
button_text: ""
|
||||
button_text: "",
|
||||
keep_cart: true,
|
||||
cart_id: null
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
},
|
||||
computed: shared_root_computed,
|
||||
methods: shared_root_methods
|
||||
methods: shared_root_methods,
|
||||
});
|
||||
create_overlay(app);
|
||||
app.$nextTick(function () {
|
||||
|
||||
@@ -201,13 +201,16 @@ footer nav li:not(:first-child):before {
|
||||
width: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
footer nav .btn,
|
||||
footer nav .btn-link {
|
||||
display: inline;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
footer nav .btn-link {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.js-only {
|
||||
display: none;
|
||||
|
||||
@@ -966,6 +966,7 @@ $table-bg-accent: rgba(128, 128, 128, 0.05);
|
||||
width: 80vw;
|
||||
max-width: 1080px;
|
||||
height: 80vh;
|
||||
max-height: 100dvh;
|
||||
}
|
||||
.pretix-widget-frame-inner iframe {
|
||||
width: 100% !important;
|
||||
|
||||
@@ -4,12 +4,44 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Pretix Widget</title>
|
||||
<link rel="stylesheet" type="text/css" href="http://localhost:8000/testorg/testevent/widget/v2.css" crossorigin>
|
||||
<link id="widget-css" rel="stylesheet" type="text/css" crossorigin>
|
||||
</head>
|
||||
<body>
|
||||
<pretix-widget event="http://localhost:8000/testorg/testevent/"></pretix-widget>
|
||||
<!-- <script type="text/javascript" src="http://localhost:8000/widget/v2.en.js" async crossorigin></script> -->
|
||||
<div id="widget-container"></div>
|
||||
<script>
|
||||
{
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const knownParams = new Set(['type', 'host', 'org', 'event', 'mode', 'lang', 'button-text'])
|
||||
const type = params.get('type') || 'widget'
|
||||
const host = params.get('host') || 'http://localhost:8000'
|
||||
const org = params.get('org') || 'testorg'
|
||||
const event = params.get('event') || 'testevent'
|
||||
const mode = params.get('mode') || 'dev'
|
||||
const lang = params.get('lang') || 'de'
|
||||
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
const baseUrl = `${host}/${org}/${event}`
|
||||
|
||||
document.getElementById('widget-css').href = `${baseUrl}/widget/v2.css`
|
||||
|
||||
const el = document.createElement(type === 'button' ? 'pretix-button' : 'pretix-widget')
|
||||
el.setAttribute('event', `${baseUrl}/`)
|
||||
if (type === 'button') {
|
||||
el.textContent = params.get('button-text') || 'Buy tickets!'
|
||||
}
|
||||
for (const [key, value] of params) {
|
||||
if (knownParams.has(key)) continue
|
||||
el.setAttribute(key, value)
|
||||
}
|
||||
document.getElementById('widget-container').appendChild(el)
|
||||
|
||||
const script = document.createElement('script')
|
||||
if (mode === 'prod') {
|
||||
Object.assign(script, { type: 'text/javascript', src: `${host}/widget/v2.${lang}.js`, async: true, crossOrigin: 'anonymous' })
|
||||
} else {
|
||||
Object.assign(script, { type: 'module', src: '/src/main.ts' })
|
||||
}
|
||||
document.body.appendChild(script)
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -83,3 +83,11 @@ export async function checkAsyncTask (url: string) {
|
||||
}
|
||||
return await response.json() as CartResponse
|
||||
}
|
||||
|
||||
export async function createCart (url: string) {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
throw new ApiError(response.status, response.url)
|
||||
}
|
||||
return await response.json() as CartResponse
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@ export function createButtonInstance (element: Element, htmlId?: string): App {
|
||||
htmlId: htmlId || element.id || makeid(16),
|
||||
isButton: true,
|
||||
buttonItems,
|
||||
buttonText: element.innerHTML
|
||||
buttonText: element.innerHTML,
|
||||
keepCart: 'keep-cart' in element.attributes || buttonItems.length > 0,
|
||||
})
|
||||
|
||||
const observer = new MutationObserver((mutationList) => {
|
||||
@@ -54,13 +55,6 @@ export function createButtonInstance (element: Element, htmlId?: string): App {
|
||||
}
|
||||
})
|
||||
|
||||
// TODO I don't think we need this anymore in vue3
|
||||
// if (element.tagName !== 'pretix-button') {
|
||||
// element.innerHTML = '<pretix-button>' + element.innerHTML + '</pretix-button>'
|
||||
// // Vue does not replace the container, so watch container as well
|
||||
// observer.observe(element, observerOptions)
|
||||
// }
|
||||
|
||||
const app = createApp(ButtonComponent)
|
||||
app.provide(StoreKey, store)
|
||||
app.config.errorHandler = (error, _vm, info) => {
|
||||
|
||||
@@ -93,11 +93,11 @@ const showTaxline = computed(() => props.price.rate !== '0.00' && props.price.gr
|
||||
span(v-if="!freePrice && !originalPrice", v-html="priceline")
|
||||
span(v-if="!freePrice && originalPrice")
|
||||
del.pretix-widget-pricebox-original-price(:aria-label="originalPriceAriaLabel", v-html="originalLine")
|
||||
|
|
||||
|!{' '}
|
||||
ins.pretix-widget-pricebox-new-price(:aria-label="newPriceAriaLabel", v-html="priceline")
|
||||
div(v-if="freePrice")
|
||||
span.pretix-widget-pricebox-currency(:id="priceBoxId") {{ store.currency }}
|
||||
|
|
||||
|!{' '}
|
||||
input.pretix-widget-pricebox-price-input(
|
||||
type="number",
|
||||
placeholder="0",
|
||||
|
||||
@@ -48,10 +48,6 @@ window.PretixWidget = {
|
||||
}
|
||||
|
||||
async function buildWidgets () {
|
||||
// TODO what does this do?
|
||||
document.createElement('pretix-widget')
|
||||
document.createElement('pretix-button')
|
||||
|
||||
await docReady()
|
||||
const widgetElements = document.querySelectorAll('pretix-widget, div.pretix-widget-compat')
|
||||
for (const [i, el] of Array.from(widgetElements).entries()) {
|
||||
@@ -96,6 +92,7 @@ function openWidget (
|
||||
isButton: true,
|
||||
buttonItems: items ?? [],
|
||||
buttonText: '',
|
||||
keepCart: true
|
||||
})
|
||||
|
||||
const app = createApp(ButtonComponent)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { nextTick, type InjectionKey } from 'vue'
|
||||
import { createStore } from '~/lib/store'
|
||||
import { fetchProductList, submitCart, checkAsyncTask, ApiError } from '~/api'
|
||||
import { fetchProductList, submitCart, checkAsyncTask, ApiError, createCart } from '~/api'
|
||||
import type { CartResponse } from '~/api'
|
||||
import { STRINGS } from '~/i18n'
|
||||
import { setCookie, getCookie, makeid } from '~/utils'
|
||||
import { setCookie, getCookie, makeid, siteIsSecure } from '~/utils'
|
||||
import type { Category, DayEntry, EventEntry, LightboxState, MetaFilterField, WidgetData } from '~/types'
|
||||
|
||||
export const globalWidgetId = makeid(16)
|
||||
@@ -28,6 +28,7 @@ export function createWidgetStore (config: {
|
||||
variations?: string | null
|
||||
widgetData: WidgetData
|
||||
htmlId: string
|
||||
keepCart: boolean
|
||||
// Button-specific
|
||||
buttonItems?: { item: string; count: string }[]
|
||||
buttonText?: string
|
||||
@@ -54,6 +55,7 @@ export function createWidgetStore (config: {
|
||||
widgetData: config.widgetData,
|
||||
widgetId: `pretix-widget-${globalWidgetId}`,
|
||||
htmlId: config.htmlId,
|
||||
keepCart: config.keepCart,
|
||||
|
||||
// View state
|
||||
view: null as 'event' | 'events' | 'weeks' | 'days' | null,
|
||||
@@ -74,7 +76,7 @@ export function createWidgetStore (config: {
|
||||
displayAddToCart: false,
|
||||
waitingListEnabled: false,
|
||||
showVariationsExpanded: !!config.variations,
|
||||
cartId: null as string | null,
|
||||
_cartId: null as string | null,
|
||||
cartExists: false,
|
||||
vouchersExist: false,
|
||||
hasSeatingPlan: false,
|
||||
@@ -123,13 +125,18 @@ export function createWidgetStore (config: {
|
||||
getters: {
|
||||
useIframe (): boolean {
|
||||
if ((window as any).crossOriginIsolated === true) return false
|
||||
return !this.disableIframe && (this.skipSsl || /https.*/.test(document.location.protocol))
|
||||
return !this.disableIframe && (this.skipSsl || siteIsSecure())
|
||||
},
|
||||
cookieName (): string {
|
||||
return `pretix_widget_${this.targetUrl.replace(/[^a-zA-Z0-9]+/g, '_')}`
|
||||
},
|
||||
cartIdFromCookie (): string | null {
|
||||
return getCookie(this.cookieName) ?? null
|
||||
cartId (): string | null {
|
||||
if (this._cartId) {
|
||||
return this._cartId
|
||||
}
|
||||
if (this.keepCart) {
|
||||
return getCookie(this.cookieName) ?? null
|
||||
}
|
||||
},
|
||||
widgetDataJson (): string {
|
||||
const cloned = { ...this.widgetData }
|
||||
@@ -155,7 +162,15 @@ export function createWidgetStore (config: {
|
||||
return params.toString()
|
||||
},
|
||||
newTabTarget (): string {
|
||||
return this.subevent ? `${this.targetUrl}${this.subevent}/` : this.targetUrl
|
||||
let url = this.subevent ? `${this.targetUrl}${this.subevent}/` : this.targetUrl
|
||||
let parameters = this.consentParameter
|
||||
if (this.additionalURLParams) {
|
||||
parameters += `&${this.additionalURLParams}`
|
||||
}
|
||||
if (parameters) {
|
||||
url += '?' + parameters.replace(/^&/, '')
|
||||
}
|
||||
return url
|
||||
},
|
||||
formTarget (): string {
|
||||
const isFirefox = navigator.userAgent.toLowerCase().includes('firefox')
|
||||
@@ -187,12 +202,12 @@ export function createWidgetStore (config: {
|
||||
}
|
||||
|
||||
let formTarget = `${this.targetUrl}w/${globalWidgetId}/cart/add?iframe=1&next=${encodeURIComponent(checkoutUrl)}`
|
||||
if (this.cartIdFromCookie) {
|
||||
formTarget += `&take_cart_id=${this.cartIdFromCookie}`
|
||||
if (this.cartId) {
|
||||
formTarget += `&take_cart_id=${this.cartId}`
|
||||
}
|
||||
formTarget += this.consentParameter
|
||||
return formTarget
|
||||
},
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
triggerLoadCallback () {
|
||||
@@ -219,8 +234,7 @@ export function createWidgetStore (config: {
|
||||
if (this.variationFilter) url += `&variations=${encodeURIComponent(this.variationFilter)}`
|
||||
if (this.voucherCode) url += `&voucher=${encodeURIComponent(this.voucherCode)}`
|
||||
|
||||
const cartIdCookie = this.cartIdFromCookie
|
||||
if (cartIdCookie) url += `&cart_id=${encodeURIComponent(cartIdCookie)}`
|
||||
if (this.cartId) url += `&cart_id=${encodeURIComponent(this.cartId)}`
|
||||
if (this.date !== null) {
|
||||
url += `&date=${this.date.substring(0, 7)}`
|
||||
} else if (this.week !== null) {
|
||||
@@ -291,7 +305,6 @@ export function createWidgetStore (config: {
|
||||
this.displayAddToCart = data.display_add_to_cart ?? false
|
||||
this.waitingListEnabled = data.waiting_list_enabled ?? false
|
||||
this.showVariationsExpanded = data.show_variations_expanded || !!this.variationFilter
|
||||
this.cartId = cartIdCookie
|
||||
this.cartExists = data.cart_exists ?? false
|
||||
this.vouchersExist = data.vouchers_exist ?? false
|
||||
this.hasSeatingPlan = data.has_seating_plan ?? false
|
||||
@@ -335,12 +348,13 @@ export function createWidgetStore (config: {
|
||||
this.loading--
|
||||
this.triggerLoadCallback()
|
||||
}
|
||||
throw e
|
||||
}
|
||||
},
|
||||
getVoucherFormTarget (): string {
|
||||
let formTarget = `${this.targetUrl}w/${globalWidgetId}/redeem?iframe=1&locale=${LANG}`
|
||||
if (this.cartIdFromCookie) {
|
||||
formTarget += `&take_cart_id=${this.cartIdFromCookie}`
|
||||
if (this.cartId) {
|
||||
formTarget += `&take_cart_id=${this.cartId}`
|
||||
}
|
||||
if (this.subevent) {
|
||||
formTarget += `&subevent=${this.subevent}`
|
||||
@@ -357,8 +371,7 @@ export function createWidgetStore (config: {
|
||||
handleCartResponse (data: CartResponse) {
|
||||
if (data.redirect) {
|
||||
if (data.cart_id) {
|
||||
this.cartId = data.cart_id
|
||||
setCookie(this.cookieName, data.cart_id, 30)
|
||||
this.setCartId(data.cart_id)
|
||||
}
|
||||
|
||||
let url = data.redirect
|
||||
@@ -436,6 +449,7 @@ export function createWidgetStore (config: {
|
||||
this.overlay.frameLoading = false
|
||||
this.overlay.errorUrlAfter = this.newTabTarget
|
||||
this.overlay.errorUrlAfterNewTab = true
|
||||
return
|
||||
} else if (e.status === 405) {
|
||||
// Likely a redirect!
|
||||
this.targetUrl = e.responseUrl.substring(0, e.responseUrl.indexOf('/cart/add') - 18)
|
||||
@@ -448,6 +462,27 @@ export function createWidgetStore (config: {
|
||||
this.overlay.frameLoading = false
|
||||
}
|
||||
},
|
||||
async createCart () {
|
||||
const url = `${this.targetUrl}w/${globalWidgetId}/cart/create?ajax=1`
|
||||
|
||||
try {
|
||||
this.overlay.frameLoading = true
|
||||
const data = await createCart(url)
|
||||
this.setCartId(data.cart_id)
|
||||
return true
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && e.status === 429) {
|
||||
this.overlay.errorMessage = STRINGS.cart_error_429
|
||||
this.overlay.frameLoading = false
|
||||
this.overlay.errorUrlAfter = this.newTabTarget
|
||||
this.overlay.errorUrlAfterNewTab = true
|
||||
} else if (e instanceof ApiError && (e.status === 200 || (e.status >= 400 && e.status < 500))) {
|
||||
this.overlay.errorMessage = STRINGS.cart_error
|
||||
this.overlay.frameLoading = false
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
redeem (voucherCode: string, event?: Event) {
|
||||
if (!this.useIframe) return
|
||||
if (event) event.preventDefault()
|
||||
@@ -462,15 +497,24 @@ export function createWidgetStore (config: {
|
||||
window.open(redirectUrl)
|
||||
}
|
||||
},
|
||||
resume () {
|
||||
async resume () {
|
||||
if (!this.cartId && this.keepCart) {
|
||||
// create an empty cart whose id we can persist
|
||||
if (!await this.createCart()) return
|
||||
}
|
||||
let redirectUrl = `${this.targetUrl}w/${globalWidgetId}/`
|
||||
if (this.subevent && this.isButton && this.items.length === 0) {
|
||||
// button with subevent but no items
|
||||
redirectUrl += `${this.subevent}/`
|
||||
}
|
||||
if (this.subevent && !this.cartId) {
|
||||
// button with subevent but no items
|
||||
redirectUrl += `${this.subevent}/`
|
||||
}
|
||||
redirectUrl += `?iframe=1&locale=${LANG}`
|
||||
if (this.cartId) {
|
||||
redirectUrl += `&take_cart_id=${this.cartId}`
|
||||
// ajax to make sure the cart-id is used, even if the cart is currently empty
|
||||
redirectUrl += `&take_cart_id=${this.cartId}&ajax=1`
|
||||
}
|
||||
if (this.widgetData) {
|
||||
redirectUrl += `&widget_data=${encodeURIComponent(this.widgetDataJson)}`
|
||||
@@ -523,6 +567,10 @@ export function createWidgetStore (config: {
|
||||
} else {
|
||||
window.open(redirectUrl)
|
||||
}
|
||||
},
|
||||
setCartId (cartId: string) {
|
||||
this._cartId = cartId
|
||||
setCookie(this.cookieName, cartId, 30)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ export function setCookie (cname: string, cvalue: string, exdays: number): void
|
||||
const d = new Date()
|
||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000)
|
||||
const expires = `expires=${d.toUTCString()}`
|
||||
document.cookie = `${cname}=${cvalue};${expires};path=/`
|
||||
document.cookie = `${cname}=${cvalue};${expires};${siteIsSecure() ? 'SameSite=None;Secure;' : ''}path=/`
|
||||
}
|
||||
|
||||
export function getCookie (name: string): string | null {
|
||||
|
||||
@@ -38,6 +38,7 @@ export function createWidgetInstance (element: Element, htmlId?: string): App {
|
||||
variations: element.attributes.variations?.value || null,
|
||||
widgetData,
|
||||
htmlId: htmlId || element.id || makeid(16),
|
||||
keepCart: true
|
||||
})
|
||||
|
||||
const observer = new MutationObserver((mutationList) => {
|
||||
@@ -50,13 +51,6 @@ export function createWidgetInstance (element: Element, htmlId?: string): App {
|
||||
}
|
||||
})
|
||||
|
||||
// TODO I don't think we need this anymore in vue3
|
||||
// if (element.tagName !== 'pretix-widget') {
|
||||
// element.innerHTML = '<pretix-widget></pretix-widget>'
|
||||
// // we need to watch the container as well as the replaced root-node (see mounted())
|
||||
// observer.observe(element, observerOptions)
|
||||
// }
|
||||
|
||||
const app = createApp(WidgetComponent)
|
||||
app.provide(StoreKey, store)
|
||||
app.config.errorHandler = (error, _vm, info) => {
|
||||
|
||||
Reference in New Issue
Block a user