import { mountLogo } from "./ds/logo.js";
const app = document.querySelector("#app"),
npub = new URLSearchParams(location.search).get("npub")?.trim() || "";
const esc = (value) =>
String(value ?? "").replace(
/[&<>"']/g,
(char) =>
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[
char
],
);
const compact = (value) =>
new Intl.NumberFormat(undefined, {
notation: "compact",
maximumFractionDigits: 1,
}).format(Number(value || 0));
const sats = (value) => `${Number(value || 0).toLocaleString()} sats`,
euro = (value) =>
new Intl.NumberFormat(undefined, {
style: "currency",
currency: "EUR",
}).format(Number(value || 0) / 100),
pct = (value) => (value == null ? "—" : `${(value * 100).toFixed(1)}%`);
function chart(rows) {
const w = 900,
h = 250,
p = 18,
max = Math.max(
1,
...rows.flatMap((row) => [row.pageViews, row.interactions]),
);
const points = (key) =>
rows
.map(
(row, i) =>
`${p + (i / Math.max(1, rows.length - 1)) * (w - p * 2)},${h - p - (Number(row[key] || 0) / max) * (h - p * 2)}`,
)
.join(" ");
return `
${esc(rows[0]?.date || "")} ${esc(rows.at(-1)?.date || "")}
`;
}
function render(data) {
const m = data.merchant,
a = data.analytics || {};
document.title = `${m.name} · Stats`;
app.innerHTML = `Merchant intelligence · ${data.periodDays} days
${esc(m.name)} ${esc(m.npub)}
Back to Business ↙ ${[
["Routed views", compact(a.pageViews)],
["Interactions", compact(a.interactions)],
["Engagement", pct(a.engagementRate)],
["Revenue", euro(a.revenueCents)],
["Settled sats", sats(a.paymentSats)],
["Paid", a.paidPayments || 0],
]
.map(
([label, value]) =>
`${label} ${value} `,
)
.join(
"",
)} Audience & intent
What moves people. Views Interactions
${chart(a.daily || [])}Business outcomes
From attention to action. ${[
["Orders", a.orders || 0],
["Bookings", a.bookings || 0],
["Payment attempts", a.paymentAttempts || 0],
["Payment conversion", pct(a.paymentConversionRate)],
["Public tips", sats(m.publicTipSats)],
]
.map(
([label, value]) =>
`${label} ${value}
`,
)
.join(
"",
)}Top actions
What customers do. ${a.actions?.length ? a.actions.map((item) => `
${esc(item.action.replaceAll("_", " "))} ${item.count}
`).join("") : '
No tracked interactions yet. Collection starts with the analytics release.
'}
Modules
Product footprint. ${m.modules?.map((item) => `
${esc(item.id)} ${item.enabled ? "On" : "Off"} · ${esc(item.status)}
`).join("") || '
No Business modules configured.
'}
${esc(data.notes?.traffic || "")} `;
document
.querySelectorAll("[data-logo]")
.forEach((node) => mountLogo(node, { icon: true }));
}
function fail(message) {
app.innerHTML = ``;
}
if (!npub) fail("Add a merchant npub to open its stats.");
else
fetch(
`https://justworks.cash/api/merchant-stats?npub=${encodeURIComponent(npub)}`,
{ cache: "no-store" },
)
.then(async (response) => {
const data = await response.json();
if (!response.ok) throw new Error(data.error || "Stats unavailable");
render(data);
})
.catch((error) => fail(error.message));