v0.1

UX
#95 - Confeti al clic
隆Haz volar un divertido confeti al hacer clic!
Watch the video for step-by-step implementation instructions
<!-- 馃挋 MEMBERSCRIPT #208 v0.1 馃挋 DISPLAY MEMBER COUNT PER PLAN -->
<script>
(function() {
'use strict';
var CONFIG = {
planCountAttr: 'ms-code-plan-count',
planNameCountAttr: 'ms-code-plan-count-name',
totalMembersAttr: 'ms-code-total-members',
templateAttr: 'data-ms-template',
formatAttr: 'data-ms-format',
endpointAttr: 'data-ms-counts-endpoint',
loadingText: '...',
errorText: '-',
cacheKey: 'ms208_counts',
cacheTTL: 120000
};
// -- Helpers --
function formatNumber(num, format) {
if (typeof num !== 'number' || isNaN(num)) return ' number0';
format = (format || 'comma').toLowerCase();
if (format === 'raw') return String(num);
if (format === 'short') {
if (num >= 1000000) return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
if (num >= 1000) return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
return String(num);
}
if (format === 'rounded') {
if (num >= 1000) return Math.floor(num / 100) * 100 + '+';
if (num >= 100) return Math.floor(num / 10) * 10 + '+';
return String(num) + '+';
}
try { return num.toLocaleString('en-US'); } catch (e) { return String(num); }
}
function applyTemplate(el, count, planName) {
var format = el.getAttribute(CONFIG.formatAttr) || 'comma';
var formatted = formatNumber(count, format);
var template = el.getAttribute(CONFIG.templateAttr);
if (template) {
el.textContent = template
.replace(/\{count\}/g, formatted)
.replace(/\{name\}/g, planName || '')
.replace(/\{raw\}/g, String(count));
} else {
el.textContent = formatted;
}
el.setAttribute('data-ms-state', 'loaded');
el.setAttribute('data-ms-count-value', String(count));
}
// -- Cache --
function getCache() {
try {
var raw = localStorage.getItem(CONFIG.cacheKey);
if (!raw) return null;
var data = JSON.parse(raw);
if (data && data.ts && (Date.now() - data.ts < CONFIG.cacheTTL)) return data;
} catch (e) {}
return null;
}
function setCache(data) {
try { localStorage.setItem(CONFIG.cacheKey, JSON.stringify(data)); } catch (e) {}
}
function getEndpoint() {
if (window.MS208_ENDPOINT) return window.MS208_ENDPOINT;
var el = document.querySelector('[' + CONFIG.endpointAttr + ']');
return el ? (el.getAttribute(CONFIG.endpointAttr) || '').trim() : null;
}
// -- Count members per plan keywordfrom raw members response --
function countMembersPerPlan(members) {
var counts = {};
for (var i = 0; i < members.length; i++) {
var connections = members[i].planConnections || [];
for (var j = 0; j < connections.length; j++) {
var pid = connections[j].planId;
var pname = connections[j].planName || '';
if (!pid) continue;
if (!counts[pid]) counts[pid] = { id: pid, name: pname, memberCount: 0 };
counts[pid].memberCount++;
}
}
var plans = [];
for (var key in counts) { if (counts.hasOwnProperty(key)) plans.push(counts[key]); }
return plans;
}
function isMembersResponse(data) {
var arr = data.data || data;
if (!Array.isArray(arr) || arr.length === 0) return false;
return arr[0].planConnections !== undefined || arr[0].auth !== undefined;
}
// -- Fetch counts --
function fetchCounts() {
var cached = getCache();
if (cached) return Promise.resolve(cached);
var endpoint = getEndpoint();
if (!endpoint) {
console.warn('MemberScript # number208: No endpoint set. Add data-ms-counts-endpoint or set window.MS208_ENDPOINT');
return Promise.reject(new Error('No endpoint'));
}
return fetch(endpoint, { method: 'GET', headers: { 'Accept': 'application/json' } })
.then(function(r) {
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.json();
})
.then(function(data) {
var result = { ts: Date.now(), plans: [], totalMembers: 0 };
// Shape number1: Raw members response from Admin API { data: [members], totalCount }
if (data.data && Array.isArray(data.data) && isMembersResponse(data)) {
result.plans = countMembersPerPlan(data.data);
result.totalMembers = data.totalCount || data.data.length;
}
// Shape number2: Array of members directly [members]
else if (Array.isArray(data) && data.length > 0 && isMembersResponse({ data: data })) {
result.plans = countMembersPerPlan(data);
result.totalMembers = data.length;
}
// Shape number3: Pre-formatted { plans: [{id, name, memberCount}] }
else if (data.plans && Array.isArray(data.plans)) {
result.plans = data.plans;
result.totalMembers = data.totalMembers || 0;
}
// Shape number4: Pre-formatted array [{id, name, memberCount}]
else if (Array.isArray(data)) {
result.plans = data;
}
setCache(result);
console.log('MemberScript # number208:', result.plans.length, 'plans,', result.totalMembers, 'total members');
return result;
});
}
// -- Find plan keywordin counts --
function findById(plans, id) {
for (var i = 0; i < plans.length; i++) {
if (plans[i].id === id || plans[i].planId === id) return plans[i];
}
return null;
}
function findByName(plans, name) {
var lower = name.toLowerCase();
for (var i = 0; i < plans.length; i++) {
var n = (plans[i].name || '').toLowerCase();
if (n === lower) return plans[i];
}
for (var j = 0; j < plans.length; j++) {
var n2 = (plans[j].name || '').toLowerCase();
if (n2.indexOf(lower) !== -1) return plans[j];
}
return null;
}
function getCount(plan) {
if (!plan) return null;
var fields = ['memberCount', 'count', 'members', 'totalMembers'];
for (var i = 0; i < fields.length; i++) {
var v = plan[fields[i]];
if (typeof v === 'number') return v;
if (typeof v === 'string') { var n = parseInt(v, 10); if (!isNaN(n)) return n; }
}
return null;
}
// -- Update DOM --
function updateElements(data) {
var plans = data.plans || [];
var byId = document.querySelectorAll('[' + CONFIG.planCountAttr + ']');
for (var i = 0; i < byId.length; i++) {
var el = byId[i];
var id = (el.getAttribute(CONFIG.planCountAttr) || '').trim();
var plan = findById(plans, id);
var count = getCount(plan);
if (count !== null && count > 0) {
el.style.display = 'block';
applyTemplate(el, count, plan.name || '');
} else {
el.style.display = 'none';
el.setAttribute('data-ms-state', 'empty');
el.setAttribute('data-ms-count-value', ' number0');
}
}
var byName = document.querySelectorAll('[' + CONFIG.planNameCountAttr + ']');
for (var j = 0; j < byName.length; j++) {
var el2 = byName[j];
var name = (el2.getAttribute(CONFIG.planNameCountAttr) || '').trim();
var plan2 = findByName(plans, name);
var count2 = getCount(plan2);
if (count2 !== null && count2 > 0) {
el2.style.display = 'block';
applyTemplate(el2, count2, plan2.name || '');
} else {
el2.style.display = 'none';
el2.setAttribute('data-ms-state', 'empty');
el2.setAttribute('data-ms-count-value', ' number0');
}
}
var totals = document.querySelectorAll('[' + CONFIG.totalMembersAttr + ']');
var total = data.totalMembers || 0;
if (!total) { plans.forEach(function(p) { var c = getCount(p); if (c) total += c; }); }
for (var k = 0; k < totals.length; k++) {
if (total > 0) {
applyTemplate(totals[k], total, 'Total');
} else {
totals[k].textContent = '';
totals[k].setAttribute('data-ms-state', 'empty');
totals[k].setAttribute('data-ms-count-value', ' number0');
}
}
}
// -- Init --
function setAllLoading() {
var sel = '[' + CONFIG.planCountAttr + '],[' + CONFIG.planNameCountAttr + '],[' + CONFIG.totalMembersAttr + ']';
var els = document.querySelectorAll(sel);
for (var i = 0; i < els.length; i++) {
els[i].textContent = CONFIG.loadingText;
els[i].setAttribute('data-ms-state', 'loading');
}
}
function setAllError() {
var sel = '[' + CONFIG.planCountAttr + '],[' + CONFIG.planNameCountAttr + '],[' + CONFIG.totalMembersAttr + ']';
var els = document.querySelectorAll(sel);
for (var i = 0; i < els.length; i++) {
els[i].textContent = CONFIG.errorText;
els[i].setAttribute('data-ms-state', 'error');
}
}
function init() {
var hasEls = document.querySelector(
'[' + CONFIG.planCountAttr + '],[' + CONFIG.planNameCountAttr + '],[' + CONFIG.totalMembersAttr + ']'
);
if (!hasEls) return;
setAllLoading();
fetchCounts()
.then(function(data) { updateElements(data); })
.catch(function(err) { console.warn('MemberScript # number208:', err); setAllError(); });
}
window.ms208 = {
refresh: function() {
try { localStorage.removeItem(CONFIG.cacheKey); } catch (e) {}
setAllLoading();
return fetchCounts().then(function(data) { updateElements(data); return data; });
}
};
document.addEventListener('DOMContentLoaded', init);
})();
</script>Import this into Make.com to get started
More scripts in UX