feat: paypal client interface

This commit is contained in:
dragonwocky 2023-09-11 19:53:29 +10:00
parent 83e4bd88a8
commit b9bd9cb491
Signed by: dragonwocky
GPG Key ID: 7998D08F7D7BD7A8
6 changed files with 217 additions and 51 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,12 +4,13 @@ import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';
import shave from 'shave';
import GhostContentAPI from '@tryghost/content-api';
/* pyv */
import fuzzysort from 'fuzzysort';
/* /pyv */
import Swiper, { FreeMode, A11y } from 'swiper';
import 'swiper/css';
import { isRTL, formatDate, isMobile } from './helpers';
/* pyv */
import fuzzysort from 'fuzzysort';
import './paypal';
/* /pyv */
$(() => {
if (isRTL()) {

86
src/js/paypal.js Normal file
View File

@ -0,0 +1,86 @@
/* pyv */
{
/* <div class="paypal-cart" data-id="test-cart-id"><div class="buttons"><div class="paypal-item" data-id="youth" data-cost="10"><div class="paypal-item-name">Youth</div></div><div class="paypal-item" data-id="leader" data-cost="10"><div class="paypal-item-name">Leader</div></div></div><div class="paypal-confirmation"><span>Payment successful.</span></div><noscript><p class="javascript-required">You must enable JavaScript to checkout via PayPal.</p></noscript></div> */
}
const ITEM_REMOVE = `<div class="paypal-item-remove m-button primary focusable" title="-1" role="button" tabindex="0"><i class="icon-minus"></i></div>`,
ITEM_ADD = `<div class="paypal-item-add m-button primary focusable" title="+1" role="button" tabindex="0"><i class="icon-plus"></i></div>`,
CHECKOUT = `<div class="paypal-order">
<div class="paypal-order-total"></div>
<div class="paypal-order-checkout m-button primary focusable" role="button" tabindex="0"><span>Checkout with</span> <span title="PayPal" class="paypal-checkout-logo"></span></div>
</div>`.replace(/\s+/g, ' ');
const createElement = (html) => {
const tmpl = document.createElement('template');
tmpl.innerHTML = html.trim();
return tmpl.content.firstChild;
},
getDataAttrAsInt = (elem, attr) => {
let value = parseInt(elem.dataset[attr]);
if (isNaN(value)) value = 0;
return value;
},
onClick = (elem, handler) => {
elem.addEventListener('click', handler);
elem.addEventListener('keydown', (event) => {
if (['Space', 'Enter'].includes(event.code)) handler();
});
};
const hydrateCart = (cart) => {
const { id: cartId } = cart.dataset,
order = createElement(CHECKOUT),
items = [...cart.querySelectorAll('.paypal-item')],
total = order.querySelector('.paypal-order-total'),
checkout = order.querySelector('.paypal-order-checkout'),
confirmation = cart.querySelector('.paypal-confirmation');
if (confirmation) confirmation.before(order);
else cart.append(order);
const getCount = (item) => getDataAttrAsInt(item, 'count'),
getCost = (item) => getDataAttrAsInt(item, 'cost'),
getTotal = () =>
items.reduce((charge, item) => {
return charge + getCount(item) * getCost(item);
}, 0);
const updateTotal = () => (total.innerText = `$${getTotal()}`),
updateCount = (item, increment) => {
item.dataset.count = Math.max(getCount(item) + increment, 0);
updateTotal();
};
updateTotal();
const showConfirmation = () => {
if (!confirmation) return;
confirmation.style.setProperty('display', 'flex');
};
for (const item of items) {
const add = createElement(ITEM_ADD),
remove = createElement(ITEM_REMOVE);
onClick(add, () => updateCount(item, +1));
onClick(remove, () => updateCount(item, -1));
item.append(remove, add);
}
onClick(checkout, () => {
console.log({
cart: cartId,
items: items.map((item) => {
return {
id: item.dataset.id,
count: getCount(item),
};
}),
});
showConfirmation();
});
};
document.addEventListener('DOMContentLoaded', () => {
const carts = document.querySelectorAll('.paypal-cart');
for (const cart of carts) hydrateCart(cart);
});
/* /pyv */

View File

@ -1,3 +1,4 @@
html:not([data-theme]),
[data-theme="light"] {
--background-color: #fff;
--primary-foreground-color: #4a4a4a;

View File

@ -27,6 +27,8 @@
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
}
}
@ -49,20 +51,20 @@
.columns > * > .kg-embed-card + .kg-embed-card {
margin-top: -50px;
}
.buttons .kg-button-card a {
width: 100%;
.buttons {
.kg-button-card a {
width: 100%;
}
@media screen and (max-width: $break-large) {
& > *:not(:last-child) {
margin-bottom: 14px !important;
}
}
}
.kg-button-card {
padding: 0 !important;
}
/* default to white nav text + icons for contrast until theme loads */
html:not([data-theme]) {
--toggle-darkmode-button-display-moon: none;
--toggle-darkmode-button-display-sun: block;
--titles-color: white;
}
/* transparent nav on homepage until scroll */
.home-template {
.in-mobile-topbar {
@ -133,7 +135,7 @@ body:not(.home-template) {
display: none;
}
}
@media only screen and (max-width: 48rem) {
@media only screen and (max-width: $break-medium) {
.m-nav__left .m-secondary-menu + .more {
display: none;
}
@ -247,12 +249,12 @@ body:not(.home-template) {
margin: 0 max(calc(50% - 430px), 0px);
}
}
@media only screen and (min-width: 35.5rem) and (max-width: 64rem) {
@media only screen and (min-width: $break-small) and (max-width: $break-large) {
.l-grid > :nth-child(2n + 1 of .m-article-card):last-child {
width: 100%;
}
}
@media only screen and (max-width: 48rem) {
@media only screen and (max-width: $break-medium) {
.l-post-content + .l-grid.centered {
margin-top: 40px;
}
@ -294,8 +296,12 @@ body:not(.home-template) {
.icon-minus {
mask: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1pbnVzIj48cGF0aCBkPSJNNSAxMmgxNCIvPjwvc3ZnPg==);
}
.icon-check-circle {
mask: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWNoZWNrLWNpcmNsZSI+PHBhdGggZD0iTTIyIDExLjA4VjEyYTEwIDEwIDAgMSAxLTUuOTMtOS4xNCIvPjxwb2x5bGluZSBwb2ludHM9IjIyIDQgMTIgMTQuMDEgOSAxMS4wMSIvPjwvc3ZnPg==);
}
.icon-plus,
.icon-minus {
.icon-minus,
.icon-check-circle {
width: 1em;
height: 1em;
mask-repeat: no-repeat;
@ -305,19 +311,45 @@ body:not(.home-template) {
}
/* payment buttons */
.paypal-item {
.paypal-cart {
display: flex;
flex-direction: column;
margin-bottom: 25px;
}
.paypal-item,
.paypal-confirmation {
align-items: center;
border-radius: 5px;
border: 1px solid rgb(124 139 154/25%);
display: flex;
font-size: 1rem;
font-weight: 600;
letter-spacing: 0.2px;
line-height: 1;
width: 100%;
margin-bottom: 25px;
}
.paypal-item {
border: 1px solid rgb(124 139 154/25%);
margin-bottom: 14px;
&::before {
content: " ($" attr(data-cost) ") x " attr(data-count);
padding: 13px 25px 13px 0;
white-space: pre;
order: 1;
}
&:is([data-count="0"], :not([data-count])) {
/* prevent ordering <0 items */
&::before {
content: " ($" attr(data-cost) ") x 0";
}
.paypal-item-remove {
pointer-events: none;
cursor: default;
opacity: 0.5;
}
}
.paypal-item-name {
padding: 13px 25px;
padding: 13px 0 13px 25px;
order: 0;
}
.paypal-item-remove {
margin-left: auto;
@ -334,39 +366,85 @@ body:not(.home-template) {
align-items: center;
justify-content: center;
padding: 0;
&[disabled],
&[aria-disabled="true"] {
pointer-events: none;
opacity: 0.6;
}
order: 2;
}
}
.paypal-checkout-btn {
.paypal-order {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 14px;
.paypal-order-total {
margin: 0 0 0 auto;
@media screen and (min-width: 24rem) and (max-width: $break-small) {
margin: 0 auto 0 0;
}
font-weight: 600;
font-size: 2rem;
}
}
.kg-button-card a .paypal-btn {
height: 42px;
width: 115.94px;
display: inline-block;
padding: 11px 23px 11px 13px;
margin: -13px -26px -15px 13px;
background-color: #fff;
background-image: url(https://www.paypalobjects.com/webstatic/en_US/i/buttons/PP_logo_h_150x38.png);
background-size: contain;
background-repeat: no-repeat;
background-origin: content-box;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
transition: all 0.25s cubic-bezier(0.02, 0.01, 0.47, 1);
.paypal-order-checkout {
width: 100%;
@media screen and (min-width: 24rem) {
width: auto;
}
display: flex !important;
align-items: normal !important;
text-decoration: none !important;
&,
.paypal-checkout-logo {
transition: all 0.25s cubic-bezier(0.02, 0.01, 0.47, 1) !important;
}
&:hover {
color: $white !important;
}
:first-child {
margin-right: 13px;
}
.paypal-checkout-logo {
height: 42px;
width: 115.94px;
display: inline-block;
padding: 11px 23px 11px 13px;
margin: -13px -26px -15px auto;
background-color: $white;
background-image: url(https://www.paypalobjects.com/webstatic/en_US/i/buttons/PP_logo_h_150x38.png);
background-size: contain;
background-repeat: no-repeat;
background-origin: content-box;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
&:focus .paypal-checkout-logo {
width: 113.94px;
height: 38px;
padding-top: 9px;
padding-bottom: 9px;
padding-right: 21px;
margin-right: -24px;
margin-top: -11px;
border-radius: 5px;
}
}
.kg-button-card a:focus .paypal-btn {
width: 113.94px;
height: 38px;
padding-top: 9px;
padding-bottom: 9px;
padding-right: 21px;
margin-right: -24px;
margin-bottom: -13px;
border-radius: 5px;
.paypal-confirmation {
display: none;
color: rgba(52, 183, 67, 0.94);
background: rgba(52, 183, 67, 0.16);
margin-top: 14px;
&::before {
content: "";
font-size: 20px;
margin: auto 0 auto 25px;
@extend .icon-check-circle;
}
span {
padding: 13px 25px;
}
}
.javascript-required {
color: #ff4a4a;
font-size: 1rem !important;
}
/* page cover sizing & readability (inc. uncropped post covers) */