@thanku/postpaid-widget
Version:
The ThankU Postpaid Widget is a Web Component that is intended to be integrated on e.g. checkout success pages of online shops
368 lines (343 loc) • 9.09 kB
JavaScript
// CONFIG
const origin = "https://www.thanku.social";
const thankuLogoUrl = `${origin}/thanku-logo-row.v0000001.svg`;
// HELPERS
function toCollectUrl({ id, lang }) {
return `${origin}/${lang}/app/collect/${id}`;
}
function toCauseImageUrl(cause) {
return `${origin}/donateeGroup/${cause}.v0000001.png`;
}
function toApiEndpointUrl({ simulate }) {
return `${origin}/api/donate/postpaid${simulate ? "?simulate=true" : ""}`;
}
function htmlEscape(string) {
return string
.replace(/&/g, "&")
.replace(/"/g, """)
.replace(/'/g, "'")
.replace(/</g, "<")
.replace(/>/g, ">");
}
function html(strings, ...args) {
return strings
.map((str, i) =>
i < args.length
? str + (args[i].__html ? args[i].__html : htmlEscape(String(args[i])))
: str
)
.join("");
}
// API CLIENT
const Api = {
donatePostpaid({
slug,
donateeGroup,
impactValue,
message,
language,
pid,
sig,
orderId,
simulate,
}) {
return fetch(toApiEndpointUrl({ simulate }), {
method: "POST",
headers: { "Content-Type": "application/json", "x-sig": sig },
body: JSON.stringify({
slug,
donateeGroup,
impactValue,
message,
language,
pid,
orderId,
}),
}).then(
async (res) => {
if (res.status >= 400) {
throw Error("request invalid");
}
return res.json().catch(() => {
throw Error("response data invalid");
});
},
() => {
throw Error("connection problems");
}
);
},
};
// STYLES
const styles = html`
<style>
:host {
--bg-image: linear-gradient(90deg, #eaf6f1, #d4edf7);
--bg-color: #dff1f4;
--bg-color-button: #01888b;
--bg-color-button-hover: #00666b;
--bg-color-impact: white;
--bg-color-message: white;
--color-text-base: #202c55;
--color-text-headline: #01888b;
--color-text-impact: #ec7614;
--color-text-button: white;
--color-text-message: #202c55;
--shadow-color-message: rgba(95, 194, 197, 0.4);
--font-family: "Exo", sans-serif;
--font-size-base: 16px;
--font-size-sm: 115%;
--font-size-md: 125%;
--font-size-lg: 135%;
display: block;
}
.container {
background-color: var(--bg-color);
background-image: var(--bg-image);
border-radius: 0.75em;
color: var(--color-text-base);
display: block;
font-family: var(--font-family);
font-size: var(--base-font-size);
padding: 1.5em 1.5em 1.25em 1.5em;
text-decoration: none;
line-height: 1.5;
}
@media (min-width: 640px) {
.container {
font-size: var(--font-size-sm);
}
}
@media (min-width: 768px) {
.container {
font-size: var(--font-size-md);
}
}
@media (min-width: 1024px) {
.container {
font-size: var(--font-size-lg);
}
}
.headline {
color: var(--color-text-headline);
font-size: 1.25em;
margin: 0;
padding: 0;
text-align: center;
}
.message {
align-items: center;
background-color: var(--bg-color-message);
border-radius: 0.75em;
box-shadow: 0 0 0.75em 0 var(--shadow-color-message);
color: var(--color-text-message);
display: flex;
margin: 1em auto 0 auto;
max-width: 32em;
padding: 0.75em 1.25em;
position: relative;
}
.btn {
background-color: var(--bg-color-button);
border-radius: 9999px;
color: var(--color-text-button);
display: inline-block;
font-size: 1em;
font-weight: 600;
padding: 0.75em 2.5em;
text-align: center;
text-decoration: none;
transition-duration: 0.3s;
transition-property: background-color;
}
.btn:hover {
background-color: var(--bg-color-button-hover);
}
.donation {
display: flex;
justify-content: center;
margin: 1.5em 0 0 0;
padding: 0;
}
.impact {
align-items: center;
background-color: var(--bg-color-impact);
border-radius: 9999px;
box-sizing: border-box;
color: var(--color-text-impact);
display: inline-flex;
flex-direction: column;
height: 6em;
justify-content: center;
line-height: 1;
padding: 0.5em;
text-align: center;
width: 6em;
}
.impact-value {
font-size: 2.25em;
}
.impact-name {
font-size: 0.875em;
line-height: 1.25;
margin-bottom: -0.25em;
}
.impact-info {
margin: 1em 0 0 0;
padding: 0;
text-align: center;
}
.cause {
height: 6em;
margin-left: 1em;
width: 6em;
}
.action,
.powered-by {
padding: 0;
text-align: center;
margin: 1.5em 0 0 0;
}
.powered-by-text {
font-size: 0.85em;
}
.logo {
background-color: white;
border-radius: 0.25rem;
display: inline-block;
height: 1em;
margin-left: 0.25rem;
padding: 0.35em 0.5rem 0.25rem 0.5rem;
vertical-align: middle;
width: 5em;
}
.link {
color: inherit;
text-decoration: underline;
}
</style>
`;
// TRANSLATIONS
const translations = {
de: {
headlne: "Ein ThankU für Dich",
impactUnit: {
CleanOcean: () => "kg",
PlantTrees: ({ value }) => (value === 1 ? "Baum" : "Bäume"),
ProtectWildlife: () => "m²",
},
impactInfo: {
CleanOcean: ({ value }) =>
`Dieses ThankU entfernt ${value} kg Plastik aus dem Ozean.`,
PlantTrees: ({ value }) =>
`Dieses ThankU pflanzt ${
value === 1 ? "einen neuen Baum" : `${value} neue Bäume`
}.`,
ProtectWildlife: ({ value }) =>
`Dieses ThankU schützt ${value} m² Wildtierlebensraum.`,
},
collectLinkTitle: "ThankU jetzt einsammeln",
thankuLinkTitle: "ThankU besuchen",
},
en: {
headlne: "A ThankU for you",
impactUnit: {
CleanOcean: () => "kg",
PlantTrees: ({ value }) => (value === 1 ? "tree" : "trees"),
ProtectWildlife: () => "m²",
},
impactInfo: {
CleanOcean: ({ value }) =>
`This ThankU removes ${value} kg ocean plastic.`,
PlantTrees: ({ value }) =>
`This ThankU plants ${
value === 1 ? "one new tree" : `${value} new trees`
}.`,
ProtectWildlife: ({ value }) =>
`This ThankU protects ${value} m² wildlife habitat.`,
},
collectLinkTitle: "Collect ThankU now",
thankuLinkTitle: "Visit ThankU",
},
};
// RENDER HELPER
const renderData = ({ t, lang, id, cause, impact, message }) => html`
${{ __html: styles }}
<div class="container">
<h2 class="headline">${t.headlne}</h2>
<p class="message">${message}</p>
<p class="donation">
<span class="impact">
<span class="impact-value">${impact}</span>
<span class="impact-name">
${t.impactUnit[cause]({ value: impact })}
</span>
</span>
<img
src="${toCauseImageUrl(cause)}"
alt="${cause}"
width="100"
height="100"
class="cause"
/>
</p>
<p class="impact-info">${t.impactInfo[cause]({ value: impact })}</p>
<p class="action">
<a href="${toCollectUrl({ id, lang })}" class="btn">
${t.collectLinkTitle}
</a>
</p>
<p class="powered-by">
<span class="powered-by-text">powered by</span>
<a href="${origin}" class="link" title="${t.thankuLinkTitle}">
<img
src="${thankuLogoUrl}"
width="100"
height="20"
alt="ThankU"
class="logo"
/>
</a>
</p>
</div>
`;
// WEB COMPONENT
class ThankUPostpaidWidget extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
if (!this.isConnected || this._isDataRequested) return;
const lang = this.getAttribute("lang") || "en";
const slug = this.getAttribute("slug");
const cause = this.getAttribute("cause");
const impact = parseInt(this.getAttribute("impact"));
const pid = this.getAttribute("pid");
const sig = this.getAttribute("sig");
const message = this.getAttribute("message");
const simulate = this.getAttribute("simulate") === "";
const orderId = this.getAttribute("oid");
Api.donatePostpaid({
language: lang,
slug,
donateeGroup: cause,
impactValue: impact,
pid,
sig,
message,
simulate,
orderId,
})
.then(({ id }) => {
const t = translations[lang] || translations.en;
const shadow = this.attachShadow({ mode: "open" });
shadow.innerHTML = renderData({ id, cause, impact, message, t, lang });
})
.catch(() => {
// ignore error and continue to show the fallback markup to the user
});
this._isDataRequested = true;
}
}
customElements.define("thanku-postpaid-widget", ThankUPostpaidWidget);
export { ThankUPostpaidWidget };