Merge branch 'feature/mobile' into develop
This commit is contained in:
commit
933585a194
@ -5,7 +5,15 @@ from .models import Address, User
|
||||
class AddressForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Address
|
||||
fields = '__all__'
|
||||
fields = (
|
||||
'first_name',
|
||||
'last_name',
|
||||
'street_address_1',
|
||||
'street_address_2',
|
||||
'city',
|
||||
'state',
|
||||
'postal_code',
|
||||
)
|
||||
|
||||
|
||||
class AccountCreateForm(UserCreationForm):
|
||||
@ -16,8 +24,24 @@ class AccountCreateForm(UserCreationForm):
|
||||
class AccountUpdateForm(UserChangeForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
fields = (
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
]
|
||||
'default_shipping_address',
|
||||
'addresses',
|
||||
)
|
||||
|
||||
class CustomerUpdateForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['default_shipping_address'].queryset = kwargs['instance'].addresses
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = (
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'default_shipping_address',
|
||||
)
|
||||
|
||||
@ -73,6 +73,12 @@ class ProductPhoto(models.Model):
|
||||
def __str__(self):
|
||||
return self.product.name
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
storage, path = self.image.storage, self.image.path
|
||||
|
||||
super(ProductPhoto, self).delete(*args, **kwargs)
|
||||
storage.delete(path)
|
||||
|
||||
# def save(self, *args, **kwargs):
|
||||
# super().save(*args, **kwargs)
|
||||
|
||||
@ -109,6 +115,9 @@ class Coupon(models.Model):
|
||||
class Meta:
|
||||
ordering = ("code",)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
today = timezone.localtime(timezone.now())
|
||||
@ -224,6 +233,9 @@ class Order(models.Model):
|
||||
def get_absolute_url(self):
|
||||
return reverse('dashboard:order-detail', kwargs={'pk': self.pk})
|
||||
|
||||
class Meta:
|
||||
ordering = ('-created_at',)
|
||||
|
||||
|
||||
|
||||
class Transaction(models.Model):
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<h1><img src="{% static 'images/coupon.png' %}" alt=""> Coupon</h1>
|
||||
</header>
|
||||
<section class="coupon__detail object__panel">
|
||||
<form method="post">{% csrf_token %}
|
||||
<form method="post" class="panel__item">{% csrf_token %}
|
||||
<p>Are you sure you want to delete "{{ object }}"?</p>
|
||||
{{ form.as_p }}
|
||||
<p>
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
<header class="object__header">
|
||||
<h1><img src="{% static 'images/coupon.png' %}" alt=""> {{ coupon.name }}</h1>
|
||||
<div class="object__menu">
|
||||
<a href="" class="action-button action-button--warning">Delete</a>
|
||||
<a href="" class="action-button">Edit</a>
|
||||
<a href="{% url 'dashboard:coupon-delete' coupon.pk %}" class="action-button action-button--warning">Delete</a>
|
||||
<a href="{% url 'dashboard:coupon-update' coupon.pk %}" class="action-button">Edit</a>
|
||||
</div>
|
||||
</header>
|
||||
<section class="product__detail object__panel">
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
{% block content %}
|
||||
<article class="product">
|
||||
<p><a href="{% url 'storefront:customer-detail' customer.pk %}">← Back</a></p>
|
||||
<header class="object__header">
|
||||
<h1>Update Customer</h1>
|
||||
</header>
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
{% with product=item.product %}
|
||||
<figure class="item__figure">
|
||||
<img class="product__image product__image--small" src="{{product.productphoto_set.first.image.url}}" alt="{{product.productphoto_set.first.image}}">
|
||||
<figcaption><strong>{{product.name}}</strong></figcaption>
|
||||
<figcaption><strong>{{product.name}}</strong><br>Grind: {{item.customer_note}}</figcaption>
|
||||
</figure>
|
||||
<span>{{product.sku}}</span>
|
||||
<span>{{item.quantity}}</span>
|
||||
|
||||
@ -2,9 +2,11 @@
|
||||
|
||||
{% block content %}
|
||||
<article class="product">
|
||||
<header class="object__header">
|
||||
<h1>Create product</h1>
|
||||
<section>
|
||||
<form method="POST" action="{% url 'dashboard:product-create' %}">
|
||||
</header>
|
||||
<section class="object__panel">
|
||||
<form class="panel__item" method="POST" action="{% url 'dashboard:product-create' %}">
|
||||
{% csrf_token %}
|
||||
{{form.as_p}}
|
||||
<p class="form__submit">
|
||||
|
||||
@ -116,9 +116,10 @@ class OrderListView(LoginRequiredMixin, ListView):
|
||||
|
||||
def get_queryset(self):
|
||||
query = self.request.GET.get('status')
|
||||
if query:
|
||||
if query == 'unfulfilled':
|
||||
object_list = Order.objects.filter(
|
||||
status=query
|
||||
Q(status=OrderStatus.UNFULFILLED) |
|
||||
Q(status=OrderStatus.PARTIALLY_FULFILLED)
|
||||
).order_by(
|
||||
'-created_at'
|
||||
).select_related(
|
||||
@ -279,7 +280,7 @@ class CustomerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = User
|
||||
template_name = 'dashboard/customer_form.html'
|
||||
context_object_name = 'customer'
|
||||
success_message = '%(name)s saved.'
|
||||
success_message = 'Customer saved.'
|
||||
fields = (
|
||||
'first_name',
|
||||
'last_name',
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 55 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
@ -9,6 +9,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
# Add Your Required Allow Host
|
||||
if DEBUG == False:
|
||||
ALLOWED_HOSTS = ['ptcoffee-dev.windmillapps.org']
|
||||
else:
|
||||
ALLOWED_HOSTS = ['192.168.68.106', '127.0.0.1', 'localhost']
|
||||
|
||||
INTERNAL_IPS = [
|
||||
'127.0.0.1',
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 43 KiB |
1
src/static/images/site_logo.svg
Normal file
1
src/static/images/site_logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 36 KiB |
@ -16,9 +16,9 @@ export function getCookie(name) {
|
||||
|
||||
const twentyYears = 20 * 365 * 24 * 60 * 60 * 1000
|
||||
|
||||
export function setCookie(name, value) {
|
||||
export function setCookie(name, value, expiration=twentyYears) {
|
||||
const body = [ name, value ].map(encodeURIComponent).join("=")
|
||||
const expires = new Date(Date.now() + twentyYears).toUTCString()
|
||||
const expires = new Date(Date.now() + expiration).toUTCString()
|
||||
const cookie = `${body}; domain=; path=/; SameSite=Lax; expires=${expires}`
|
||||
document.cookie = cookie
|
||||
}
|
||||
|
||||
37
src/static/scripts/index.js
Normal file
37
src/static/scripts/index.js
Normal file
@ -0,0 +1,37 @@
|
||||
import { getCookie, setCookie } from "./cookie.js"
|
||||
|
||||
// Get the modal
|
||||
const modal = document.querySelector(".modal-menu");
|
||||
|
||||
// Get the <span> element that closes the modal
|
||||
const closeBtn = document.querySelector(".close-modal");
|
||||
|
||||
const oneDay = 1 * 24 * 60 * 60 * 1000
|
||||
|
||||
// When the user clicks on <span> (x), close the modal
|
||||
closeBtn.addEventListener("click", event => {
|
||||
modal.style.display = "none";
|
||||
setCookie('newsletter-modal', 'true', oneDay)
|
||||
})
|
||||
|
||||
const scrollFunction = () => {
|
||||
let modalDismissed = getCookie('newsletter-modal')
|
||||
console.log(modalDismissed)
|
||||
if (modalDismissed != 'true') {
|
||||
if (document.body.scrollTop > 600 || document.documentElement.scrollTop > 600) {
|
||||
modal.style.display = "block";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.onscroll = () => {
|
||||
scrollFunction();
|
||||
};
|
||||
|
||||
// When the user clicks anywhere outside of the modal, close it
|
||||
window.addEventListener("click", event => {
|
||||
if (event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
setCookie('newsletter-modal', 'true', oneDay)
|
||||
});
|
||||
@ -1,6 +1,6 @@
|
||||
import { getCookie } from "./cookie.js"
|
||||
|
||||
let form = document.querySelector('form.order__form')
|
||||
let form = document.querySelector('form')
|
||||
|
||||
// Render the PayPal button into #paypal-button-container
|
||||
paypal.Buttons({
|
||||
|
||||
12
src/static/scripts/product_gallery.js
Normal file
12
src/static/scripts/product_gallery.js
Normal file
@ -0,0 +1,12 @@
|
||||
const gallery = document.querySelectorAll('.gallery__thumbnail')
|
||||
const productImage = document.querySelector('.product__image')
|
||||
let currentImg = document.querySelector('.gallery__thumbnail--focus')
|
||||
|
||||
gallery.forEach(image => {
|
||||
image.addEventListener('mouseover', event => {
|
||||
currentImg.classList.remove('gallery__thumbnail--focus')
|
||||
event.target.classList.add('gallery__thumbnail--focus')
|
||||
currentImg = event.target
|
||||
productImage.src = currentImg.src
|
||||
})
|
||||
})
|
||||
@ -1,9 +1,11 @@
|
||||
:root {
|
||||
--fg-color: #333;
|
||||
--fg-color: #34201A;
|
||||
--bg-color: #f5f5f5;
|
||||
--bg-alt-color: #eee5d3;
|
||||
--gray-color: #9d9d9d;
|
||||
--yellow-color: #f8a911;
|
||||
--yellow-alt-color: #ffce6f;
|
||||
--yellow-dark-color: #b27606;
|
||||
|
||||
--default-border: 2px solid var(--gray-color);
|
||||
}
|
||||
@ -13,17 +15,22 @@ html {
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg-color);
|
||||
background-color: var(--bg-color);
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 400;
|
||||
max-width: 1024px;
|
||||
padding: 1rem;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
line-height: 1.75;
|
||||
color: var(--fg-color);
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Typography
|
||||
========================================================================== */
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@ -33,9 +40,9 @@ a {
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
margin: 0;
|
||||
font-family: 'Eczar', serif;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@ -59,61 +66,61 @@ h5 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
small, .text_small {
|
||||
small, .text-small {
|
||||
font-size: 0.833rem;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit;
|
||||
/* ==========================================================================
|
||||
Tables
|
||||
========================================================================== */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid var(--gray-color);
|
||||
}
|
||||
|
||||
input {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
label {
|
||||
display: block;
|
||||
td,
|
||||
th {
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
font-weight: 700;
|
||||
font-size: 0.85em;
|
||||
padding: 2px 10px;
|
||||
border-bottom: 1px solid;
|
||||
border-color: var(--gray-color);
|
||||
}
|
||||
|
||||
select {
|
||||
text-align: left;
|
||||
border: var(--default-border);
|
||||
padding: 0.5em;
|
||||
outline: 0;
|
||||
th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table a {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Forms
|
||||
========================================================================== */
|
||||
input[type=text],
|
||||
input[type=email],
|
||||
input[type=number],
|
||||
input[type=date],
|
||||
input[type=password],
|
||||
select[multiple=multiple],
|
||||
select,
|
||||
textarea {
|
||||
display: block;
|
||||
text-align: left;
|
||||
color: var(--fg-color);
|
||||
border: var(--default-border);
|
||||
padding: 0.5rem;
|
||||
outline: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
@ -122,7 +129,7 @@ textarea:focus {
|
||||
}
|
||||
|
||||
select[multiple=multiple] {
|
||||
height: 125px;
|
||||
height: 8rem;
|
||||
}
|
||||
|
||||
|
||||
@ -142,7 +149,6 @@ input[type=checkbox] + label {
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 6.25rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
@ -156,36 +162,36 @@ textarea {
|
||||
}
|
||||
|
||||
|
||||
.action-button,
|
||||
button,
|
||||
input[type=submit],
|
||||
button {
|
||||
.action-button {
|
||||
font-family: inherit;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
color: var(--fg-color);
|
||||
font-size: 1.5rem;
|
||||
text-decoration: none;
|
||||
text-transform: lowercase;
|
||||
font-variant: small-caps;
|
||||
white-space: nowrap;
|
||||
|
||||
color: var(--fg-color);
|
||||
background-color: var(--yellow-color);
|
||||
|
||||
padding: 0.25rem 1rem;
|
||||
border-radius: 0.2rem;
|
||||
border: none;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button,
|
||||
input[type=submit]:hover,
|
||||
.action-button:hover {
|
||||
background-color: var(--yellow-alt-color);
|
||||
}
|
||||
|
||||
.action-button--large {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
form input,
|
||||
form select {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -196,249 +202,481 @@ figure {
|
||||
}
|
||||
img {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.product__item {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 0 2rem;
|
||||
}
|
||||
|
||||
.product__list-item button {
|
||||
grid-column: 1/3;
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.product__image {
|
||||
/*object-fit: cover;*/
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.product__form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
gap: 1rem;
|
||||
|
||||
/* ==========================================================================
|
||||
Base Layout
|
||||
========================================================================== */
|
||||
.site__header > nav,
|
||||
main > article,
|
||||
footer > section {
|
||||
max-width: 1024px;
|
||||
padding: 1rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.product__form input[type=submit] {
|
||||
grid-column: 2;
|
||||
justify-self: end;
|
||||
|
||||
/* ==========================================================================
|
||||
Modal
|
||||
========================================================================== */
|
||||
.modal-menu {
|
||||
display: none;
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1; /* Sit on top */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
overflow: auto; /* Enable scroll if needed */
|
||||
background-color: rgb(0,0,0); /* Fallback color */
|
||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
}
|
||||
|
||||
.site__logo {
|
||||
text-decoration: none;
|
||||
/* Modal Content/Box */
|
||||
.modal-menu__content {
|
||||
background-color: var(--bg-color);
|
||||
margin: 25vh auto;
|
||||
padding: 20px;
|
||||
border: var(--default-border);
|
||||
max-width: 40rem;
|
||||
}
|
||||
|
||||
.site__header div,
|
||||
.site__header nav {
|
||||
|
||||
.modal-menu__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
nav a {
|
||||
text-transform: lowercase;
|
||||
font-variant: small-caps;
|
||||
/* The Close Button */
|
||||
.close-modal {
|
||||
color: #aaa;
|
||||
font-size: 2rem;
|
||||
line-height: 0;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
nav {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.site__copyright {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.keep_calm {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
.keep_calm__img {
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
|
||||
.site__cart {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--yellow-color);
|
||||
padding: 0.25rem 1rem;
|
||||
text-decoration: none;
|
||||
border-radius: 0.2rem;
|
||||
.close-modal:hover,
|
||||
.close-modal:focus {
|
||||
color: var(--fg-color);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Site
|
||||
========================================================================== */
|
||||
.site__ft-stamp {
|
||||
max-width: 6rem;
|
||||
}
|
||||
|
||||
/* Site Header
|
||||
========================================================================== */
|
||||
.site__header {
|
||||
background-color: var(--bg-alt-color);
|
||||
}
|
||||
|
||||
.site__logo > img {
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
/* Site Nav
|
||||
========================================================================== */
|
||||
|
||||
.site__nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.site__logo,
|
||||
.nav__main,
|
||||
.nav__account {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.site__logo,
|
||||
.nav__main,
|
||||
.nav__account {
|
||||
margin-right: 0;
|
||||
}
|
||||
.site__nav {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 0.5fr 0.5fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
.site__logo {
|
||||
grid-column: 1;
|
||||
}
|
||||
.nav__main {
|
||||
grid-column: 1/4;
|
||||
grid-row: 2;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.nav__account {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
.site__cart {
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
.site__nav {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
.site__logo {
|
||||
grid-column: span 2;
|
||||
justify-self: center;
|
||||
}
|
||||
.nav__main {
|
||||
grid-column: 1/3;
|
||||
grid-row: 3;
|
||||
}
|
||||
.nav__account {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
}
|
||||
.site__cart {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.nav__list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.nav__menu {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav__link {
|
||||
text-transform: lowercase;
|
||||
font-variant: small-caps;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nav__list li:not(:last-child) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.nav__list.menu {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav__dropdown {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--bg-color);
|
||||
border: var(--default-border);
|
||||
padding: 0.5rem;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
.nav__dropdown {
|
||||
left: 0;
|
||||
right: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.nav__menu:hover .nav__dropdown {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Site Cart
|
||||
========================================================================== */
|
||||
.site__cart {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
justify-self: end;
|
||||
|
||||
font-family: inherit;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
text-transform: lowercase;
|
||||
font-variant: small-caps;
|
||||
|
||||
color: var(--fg-color);
|
||||
background-color: var(--yellow-color);
|
||||
padding: 0.25rem 1rem;
|
||||
border-radius: 0.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cart__count {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.cart__icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.cart__length {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
font-family: 'Inter', sans-serif;
|
||||
|
||||
/* ==========================================================================
|
||||
Articles
|
||||
========================================================================== */
|
||||
article > header {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.cart__item {
|
||||
/* Products
|
||||
========================================================================== */
|
||||
.product__list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 5fr;
|
||||
gap: 1rem;
|
||||
padding: 2rem 0;
|
||||
border-bottom: 0.05rem solid var(--gray-color);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4rem;
|
||||
}
|
||||
|
||||
.cart__total_price {
|
||||
.product__item {
|
||||
text-decoration: none;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.product__item h3 {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.product__list {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 6rem;
|
||||
}
|
||||
.product__figure {
|
||||
grid-row: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.product__item {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.product__figure {
|
||||
grid-row: 1;
|
||||
justify-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Product Detail
|
||||
========================================================================== */
|
||||
.product__detail {
|
||||
display: grid;
|
||||
grid-template-columns: 0.25fr 2fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.gallery__thumbnail {
|
||||
border: var(--default-border);
|
||||
border-color: var(--bg-color);
|
||||
cursor: pointer;
|
||||
object-fit: cover;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
.gallery__image {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.gallery__thumbnail--focus {
|
||||
border-color: var(--yellow-color);
|
||||
}
|
||||
|
||||
.product__form input,
|
||||
.product__form select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.product__detail {
|
||||
grid-template-columns: 0.25fr 2fr;
|
||||
}
|
||||
|
||||
.product__info {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.product__detail {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 4rem 1fr auto;
|
||||
}
|
||||
.product__gallery {
|
||||
display: flex;
|
||||
}
|
||||
.gallery__thumbnail {
|
||||
max-height: 4rem;
|
||||
}
|
||||
.gallery__thumbnail:not(:last-child) {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.product__image {
|
||||
max-height: 16rem;
|
||||
}
|
||||
.product__info {
|
||||
grid-column: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Shopping Cart
|
||||
========================================================================== */
|
||||
.cart__list {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.cart__item {
|
||||
padding: 1rem 0;
|
||||
border-bottom: var(--default-border);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.cart__table-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.cart__summary {
|
||||
text-align: right;
|
||||
}
|
||||
.cart__totals {
|
||||
width: unset;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.cart__total {
|
||||
|
||||
.cart__totals th,
|
||||
.cart__totals td {
|
||||
border: none;
|
||||
}
|
||||
.cart__totals th:first-child,
|
||||
.cart__totals td:first-child {
|
||||
text-align: right;
|
||||
|
||||
}
|
||||
|
||||
.cart__proceed {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.coupon__form {
|
||||
max-width: 16rem;
|
||||
align-self: flex-end;
|
||||
.cart__proceed > .action-button {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.item__figure img {
|
||||
vertical-align: middle;
|
||||
.item__image {
|
||||
max-height: 12rem;
|
||||
}
|
||||
|
||||
.item__price {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.item__form p,
|
||||
.coupon__form p {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.coupon__form p {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.item__form input[type=number] {
|
||||
max-width: 6rem;
|
||||
}
|
||||
|
||||
.coupon__form input[type=text] {
|
||||
max-width: 8rem;
|
||||
}
|
||||
|
||||
:is(.item__form, .coupon__form) label,
|
||||
:is(.item__form, .coupon__form) input:not(:last-child) {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.cart__item {
|
||||
grid-template-columns: 2fr 1fr;
|
||||
}
|
||||
|
||||
.cart__proceed {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item__info {
|
||||
grid-column: span 2;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.item__price {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.product__list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8rem;
|
||||
|
||||
/* Checkout / Shipping Address
|
||||
========================================================================== */
|
||||
.checkout__address-form input,
|
||||
.checkout__address-form select {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 24rem;
|
||||
}
|
||||
|
||||
.product__list-item {
|
||||
text-decoration: none;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
.checkout__address {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.product__list-item form {
|
||||
grid-column: 1/3;
|
||||
}
|
||||
|
||||
.product__list-item figure {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
#paypal-button-container {
|
||||
max-width: 24rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
|
||||
.order__shipping {
|
||||
}
|
||||
|
||||
|
||||
.shipping__details {
|
||||
margin-bottom: 3rem;
|
||||
max-width: 32rem;
|
||||
}
|
||||
|
||||
.shipping__details input[type=submit] {
|
||||
font-size: 1.25rem;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.order__total {
|
||||
margin: 3rem 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Footer
|
||||
========================================================================== */
|
||||
|
||||
footer {
|
||||
margin: 4rem 0 0;
|
||||
box-sizing: border-box;
|
||||
border-top: var(--default-border);
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.object__header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.object__list,
|
||||
.object__panel {
|
||||
background-color: white;
|
||||
border-radius: 0.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.object__item {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
border-bottom: 0.05rem solid var(--gray-color);
|
||||
text-decoration: none;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.panel__item:last-child,
|
||||
.object__item:last-child {
|
||||
border-bottom: unset;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
}
|
||||
|
||||
.object__item:hover {
|
||||
padding: 1rem 0;
|
||||
background-color: var(--bg-alt-color);
|
||||
}
|
||||
|
||||
.panel__header {
|
||||
font-weight: bold;
|
||||
background-color: var(--bg-alt-color);
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
}
|
||||
|
||||
.panel__item {
|
||||
padding: 1rem;
|
||||
border-bottom: 0.05rem solid var(--gray-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.product__detail {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
|
||||
.user__emails {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
._form_1 {
|
||||
margin: 0;
|
||||
}
|
||||
._form_1 div form {
|
||||
margin: 1rem 0 !important;
|
||||
padding: 0 !important;
|
||||
footer > section {
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
349
src/static/styles/normalize.css
vendored
Normal file
349
src/static/styles/normalize.css
vendored
Normal file
@ -0,0 +1,349 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@ -23,18 +23,15 @@ class Cart:
|
||||
cart = self.session[settings.CART_SESSION_ID] = {}
|
||||
self.cart = cart
|
||||
|
||||
def add(self, product, quantity=1, roast='', update_quantity=False):
|
||||
def add(self, product, quantity=1, grind='', update_quantity=False):
|
||||
product_id = str(product.id)
|
||||
if product_id not in self.cart:
|
||||
self.cart[product_id] = {
|
||||
'quantity': 0,
|
||||
'roast': roast,
|
||||
'grind': grind,
|
||||
'price': str(product.price)
|
||||
}
|
||||
elif product_id in self.cart:
|
||||
self.cart[product_id].update({
|
||||
'roast': roast,
|
||||
})
|
||||
|
||||
if update_quantity:
|
||||
self.cart[product_id]['quantity'] = quantity
|
||||
else:
|
||||
@ -71,7 +68,7 @@ class Cart:
|
||||
|
||||
def clear(self):
|
||||
del self.session[settings.CART_SESSION_ID]
|
||||
del self.coupon_code
|
||||
del self.session['coupon_code']
|
||||
self.session.modified = True
|
||||
|
||||
def build_order_params(self):
|
||||
@ -97,7 +94,7 @@ class Cart:
|
||||
bulk_list = [OrderLine(
|
||||
order=order,
|
||||
product=item['product'],
|
||||
customer_note=item['roast'],
|
||||
customer_note=item['grind'],
|
||||
unit_price=item['price'],
|
||||
quantity=item['quantity'],
|
||||
tax_rate=2,
|
||||
|
||||
@ -19,7 +19,7 @@ class AddToCartForm(forms.Form):
|
||||
AEROPRESS = 'AeroPress'
|
||||
PERCOLATOR = 'Percolator'
|
||||
OTHER = 'Other'
|
||||
ROAST_CHOICES = [
|
||||
GRIND_CHOICES = [
|
||||
(WHOLE, 'Whole Beans'),
|
||||
(ESPRESSO, 'Espresso'),
|
||||
(CONE_DRIP, 'Cone Drip'),
|
||||
@ -31,11 +31,34 @@ class AddToCartForm(forms.Form):
|
||||
(OTHER, 'Other (enter below)')
|
||||
]
|
||||
|
||||
ONE_TIME = 'One-time purchase'
|
||||
SUBSCRIBE = 'Subscribe'
|
||||
PURCHASE_TYPE_CHOICES = [
|
||||
(ONE_TIME, 'One-time purchase'),
|
||||
(SUBSCRIBE, 'Subscribe and save 10%'),
|
||||
grind = forms.ChoiceField(choices=GRIND_CHOICES)
|
||||
quantity = forms.IntegerField(min_value=1, max_value=20, initial=1)
|
||||
|
||||
class UpdateCartItemForm(forms.Form):
|
||||
quantity = forms.IntegerField(min_value=1, max_value=20, initial=1)
|
||||
update = forms.BooleanField(required=False, initial=True, widget=forms.HiddenInput)
|
||||
|
||||
|
||||
class AddToSubscriptionForm(forms.Form):
|
||||
WHOLE = 'Whole Beans'
|
||||
ESPRESSO = 'Espresso'
|
||||
CONE_DRIP = 'Cone Drip'
|
||||
BASKET_DRIP = 'Basket Drip'
|
||||
FRENCH_PRESS = 'French Press'
|
||||
STOVETOP_ESPRESSO = 'Stovetop Espresso (Moka Pot)'
|
||||
AEROPRESS = 'AeroPress'
|
||||
PERCOLATOR = 'Percolator'
|
||||
OTHER = 'Other'
|
||||
GRIND_CHOICES = [
|
||||
(WHOLE, 'Whole Beans'),
|
||||
(ESPRESSO, 'Espresso'),
|
||||
(CONE_DRIP, 'Cone Drip'),
|
||||
(BASKET_DRIP, 'Basket Drip'),
|
||||
(FRENCH_PRESS, 'French Press'),
|
||||
(STOVETOP_ESPRESSO, 'Stovetop Espresso (Moka Pot)'),
|
||||
(AEROPRESS, 'AeroPress'),
|
||||
(PERCOLATOR, 'Percolator'),
|
||||
(OTHER, 'Other (enter below)')
|
||||
]
|
||||
|
||||
SEVEN_DAYS = 7
|
||||
@ -48,10 +71,8 @@ class AddToCartForm(forms.Form):
|
||||
]
|
||||
|
||||
quantity = forms.IntegerField(min_value=1, initial=1)
|
||||
roast = forms.ChoiceField(choices=ROAST_CHOICES)
|
||||
purchase_type = forms.ChoiceField(choices=PURCHASE_TYPE_CHOICES, initial=ONE_TIME)
|
||||
grind = forms.ChoiceField(choices=GRIND_CHOICES)
|
||||
schedule = forms.ChoiceField(choices=SCHEDULE_CHOICES)
|
||||
update = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)
|
||||
|
||||
|
||||
class AddressForm(forms.Form):
|
||||
@ -83,7 +104,7 @@ class OrderCreateForm(forms.ModelForm):
|
||||
}
|
||||
|
||||
class CouponApplyForm(forms.Form):
|
||||
code = forms.CharField(label='Enter coupon')
|
||||
code = forms.CharField(label='Coupon code')
|
||||
|
||||
class ContactForm(forms.Form):
|
||||
GOOGLE = 'Google Search'
|
||||
|
||||
@ -3,10 +3,14 @@
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<h1>About PT Coffee</h1>
|
||||
</header>
|
||||
<section>
|
||||
<figure>
|
||||
<img src="{% static 'images/coffee_banner.jpeg' %}" alt="">
|
||||
</figure>
|
||||
</section>
|
||||
<section>
|
||||
<h2>We love coffee!</h2>
|
||||
<p><strong>If you’ve found Port Townsend Coffee Roasting Co., you probably love coffee so much that you seek out the best tasting, Certified Fair Trade Organic coffees available.</strong></p>
|
||||
@ -27,7 +31,7 @@
|
||||
<p>We roast our coffee much more slowly than most roasters, especially coffee roasters in the Pacific Northwest. The slower roasting process allows for greater bean development. The bean is evenly roasted right to the center and our process mutes some of the acidic compounds, which smooths out the flavor.</p>
|
||||
<p>Port Townsend Coffee is known for roasts that are darker than others available in the Northwest. Due to our roasting process, which emphasizes patience with the beans as well as air flow adjustments, the darker roasts are smooth and syrupy. Dark roasts from other companies can taste bitter, or slightly burnt due to the size and speed of the roast.</p>
|
||||
<p>We have been buying beans from the same brokers for many years. They understand the flavor profiles we prefer and seek to accommodate our needs.</p>
|
||||
<div class="column__layout">
|
||||
<div>
|
||||
<h5>Fair Trade and Organic</h5>
|
||||
<p>We pay a steep premium for these beans, which are typically from smaller farms that are organized into co-ops. These farms take pride in their coffees, as the farmers make a living wage and their families are able to live in a healthier, more secure environment than farmers who grow a conventional coffee crop. The quality of our coffee is consistent, in part due to the quality of organic and fair trade beans.</p>
|
||||
<h5>Freshness and Storage</h5>
|
||||
|
||||
19
src/storefront/templates/storefront/address_form.html
Normal file
19
src/storefront/templates/storefront/address_form.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<p><a href="{% url 'storefront:customer-detail' user.pk %}">← Back</a></p>
|
||||
<h1>Update Address</h1>
|
||||
</header>
|
||||
<section>
|
||||
<form method="post" action="{% url 'storefront:address-update' customer.pk address.pk %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<p>
|
||||
<input type="submit" value="Save changes">
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -2,51 +2,73 @@
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h2>Shopping Cart</h2>
|
||||
<section>
|
||||
<header>
|
||||
<h1>Shopping Cart</h1>
|
||||
</header>
|
||||
<section class="cart__list">
|
||||
{% for item in cart %}
|
||||
<div class="cart__item">
|
||||
{% with product=item.product %}
|
||||
<figure class="item__figure">
|
||||
<img class="product__image product__image--small" src="{{product.productphoto_set.first.image.url}}" alt="{{product.productphoto_set.first.image}}">
|
||||
<img class="item__image" src="{{product.productphoto_set.first.image.url}}" alt="{{product.productphoto_set.first.image}}">
|
||||
</figure>
|
||||
<div class="item__details">
|
||||
<p>{{product.name}}<br> ${{item.price}}</p>
|
||||
<div class="item__info">
|
||||
<h4>{{product.name}}</h4>
|
||||
<p><strong>Grind</strong>: {{item.grind}}</p>
|
||||
<form class="item__form" action="{% url 'storefront:cart-update' product.pk %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
{{ item.update_quantity_form }}
|
||||
<input type="submit" value="Update">
|
||||
</p>
|
||||
</form>
|
||||
<p>
|
||||
<a href="{% url 'storefront:cart-remove' product.pk %}">Remove from cart</a>
|
||||
</p>
|
||||
<form class="product__form" action="{% url 'storefront:cart-add' product.pk %}" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ item.update_quantity_form }}
|
||||
<input type="submit" value="Update">
|
||||
</form>
|
||||
</div>
|
||||
<div class="item__price">
|
||||
<p><strong>${{item.price}}</strong></p>
|
||||
</div>
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<div>
|
||||
<p>No items in cart yet.</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
<section>
|
||||
<div class="cart__total">
|
||||
<form action="{% url 'storefront:coupon-apply' %}" method="post" class="coupon__form">
|
||||
<section class="cart__summary">
|
||||
<h4>Cart Totals</h4>
|
||||
<div>
|
||||
<form class="coupon__form" action="{% url 'storefront:coupon-apply' %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{ coupon_apply_form.as_p }}
|
||||
<p>
|
||||
{{ coupon_apply_form }}
|
||||
<input type="submit" value="Apply" class="action-button">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<p class="cart__total_price">
|
||||
<span class="">Subtotal: ${{cart.get_total_price|floatformat:"2"}}</span><br>
|
||||
<div class="cart__table-wrapper">
|
||||
<table class="cart__totals">
|
||||
<tr>
|
||||
<td>Subtotal</td>
|
||||
<td>${{cart.get_total_price|floatformat:"2"}}</td>
|
||||
</tr>
|
||||
{% if cart.coupon %}
|
||||
<span class="">Coupon: {{cart.coupon.discount_value}} {{cart.coupon.get_discount_value_type_display}}</span><br>
|
||||
<tr>
|
||||
<td>Coupon</td>
|
||||
<td>{{cart.coupon.discount_value}} {{cart.coupon.get_discount_value_type_display}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<span class="cart__total_price">Cart total: <strong>${{cart.get_total_price_after_discount|floatformat:"2"}}</strong></span>
|
||||
</p>
|
||||
<p class="cart__total">
|
||||
<a href="{% url 'storefront:product-list' %}">Continue Shopping</a> or <a class="action-button action-button--large" href="{% url 'storefront:checkout-address' %}">Proceed to Checkout</a>
|
||||
<tr>
|
||||
<th>Total</th>
|
||||
<td><strong>${{cart.get_total_price_after_discount|floatformat:"2"}}</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<p class="cart__proceed">
|
||||
<a href="{% url 'storefront:product-list' %}">Continue Shopping</a>{% if cart|length > 0 %} or <a class="action-button" href="{% url 'storefront:checkout-address' %}">Proceed to Checkout</a>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
||||
@ -3,16 +3,18 @@
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h2>Checkout</h2>
|
||||
<section class="order__shipping">
|
||||
<div class="shipping__details">
|
||||
<header>
|
||||
<h1>Checkout</h1>
|
||||
</header>
|
||||
<section>
|
||||
<h3>Shipping Address</h3>
|
||||
<form action="" method="POST" class="address__form">
|
||||
<form class="checkout__address-form" action="" method="POST">
|
||||
{% csrf_token %}
|
||||
{{form.as_p}}
|
||||
<p>
|
||||
<input type="submit" value="Continue to Payment">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h2>Checkout</h2>
|
||||
<section class="order__details">
|
||||
<div class="order__shipping">
|
||||
<h3>Shipping Method</h3>
|
||||
<form action="post" class="address__form">
|
||||
{{form.as_p}}
|
||||
<input type="submit" value="Review order">
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -2,12 +2,16 @@
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1>Contact PT Coffee</h1>
|
||||
<header>
|
||||
<h1>Contact us</h1>
|
||||
</header>
|
||||
<section>
|
||||
<form action="{% url 'storefront:contact' %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{form.as_p}}
|
||||
<p>
|
||||
<input type="submit" value="Send message">
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
@ -3,20 +3,18 @@
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header class="object__header">
|
||||
<header>
|
||||
<h1>{{customer.get_full_name}}</h1>
|
||||
<a href="{% url 'storefront:customer-update' customer.pk %}" class="action-button">Edit profile</a>
|
||||
</header>
|
||||
<section class="object__panel">
|
||||
<div class="object__item panel__header">
|
||||
<section>
|
||||
<h4>Info</h4>
|
||||
</div>
|
||||
<div class="panel__item">
|
||||
<div>
|
||||
<strong>Email address</strong><br>
|
||||
{{customer.email}}<br>
|
||||
<a href="{% url 'account_email' %}">Manage</a>
|
||||
</div>
|
||||
<div class="panel__item">
|
||||
<div>
|
||||
<strong>Default shipping address</strong><br>
|
||||
{% with shipping_address=customer.default_shipping_address %}
|
||||
<address>
|
||||
@ -28,11 +26,10 @@
|
||||
{% endif %}
|
||||
{{shipping_address.city}}, {{shipping_address.state}}, {{shipping_address.postal_code}}
|
||||
</address>
|
||||
<a href="address-update">Edit</a>
|
||||
{% endwith %}
|
||||
</div>
|
||||
<div class="panel__item">
|
||||
<strong>Other addresses</strong><br>
|
||||
<div>
|
||||
<p><strong>All addresses</strong></p>
|
||||
{% for address in customer.addresses.all %}
|
||||
<p>
|
||||
<address>
|
||||
@ -44,7 +41,7 @@
|
||||
{% endif %}
|
||||
{{address.city}}, {{address.state}}, {{address.postal_code}}
|
||||
</address>
|
||||
<a href="address-update">Edit</a>
|
||||
<a href="{% url 'storefront:address-update' customer.pk address.pk %}">Edit</a>
|
||||
</p>
|
||||
{% empty %}
|
||||
<p>No other addresses.</p>
|
||||
@ -52,21 +49,29 @@
|
||||
</div>
|
||||
</section>
|
||||
{% with order_list=customer.orders.all %}
|
||||
<section class="object__list">
|
||||
<div class="object__item panel__header" href="order-detail">
|
||||
<span>Order #</span>
|
||||
<span>Date</span>
|
||||
<span>Total</span>
|
||||
</div>
|
||||
<h3>Your orders</h3>
|
||||
<section>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order #</th>
|
||||
<th>Date</th>
|
||||
<th colspan="2">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order in order_list %}
|
||||
<a class="object__item" href="">
|
||||
<span>#{{order.pk}}</span>
|
||||
<span>{{order.created_at|date:"D, M j Y"}}</span>
|
||||
<span>${{order.total_net_amount}}</span>
|
||||
</a>
|
||||
<tr>
|
||||
<td>#{{order.pk}}</td>
|
||||
<td>{{order.created_at|date:"M j, Y"}}</td>
|
||||
<td>${{order.total_net_amount}}</td>
|
||||
<td><a href="{% url 'storefront:order-detail' customer.pk order.pk %}">See details →</a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<span class="object__item">No orders</span>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% endwith %}
|
||||
</article>
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<article class="product">
|
||||
<article>
|
||||
<header>
|
||||
<p><a href="{% url 'storefront:customer-detail' customer.pk %}">← Back</a></p>
|
||||
<h1>Update your profile</h1>
|
||||
</header>
|
||||
<section>
|
||||
<form method="POST" action="{% url 'storefront:customer-update' customer.pk %}">
|
||||
{% csrf_token %}
|
||||
{{form.as_p}}
|
||||
<p class="form__submit">
|
||||
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'storefront:customer-detail' customer.pk %}">cancel</a>
|
||||
<p>
|
||||
<input type="submit" value="Save changes">
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
63
src/storefront/templates/storefront/order_detail.html
Normal file
63
src/storefront/templates/storefront/order_detail.html
Normal file
@ -0,0 +1,63 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<p><a href="{% url 'storefront:customer-detail' customer.pk %}">← Back</a></p>
|
||||
<header>
|
||||
<h1>Order #{{order.pk}}</h1>
|
||||
<h3>Placed on {{order.created_at|date:"M j, Y"}}</h3>
|
||||
</header>
|
||||
<section>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Product</th>
|
||||
<th>Quantity</th>
|
||||
<th>Price</th>
|
||||
<th>Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in order.lines.all %}
|
||||
<tr>
|
||||
{% with product=item.product %}
|
||||
<td>
|
||||
<img src="{{product.productphoto_set.first.image.url}}" alt="{{product.productphoto_set.first.image}}">
|
||||
</td>
|
||||
<td><strong>{{product.name}}</strong></td>
|
||||
<td>{{item.quantity}}</td>
|
||||
<td>${{product.price}}</td>
|
||||
<td>${{item.get_total}}</td>
|
||||
{% endwith %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5">No items in order</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h4>Payment</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Subtotal</td>
|
||||
<td>${{order.total_net_amount}}</td>
|
||||
</tr>
|
||||
{% if order.coupon %}
|
||||
<tr>
|
||||
<td>Discount</td>
|
||||
<td>{{order.coupon.discount_value}} {{order.coupon.get_discount_value_type_display}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>Total</th>
|
||||
<td><strong>${{order.get_total_price_after_discount}}</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock content %}
|
||||
@ -2,16 +2,17 @@
|
||||
{% load static %}
|
||||
|
||||
{% block head %}
|
||||
<script defer src="https://www.paypal.com/sdk/js?client-id=AVELl5svEDTPo3FRNViyjWnqODfFsAHbKKdm5BlwpKCwZCVxTraFF9Ax3N1xlkoEWSpOj7AI_T9xYMRu¤cy=USD"></script>
|
||||
<script defer src="https://www.paypal.com/sdk/js?client-id={{PAYPAL_CLIENT_ID}}¤cy=USD"></script>
|
||||
<script type="module" defer src="{% static 'scripts/payment.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h2>Checkout</h2>
|
||||
<section class="order__details">
|
||||
<div class="order__shipping">
|
||||
<h3>Shipping Address</h3>
|
||||
<header>
|
||||
<h1>Checkout</h1>
|
||||
</header>
|
||||
<section class="checkout__address">
|
||||
<h3>Shipping address</h3>
|
||||
<address>
|
||||
{{shipping_address.first_name}}
|
||||
{{shipping_address.last_name}}<br>
|
||||
@ -21,40 +22,53 @@
|
||||
{% endif %}
|
||||
{{shipping_address.city}}, {{shipping_address.state}}, {{shipping_address.postal_code}}
|
||||
</address>
|
||||
<a class="action-button" href="{% url 'storefront:checkout-address' %}">edit</a>
|
||||
</div>
|
||||
<div class="order__list">
|
||||
<h3>Cart Summary</h3>
|
||||
<a class="action-button" href="{% url 'storefront:checkout-address' %}">Change</a>
|
||||
</section>
|
||||
<section class="cart__list">
|
||||
<h3>Review items</h3>
|
||||
{% for item in cart %}
|
||||
<div class="cart__item">
|
||||
{% with product=item.product %}
|
||||
<figure class="item__figure">
|
||||
{{item.quantity}} x
|
||||
<img class="product__image product__image--small" src="{{product.productphoto_set.first.image.url}}" alt="{{product.productphoto_set.first.image}}">
|
||||
<figure>
|
||||
<img src="{{product.productphoto_set.first.image.url}}" alt="{{product.productphoto_set.first.image}}">
|
||||
</figure>
|
||||
<div class="item__details">
|
||||
<p>{{product.name}}<br> ${{item.price}}</p>
|
||||
<p><strong>Grind options</strong>: {{item.customer_note}}</p>
|
||||
<div>
|
||||
<h4>{{product.name}}</h4>
|
||||
<p><strong>Grind options</strong>: {{item.grind}}</p>
|
||||
<p><strong>Quantity</strong>: {{item.quantity}}</p>
|
||||
</div>
|
||||
<div class="item__price">
|
||||
<p><strong>${{item.price}}</strong></p>
|
||||
</div>
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="order__total">
|
||||
<form action="" method="POST" class="order__form">
|
||||
</section>
|
||||
<section class="cart__summary">
|
||||
<h3>Order summary</h3>
|
||||
<form action="" method="POST">
|
||||
{% csrf_token %}
|
||||
{{form.as_p}}
|
||||
{# <input type="submit" value="Place order"> #}
|
||||
</form>
|
||||
<p class="cart__total_price">
|
||||
<span class="">Subtotal: ${{cart.get_total_price|floatformat:"2"}}</span><br>
|
||||
<div class="cart__table-wrapper">
|
||||
<table class="cart__totals">
|
||||
<tr>
|
||||
<td>Subtotal</td>
|
||||
<td>${{cart.get_total_price|floatformat:"2"}}</td>
|
||||
</tr>
|
||||
{% if cart.coupon %}
|
||||
<span class="">Coupon: {{cart.coupon.discount_value}} {{cart.coupon.get_discount_value_type_display}}</span><br>
|
||||
<tr>
|
||||
<td>Coupon</td>
|
||||
<td>{{cart.coupon.discount_value}} {{cart.coupon.get_discount_value_type_display}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<span class="cart__total_price">Cart total: <strong>${{cart.get_total_price_after_discount|floatformat:"2"}}</strong></span>
|
||||
</p>
|
||||
<div id="paypal-button-container"></div>
|
||||
<tr>
|
||||
<th>Total</th>
|
||||
<td><strong>${{cart.get_total_price_after_discount|floatformat:"2"}}</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="paypal-button-container"></div>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
||||
@ -2,24 +2,30 @@
|
||||
{% load static %}
|
||||
|
||||
{% block head %}
|
||||
<script defer src="{% static 'scripts/product_form.js' %}"></script>
|
||||
<script defer src="{% static 'scripts/product_gallery.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article class="product__item">
|
||||
<figure class="product__figure">
|
||||
<img class="product__image" src="{{product.productphoto_set.first.image.url}}" alt="{{product.productphoto_set.first.image}}">
|
||||
<article class="product__detail">
|
||||
<section class="product__gallery">
|
||||
{% for photo in product.productphoto_set.all %}
|
||||
<img class="gallery__thumbnail {% if forloop.first %}gallery__thumbnail--focus{% endif %}" src="{{ photo.image.url }}" alt="{{ photo }}">
|
||||
{% endfor %}
|
||||
</section>
|
||||
<figure class="gallery__image">
|
||||
<img class="product__image" src="{{ product.productphoto_set.first.image.url }}" alt="{{ product.name }}">
|
||||
</figure>
|
||||
<section>
|
||||
<section class="product__info">
|
||||
<h1>{{product.name}}</h1>
|
||||
<p>{{product.description}}</p>
|
||||
<p class="site__ft-stamp"><img class="fair_trade--small" src="{% static 'images/fair_trade_stamp.png' %}" alt=""></p>
|
||||
<p>$<strong>{{product.price}}</strong></p>
|
||||
<p>{{product.weight.oz}} oz</p>
|
||||
<form method="post" action="{% url 'storefront:cart-add' product.pk %}">
|
||||
<form class="product__form" method="post" action="{% url 'storefront:cart-add' product.pk %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<p>
|
||||
<input type="submit" value="Add to cart" class="action-button">
|
||||
<input type="submit" value="Add to cart">
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@ -2,18 +2,20 @@
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<h1>Coffee</h1>
|
||||
</header>
|
||||
<section class="product__list">
|
||||
{% for product in product_list %}
|
||||
<a href="{% url 'storefront:product-detail' product.pk %}" class="product__list-item">
|
||||
<a class="product__item" href="{% url 'storefront:product-detail' product.pk %}">
|
||||
<figure class="product__figure">
|
||||
<img class="product__image" src="{{product.productphoto_set.first.image.url}}" alt="{{product.productphoto_set.first.image}}">
|
||||
</figure>
|
||||
<div>
|
||||
<h3>{{ product.name }}</h3><br>
|
||||
<h3>$<strong>{{product.price}}</strong></h3>
|
||||
<p>{{product.description}}</p>
|
||||
<h3>{{ product.name }}</h3>
|
||||
<p>{{product.description|truncatewords:20}}</p>
|
||||
<p>$<strong>{{product.price}}</strong> | {{product.weight.oz}} oz</p>
|
||||
</div>
|
||||
<button class="action-button">View product</button>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
@ -12,6 +12,7 @@ urlpatterns = [
|
||||
|
||||
path('cart/', views.CartView.as_view(), name='cart-detail'),
|
||||
path('cart/<int:pk>/add/', views.CartAddProductView.as_view(), name='cart-add'),
|
||||
path('cart/<int:pk>/update/', views.CartUpdateProductView.as_view(), name='cart-update'),
|
||||
path('cart/<int:pk>/remove/', views.cart_remove_product_view, name='cart-remove'),
|
||||
|
||||
path('coupon/apply/', views.CouponApplyView.as_view(), name='coupon-apply'),
|
||||
@ -28,5 +29,9 @@ urlpatterns = [
|
||||
path('', views.CustomerDetailView.as_view(), name='customer-detail'),
|
||||
path('update/', views.CustomerUpdateView.as_view(), name='customer-update'),
|
||||
# path('delete/', views.CustomerDeleteView.as_view(), name='customer-delete'),
|
||||
|
||||
path('orders/<int:order_pk>/', views.OrderDetailView.as_view(), name='order-detail'),
|
||||
path('addresses/<int:address_pk>/update/', views.AddressUpdateView.as_view(), name='address-update'),
|
||||
])),
|
||||
|
||||
]
|
||||
|
||||
@ -23,10 +23,11 @@ from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
|
||||
|
||||
from accounts.models import User, Address
|
||||
from accounts.utils import get_or_create_customer
|
||||
from accounts.forms import AddressForm as AccountAddressForm, CustomerUpdateForm
|
||||
from core.models import Product, Order, Transaction, OrderLine, Coupon
|
||||
from core.forms import ShippingMethodForm
|
||||
|
||||
from .forms import AddToCartForm, OrderCreateForm, AddressForm, CouponApplyForm, ContactForm
|
||||
from .forms import AddToCartForm, UpdateCartItemForm, OrderCreateForm, AddressForm, CouponApplyForm, ContactForm
|
||||
from .cart import Cart
|
||||
from .payments import CaptureOrder
|
||||
|
||||
@ -39,11 +40,9 @@ class CartView(TemplateView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
cart = Cart(self.request)
|
||||
for item in cart:
|
||||
item['update_quantity_form'] = AddToCartForm(
|
||||
item['update_quantity_form'] = UpdateCartItemForm(
|
||||
initial={
|
||||
'quantity': item['quantity'],
|
||||
'roast': item['roast'],
|
||||
'update': True
|
||||
}
|
||||
)
|
||||
context['cart'] = cart
|
||||
@ -63,7 +62,30 @@ class CartAddProductView(SingleObjectMixin, FormView):
|
||||
if form.is_valid():
|
||||
cart.add(
|
||||
product=self.get_object(),
|
||||
roast=form.cleaned_data['roast'],
|
||||
grind=form.cleaned_data['grind'],
|
||||
quantity=form.cleaned_data['quantity']
|
||||
)
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class CartUpdateProductView(SingleObjectMixin, FormView):
|
||||
model = Product
|
||||
form_class = UpdateCartItemForm
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('storefront:cart-detail')
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
cart = Cart(request)
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
cart.add(
|
||||
product=self.get_object(),
|
||||
quantity=form.cleaned_data['quantity'],
|
||||
update_quantity=form.cleaned_data['update']
|
||||
)
|
||||
@ -181,6 +203,7 @@ class OrderCreateView(CreateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['shipping_address'] = self.request.session.get('shipping_address')
|
||||
context['PAYPAL_CLIENT_ID'] = settings.PAYPAL_CLIENT_ID
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -227,25 +250,46 @@ class PaymentCanceledView(TemplateView):
|
||||
template_name = 'storefront/payment_canceled.html'
|
||||
|
||||
|
||||
class CustomerDetailView(DetailView):
|
||||
class CustomerDetailView(LoginRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'storefront/customer_detail.html'
|
||||
context_object_name = 'customer'
|
||||
|
||||
class CustomerUpdateView(UpdateView):
|
||||
class CustomerUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = User
|
||||
template_name = 'storefront/customer_form.html'
|
||||
context_object_name = 'customer'
|
||||
fields = (
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'default_shipping_address'
|
||||
)
|
||||
form_class = CustomerUpdateForm
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('storefront:customer-detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
class OrderDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Order
|
||||
template_name = 'storefront/order_detail.html'
|
||||
pk_url_kwarg = 'order_pk'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['customer'] = User.objects.get(pk=self.kwargs['pk'])
|
||||
return context
|
||||
|
||||
class AddressUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = Address
|
||||
pk_url_kwarg = 'address_pk'
|
||||
template_name = 'storefront/address_form.html'
|
||||
form_class = AccountAddressForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['customer'] = User.objects.get(pk=self.kwargs['pk'])
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('storefront:customer-detail',kwargs={'pk': self.kwargs['pk']})
|
||||
|
||||
|
||||
|
||||
class AboutView(TemplateView):
|
||||
template_name = 'storefront/about.html'
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<p><a href="{% url 'storefront:customer-detail' user.pk %}">← Back</a></p>
|
||||
<section>
|
||||
<h1>{% trans "E-mail Addresses" %}</h1>
|
||||
</section>
|
||||
|
||||
@ -14,73 +14,77 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Eczar:wght@400;700&family=Inter:wght@100;400;700&display=swap" rel="stylesheet">
|
||||
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static "styles/normalize.css" %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static "styles/main.css" %}">
|
||||
{% endcompress %}
|
||||
|
||||
<script type="module" defer src="{% static 'scripts/initializers/timezone.js' %}"></script>
|
||||
<script type="module" defer src="{% static 'scripts/index.js' %}"></script>
|
||||
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<header class="site__header">
|
||||
<div>
|
||||
<h1><a class="site__logo" href="{% url 'storefront:product-list' %}">Port Townsend Coffee</a></h1>
|
||||
{% if user.is_authenticated %}
|
||||
<p>
|
||||
Hi <a href="{% url 'storefront:customer-detail' user.pk %}">{{user.first_name}} {{user.last_name}}</a>!
|
||||
</p>
|
||||
{% if user.is_staff %}
|
||||
<p><a href="{% url 'dashboard:home' %}">Dashboard</a></p>
|
||||
{% endif %}
|
||||
<p><a href="{% url 'account_logout' %}">Log Out</a></p>
|
||||
{% else %}
|
||||
<p>
|
||||
<a href="{% url 'account_login' %}">Log In</a> | <a href="{% url 'account_signup' %}">Sign Up</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<div class="modal-menu">
|
||||
<div class="modal-menu__content">
|
||||
<div class="modal-menu__header">
|
||||
<h4>Sign up for our newsletter</h4>
|
||||
<span class="close-modal">×</span>
|
||||
</div>
|
||||
<nav>
|
||||
<a href="{% url 'storefront:product-list' %}">Shop</a>
|
||||
{# <a href="">Wholesale</a> #}
|
||||
<a href="">Subscribe</a>
|
||||
<a href="">Cafe</a>
|
||||
<a href="">Fair Trade</a>
|
||||
<a href="{% url 'storefront:about' %}">About</a>
|
||||
<a href="{% url 'storefront:contact' %}">Contact</a>
|
||||
<div class="modal-menu__form">
|
||||
<div class="_form_1"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header class="site__header">
|
||||
<nav class="site__nav">
|
||||
<a class="site__logo" href="{% url 'storefront:product-list' %}"><img src="{% static 'images/site_logo.svg' %}" alt="Port Townsend Roasting Co."></a>
|
||||
<ul class="nav__list nav__main">
|
||||
<li><a class="nav__link" href="{% url 'storefront:product-list' %}">Shop</a></li>
|
||||
<li><a class="nav__link" href="">Cafe</a></li>
|
||||
<li><a class="nav__link" href="">Fair trade</a></li>
|
||||
<li><a class="nav__link" href="{% url 'storefront:about' %}">About</a></li>
|
||||
<li><a class="nav__link" href="{% url 'storefront:contact' %}">Contact</a></li>
|
||||
</ul>
|
||||
{% if user.is_authenticated %}
|
||||
<div class="nav__menu nav__account">
|
||||
<a class="nav__link" href="{% url 'storefront:customer-detail' user.pk %}">Account ▼</a>
|
||||
<ul class="nav__dropdown">
|
||||
{% if user.is_staff %}
|
||||
<li><a class="nav__link" href="{% url 'dashboard:home' %}">Dashboard</a></li>
|
||||
{% endif %}
|
||||
<li><a class="nav__link" href="{% url 'storefront:customer-detail' user.pk %}">Orders</a></li>
|
||||
<li><a class="nav__link" href="{% url 'account_logout' %}">Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<ul class="nav__list nav__account">
|
||||
<li><a class="nav__link" href="{% url 'account_login' %}">Login</a></li>
|
||||
<li>/</li>
|
||||
<li><a class="nav__link" href="{% url 'account_signup' %}">Signup</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<a class="site__cart" href="{% url 'storefront:cart-detail' %}">
|
||||
<span class="cart__length">{{cart|length}}</span>
|
||||
<span class="cart__count">{{cart|length}}</span>
|
||||
<img class="cart__icon" src="{% static 'images/shopping_cart.svg' %}" alt="Shopping cart">
|
||||
</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<aside>
|
||||
{% block aside %}
|
||||
{% endblock %}
|
||||
</aside>
|
||||
<main>
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
</main>
|
||||
<footer>
|
||||
<div class="keep_calm">
|
||||
<figure>
|
||||
<img class="keep_calm__img" src="{% static 'images/keep_calm.jpg' %}" alt="Keep calm and drink coffee">
|
||||
</figure>
|
||||
<div>
|
||||
<h4>Problem with your order?<br>Have a question?</h4>
|
||||
<p>Please contact us, we’re happy to help you over the phone at <a href="tel:+13603855856">(360) 385-5856</a> between 8:00 am and 10:00 pm Pacific Time.</p>
|
||||
<div class="_form_1"></div><script src="https://bltc999.activehosted.com/f/embed.php?id=1" type="text/javascript" charset="utf-8"></script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="site__copyright">
|
||||
<section>
|
||||
<p>
|
||||
<small>Copyright © 2016-{% now "Y" %} Better Living Food Company Inc.</small><br>
|
||||
<small>Fair Trade | Organic | Port Townsend, WA 98368</small><br><br>
|
||||
<img src="{% static 'images/fair_trade_stamp.png' %}" alt="">
|
||||
<img class="site__ft-stamp" src="{% static 'images/fair_trade_stamp.png' %}" alt="">
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</footer>
|
||||
|
||||
<script src="https://ptcoffee.activehosted.com/f/embed.php?id=1" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user