Skip to content

Instantly share code, notes, and snippets.

@panoply
Created August 26, 2020 22:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save panoply/4055c6870e6baacc6b60c9b4f9d51d30 to your computer and use it in GitHub Desktop.
Save panoply/4055c6870e6baacc6b60c9b4f9d51d30 to your computer and use it in GitHub Desktop.
Mithril Shopify Cart
/* Modules */
import m from 'mithril'
import stream from 'mithril/stream'
export default class CartAPI {
stream = stream('')
constructor () {
this.get()
}
/**
* Gets the cart data.
*
* @param {string} api
*/
async get () {
const response = await m.request({
method: 'GET',
url: '/cart.json'
})
if (!response) return response
this.stream(response)
this.count()
return response
}
async update (data) {
const response = await m.request({
method: 'POST',
url: '/cart/update.js',
data
})
this.stream(response)
this.count()
return response
}
/**
* @param {object} data
*/
add (data) {
return m.request({
method: 'POST',
url: '/cart/add.js',
body: data
}).then(response => {
this.get()
this.count()
}).catch(console.error)
}
change (data) {
console.log(data)
return m.request({
method: 'POST',
url: '/cart/change.js',
body: data
}).then(response => {
this.stream(response)
this.count()
}).catch(console.error)
}
async clear (data) {
const response = await m.request({
method: 'POST',
url: '/cart/clear.js',
data
})
this.stream(response)
this.count()
return response
}
async shipping (data) {
const response = await m.request({
method: 'POST',
url: '/cart/shipping_rates.json',
body: data
})
return response
}
async discount (data) {
const response = await m.request({
method: 'POST',
url: '/cart/shipping_rates.json',
body: data
})
return response
}
async bogo (data) {
const response = await m.request({
method: 'POST',
url: '/cart/shipping_rates.json',
data
})
return response
}
/**
* Cart items count
* Updates all span items that contain the cart
* item count
*
*/
count () {
const { item_count } = this.stream()
const elements = document.querySelectorAll('span[data-cart="count"]')
for (const element of elements) element.innerText = item_count
}
}
import m from 'mithril'
export default {
view: () => m('.cart__empty.d-flex', [
m('.align-self-center.text-center.m-auto', [
m('svg.icon', [
m('use[xlink:href="#thumbs-down"]')
]),
m('.title.pt-2', 'Your Cart is Empty')
])
])
}
/* Modules */
import m from 'mithril'
import { money } from '../../application/Utilities'
/* Partials */
import CartAPI from './api'
import Empty from './empty'
import LineItem from './line-item'
export const AjaxCart = (() => {
const api = new CartAPI()
// console.log(api.stream())
return {
api,
render: (element) => {
const { flag_path } = Object
.values(App.locale.countries)
.find(({ code }) => code === 'SE')
m.mount(element, {
oninit: ({ state }) => {
state.paymentText = 'Shipping & Payment'
state.paymentIcon = 'payment'
state.paymentFallback = false
},
view: ({ state }) => api.stream().item_count === 0 ? m(Empty) : [
m('.row.jc-center.ac-center', [
m('.col-12', [
// LINE ITEM
m(LineItem, { api }),
// SUB TOTAL
m('.row.jc-between.ai-center.py-2.cart__total', [
m('.col-auto.cart__label', 'SUBTOTAL'),
m('.col-auto.cart__price', money(api.stream().total_price))
]),
// SHIPPING
m('.row.jc-between.ai-center.py-2.cart__shipping', [
m('.col-auto', [
m('span.pr-2.cart__label', 'Shipping to'),
m(`img.flag[src="${flag_path}"]`)
]),
m('.col-auto', [
m('.span.cart__price', api.stream().shipping_rate !== 0
? 'FREE'
: 'FREE')
])
]),
// CART TOTAL
m('.row.jc-between.ai-center.py-2.cart__total', [
m('.col-auto.cart__label', 'TOTAL'),
m('.col-auto.cart__price', money(api.stream().total_price))
]),
/* m(Charity, {
currency: currency,
total_price: api.cart().total_price
}), */
/* m(Checkout, {
currency: locale().currency
}) */
m('button.mt-3.cart__checkout[type="submit"][name="checkout"]', {
onclick: ({
currentTarget: {
lastElementChild
}
}) => {
state.paymentIcon = 'loading'
state.paymentText = 'Securing Checkout'
setTimeout(() => {
state.paymentText = 'Redirecting'
state.paymentIcon = 'tick'
m.redraw()
}, 2000)
setTimeout(() => {
state.paymentFallback = true
m.redraw()
}, 6000)
return location.replace('https://www.brixtoltextiles.com/checkout')
}
}, [
m('svg.icon', m(`use[xlink:href="#${state.paymentIcon}"]`)),
m('span', `${state.paymentText}`)
]),
state.paymentFallback ? m('.mt-3', [
m('a.h6.d-block.text-center[href="/cart"]', 'Goto Checkout')
]) : null
])
])
]
})
}
}
})()
import m from 'mithril'
import Quantity from './quantity'
import Remove from './remove'
import { imgSrc, money } from '../../application/Utilities'
export default {
oninit () {
this.line = 1
},
view: ({
state: {
line
},
attrs: {
api
}
}) => api.stream().items ? api.stream().items.map((item, index) => [
m('.row.jc-between.ai-center.cart__line-item', [
m('button.remove[type="button"]', {
onclick: () => api.change({ line: line, quantity: 0 })
}, m('svg.icon-close[viewBox="0 0 24 30"][xmlns="http://www.w3.org/2000/svg"]', [
m('path[d=M20.827 4.357l-.707-.707-8.131 8.132L3.857 3.65l-.707.707 8.132 8.132-8.132 8.132.707.707 8.132-8.132 8.131 8.132.707-.707-8.131-8.132z]')
])),
m('.col-auto.px-0', [
m(`img.img-fluid[src="${imgSrc(item.image, 420)}"]`, {
onclick: event => {
event.preventDefault()
return Turbolinks.visit(item.url)
}
})
]),
m('.col', [
m('ul.d-block.mb-2', { onclick: () => Turbolinks.visit(item.url) }, [
m('li.title', item.product_title),
m('li.variant', item.variant_title)
]),
m('.row.jc-between', [
m('.col-auto', [
m(Quantity, {
api,
data: {
id: item.id,
index: index + line,
quantity: item.quantity,
min_quantity: item.min_quantity ? item.min_quantity : false
}
})
]),
m('.col-auto.cart__price', [
item.discounts.length >= 1 ? [
item.quantity === 1 && item.discounts.length === 1 ? [
m('s', item.discounted_price),
m('.d-block', 'FREE')
] : item.quantity > item.discounts.length ? [
m('span', (
item.original_price * item.quantity
) - (
item.discounted_price * item.discounts.length
)),
m('.d-block', [
m('small', [
m('span', 'Save: ')
]),
m('span', item.discounted_price * item.discounts.length)
])
] : [
m('s', item.original_line_price),
m('.d-block', 'FREE')
]
] : money(item.line_price)
])
])
])
])
]) : null
}
.cart {
font-family: var(--heading-font-family);
&__header {
height: 49px;
color: $black;
font-family: var(--heading-font-family);
//border-bottom: 0.01em solid $black-600;
.icon {
width: 20px;
height: 20px;
}
.item-count {
.counter {
position: absolute;
top: 7px;
right: 0;
width: 16px;
height: 16px;
border-radius: 100%;
background: red;
color: $white;
text-align: center;
font-size: 0.825rem;
font-weight: 500;
line-height: 16px;
z-index: 2;
font-family: Arial;
}
.cart {
position: absolute;
right: 8px;
top: 10px;
z-index: 1;
.icon-cart {
width: 25px;
fill: $white;
}
}
}
}
&__checkout,
&__shipping,
&__price,
&__label {
text-transform: uppercase;
font-size: 0.945rem;
}
&__total {
border-bottom: 1px solid $gray-200;
}
&__shipping {
border-bottom: 1px solid $gray-200;
.flag {
width: 16px;
}
}
&__line-item {
border-bottom: 1px solid $gray-200;
img {
display: block;
margin: 0 auto;
width: 100%;
max-width: 90px;
border-right: 1px solid $gray-300;
}
.details {
height: 100%;
}
.title {
text-transform: uppercase;
font-size: 0.945rem;
color: $black-300;
font-family: inherit;
&:hover,
&:focus {
text-decoration: underline;
cursor: pointer;
}
}
.variant {
text-transform: uppercase;
font-size: 0.945rem;
color: $gray-500;
font-family: inherit;
}
.remove {
position: absolute;
top: 5px;
right: 10px;
.icon-close {
width: 15px;
fill: $gray-500;
&:hover,
&:focus {
fill: $black;
}
}
}
&--property {
font-size: 0.75rem;
text-transform: uppercase;
color: #999;
.icon {
width: 8px;
height: 8px;
svg {
fill: #333;
}
}
}
}
&__checkout {
background-color: transparent; //lighten($black-600, 5%); //$green;
border-radius: 4px;
border: 1px solid lighten($black-600, 5%);
padding: 0 24px;
max-width: 100%;
width: 100%;
text-align: center;
color: $black-600;
height: 45px;
text-transform: uppercase;
cursor: pointer;
.additional-checkout-button {
border-radius: 4px;
width: 100%;
cursor: pointer !important;
}
@include media-breakpoint-up(md) {
&:hover {
background-color: lighten($green, 7%);
}
}
.icon {
position: absolute;
top: 10px;
right: 17px;
width: 25px;
height: 25px;
fill: $black-600;
}
&--or {
color: #222;
font-weight: 600;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment