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:
michilis
2026-08-22 03:44:43 +02:00
co-authored by Cursor
parent 2a9444942b
commit c74c7fc187
66 changed files with 7603 additions and 1241 deletions
+38 -16
View File
@@ -1,9 +1,10 @@
---
import {
federationIdFromSlug, getMintWarnings, mintChip,
type MintCapabilities, type MintListItem,
baseUrlFromKey, federationIdFromSlug, getMintWarnings, mintChip,
type LnurlDetail, type MintCapabilities, type MintListItem,
} from '@cashumints/shared';
import { iconUrl } from '../lib/api';
import { targetPath } from '../lib/feed-resolve';
import { displayDomain, iconGradient, initials, transitionName } from '../lib/format';
import { localePath, useI18n } from '../i18n';
import { formatters, starString } from '../i18n/format';
@@ -28,28 +29,43 @@ interface Props {
* from the detail it already has. Left off, the card simply has no chip.
*/
capabilities?: MintCapabilities | null;
/**
* The LNURL states a chip can be drawn from, for a caller that already has the
* detail. The Cashu counterpart is `capabilities` above, and this is separate rather
* than merged with it because the two ecosystems' chips are read from entirely
* different facts: NUT switches there, an advertised withdraw ceiling and a reachable
* Lightning node here. Left off, an LNURL card simply has no chip.
*/
lnurl?: Pick<LnurlDetail, 'max_withdrawable_msat' | 'funding_available'> | null;
}
const { mint, rank, sentiment, revealDelay, capabilities } = Astro.props;
const { mint, rank, sentiment, revealDelay, capabilities, lnurl } = Astro.props;
const { locale } = pageLocale(Astro);
const t = useI18n(locale);
const f = formatters(t);
/*
* One card, both ecosystems.
* One card, all three ecosystems.
*
* A federation has no URL, so the second line cannot be a domain: `fedimint:aeca6c…`
* is a database key and means nothing to a reader. It shows the federation id instead,
* shortened, which is the thing that actually identifies it — and which is what the
* search box below matches on, so pasting an id finds its federation.
*
* An LNURL mint does have a URL, but its row key carries an `lnurl:` scheme in front of
* it, so the domain comes out of the key rather than off it directly. A reader should
* see `lnurl.21mint.me`, not `lnurl:https://lnurl.21mint.me`.
*
* Everything else on the card is identical, and deliberately: rating, review count,
* sentiment bar and status all mean the same thing for both, and a reader moving
* between /mints and /fedimints should not have to re-learn a card.
* sentiment bar and status all mean the same thing for all three, and a reader moving
* between the three indexes should not have to re-learn a card.
*/
const fedimint = mint.type === 'fedimint';
const domain = fedimint ? federationIdFromSlug(mint.host) : displayDomain(mint.url);
const lnurlBase = baseUrlFromKey(mint.url);
const domain = fedimint
? federationIdFromSlug(mint.host)
: displayDomain(lnurlBase ?? mint.url);
const name = fedimint
? mint.name ?? t('card.unnamedFederation')
: mint.name ?? domain.split('/')[0] ?? domain;
@@ -57,7 +73,9 @@ const icon = iconUrl(mint.icon);
const offline = mint.status === 'offline';
/** Announced on Nostr and never confirmed by any check. Federations only. */
const announced = mint.status === 'announced';
const href = localePath(`${fedimint ? '/fedimint' : '/mint'}/${mint.host}`, locale);
// One route per ecosystem, from the same table the review feed's links use, so a card
// and a feed row can never disagree about where a listing lives.
const href = localePath(targetPath(mint), locale);
/*
Two words at most. Offline already has its own card styling, so only the states a
@@ -65,14 +83,18 @@ const href = localePath(`${fedimint ? '/fedimint' : '/mint'}/${mint.host}`, loca
cannot pay back out, or one that has stopped moving sats in either direction. The
mint page carries the explanation.
*/
const chip = capabilities
? mintChip(
getMintWarnings(
{ status: mint.status, last_online: mint.last_online, capabilities },
warningOptions(f),
),
chipStrings(t),
)
const chipSource = capabilities || lnurl
? {
type: mint.type,
status: mint.status,
last_online: mint.last_online,
capabilities: capabilities ?? null,
...(lnurl ?? {}),
}
: null;
const chip = chipSource
? mintChip(getMintWarnings(chipSource, warningOptions(f)), chipStrings(t))
: null;
const status = statusLabel(mint.status, t);