Expand ecash explorer capabilities

Add Fedimint discovery, dual SQLite/Postgres storage, richer review handling, and generated social imagery.
This commit is contained in:
michilis
2026-08-21 02:10:48 +02:00
parent aa1771ea20
commit 6f17b572b1
80 changed files with 7580 additions and 704 deletions
+40 -12
View File
@@ -1,5 +1,8 @@
---
import { getMintWarnings, mintChip, type MintCapabilities, type MintListItem } from '@cashumints/shared';
import {
federationIdFromSlug, getMintWarnings, mintChip,
type MintCapabilities, type MintListItem,
} from '@cashumints/shared';
import { iconUrl } from '../lib/api';
import { displayDomain, iconGradient, initials, transitionName } from '../lib/format';
import { localePath, useI18n } from '../i18n';
@@ -33,10 +36,28 @@ const { locale } = pageLocale(Astro);
const t = useI18n(locale);
const f = formatters(t);
const domain = displayDomain(mint.url);
const name = mint.name ?? domain.split('/')[0] ?? domain;
/*
* One card, both 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.
*
* 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.
*/
const fedimint = mint.type === 'fedimint';
const domain = fedimint ? federationIdFromSlug(mint.host) : displayDomain(mint.url);
const name = fedimint
? mint.name ?? t('card.unnamedFederation')
: mint.name ?? domain.split('/')[0] ?? domain;
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);
/*
Two words at most. Offline already has its own card styling, so only the states a
@@ -87,7 +108,7 @@ const bar =
*/}
<a
class:list={['mint-card', { 'is-offline': offline }]}
href={localePath(`/mint/${mint.host}`, locale)}
href={href}
data-reveal
data-reveal-delay={revealDelay}
data-mint-card
@@ -126,7 +147,7 @@ const bar =
}
<span class="mc-id">
<span class="mc-name" data-vt-name={transitionName('name', mint.host)}>{name}</span>
<span class="mc-domain">{domain}</span>
<span class:list={['mc-domain', { mono: fedimint }]}>{domain}</span>
</span>
{rank !== undefined && <span class:list={['mc-rank', { gold: rank === 1 }]}>#{rank}</span>}
</div>
@@ -164,13 +185,20 @@ const bar =
{chip && <span class:list={['mc-chip', chip.severity]}>{chip.label}</span>}
<span class="last">
{
offline
? mint.last_online
? t('card.lastSeen', { when: f.relative(mint.last_online) })
: t('card.neverSeen')
: mint.last_review_at
? t('card.reviewed', { when: f.relative(mint.last_review_at) })
: t('card.noReviews')
/*
"Announced" has no "last seen" to print, and printing one anyway is exactly
the fake status this site refuses to show. What it has instead is the fact
that nothing has checked it, said in as many words.
*/
announced
? t('card.notChecked')
: offline
? mint.last_online
? t('card.lastSeen', { when: f.relative(mint.last_online) })
: t('card.neverSeen')
: mint.last_review_at
? t('card.reviewed', { when: f.relative(mint.last_review_at) })
: t('card.noReviews')
}
</span>
</div>