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:
@@ -3,7 +3,10 @@ import Base from '../../layouts/Base.astro';
|
||||
import ColorBends from '../../components/ColorBends.astro';
|
||||
import MintCard from '../../components/MintCard.astro';
|
||||
import PulseTicker from '../../components/PulseTicker.astro';
|
||||
import { fetchFedimint, fetchFedimints, fetchHealth, fetchMint, fetchMints, fetchStats } from '../../lib/api';
|
||||
import {
|
||||
fetchFedimint, fetchFedimints, fetchHealth, fetchLnurlMint, fetchLnurlMints, fetchMint,
|
||||
fetchMints, fetchStats,
|
||||
} from '../../lib/api';
|
||||
import { fetchLatestReviews } from '../../lib/nostr-build';
|
||||
import { feedTargets, targetPath } from '../../lib/feed-resolve';
|
||||
import { displayDomain, iconGradient, sentiment, shortNpub } from '../../lib/format';
|
||||
@@ -23,23 +26,27 @@ const { locale } = Astro.props;
|
||||
const t = useI18n(locale);
|
||||
const f = formatters(t);
|
||||
|
||||
const [mints, federations, stats, health] = await Promise.all([
|
||||
const [mints, federations, lnurlMints, stats, health] = await Promise.all([
|
||||
fetchMints(),
|
||||
fetchFedimints(),
|
||||
fetchLnurlMints(),
|
||||
fetchStats(),
|
||||
fetchHealth(),
|
||||
]);
|
||||
|
||||
const top = mints.slice(0, 6);
|
||||
const topFederations = federations.slice(0, 6);
|
||||
const topLnurl = lnurlMints.slice(0, 6);
|
||||
|
||||
/*
|
||||
* The reviews strip resolves against both ecosystems, because its heading is "latest
|
||||
* The reviews strip resolves against every ecosystem, because its heading is "latest
|
||||
* reviews" and not "latest mint reviews". Federations rarely reach it in practice — the
|
||||
* junk filter wants a written body and nearly every 38173 review is a bare `[5/5]` —
|
||||
* but when one does, it is genuinely among the latest and it links to its own page.
|
||||
* but when one does, it is genuinely among the latest and it links to its own page. The
|
||||
* same is true of an LNURL mint, which today has the further handicap that 38174 is a
|
||||
* proposed kind with barely any events on the network at all.
|
||||
*/
|
||||
const reviewTargets = feedTargets(mints, federations);
|
||||
const reviewTargets = feedTargets(mints, federations, lnurlMints);
|
||||
|
||||
/*
|
||||
* The mint details and the relay read have nothing to say to each other — the reviews
|
||||
@@ -50,10 +57,11 @@ const reviewTargets = feedTargets(mints, federations);
|
||||
* few enough that they are all real, recent, written reviews. Anything the relays
|
||||
* cannot fill is simply fewer cards, and the carousel's controls disappear with them.
|
||||
*/
|
||||
const [details, fediDetails, latest] = await Promise.all([
|
||||
const [details, fediDetails, lnurlDetails, latest] = await Promise.all([
|
||||
// Distributions give the card sentiment bars real data instead of a rating proxy.
|
||||
Promise.all(top.map((m) => fetchMint(m.host).catch(() => null))),
|
||||
Promise.all(topFederations.map((federation) => fetchFedimint(federation.host).catch(() => null))),
|
||||
Promise.all(topLnurl.map((mint) => fetchLnurlMint(mint.host).catch(() => null))),
|
||||
fetchLatestReviews(reviewTargets, 10),
|
||||
]);
|
||||
|
||||
@@ -63,6 +71,20 @@ const capabilities = details.map((d) => (d ? readCapabilities(d.info?.nuts) : nu
|
||||
// No capability read for federations: they publish no NUT switches to draw a chip from.
|
||||
const fediSentiments = fediDetails.map((d) => (d ? sentiment(d.rating_distribution) : undefined));
|
||||
|
||||
const lnurlSentiments = lnurlDetails.map((d) => (d ? sentiment(d.rating_distribution) : undefined));
|
||||
/*
|
||||
* An LNURL card's chip comes from two probed facts rather than from NUT switches: an
|
||||
* advertised withdraw ceiling of zero, and a Lightning node the mint cannot reach.
|
||||
*/
|
||||
const lnurlChips = lnurlDetails.map((d) =>
|
||||
d
|
||||
? {
|
||||
max_withdrawable_msat: d.max_withdrawable_msat ?? null,
|
||||
funding_available: d.funding_available ?? null,
|
||||
}
|
||||
: null,
|
||||
);
|
||||
|
||||
const description = t('home.description', {
|
||||
mints: stats.mints_total,
|
||||
reviews: stats.reviews_total,
|
||||
@@ -191,6 +213,37 @@ const signedBody = t('home.why.signed.body', {
|
||||
)
|
||||
}
|
||||
|
||||
{/*
|
||||
The LNURL grid: the third ecosystem strip, in the same order the nav lists them.
|
||||
Same ranked cards as /lnurl-mints, same real sentiment bars, and the same chip rule
|
||||
— so a reader moving down the page meets one card three times rather than three
|
||||
cards once each.
|
||||
*/}
|
||||
{
|
||||
topLnurl.length > 0 && (
|
||||
<section class="section" aria-labelledby="top-lnurl">
|
||||
<div class="sec-head" data-reveal>
|
||||
<h2 class="sec-title" id="top-lnurl">{t('home.topLnurl.title')}</h2>
|
||||
<span class="sec-sub">{t('home.topLnurl.sub')}</span>
|
||||
<a class="sec-link" href={localePath('/lnurl-mints', locale)}>
|
||||
{t('home.topLnurl.all', { n: stats.lnurl_total })}
|
||||
</a>
|
||||
</div>
|
||||
<div class="mint-grid">
|
||||
{topLnurl.map((mint, i) => (
|
||||
<MintCard
|
||||
mint={mint}
|
||||
rank={i + 1}
|
||||
sentiment={lnurlSentiments[i]}
|
||||
lnurl={lnurlChips[i]}
|
||||
revealDelay={i * 50}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
<section class="section" aria-labelledby="latest-reviews-title" id="latest-reviews">
|
||||
<div class="sec-head" data-reveal>
|
||||
<h2 class="sec-title" id="latest-reviews-title">{t('home.latest.title')}</h2>
|
||||
|
||||
Reference in New Issue
Block a user