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+
2801228283const 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: () => {
0 commit comments