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:
@@ -16,8 +16,9 @@
|
||||
* there is nothing to translate in a lookup anyway.
|
||||
*/
|
||||
import {
|
||||
federationIdFromKey, federationIdFromSlug, mintUrlsFromEvent, normalizeMintUrl,
|
||||
reviewEcosystem, reviewTargetId, type MintListItem, type NostrEventLike,
|
||||
baseUrlFromKey, federationIdFromKey, federationIdFromSlug, hostIdentifier, mintUrlsFromEvent,
|
||||
normalizeMintUrl, reviewEcosystem, reviewTargetId,
|
||||
type MintListItem, type NostrEventLike,
|
||||
} from '@cashumints/shared';
|
||||
import { displayDomain } from './format';
|
||||
|
||||
@@ -30,13 +31,14 @@ export interface FeedTarget {
|
||||
name: string;
|
||||
/** The label a card prints: a domain, or a shortened federation id. */
|
||||
domain: string;
|
||||
/** `cashu` or `fedimint`. Picks both the resolver and the link's route. */
|
||||
/** `cashu`, `fedimint` or `lnurl`. Picks both the resolver and the link's route. */
|
||||
type: string;
|
||||
/**
|
||||
* The `d` identifier reviews of this thing carry, when that is how it is found.
|
||||
*
|
||||
* A federation id. null for a mint, which is resolved through its `u` tag instead —
|
||||
* that is how every legacy review points at one, and changing it would strand them.
|
||||
* A federation id, or an LNURL mint's normalized host. null for a Cashu mint, which is
|
||||
* resolved through its `u` tag instead — that is how every legacy review points at
|
||||
* one, and changing it would strand them.
|
||||
*/
|
||||
ref: string | null;
|
||||
}
|
||||
@@ -74,6 +76,29 @@ export function resolveFeedTarget(event: NostrEventLike, index: FeedIndex): Feed
|
||||
return d ? index.byRef.get(d.toLowerCase()) ?? null : null;
|
||||
}
|
||||
|
||||
/*
|
||||
* LNURL resolves by `d` and then by `u`, because both are genuinely used.
|
||||
*
|
||||
* The `d` a review carries is either the mint's funding-node pubkey or its host, and
|
||||
* the feed index only knows the host — the list payload carries no pubkey. So a
|
||||
* pubkey-`d` review falls through to `u`, which an LNURL review can actually be
|
||||
* resolved by: unlike a federation's opaque invite code, it is a real URL that the
|
||||
* same normalizer the Cashu branch uses can turn into a row key.
|
||||
*/
|
||||
if (ecosystem === 'lnurl') {
|
||||
const d = reviewTargetId(event);
|
||||
const byRef = d ? index.byRef.get(d.trim().toLowerCase()) : undefined;
|
||||
if (byRef) return byRef;
|
||||
|
||||
for (const raw of mintUrlsFromEvent(event)) {
|
||||
const normalized = normalizeMintUrl(raw);
|
||||
if (!normalized) continue;
|
||||
const found = index.byUrl.get(`lnurl:${normalized.url}`);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ecosystem !== 'cashu') return null;
|
||||
|
||||
const raw = mintUrlsFromEvent(event)[0];
|
||||
@@ -83,9 +108,21 @@ export function resolveFeedTarget(event: NostrEventLike, index: FeedIndex): Feed
|
||||
return index.byUrl.get(normalized.url) ?? null;
|
||||
}
|
||||
|
||||
/** The page a card links to. One route per ecosystem, decided in one place. */
|
||||
/**
|
||||
* The page a card links to. One route per ecosystem, decided in one place.
|
||||
*
|
||||
* The default is `/mint/`, not the newest ecosystem: a row of some type this build has
|
||||
* never heard of is far more likely to be an older one, and every row written before
|
||||
* `mints.type` existed means Cashu.
|
||||
*/
|
||||
const ROUTES: Record<string, string> = {
|
||||
fedimint: '/fedimint',
|
||||
lnurl: '/lnurl-mint',
|
||||
cashu: '/mint',
|
||||
};
|
||||
|
||||
export function targetPath(target: Pick<FeedTarget, 'type' | 'host'>): string {
|
||||
return `/${target.type === 'fedimint' ? 'fedimint' : 'mint'}/${target.host}`;
|
||||
return `${ROUTES[target.type] ?? '/mint'}/${target.host}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,14 +140,27 @@ export function targetPath(target: Pick<FeedTarget, 'type' | 'host'>): string {
|
||||
export function feedTargets(...lists: Array<readonly MintListItem[]>): FeedTarget[] {
|
||||
return lists.flat().map((item) => {
|
||||
const federationId = federationIdFromKey(item.url);
|
||||
const domain = federationId ? federationIdFromSlug(item.host) : displayDomain(item.url);
|
||||
const lnurlBase = baseUrlFromKey(item.url);
|
||||
|
||||
/*
|
||||
* An LNURL row's key is `lnurl:https://host`, so the label has to come out of the
|
||||
* key rather than off `item.url` directly — `displayDomain` on the raw key would
|
||||
* print the scheme prefix at a reader. Its `ref` is the host identifier, which is
|
||||
* the `d` a review carries whenever the mint has no funding-node pubkey; reviews
|
||||
* that carry the pubkey instead resolve through `u`, which is why this does not
|
||||
* need one.
|
||||
*/
|
||||
const domain = federationId
|
||||
? federationIdFromSlug(item.host)
|
||||
: displayDomain(lnurlBase ?? item.url);
|
||||
|
||||
return {
|
||||
url: item.url,
|
||||
host: item.host,
|
||||
name: item.name ?? domain.split('/')[0] ?? domain,
|
||||
domain,
|
||||
type: item.type,
|
||||
ref: federationId,
|
||||
ref: federationId ?? (lnurlBase ? hostIdentifier(lnurlBase) : null),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user