Skip to content

Commit daf7623

Browse files
authored
Merge pull request #86 from 4site-interactive-studios/feature/gift-history
Feature/gift history
2 parents 5572cbf + fa57b7b commit daf7623

7 files changed

Lines changed: 664 additions & 9 deletions

File tree

dist/engrid.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*
2020
* ENGRID PAGE TEMPLATE ASSETS
2121
*
22-
* Date: Tuesday, April 21, 2026 @ 12:14:51 ET
22+
* Date: Monday, May 18, 2026 @ 04:18:17 ET
2323
* By: michael
2424
* ENGrid styles: v0.25.0
2525
* ENGrid scripts: v0.25.1
@@ -8143,7 +8143,7 @@ body[data-engrid-debug]:before{
81438143
--engrid__content-footer_background-color:#fff;
81448144
--engrid__body_box-shadow:none;
81458145
--default_font-family:"Open Sans", sans-serif;
8146-
--default_color:##1f2937;
8146+
--default_color:#1f2937;
81478147
--default_font-size:20px;
81488148
--default_line-height:32px;
81498149
--h1_font-size:46px;

dist/engrid.js

Lines changed: 273 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*
1818
* ENGRID PAGE TEMPLATE ASSETS
1919
*
20-
* Date: Tuesday, April 21, 2026 @ 12:14:51 ET
20+
* Date: Monday, May 18, 2026 @ 04:18:17 ET
2121
* By: michael
2222
* ENGrid styles: v0.25.0
2323
* ENGrid scripts: v0.25.1
@@ -27987,6 +27987,276 @@ class Quiz {
2798727987
});
2798827988
}
2798927989

27990+
}
27991+
;// CONCATENATED MODULE: ./src/scripts/gift-history.ts
27992+
27993+
27994+
class GiftHistory {
27995+
constructor() {
27996+
_defineProperty(this, "remoteGiftHistory", []);
27997+
27998+
_defineProperty(this, "remoteGiftHistoryFetched", false);
27999+
28000+
_defineProperty(this, "logger", new logger_EngridLogger("Gift History"));
28001+
28002+
if (!this.shouldRun()) {
28003+
return;
28004+
}
28005+
28006+
this.run().then(() => {});
28007+
}
28008+
28009+
shouldRun() {
28010+
return engrid_ENGrid.getPageType() === "SUPPORTERHUB" && engrid_ENGrid.getPageNumber() === 2;
28011+
}
28012+
28013+
async run() {
28014+
const targetElement = document.querySelector(".en__component--page");
28015+
28016+
if (!targetElement) {
28017+
this.logger.log("Target element for gift history not found, cannot merge remote gift history");
28018+
return;
28019+
} //This mutation observer is used to detect when new transactions are added to the DOM
28020+
//When this happens, we merge in the remote gift history
28021+
28022+
28023+
const observer = new MutationObserver(async mutationsList => {
28024+
for (const mutation of mutationsList) {
28025+
if (mutation.type === "childList") {
28026+
const newTransactionsAdded = [...mutation.addedNodes].some(node => this.isElementWithClass(node, "en__hubTxnGiving__transactions__list"));
28027+
28028+
if (newTransactionsAdded) {
28029+
this.logger.log("New EN transactions added to DOM");
28030+
this.remoteGiftHistory = await this.fetchRemoteGiftHistory();
28031+
this.updateTotalAmountDonated();
28032+
this.renderMergedGiftHistory();
28033+
}
28034+
}
28035+
}
28036+
});
28037+
observer.observe(targetElement, {
28038+
childList: true,
28039+
subtree: true
28040+
});
28041+
document.head.insertAdjacentHTML("beforeend", `<style>.en__hubTxnGiving__transactions__list:not([data-engrid-transactions-loaded]) { display: none }</style>`);
28042+
}
28043+
28044+
isElementWithClass(node, className) {
28045+
return node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className);
28046+
}
28047+
28048+
parseISODate(dateString) {
28049+
const parts = dateString.split("-").map(Number);
28050+
return {
28051+
year: parts[0],
28052+
month: parts[1],
28053+
day: parts[2]
28054+
};
28055+
}
28056+
28057+
dateToComparable(dateString) {
28058+
// Handles both "yyyy-MM-dd" and "MM/dd/yyyy" formats
28059+
if (dateString.includes("-")) {
28060+
const parts = dateString.split("-").map(Number);
28061+
return parts[0] * 10000 + parts[1] * 100 + parts[2];
28062+
} else {
28063+
const parts = dateString.split("/").map(Number);
28064+
return parts[2] * 10000 + parts[0] * 100 + parts[1];
28065+
}
28066+
}
28067+
28068+
renderMergedGiftHistory() {
28069+
const transactionsList = document.querySelector(".en__hubTxnGiving__transactions__list");
28070+
transactionsList?.removeAttribute("data-engrid-transactions-loaded"); // Remove previously inserted remote gifts to avoid duplication on pagination
28071+
28072+
transactionsList?.querySelectorAll(".en__hubTxnGiving__transaction--remote").forEach(el => el.remove());
28073+
const enGiftHistory = this.getENGiftHistoryOnPage();
28074+
const giftHistoryToRender = this.mergeRemoteGiftHistoryEntries(enGiftHistory);
28075+
this.addGiftHistoryToDOM(giftHistoryToRender);
28076+
transactionsList?.setAttribute("data-engrid-transactions-loaded", "");
28077+
}
28078+
28079+
getENGiftHistoryOnPage() {
28080+
const giftHeaders = document.querySelectorAll(".en__hubTxnGiving__transaction .en__hubTxnGiving__transaction__header");
28081+
const enGifts = [...giftHeaders].map(giftHeader => {
28082+
return giftHeader.textContent ? this.getGiftDateFromGiftHeaderString(giftHeader.textContent.trim()) : null;
28083+
}).filter(gift => gift !== null);
28084+
return enGifts;
28085+
}
28086+
28087+
getGiftDateFromGiftHeaderString(headerString) {
28088+
const date = headerString.match(/^.*?(\d{1,2}\/\d{1,2}\/\d{4}).*?$/);
28089+
28090+
if (date) {
28091+
return {
28092+
//createdOn: Date.parse(date[1]),
28093+
date: date[1],
28094+
source: "EngagingNetworks"
28095+
};
28096+
} else {
28097+
this.logger.log(`Gift string did not match expected format: ${headerString}}`);
28098+
return null;
28099+
}
28100+
}
28101+
28102+
mergeRemoteGiftHistoryEntries(enGiftHistory) {
28103+
const onFirstPage = document.querySelector(".en__pagination__prev")?.hasAttribute("disabled");
28104+
const onLastPage = document.querySelector(".en__pagination__next")?.hasAttribute("disabled");
28105+
const transactionsDate = document.getElementById("en__hubTxnGiving__transactions__date__select")?.value;
28106+
let remoteGiftHistoryToMerge = [];
28107+
28108+
if (enGiftHistory.length > 0) {
28109+
//if the page has gifts, we want to merge in remote gifts based on the date range of the gifts on the page
28110+
const mostRecentENGift = this.dateToComparable(enGiftHistory[0].date);
28111+
const oldestENGift = this.dateToComparable(enGiftHistory[enGiftHistory.length - 1].date);
28112+
remoteGiftHistoryToMerge = this.remoteGiftHistory.filter(remoteGift => {
28113+
//If we're on the first page, merge in gifts that are newer than the oldest gift on the page
28114+
//If we're on the last page, merge in gifts that are older than the most recent gift on the page
28115+
//Otherwise, we want to merge in all gifts between the oldest and most recent gifts on the page
28116+
//Also, make sure the year is the same as the year filter (or "all time");
28117+
const giftYearMatchesOrAllTime = transactionsDate === "0" || transactionsDate === this.parseISODate(remoteGift.date).year.toString();
28118+
const remoteGiftDate = this.dateToComparable(remoteGift.date);
28119+
28120+
if (onFirstPage) {
28121+
return remoteGiftDate >= oldestENGift && giftYearMatchesOrAllTime;
28122+
} else if (onLastPage) {
28123+
return remoteGiftDate <= mostRecentENGift && giftYearMatchesOrAllTime;
28124+
}
28125+
28126+
return remoteGiftDate >= oldestENGift && remoteGiftDate <= mostRecentENGift && giftYearMatchesOrAllTime;
28127+
});
28128+
} else {
28129+
remoteGiftHistoryToMerge = this.remoteGiftHistory.filter(remoteGift => {
28130+
// If the date filter is set to "All time", merge in all gifts
28131+
if (transactionsDate === "0") {
28132+
return true;
28133+
} // Otherwise, merge in gifts that match the year of the date filter
28134+
28135+
28136+
return this.parseISODate(remoteGift.date).year === parseInt(transactionsDate);
28137+
});
28138+
}
28139+
28140+
return [...enGiftHistory, ...remoteGiftHistoryToMerge].sort((a, b) => this.dateToComparable(b.date) - this.dateToComparable(a.date));
28141+
}
28142+
28143+
updateTotalAmountDonated() {
28144+
const el = document.querySelector(".en__hubTxnGiving__transactions__total > span");
28145+
const enTotal = el?.textContent?.trim().replace("$", "").replace(",", "");
28146+
const transactionsDate = document.getElementById("en__hubTxnGiving__transactions__date__select")?.value;
28147+
let remoteTotal; //All time donations
28148+
28149+
if (transactionsDate === "0") {
28150+
remoteTotal = this.remoteGiftHistory.reduce((total, gift) => {
28151+
return total + gift.amount;
28152+
}, 0);
28153+
} else {
28154+
// The value of the year select is a year like "2023".
28155+
// Filter the remote gift history to only include gifts from that year and then sum the USD values
28156+
remoteTotal = this.remoteGiftHistory.filter(gift => {
28157+
return this.parseISODate(gift.date).year === parseInt(transactionsDate);
28158+
}).reduce((total, gift) => {
28159+
return total + parseFloat(gift.amount);
28160+
}, 0);
28161+
}
28162+
28163+
if (enTotal && remoteTotal) {
28164+
const total = parseFloat(enTotal) + remoteTotal;
28165+
el.textContent = `$${total.toFixed(2)}`;
28166+
}
28167+
}
28168+
28169+
addGiftHistoryToDOM(giftHistoryToRender) {
28170+
const transactionsList = document.querySelector(".en__hubTxnGiving__transactions__list > ol");
28171+
28172+
if (transactionsList) {
28173+
giftHistoryToRender.forEach((gift, index) => {
28174+
if (!gift.source || gift.source !== "EngagingNetworks") {
28175+
transactionsList.insertBefore(this.createGiftElement(gift), transactionsList.children[index]);
28176+
}
28177+
});
28178+
} else {
28179+
// If this "ol" doesn't exist, it means there are no EN transactions on the page
28180+
// So we make a list element and add the remote gifts to it
28181+
const transactionsList = document.querySelector(".en__hubTxnGiving__transactions__list")?.appendChild(document.createElement("ol"));
28182+
28183+
if (transactionsList) {
28184+
giftHistoryToRender.forEach(gift => {
28185+
if (!gift.source || gift.source !== "EngagingNetworks") {
28186+
transactionsList.appendChild(this.createGiftElement(gift));
28187+
}
28188+
});
28189+
}
28190+
}
28191+
28192+
if (giftHistoryToRender.length > 0) {
28193+
document.querySelector(".en__hubTxnGiving__transactions__empty")?.remove();
28194+
}
28195+
}
28196+
28197+
createGiftElement(gift) {
28198+
const giftEl = document.createElement("li");
28199+
giftEl.classList.add("en__hubTxnGiving__transaction");
28200+
giftEl.classList.add("en__hubTxnGiving__transaction--remote");
28201+
28202+
if (gift.type.toLowerCase().includes("recurring")) {
28203+
giftEl.classList.add("en__hubTxnGiving__transaction--recurring");
28204+
} else {
28205+
giftEl.classList.add("en__hubTxnGiving__transaction--single");
28206+
}
28207+
28208+
let paymentString = "";
28209+
28210+
switch (gift.method.toLowerCase()) {
28211+
case "credit card":
28212+
giftEl.classList.add("en__hubTxnGiving__transaction--card");
28213+
paymentString = `Credit Card Payment`;
28214+
break;
28215+
28216+
case "check":
28217+
paymentString = `Check Payment`;
28218+
break;
28219+
28220+
case "bank":
28221+
giftEl.classList.add("en__hubTxnGiving__transaction--bank");
28222+
paymentString = `Bank Payment`;
28223+
break;
28224+
28225+
default:
28226+
paymentString = `${gift.method} Payment`;
28227+
break;
28228+
}
28229+
28230+
const date = this.parseISODate(gift.date);
28231+
const formattedDate = `${date.month}/${date.day}/${date.year}`;
28232+
const formattedAmount = parseFloat(gift.amount.toString()).toFixed(2);
28233+
giftEl.innerHTML = `
28234+
<div class="en__hubTxnGiving__transaction__header">
28235+
<p>$${formattedAmount} on ${formattedDate}</p>
28236+
</div>
28237+
<div class="en__hubTxnGiving__transaction__payment"><p>${paymentString}</p></div>
28238+
`;
28239+
return giftEl;
28240+
}
28241+
28242+
async fetchRemoteGiftHistory() {
28243+
if (this.remoteGiftHistoryFetched) {
28244+
this.logger.log("Remote gift history already fetched, skipping fetch");
28245+
return this.remoteGiftHistory;
28246+
}
28247+
28248+
const constituentId = window.constituentId || null;
28249+
28250+
if (!constituentId) {
28251+
this.logger.log("No constituent ID found, cannot fetch remote gift history");
28252+
return [];
28253+
}
28254+
28255+
const req = await fetch(`https://encrmgifthistapi.wwfus.org/api/supporter/${constituentId}?code=4ZoWptvxmdnaZEKLAS65bFH7ErI17TY0YeE305o2HDLnAzFugcpdAw==`);
28256+
this.remoteGiftHistoryFetched = true;
28257+
return await req.json();
28258+
}
28259+
2799028260
}
2799128261
;// CONCATENATED MODULE: ./src/index.ts
2799228262
// Uses ENGrid via NPM
@@ -28009,6 +28279,7 @@ class Quiz {
2800928279

2801028280

2801128281

28282+
2801228283
const options = {
2801328284
AutoYear: true,
2801428285
applePay: false,
@@ -28198,6 +28469,7 @@ const options = {
2819828469

2819928470
new Quiz();
2820028471
new Bridger();
28472+
new GiftHistory();
2820128473
},
2820228474
onResize: () => console.log("Starter Theme Window Resized"),
2820328475
onSubmit: () => {

dist/engrid.min.css

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/engrid.min.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { AddDAF } from "./scripts/add-daf";
2525
import { Bridger } from "./scripts/Bridger";
2626

2727
import { Quiz } from "./scripts/quiz";
28+
import GiftHistory from "./scripts/gift-history";
2829

2930
const options: Options = {
3031
AutoYear: true,
@@ -236,6 +237,7 @@ const options: Options = {
236237
}
237238
new Quiz();
238239
new Bridger();
240+
new GiftHistory();
239241
},
240242
onResize: () => console.log("Starter Theme Window Resized"),
241243

src/sass/themes/wwf.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050

5151
// TYPOGRAPHY DEFAULT
5252
--default_font-family: "Open Sans", sans-serif;
53-
--default_color: ##1f2937;
53+
--default_color: #1f2937;
5454
--default_font-size: 20px;
5555
// --default_font-weight: initial;
5656
--default_line-height: 32px;

0 commit comments

Comments
 (0)