Ship LNURL mint pages, indexing UI, and reviews rewrite.
Add lnurl list/detail routes, OG fixtures, i18n strings, and the write/ index client flows so the site surfaces the new mint type end to end. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,10 +14,15 @@
|
||||
* The warning logic is imported from shared/, not restated: the image must never
|
||||
* disagree with the page it decorates about whether a mint is frozen or gone.
|
||||
*/
|
||||
import { getMintWarnings, parseLimits, readCapabilities } from '@cashumints/shared';
|
||||
import { getMintWarnings, msatToSat, parseLimits, readCapabilities } from '@cashumints/shared';
|
||||
|
||||
/** Bump when the template's layout or copy changes, to regenerate every image. */
|
||||
export const TEMPLATE_VERSION = 1;
|
||||
/**
|
||||
* Bump when the template's layout or copy changes, to regenerate every image.
|
||||
*
|
||||
* 2: the LNURL variant. Its badge, glow and third cell are new, and the shared
|
||||
* `warningCell` learned three LNURL warning kinds, so every card's hash input moved.
|
||||
*/
|
||||
export const TEMPLATE_VERSION = 2;
|
||||
|
||||
const DAY = 86_400;
|
||||
|
||||
@@ -115,6 +120,11 @@ function warningCell(warning, mint, now) {
|
||||
'offline-long': 'Offline',
|
||||
'offline': 'Offline',
|
||||
'fedimint-offline': 'Reported offline',
|
||||
// LNURL. `lnurl-withdrawals-disabled` gets the same two words as the Cashu
|
||||
// melt-disabled cell, because it is the same statement to a reader.
|
||||
'lnurl-withdrawals-disabled': 'No withdrawals',
|
||||
'lnurl-no-funding': 'No mint / melt',
|
||||
'lnurl-invalid': 'Not responding properly',
|
||||
}[warning.kind];
|
||||
if (!label) return null;
|
||||
|
||||
@@ -135,11 +145,26 @@ function warningCell(warning, mint, now) {
|
||||
case 'frozen':
|
||||
sub = 'Nothing moves in or out';
|
||||
break;
|
||||
case 'lnurl-withdrawals-disabled':
|
||||
sub = 'Notes cannot be redeemed';
|
||||
break;
|
||||
case 'lnurl-no-funding':
|
||||
// The precise half of the story, and the half that stops a reader writing the
|
||||
// mint off: its notes still work, only Lightning is shut.
|
||||
sub = 'Notes still rotate and split';
|
||||
break;
|
||||
case 'lnurl-invalid':
|
||||
sub = 'Endpoint answered with junk';
|
||||
break;
|
||||
}
|
||||
|
||||
// The mint is also offline while a capability warning outranks that fact: the cell
|
||||
// still has to say so, the way the page banner folds it into its last sentence.
|
||||
if (['melt-only', 'melt-disabled', 'frozen'].includes(warning.kind) && mint.status === 'offline' && since !== null) {
|
||||
if (
|
||||
['melt-only', 'melt-disabled', 'frozen', 'lnurl-withdrawals-disabled'].includes(warning.kind) &&
|
||||
mint.status === 'offline' &&
|
||||
since !== null
|
||||
) {
|
||||
sub = `Offline since ${shortDate(since)}`;
|
||||
}
|
||||
|
||||
@@ -227,6 +252,53 @@ export function fedimintModel(fed, { iconHash = null, now = Math.floor(Date.now(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* One LNURL mint's card model.
|
||||
*
|
||||
* The same skeleton as the other two, with the third cell answering this ecosystem's
|
||||
* own question: how much can one of this mint's bearer notes be worth? That is the
|
||||
* withdraw range, in sats, converted here once from the millisatoshi the wire carries.
|
||||
*
|
||||
* `withdraw` is null when the ceiling is zero, and deliberately: a card printing
|
||||
* "max 0 sat" reads as a number rather than as a refusal, and the warning cell that
|
||||
* outranks it says the true thing instead.
|
||||
*/
|
||||
export function lnurlModel(mint, { iconHash = null, now = Math.floor(Date.now() / 1000) } = {}) {
|
||||
const baseUrl = mint.base_url ?? mint.url.replace(/^lnurl:/, '');
|
||||
const domain = displayDomain(baseUrl);
|
||||
const name = (mint.name ?? '').trim() || domain.split('/')[0] || domain;
|
||||
const warning = getMintWarnings(mint, { now })[0] ?? null;
|
||||
const offline = mint.status === 'offline';
|
||||
const offlineDays =
|
||||
offline && mint.last_online !== null
|
||||
? Math.max(0, Math.floor((now - mint.last_online) / DAY))
|
||||
: null;
|
||||
|
||||
const min = msatToSat(mint.min_withdrawable_msat ?? null);
|
||||
const max = msatToSat(mint.max_withdrawable_msat ?? null);
|
||||
|
||||
return {
|
||||
v: TEMPLATE_VERSION,
|
||||
type: 'lnurl',
|
||||
slug: mint.host,
|
||||
name,
|
||||
domain,
|
||||
status: mint.status,
|
||||
offlineDays,
|
||||
rating: mint.rating_avg,
|
||||
reviewCount: mint.review_count,
|
||||
sentiment: sentiment(mint.rating_distribution ?? {}),
|
||||
lastReviewMonth: mint.last_review_at ? monthYear(mint.last_review_at) : null,
|
||||
withdraw: max !== null && max > 0 ? { min, max } : null,
|
||||
/** The LUD-16 address, which is the one string a reader might actually copy. */
|
||||
address: mint.lightning_address ?? null,
|
||||
warning: warningCell(warning, mint, now),
|
||||
iconHash,
|
||||
gradient: iconHash ? null : iconGradient(domain),
|
||||
initials: iconHash ? null : initials(name),
|
||||
};
|
||||
}
|
||||
|
||||
/** The default card: brand, tagline, and the network's own numbers. */
|
||||
export function defaultModel(stats) {
|
||||
return {
|
||||
@@ -235,6 +307,7 @@ export function defaultModel(stats) {
|
||||
slug: 'default',
|
||||
mints: stats.cashu_total,
|
||||
federations: stats.fedimint_total,
|
||||
lnurl: stats.lnurl_total,
|
||||
reviews: stats.reviews_total,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user