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:
Raphael Michel
2017-11-16 21:24:55 +01:00
committed by GitHub
parent e4167380b9
commit d22427f578
7 changed files with 141 additions and 4 deletions

View File

@@ -6,7 +6,7 @@ from django.http import HttpResponseNotAllowed, JsonResponse
from django.shortcuts import redirect
from django.utils import translation
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 pretix.base.models import Order
@@ -33,6 +33,7 @@ from pretix.presale.views.questions import QuestionsViewMixin
class BaseCheckoutFlowStep:
requires_valid_cart = True
icon = 'pencil'
def __init__(self, event):
self.event = event
@@ -42,6 +43,10 @@ class BaseCheckoutFlowStep:
def identifier(self):
raise NotImplementedError()
@property
def label(self):
return pgettext_lazy('checkoutflow', 'Step')
@property
def priority(self):
return 100
@@ -137,6 +142,11 @@ class TemplateFlowStep(TemplateResponseMixin, BaseCheckoutFlowStep):
kwargs.setdefault('step', self)
kwargs.setdefault('event', self.event)
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
def render(self, **kwargs):
@@ -166,6 +176,8 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
task = set_cart_addons
known_errortypes = ['CartError']
requires_valid_cart = False
label = pgettext_lazy('checkoutflow', 'Add-on products')
icon = 'puzzle-piece'
def is_applicable(self, request):
if not hasattr(request, '_checkoutflow_addons_applicable'):
@@ -173,6 +185,8 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
return request._checkoutflow_addons_applicable
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(
'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():
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:
self._completed = False
return False
self._completed = True
return True
@cached_property
@@ -283,6 +299,7 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
priority = 50
identifier = "questions"
template_name = "pretixpresale/event/checkout_questions.html"
label = pgettext_lazy('checkoutflow', 'Your information')
def is_applicable(self, request):
return True
@@ -397,6 +414,8 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
priority = 200
identifier = "payment"
template_name = "pretixpresale/event/checkout_payment.html"
label = pgettext_lazy('checkoutflow', 'Payment')
icon = 'credit-card'
@cached_property
def _total_order_value(self):
@@ -479,6 +498,8 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
template_name = "pretixpresale/event/checkout_confirm.html"
task = perform_order
known_errortypes = ['OrderError']
label = pgettext_lazy('checkoutflow', 'Review order')
icon = 'eye'
def is_applicable(self, request):
return True

View File

@@ -40,6 +40,7 @@
</div>
</div>
<h2>{% trans "Checkout" %}</h2>
{% include "pretixpresale/event/fragment_checkoutflow.html" %}
{% block inner %}
{% endblock %}
{% endblock %}

View File

@@ -3,9 +3,10 @@
{% load bootstrap3 %}
{% load eventurl %}
{% load eventsignal %}
{% block title %}{% trans "Confirm order" %}{% endblock %}
{% block title %}{% trans "Review order" %}{% endblock %}
{% 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>
<form method="post" data-asynctask>
{% csrf_token %}

View File

@@ -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>

View File

@@ -41,7 +41,7 @@ class CheckoutView(View):
except CartError as e:
cart_error = e
flow = get_checkout_flow(self.request.event)
flow = request._checkout_flow = get_checkout_flow(self.request.event)
previous_step = None
for step in flow:
if not step.is_applicable(request):
@@ -64,4 +64,6 @@ class CheckoutView(View):
return handler(request)
else:
previous_step = step
step.c_is_before = True
step.c_resolved_url = step.get_step_url(request)
raise Http404()

View 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;
}
}

View File

@@ -7,6 +7,7 @@
@import "_cart.scss";
@import "_forms.scss";
@import "_calendar.scss";
@import "_checkout.scss";
@import "../../pretixbase/scss/webfont.scss";
footer {