v0.1

Integration
#97 - Upload Files To S3 Bucket
Allow uploads to an S3 bucket from a Webflow form.
Show members their full purchase history, stats, and receipt links.
Watch the video for step-by-step implementation instructions
<!-- π MEMBERSCRIPT #221 v0.1 π PURCHASE HISTORY WITH STATS, FILTERS & PAGINATION -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
var CONFIG = {
tableName: "purchases",
ownerField: "member",
perPage: 10,
currency: "$",
dateLocale: "en-US",
statusClasses: {
paid: "is-paid",
refunded: "is-refunded",
failed: "is-failed",
pending: "is-pending",
cancelled: "is-cancelled",
canceled: "is-cancelled",
active: "is-active"
}
};
var memberstack = window.$memberstackDom;
if (!memberstack) {
console.warn("Memberscript # number221: Memberstack not found");
return;
}
function show(el, type) { if (el) el.style.display = type || ""; }
function hide(el) { if (el) el.style.display = "none"; }
function formatDate(iso) {
var d = new Date(iso);
if (isNaN(d)) return iso || "";
return d.toLocaleDateString(CONFIG.dateLocale, {
month: "short", day: "numeric", year: "numeric"
});
}
function formatAmount(value) {
var num = parseFloat(value);
if (isNaN(num)) return value || "";
return CONFIG.currency + num.toFixed(2);
}
function daysBetween(dateStr) {
var d = new Date(dateStr);
if (isNaN(d)) return Infinity;
var now = new Date();
return Math.floor((now - d) / 86400000);
}
// βββ GET MEMBER βββ
var memberResult;
try {
memberResult = await memberstack.getCurrentMember();
} catch (err) {
console.warn("Memberscript # number221: Could not get member", err);
return;
}
var member = memberResult?.data || memberResult;
if (!member || !member.id) return;
var planConnections = member.planConnections || [];
// βββ DOM REFERENCES βββ
var listContainer = document.querySelector('[data-ms-code="list-container"]');
if (!listContainer) return;
var tableName = listContainer.getAttribute("ms-code-table") || CONFIG.tableName;
var template = listContainer.querySelector('[data-ms-code="list-template"]');
var loadingEl = listContainer.querySelector('[data-ms-code="list-loading"]');
var errorEl = listContainer.querySelector('[data-ms-code="list-error"]');
var emptyEl = listContainer.querySelector('[data-ms-code="list-empty"]');
var paginationEl = listContainer.querySelector('[data-ms-code="list-pagination"]');
var prevBtn = listContainer.querySelector('[data-ms-code="list-prev"]');
var nextBtn = listContainer.querySelector('[data-ms-code="list-next"]');
var pageInfo = listContainer.querySelector('[data-ms-code="list-page-info"]');
var tableHeader = listContainer.querySelector(". proplist_table-header");
var templateWrapper = template ? template.parentElement : null;
var statTotal = document.querySelector('[data-ms-field="total_spent"]');
var statActive = document.querySelector('[data-ms-field="active_count"]');
var statLastDate = document.querySelector('[data-ms-field="last_payment_date"]');
var filterSelects = document.querySelectorAll(". propfilter_select");
var statusFilter = filterSelects[0] || null;
var dateFilter = filterSelects[1] || null;
if (!template) {
console.warn("Memberscript # number221: list-template not found");
return;
}
var templateClone = template.cloneNode(true);
templateClone.removeAttribute("data-ms-code");
hide(template);
hide(listContainer);
if (loadingEl) {
listContainer.parentNode.insertBefore(loadingEl, listContainer);
show(loadingEl);
}
// βββ FETCH PURCHASES βββ
var allRecords = [];
async function fetchPurchases() {
var all = [];
var skip = 0;
var page;
do {
var result = await memberstack.queryDataRecords({
table: tableName,
query: {
where: { [CONFIG.ownerField]: { equals: member.id } },
orderBy: { date: "desc" },
take: 100,
skip: skip
}
});
page = result.data?.records || result.data || [];
all.push.apply(all, page);
skip += page.length;
} while (page.length === 100);
return all;
}
try {
allRecords = await fetchPurchases();
} catch (err) {
console.error("Memberscript # number221: Failed to load purchases", err);
hide(loadingEl);
show(listContainer, "block");
show(errorEl);
return;
}
hide(loadingEl);
hide(errorEl);
hide(emptyEl);
show(listContainer, "block");
// βββ COMPUTE STATS βββ
function computeStats(records) {
var totalSpent = 0;
var activeCount = 0;
var lastDate = null;
records.forEach(function(r) {
var d = r.data || {};
var amount = parseFloat(d.amount) || 0;
var status = (d.status || "").toLowerCase();
if (status === "paid" || status === "active") {
totalSpent += amount;
}
if ((status === "paid" || status === "active") && (d.type || "").toLowerCase() === "subscription") {
activeCount++;
}
if (d.date) {
var dt = new Date(d.date);
if (!isNaN(dt) && (!lastDate || dt > lastDate)) {
lastDate = dt;
}
}
});
if (statTotal) statTotal.textContent = formatAmount(totalSpent);
if (statActive) statActive.textContent = activeCount;
if (statLastDate) {
statLastDate.textContent = lastDate
? formatDate(lastDate.toISOString())
: "N/A";
}
}
var activePlans = planConnections.filter(function(pc) {
return pc.status === "ACTIVE" && pc.type !== "ONETIME" && pc.type !== "FREE";
});
computeStats(allRecords);
if (statActive && activePlans.length > 0) {
var tableActiveCount = parseInt(statActive.textContent) || 0;
var mergedCount = Math.max(tableActiveCount, activePlans.length);
statActive.textContent = mergedCount;
}
// βββ FILTERING βββ
var filteredRecords = allRecords.slice();
function applyFilters() {
var statusVal = statusFilter ? statusFilter.value : "all";
var dateVal = dateFilter ? dateFilter.value : "all";
filteredRecords = allRecords.filter(function(r) {
var d = r.data || {};
var status = (d.status || "").toLowerCase();
if (statusVal !== "all" && status !== statusVal) return false;
if (dateVal !== "all") {
var days = daysBetween(d.date);
if (dateVal === " number30" && days > 30) return false;
if (dateVal === " number90" && days > 90) return false;
if (dateVal === "year" && days > 365) return false;
}
return true;
});
currentPage = 1;
render();
}
if (statusFilter) statusFilter.addEventListener("change", applyFilters);
if (dateFilter) dateFilter.addEventListener("change", applyFilters);
// βββ PAGINATION & RENDER βββ
var currentPage = 1;
function getTotalPages() {
return Math.max(1, Math.ceil(filteredRecords.length / CONFIG.perPage));
}
function render() {
var wrapper = templateWrapper || listContainer;
var existing = wrapper.querySelectorAll(". proplist_row:not([data-ms-code])");
existing.forEach(function(el) { el.remove(); });
var total = filteredRecords.length;
if (total === 0) {
show(emptyEl);
hide(paginationEl);
if (tableHeader) hide(tableHeader);
return;
}
hide(emptyEl);
if (tableHeader) show(tableHeader);
var totalPages = getTotalPages();
if (currentPage > totalPages) currentPage = totalPages;
var start = (currentPage - 1) * CONFIG.perPage;
var end = Math.min(start + CONFIG.perPage, total);
var pageRecords = filteredRecords.slice(start, end);
pageRecords.forEach(function(record) {
var row = templateClone.cloneNode(true);
var data = record.data || {};
var fields = row.querySelectorAll("[data-ms-field]");
fields.forEach(function(el) {
var name = el.getAttribute("data-ms-field");
var type = el.getAttribute("ms-field") || "text";
var value = data[name];
if (name === "status") {
var statusText = (value || "").toString();
el.textContent = statusText.charAt(0).toUpperCase() + statusText.slice(1);
Object.keys(CONFIG.statusClasses).forEach(function(key) {
el.classList.remove(CONFIG.statusClasses[key]);
});
var cls = CONFIG.statusClasses[statusText.toLowerCase()];
if (cls) el.classList.add(cls);
return;
}
if (type === "link" || name === "receipt_url") {
if (value) {
el.href = value;
el.setAttribute("target", "_blank");
el.setAttribute("rel", "noopener noreferrer");
show(el);
} else {
hide(el);
}
return;
}
if (value === undefined || value === null || value === "") {
el.textContent = "";
return;
}
if (type === "date") {
el.textContent = formatDate(value);
} else if (name === "amount") {
el.textContent = formatAmount(value);
} else {
el.textContent = value;
}
});
wrapper.appendChild(row);
});
if (totalPages > 1) {
show(paginationEl);
if (pageInfo) pageInfo.textContent = "Page " + currentPage + " keywordof " + totalPages;
if (prevBtn) prevBtn.disabled = currentPage <= 1;
if (nextBtn) nextBtn.disabled = currentPage >= totalPages;
} else {
hide(paginationEl);
}
}
if (prevBtn) {
prevBtn.addEventListener("click", function() {
if (currentPage > 1) {
currentPage--;
render();
}
});
}
if (nextBtn) {
nextBtn.addEventListener("click", function() {
if (currentPage < getTotalPages()) {
currentPage++;
render();
}
});
}
render();
});
</script>
<script>
document.getElementById("copyButton221").addEventListener("click", function() {
var code = document.getElementById("scriptCode221");
var text = code.textContent || code.innerText;
navigator.clipboard.writeText(text.trim()).then(function() {
var btn = document.getElementById("copyButton221");
btn.textContent = "Copied!";
setTimeout(function() { btn.textContent = "Copy Script"; }, 2000);
});
});
</script>Download the second Make Blueprint here: https://drive.google.com/file/d/1cPtBnsYo7AUdhJflD7V0fRBDEFoFPcL1/view?usp=sharing
Import this into Make.com to get started
More scripts in Data Tables