mirror of
https://github.com/pretix/pretix.git
synced 2026-05-09 15:54:03 +00:00
Implement progress indicator during checkout (#677)
* Implement progress indicator during checkout * Do not extend bars to the edge * Wording * Add a bit more margin
This commit is contained in:
@@ -6,7 +6,7 @@ from django.http import HttpResponseNotAllowed, JsonResponse
|
|||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||||
from django.views.generic.base import TemplateResponseMixin
|
from django.views.generic.base import TemplateResponseMixin
|
||||||
|
|
||||||
from pretix.base.models import Order
|
from pretix.base.models import Order
|
||||||
@@ -33,6 +33,7 @@ from pretix.presale.views.questions import QuestionsViewMixin
|
|||||||
|
|
||||||
class BaseCheckoutFlowStep:
|
class BaseCheckoutFlowStep:
|
||||||
requires_valid_cart = True
|
requires_valid_cart = True
|
||||||
|
icon = 'pencil'
|
||||||
|
|
||||||
def __init__(self, event):
|
def __init__(self, event):
|
||||||
self.event = event
|
self.event = event
|
||||||
@@ -42,6 +43,10 @@ class BaseCheckoutFlowStep:
|
|||||||
def identifier(self):
|
def identifier(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self):
|
||||||
|
return pgettext_lazy('checkoutflow', 'Step')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def priority(self):
|
def priority(self):
|
||||||
return 100
|
return 100
|
||||||
@@ -137,6 +142,11 @@ class TemplateFlowStep(TemplateResponseMixin, BaseCheckoutFlowStep):
|
|||||||
kwargs.setdefault('step', self)
|
kwargs.setdefault('step', self)
|
||||||
kwargs.setdefault('event', self.event)
|
kwargs.setdefault('event', self.event)
|
||||||
kwargs.setdefault('prev_url', self.get_prev_url(self.request))
|
kwargs.setdefault('prev_url', self.get_prev_url(self.request))
|
||||||
|
kwargs.setdefault('checkout_flow', [
|
||||||
|
step
|
||||||
|
for step in self.request._checkout_flow
|
||||||
|
if step.is_applicable(self.request)
|
||||||
|
])
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def render(self, **kwargs):
|
def render(self, **kwargs):
|
||||||
@@ -166,6 +176,8 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
|||||||
task = set_cart_addons
|
task = set_cart_addons
|
||||||
known_errortypes = ['CartError']
|
known_errortypes = ['CartError']
|
||||||
requires_valid_cart = False
|
requires_valid_cart = False
|
||||||
|
label = pgettext_lazy('checkoutflow', 'Add-on products')
|
||||||
|
icon = 'puzzle-piece'
|
||||||
|
|
||||||
def is_applicable(self, request):
|
def is_applicable(self, request):
|
||||||
if not hasattr(request, '_checkoutflow_addons_applicable'):
|
if not hasattr(request, '_checkoutflow_addons_applicable'):
|
||||||
@@ -173,6 +185,8 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
|||||||
return request._checkoutflow_addons_applicable
|
return request._checkoutflow_addons_applicable
|
||||||
|
|
||||||
def is_completed(self, request, warn=False):
|
def is_completed(self, request, warn=False):
|
||||||
|
if getattr(self, '_completed', None) is not None:
|
||||||
|
return self._completed
|
||||||
for cartpos in get_cart(request).filter(addon_to__isnull=True).prefetch_related(
|
for cartpos in get_cart(request).filter(addon_to__isnull=True).prefetch_related(
|
||||||
'item__addons', 'item__addons__addon_category', 'addons', 'addons__item'
|
'item__addons', 'item__addons__addon_category', 'addons', 'addons__item'
|
||||||
):
|
):
|
||||||
@@ -180,7 +194,9 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
|||||||
for iao in cartpos.item.addons.all():
|
for iao in cartpos.item.addons.all():
|
||||||
found = len([1 for p in a if p.item.category_id == iao.addon_category_id])
|
found = len([1 for p in a if p.item.category_id == iao.addon_category_id])
|
||||||
if found < iao.min_count or found > iao.max_count:
|
if found < iao.min_count or found > iao.max_count:
|
||||||
|
self._completed = False
|
||||||
return False
|
return False
|
||||||
|
self._completed = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
@@ -283,6 +299,7 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
|||||||
priority = 50
|
priority = 50
|
||||||
identifier = "questions"
|
identifier = "questions"
|
||||||
template_name = "pretixpresale/event/checkout_questions.html"
|
template_name = "pretixpresale/event/checkout_questions.html"
|
||||||
|
label = pgettext_lazy('checkoutflow', 'Your information')
|
||||||
|
|
||||||
def is_applicable(self, request):
|
def is_applicable(self, request):
|
||||||
return True
|
return True
|
||||||
@@ -397,6 +414,8 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
|||||||
priority = 200
|
priority = 200
|
||||||
identifier = "payment"
|
identifier = "payment"
|
||||||
template_name = "pretixpresale/event/checkout_payment.html"
|
template_name = "pretixpresale/event/checkout_payment.html"
|
||||||
|
label = pgettext_lazy('checkoutflow', 'Payment')
|
||||||
|
icon = 'credit-card'
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def _total_order_value(self):
|
def _total_order_value(self):
|
||||||
@@ -479,6 +498,8 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
|||||||
template_name = "pretixpresale/event/checkout_confirm.html"
|
template_name = "pretixpresale/event/checkout_confirm.html"
|
||||||
task = perform_order
|
task = perform_order
|
||||||
known_errortypes = ['OrderError']
|
known_errortypes = ['OrderError']
|
||||||
|
label = pgettext_lazy('checkoutflow', 'Review order')
|
||||||
|
icon = 'eye'
|
||||||
|
|
||||||
def is_applicable(self, request):
|
def is_applicable(self, request):
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2>{% trans "Checkout" %}</h2>
|
<h2>{% trans "Checkout" %}</h2>
|
||||||
|
{% include "pretixpresale/event/fragment_checkoutflow.html" %}
|
||||||
{% block inner %}
|
{% block inner %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load eventurl %}
|
{% load eventurl %}
|
||||||
{% load eventsignal %}
|
{% load eventsignal %}
|
||||||
{% block title %}{% trans "Confirm order" %}{% endblock %}
|
{% block title %}{% trans "Review order" %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans "Confirm order" %}</h2>
|
<h2>{% trans "Review order" %}</h2>
|
||||||
|
{% include "pretixpresale/event/fragment_checkoutflow.html" %}
|
||||||
<p>{% trans "Please review the details below and confirm your order." %}</p>
|
<p>{% trans "Please review the details below and confirm your order." %}</p>
|
||||||
<form method="post" data-asynctask>
|
<form method="post" data-asynctask>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<div class="checkout-flow">
|
||||||
|
{% for step in checkout_flow %}
|
||||||
|
<a {% if step.c_is_before %}href="{{ step.c_resolved_url }}"{% endif %} class="checkout-step {% if step.c_is_before %}step-done{% elif request.resolver_match.kwargs.step == step.identifier %}step-current{% endif %}">
|
||||||
|
<div class="checkout-step-bar-left"></div>
|
||||||
|
<div class="checkout-step-bar-right"></div>
|
||||||
|
<div class="checkout-step-icon">
|
||||||
|
<span class="fa {% if step.c_is_before %}fa-check{% elif step.icon %}fa-{{ step.icon }}{% else %}fa-pencil{% endif %}"></span>
|
||||||
|
</div>
|
||||||
|
<div class="checkout-step-label">
|
||||||
|
{{ step.label }}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
<a class="checkout-step">
|
||||||
|
<div class="checkout-step-bar-left"></div>
|
||||||
|
<div class="checkout-step-bar-right"></div>
|
||||||
|
<div class="checkout-step-icon">
|
||||||
|
<span class="fa fa-ticket"></span>
|
||||||
|
</div>
|
||||||
|
<div class="checkout-step-label">
|
||||||
|
{% trans "Order confirmed" context "checkoutflow" %}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
@@ -41,7 +41,7 @@ class CheckoutView(View):
|
|||||||
except CartError as e:
|
except CartError as e:
|
||||||
cart_error = e
|
cart_error = e
|
||||||
|
|
||||||
flow = get_checkout_flow(self.request.event)
|
flow = request._checkout_flow = get_checkout_flow(self.request.event)
|
||||||
previous_step = None
|
previous_step = None
|
||||||
for step in flow:
|
for step in flow:
|
||||||
if not step.is_applicable(request):
|
if not step.is_applicable(request):
|
||||||
@@ -64,4 +64,6 @@ class CheckoutView(View):
|
|||||||
return handler(request)
|
return handler(request)
|
||||||
else:
|
else:
|
||||||
previous_step = step
|
previous_step = step
|
||||||
|
step.c_is_before = True
|
||||||
|
step.c_resolved_url = step.get_step_url(request)
|
||||||
raise Http404()
|
raise Http404()
|
||||||
|
|||||||
86
src/pretix/static/pretixpresale/scss/_checkout.scss
Normal file
86
src/pretix/static/pretixpresale/scss/_checkout.scss
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
.checkout-flow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 15px 0 13px 0;
|
||||||
|
|
||||||
|
.checkout-step {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.checkout-step-icon {
|
||||||
|
border: 1px solid $text-muted;
|
||||||
|
display: inline-block;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 28px;
|
||||||
|
border-radius: 15px;
|
||||||
|
color: $text-muted;
|
||||||
|
z-index: 200;
|
||||||
|
background: white;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-step-label {
|
||||||
|
padding-top: 10px;
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-step-bar-left {
|
||||||
|
position: absolute;
|
||||||
|
top: 22px;
|
||||||
|
left: 0;
|
||||||
|
width: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background: $gray-lighter;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.checkout-step-bar-right {
|
||||||
|
position: absolute;
|
||||||
|
top: 22px;
|
||||||
|
left: 50%;
|
||||||
|
width: 50%;
|
||||||
|
height: 6px;
|
||||||
|
background: $gray-lighter;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.step-done .checkout-step-icon {
|
||||||
|
border: 1px solid $brand-success;
|
||||||
|
background: $brand-success;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
&.step-done .checkout-step-label {
|
||||||
|
color: $brand-success;
|
||||||
|
}
|
||||||
|
&.step-done .checkout-step-bar-left, &.step-done .checkout-step-bar-right {
|
||||||
|
background: $brand-success;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.step-current .checkout-step-icon {
|
||||||
|
border: 1px solid darken($brand-info, 20%);
|
||||||
|
background: darken($brand-info, 20%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
&.step-current .checkout-step-label {
|
||||||
|
color: darken($brand-info, 20%);
|
||||||
|
}
|
||||||
|
&.step-current .checkout-step-bar-left {
|
||||||
|
background: $brand-success;
|
||||||
|
}
|
||||||
|
&:last-child .checkout-step-bar-right,
|
||||||
|
&:first-child .checkout-step-bar-left {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover, &:active {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media(max-width: $screen-sm-max) {
|
||||||
|
.checkout-step-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
@import "_cart.scss";
|
@import "_cart.scss";
|
||||||
@import "_forms.scss";
|
@import "_forms.scss";
|
||||||
@import "_calendar.scss";
|
@import "_calendar.scss";
|
||||||
|
@import "_checkout.scss";
|
||||||
@import "../../pretixbase/scss/webfont.scss";
|
@import "../../pretixbase/scss/webfont.scss";
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
|
|||||||
Reference in New Issue
Block a user