Skip to content

Instantly share code, notes, and snippets.

@gijsbotje
Last active July 16, 2024 14:06
Show Gist options
  • Save gijsbotje/cb631ff6e59095d142f875c0eab7a6e3 to your computer and use it in GitHub Desktop.
Save gijsbotje/cb631ff6e59095d142f875c0eab7a6e3 to your computer and use it in GitHub Desktop.
Afosto 2.0 storefront scripts
<!-- twig/layouts/storefrontScripts.twig -->
<!-- Styling voor de error meldingen d.m.v. toastify-js -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
<script type="module">
import 'https://esm.sh/preact/debug';
import { h, render, Fragment } from 'https://esm.sh/preact';
import { useEffect, useState, useRef } from 'https://esm.sh/preact/hooks';
import { html } from 'https://esm.sh/htm/preact';
import Toastify from 'https://esm.sh/toastify-js';
import { createStorefrontClient } from 'https://esm.sh/@afosto/storefront@3';
const getErrorMessage = error => {
const errorResponse = error?.response || {};
const errorResponseData = errorResponse?.data || {};
const errorResponseError = errorResponseData?.error || {};
const gqlResponseErrors = errorResponse?.errors || [];
const [firstGqlError] = gqlResponseErrors || [];
const gqlErrorExtensions = firstGqlError?.extensions || {};
const pointers = errorResponseError?.details?.pointers || firstGqlError?.extensions?.pointers;
const [firstPointer] = pointers || [];
return (
firstPointer?.message ||
errorResponseError?.message ||
errorResponseData?.message ||
firstGqlError?.message
);
};
const formatPrice = (value) => {
const intl = Intl.NumberFormat(`${document.documentElement.lang}-${document.documentElement.lang.toUpperCase()}`, {
style: 'currency',
currency: document.body.dataset.afCurrencyIso,
});
const decimal = intl.formatToParts(value / 100).find(part => part.type === 'decimal').value;
return intl.format(value / 100).replace(`${decimal}00`, `${decimal}-`);
};
const CouponForm = ({ coupons }) => {
const [value, setValue] = useState('');
const [errorMessage, setErrorMessage] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const handleAddCouponToCart = async e => {
e.preventDefault();
try {
setIsSubmitting(true);
const updatedCart = await window.Storefront.addCouponToCart(value);
if (!!updatedCart) {
setValue('');
$('body').trigger('cart:updated', updatedCart);
} else {
alert('something went wrong');
}
} catch (error) {
$("#add-response-modal .modal-header .modal-title").html($("#add-response-modal").data("error-title"));
$("#add-response-modal .modal-body").addClass('text-center');
$("#add-response-modal .modal-body").html('<span class="fa-stack fa-3x text-warning"><i class="far fa-circle fa-stack-2x"></i><i class="fas fa-exclamation fa-stack-1x"></i></span><p class="lead mt-15">' + getErrorMessage(error) + '</p>');
$("#add-response-modal").modal('show');
} finally {
setIsSubmitting(false);
}
};
const handleRemoveCouponToCart = async code => {
try {
setIsSubmitting(true);
const updatedCart = await window.Storefront.removeCouponFromCart(code);
$('body').trigger('cart:updated', updatedCart);
} catch (error) {
$("#add-response-modal .modal-header .modal-title").html($("#add-response-modal").data("error-title"));
$("#add-response-modal .modal-body").addClass('text-center');
$("#add-response-modal .modal-body").html('<span class="fa-stack fa-3x text-warning"><i class="far fa-circle fa-stack-2x"></i><i class="fas fa-exclamation fa-stack-1x"></i></span><p class="lead mt-15">' + getErrorMessage(error) + '</p>');
$("#add-response-modal").modal('show');
} finally {
setIsSubmitting(false);
}
};
const handleChange = e => {
setValue(e.target.value);
};
const handleInput = () => {
setErrorMessage(null);
};
return html`
<form onSubmit="${handleAddCouponToCart}">
<div class="">
${(coupons || []).map(({ code }, index) => html`
<div class="panel panel-default d-flex justify-content-between align-items-center">
<div class="py-4 px-12">
${code || null}
</div>
<button class="btn btn-danger btn-sm" type="button" onClick="${() => handleRemoveCouponToCart(code)}">
<i class="fal fa-times" />
</button>
</div>
`)}
</div>
<div class="input-group">
<input class="form-control" placeholder="Coupon code toevoegen" onChange="${handleChange}" onInput="${handleInput}" value="${value}" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary" disabled="${isSubmitting}">
<i class="fal fa-angle-right" />
</button>
</span>
</div>
${errorMessage && errorMessage}
</form>
`;
};
const CopyCartLink = ({ id }) => {
const handleShowShareDialog = () => {
$('#share-cart-dialog').modal('show');
};
const handleCopyLink = () => {
const cartLink = `${window.location.origin}/cart?hash=${id}`;
navigator.clipboard.writeText(cartLink);
};
useEffect(() => {
if (id) {
const cartLink = `${window.location.origin}/cart?hash=${id}`;
const dialog = document.getElementById('share-cart-dialog');
dialog.innerHTML = dialog.innerHTML.replace(/\{url\}/g, cartLink);
$('[data-toggle="tooltip"]').tooltip();
$('.copy-to-clipboard').tooltip({
container: '.input-group-btn',
trigger: 'click'
}).on('mouseout', function(){
$(this).tooltip('hide');
});
new Clipboard('.copy-to-clipboard');
// new Clipboard('#copy-field');
$('input[data-clipboard-text]').on('click, focus', function() {
$(this).select();
});
}
}, [id]);
return html`
<button class="btn btn-link btn-block" data-toggle="modal" data-target="#share-cart-dialog">Winkelwagen delen</button>
`;
};
const CartPageSummary = ({ cart, checkoutUrl, isLoading }) => {
const { subtotal, total, totalExcludingVat, vat, adjustments, services, coupons, id } = cart || {};
return html`
<div class="cost-summary px-15">
<div class="cost-summary-inner">
<strong class="h4">
{{'Checkout'|t}}
</strong>
<div class="d-flex justify-content-between align-items-center">
<span class="h6 my-5 text-muted">
{{'Subtotaal'|t}}
</span>
<span class="h5 my-5 text-uppercase${isLoading ? 'text-muted' : ''}">
${isLoading ? '-' : formatPrice(subtotal || 0)}
</span>
</div>
${(adjustments || []).map(({ description, amount, isDiscount, isPercentage, outcome }, index) => html`
<div class="d-flex justify-content-between align-items-center${index > 0 ? ' mt-20' : ''}">
<span class="h6 my-5 text-muted">
${description}${isPercentage ? ` (${amount}%)` : ''}
</span>
<span class="h5 my-5">
${formatPrice((outcome.amount || 0) * (isDiscount ? -1 : 1) )}
</span>
</div>
`)}
<div class="d-flex justify-content-between align-items-center">
<span class="h6 my-5 text-muted">
{{'Totaal excl. BTW'|t}}
</span>
<span class="h5 my-5${isLoading ? 'text-muted' : ''}">
${isLoading ? '-' : formatPrice(totalExcludingVat || 0)}
</span>
</div>
${(vat || []).map(({ rate, amount }, index) => html`
<div class="d-flex justify-content-between align-items-center${index > 0 ? ' mt-20' : ''}">
<span class="h6 my-5 text-muted">
BTW ${rate || 0}%
</span>
<span class="h5 my-5">
${formatPrice(amount || 0)}
</span>
</div>
`)}
<hr />
<div class="d-flex justify-content-between align-items-center mb-32">
<span class="h6 my-0 text-muted">
{{'Totaal'|t}}
</span>
<span class="h4 my-0${isLoading ? 'text-muted' : ''}">
${isLoading ? '-' : formatPrice(total || 0)}
</span>
</div>
<${CouponForm} coupons="${coupons}" />
// <${PayPalButton} options="${paypalOptions}" />
<a class="btn btn-success btn-block mt-16" href="${checkoutUrl}" title="{{'Verder met bestellen'|t}}">
{{'Verder met bestellen'|t}}
</a>
<${CopyCartLink} id="${id}" />
</div>
</div>
`;
};
const ItemsGrid = ({ items, onRemove, onChangeQuantity, isLoading }) => {
const increaseQuantity = item => onChangeQuantity(item, item.quantity + 1);
const decreaseQuantity = item => onChangeQuantity(item, item.quantity - 1);
return html`
<div class="cart-grid px-15 d-flex flex-column">
<div class="cart-grid-header">
<div class="cart-grid-header-action"></div>
<div class="cart-grid-header-information">
<span>
{{'Informatie'|t}}
</span>
</div>
<div class="cart-grid-header-single-price">
<span>
{{'Stuksprijs'|t}}
</span>
</div>
<div class="cart-grid-header-quantity">
<span>
{{'Aantal'|t}}
</span>
</div>
<div class="cart-grid-header-subtotal">
<span>
{{'Subtotaal'|t}}
</span>
</div>
</div>
${!isLoading ? items.map(item => {
const { label, sku, total, subtotal, quantity, ids, details, image } = item || {};
const unitPrice = details[0] && details[0].pricing && details[0].pricing.amount || 0;
return html`
<div class="cart-grid-item-container" key="${sku}">
<div class="cart-grid-item">
<div class="cart-grid-item-action hidden-xs hidden-sm">
<button
type="button"
class="btn btn-link text-danger btn-block btn-icon p-10"
onClick="${() => onRemove(ids)}"
>
<i class="fa fa-times"></i>
</button>
</div>
<div class="cart-grid-item-information">
<div class="cart-grid-item-information-image">
<a href="{{cartItem.url}}" title="${label || sku}">
<div class="lazyload-wrapper lazyload-square">
<img loading="lazy" src="${image}" alt="${'{{'Afbeelding van [name]'|t}}'.replace('[name]', label || sku) }" class="lazyload-cover" />
</div>
</a>
</div>
<div class="cart-grid-item-information-inner">
<strong class="h5 cart-grid-item-product-name">
${label || sku}
</strong>
<div class="visible-xs visible-sm">
<div class="d-flex">
<div class="flex-100 d-flex align-items-center">
<label class="h6 text-muted d-block mr-10">
{{'Aantal'|t}}
</label>
<div class="quantity-counter">
<button
type="button"
class="btn"
onClick="${() => decreaseQuantity(item)}"
>
<i class="fa fa-minus"></i>
</button>
<div class="mx-10">
${quantity}
</div>
<button
type="button"
class="btn"
onClick="${() => increaseQuantity(item)}"
>
<i class="fa fa-plus"></i>
</button>
</div>
</div>
</div>
<hr class="light my-10"/>
<div class="d-flex align-items-center">
<span class="h6 my-0 text-muted mr-10">
{{'Subtotal'|t}}
</span>
<span class="h6 my-0">
${formatPrice(subtotal)}
</span>
<button
type="button"
class="btn btn-link btn-sm text-danger btn-icon p-5 ml-auto"
onClick="${() => onRemove(ids)}"
>
<i class="fa fa-times"></i>
<span>
{{'Verwijderen'|t}}
</span>
</button>
</div>
</div>
</div>
<div class="cart-grid-item-single-price hidden-xs hidden-sm text-right">
<div class="h5">
${formatPrice(unitPrice)}
</div>
</div>
<div class="cart-grid-item-quantity hidden-xs hidden-sm">
<div class="quantity-counter">
<button
type="button"
class="btn"
onClick="${() => decreaseQuantity(item)}"
>
<i class="fa fa-minus fa-sm"></i>
</button>
<div class="mx-10">
${quantity}
</div>
<button
type="button"
class="btn"
onClick="${() => increaseQuantity(item)}"
>
<i class="fa fa-plus fa-sm"></i>
</button>
</div>
</div>
<div class="cart-grid-item-subtotal hidden-xs hidden-sm text-right">
<span class="h5">
${formatPrice(subtotal)}
</span>
</div>
</div>
</div>
</div>
`;
}) : ''}
</div>
`;
};
const CartPage = () => {
const [cart, setCart] = useState({});
const [isLoadingCart, setIsLoadingCart] = useState(true);
const [error, setError] = useState(null);
const checkoutUrl = cart && cart.checkout && cart.checkout.url || '';
const handleRemoveItems = async ids => {
const updatedCart = await window.Storefront.removeCartItems(ids);
$('body').trigger('cart:updated', updatedCart);
};
const handleChangeQuantity = async (item, quantity) => {
try {
if (!quantity || quantity === item.quantity) {
return;
}
let cartResponse;
if (quantity > item.quantity) {
cartResponse = await window.Storefront.addCartItems([
{
sku: item.sku,
quantity: quantity - item.quantity,
},
]);
} else {
const difference = item.quantity - quantity;
const ids = [...(item.ids || [])].reverse().slice(0, difference);
cartResponse = await window.Storefront.removeCartItems(ids);
}
$('body').trigger('cart:updated', cartResponse);
} catch(error) {
$("#add-response-modal .modal-header .modal-title").html($("#add-response-modal").data("error-title"));
$("#add-response-modal .modal-body").addClass('text-center');
$("#add-response-modal .modal-body").html('<span class="fa-stack fa-3x text-warning"><i class="far fa-circle fa-stack-2x"></i><i class="fas fa-exclamation fa-stack-1x"></i></span><p class="lead mt-15">' + getErrorMessage(error) + '</p>');
$("#add-response-modal").modal('show');
}
};
useEffect(() => {
const updateCart = (event, val) => {
setCart(val);
if (isLoadingCart) {
setIsLoadingCart(false);
}
};
$('body').on('cart:updated', updateCart);
return () => {
$('body').off('cart:updated', updateCart);
}
}, []);
return html`
<section id="contentcart" class="container mb-20 cart-version-flex${isLoadingCart ? ' loading-ajax' : ''}">
<div class="mb-15">
<div class="cart-header d-flex align-items-center justify-content-between">
<h1 class="my-0 cart-title">
{{'Winkelwagen'|t}}
</h1>
${(cart && cart.items && cart.items.length > 0) && !isLoadingCart && html`
<a class="btn btn-success visible-sm visible-xs" href="${checkoutUrl}">
{{'Afrekenen'|t}}
</a>`}
</div>
<div class="clearfix"></div>
</div>
${error && html`
<div class="alert alert-warning">${error}</div>
`}
${(cart && cart.items && cart.items.length > 0) || isLoadingCart ? html`
<div class="d-flex flex-wrap flex-md-nowrap mx-n15">
<${ItemsGrid} isLoading="${isLoadingCart}" items="${cart.items || []}" onRemove="${handleRemoveItems}" onChangeQuantity="${handleChangeQuantity}" />
<${CartPageSummary} isLoading="${isLoadingCart}" cart="${cart}" checkoutUrl="${checkoutUrl}" />
</div>
` : html`
<h3 class="text-center text-warning">
{{'Uw winkelwagen is leeg.'|t}}
<br />
<br />
<a class="center btn btn-primary" href="{{home_url}}">{{'Bekijk onze producten'|t}}</a>
</h3>
`}
</section>
`;
};
const CartDrawerItem = ({ item, onRemove, onChangeQuantity }) => {
const { label, sku, image, ids, quantity, subtotal, total } = item;
const increaseQuantity = item => onChangeQuantity(item, item.quantity + 1);
const decreaseQuantity = item => onChangeQuantity(item, item.quantity - 1);
return html`
<div class="cart-overview-item">
<div class="cart-overview-item-inner d-flex gap-8">
<div class="cart-overview-item-delete align-self-center">
<a class="text-primary" onClick="${() => onRemove(ids)}">
<i class="fa fa-times text-danger fa-sm"></i>
</a>
</div>
<div class="cart-overview-item-image px-5">
<div class="d-block icon-75">
<div class="lazyload-wrapper lazyload-square">
<img class="lazyload lazyload-cover" data-src="${image}" alt="${'{{'Afbeelding van [name]'|t}}'.replace('[name]', label || sku) }"/>
</div>
</div>
</div>
<div class="cart-overview-item-group d-flex flex-column justify-content-between flex-fill">
<div class="cart-overview-item-name">
${label || sku}
</div>
<div class="d-flex justify-content-between">
<div class="cart-overview-item-quantity align-self-end">
<div class="quantity-counter">
<button
type="button"
class="btn"
aria-label="{{'aantal min 1'|t}}"
onClick="${() => decreaseQuantity(item)}"
>
<i class="fa fa-minus fa-sm"></i>
</button>
<div class="mx-10">
${quantity}
</div>
<button
type="button"
class="btn"
aria-label="{{'aantal plus 1'|t}}"
onClick="${() => increaseQuantity(item)}"
>
<i class="fa fa-plus fa-sm"></i>
</button>
</div>
</div>
<div class="cart-overview-item-price text-right">
<strong>
${formatPrice(subtotal)}
</strong>
</div>
</div>
</div>
</div>
</div>
`;
};
const CartDrawer = () => {
const [cart, setCart] = useState(null);
const [isLoadingCart, setIsLoadingCart] = useState(false);
const drawerRef = useRef(null);
const handleRemoveItems = async ids => {
const updatedCart = await window.Storefront.removeCartItems(ids);
setCart(updatedCart);
$('body').trigger('cart:updated', updatedCart);
};
const handleChangeQuantity = async (item, quantity) => {
try {
if (!quantity || quantity === item.quantity) {
return;
}
let cartResponse;
if (quantity > item.quantity) {
cartResponse = await window.Storefront.addCartItems([
{
sku: item.sku,
quantity: quantity - item.quantity,
},
]);
} else {
const difference = item.quantity - quantity;
const ids = [...(item.ids || [])].reverse().slice(0, difference);
cartResponse = await window.Storefront.removeCartItems(ids);
}
setCart(cartResponse);
$('body').trigger('cart:updated', cartResponse);
} catch(error) {
$("#add-response-modal .modal-header .modal-title").html($("#add-response-modal").data("error-title"));
$("#add-response-modal .modal-body").addClass('text-center');
$("#add-response-modal .modal-body").html('<span class="fa-stack fa-3x text-warning"><i class="far fa-circle fa-stack-2x"></i><i class="fas fa-exclamation fa-stack-1x"></i></span><p class="lead mt-15">' + getErrorMessage(error) + '</p>');
$("#add-response-modal").modal('show');
}
};
const getCartData = async () => {
setIsLoadingCart(true);
let tokenIsValid = true;
const { search = '', pathname } = window?.location || {};
const params = new URLSearchParams(search);
const cartToken = params.get('hash');
const isCartPage = pathname === '/cart';
const intent = isCartPage ? 'VIEW_CART' : null;
const cartResponse = await window.Storefront.getCart(cartToken, intent).catch(() => {
if (cartToken) {
tokenIsValid = false;
return window.Storefront.getCart(null, intent);
}
return Promise.reject(new Error('Cart not found'));
});
if (cartResponse && tokenIsValid && cartToken) {
await window.Storefront.storeCartTokenInStorage(cartToken);
}
// window.Storefront.getCart().then(response => {
setIsLoadingCart(false);
$('body').trigger('cart:updated', cartResponse);
// });
};
useEffect(() => {
getCartData();
}, []);
useEffect(() => {
const updateCart = (event, val) => {
if(!!val) {
setCart(val);
}
if (isLoadingCart) {
setIsLoadingCart(false);
}
};
$('body').on('cart:updated', updateCart);
return () => {
$('body').off('cart:updated', updateCart);
}
}, []);
return html`
<div class="drawer drawer-right" id="cart-drawer" ref="${drawerRef}">
<div class="drawer-header">
<div class="h4 m-0 fw-700">
{{"Winkelwagen"|t}}
</div>
<button class="btn btn-link m-4 p-0 icon-40 mr-n12" type="button" data-dismiss="drawer" aria-label="{{'Sluit winkelwagen'|t}}">
<i class="fa fa-times icon-size-20 text-black"></i>
</button>
</div>
<div class="drawer-body configurator-drawer-inner p-0">
${((cart && cart.items) || []).length === 0 ? html`
<div class="text-center">
<i class="fa fa-cart-plus text-primary fa-4x mt-40 mb-40"></i>
<span class="h4 mb-12">{{"Uw winkelwagen is leeg"|t}}</span>
<p class=" mb-40">{{"Bekijk het aanbod op onze website en voeg producten toe aan je winkelwagen."|t}}</p>
<button type="button" class="btn btn-primary" data-dismiss="drawer">
{{'Verder winkelen'|t}}
</button>
</div>
` : html`
<div class="cart-preview-items py-8">
${((cart && cart.items) || []).map(item => html`
<${CartDrawerItem} key="${item.sku}" item="${item}" onRemove="${handleRemoveItems}" onChangeQuantity="${handleChangeQuantity}" />
`)}
</div>
`}
</div>
${((cart && cart.items) || []).length ? html`
<div class="drawer-footer drawer-footer-sticky pb-32 px-32">
<hr class="m-0" />
<div class="d-flex justify-content-between py-20">
<span>
{{'Totaal'|t}}
</span>
<span>
${formatPrice(cart.total)}
</span>
</div>
<hr class="mt-0 mb-32" />
<a class="btn btn-primary btn-block" href="{{cart_url}}">
{{'Afrekenen'|t}}
</a>
</div>
` : ''}
</div>
`;
};
const AccountMenu = () => {
const [channel, setChannel] = useState(null);
const accountLink = channel?.links?.find(({ type }) => type === 'MY_ACCOUNT')?.value;
const [account, setAccount] = useState(window.Storefront.getUser());
const getChannelData = async () => {
const channelResponse = await window.Storefront.getChannel();
setChannel(channelResponse);
};
const handleSignOut = () => {
window.Storefront.signOut();
setAccount(window.Storefront.getUser());
};
useEffect(() => {
getChannelData();
}, []);
$('[data-toggle="tooltip"]').tooltip();
if (account) {
return html`
<a data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-user-check"></i>
<br />
<span class="hidden-xs account-link-label">${account.givenName}</span>
</a>
<ul class="dropdown-menu">
<li>
<a href="${accountLink}/account/orders">
{{"Mijn bestellingen"|t}}
</a>
</li>
<li>
<a href="${accountLink}/account/details">
{{"Mijn gegevens"|t}}
</a>
</li>
<li>
<a href="${accountLink}/account/addresses">
{{"Mijn adressen"|t}}
</a>
</li>
<li>
<a href="${accountLink}/account/preferences">
{{"Mijn voorkeuren"|t}}
</a>
</li>
<li role="separator" class="divider" />
<li>
<a onClick="${handleSignOut}">{{"Uitloggen"|t}}</a>
</li>
</ul>
`;
}
return html`
<a href="${accountLink}" data-toggle="tooltip" data-placement="bottom" title="Inloggen">
<i class="fa fa-user-times"></i>
<br />
<span class="hidden-xs account-link-label">{{"Inloggen"|t}}</span>
</a>
`;
};
const setupStorefront = () => {
const client = createStorefrontClient({
domain: window.location.host,
storefrontToken: '{{pluginData.orm.storefront}}',
graphQLClientOptions: {
headers: {
"Accept-Language": document.documentElement.lang,
}
}
});
if (window.visitor_id) {
client.setSessionID(window.visitor_id);
}
const renderCartDrawer = () => {
if (document.getElementById('cart-drawer-container')) {
return render(html`<${CartDrawer} />`, document.getElementById('cart-drawer-container'));
}
console.warn('[Afosto Storefront] No element fount with id "cart-drawer-container" to mount the CartDrawer on. Add the id to an element or check your storefrontScripts.twig file.')
};
const renderCartPage = () => {
if (document.getElementById('cart-container')) {
return render(html`<${CartPage} />`, document.getElementById('cart-container'));
}
console.warn('[Afosto Storefront] No element fount with id "cart-container" to mount the CartPage on. Add the id to an element or check your storefrontScripts.twig file.')
};
const renderAccountMenu = () => {
if (document.getElementById('account-display')) {
return render(html`<${AccountMenu} />`, document.getElementById('account-display'));
}
console.warn('[Afosto Storefront] No element fount with id "account-display" to mount the AccountMenu on. Add the id to an element or check your storefrontScripts.twig file.')
};
return {
...client,
client,
renderCartDrawer,
renderCartPage,
renderAccountMenu,
};
};
const afostoStorefront = setupStorefront();
window.Storefront = afostoStorefront;
const initializeCart = async () => {
afostoStorefront.renderCartDrawer();
afostoStorefront.renderAccountMenu();
if ("{{ type }}" === 'cart') {
afostoStorefront.renderCartPage();
}
};
document.addEventListener("readystatechange", (event) => {
if (document.readyState === 'complete') {
initializeCart().catch(() => {
// Do nothing.
})
}
});
</script>
<div class="modal fade" tabindex="-1" role="dialog" id="share-cart-dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{'Winkelwagen delen'|t}}</h4>
</div>
<div class="modal-body text-center">
<div class="row">
<div class="col-md-4 col-xs-3">
<a class="text-black" href="https://www.facebook.com/sharer/sharer.php?u={url}&t=" rel="noopener noreferrer" target="_blank" title="{{"Share on Facebook"|t}}" >
<i class="fab fa-3x fa-facebook"></i>
</a>
</div>
<div class="col-md-4 visible-xs visible-sm col-xs-3">
<a class="text-black" href="whatsapp://send?text={url}">
<i class="fab fa-3x fa-whatsapp"></i>
</a>
</div>
<div class="col-md-4 col-xs-3">
<a class="text-black" href="mailto:?subject=&body={url}" rel="noopener noreferrer" target="_self" title="{{"Send email"|t}}">
<i class="fas fa-3x fa-envelope"></i>
</a>
</div>
</div>
</div>
<div class="modal-footer text-left">
<p class="text-center">
{{' Kopieer deze link om op je website te plaatsen of om te gebruiken in een bericht'|t}}
</p>
<div class="form-group">
<div class="input-group">
<input class="form-control" value="{url}" id="copy-cart-url-field" data-clipboard-text="{url}">
<div class="input-group-btn">
<button type="button" class="copy-to-clipboard btn btn-secondary" data-clipboard-target="#copy-cart-url-field" data-toggle="tooltip" data-placement="bottom" title="{{'Gekopieerd!'|t}}">{{'Kopieer'|t}}</button>
</div>
</div>
</div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment