diff --git a/.env.example b/.env.example index 7d8e06d..af9ab2d 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,14 @@ FAIRCOIN_RPC_PORT=46373 FAIRCOIN_RPC_SCHEME=http # MongoDB Configuration +# Both Mongoose and the native cache client use the same db name: +# MONGODB_DB_NAME if set, otherwise the path segment of MONGODB_URI. MONGODB_URI=mongodb://localhost:27017/faircoin-explorer +# MONGODB_DB_NAME=faircoin-explorer + +# Vite dev proxy target (optional). Defaults to http://localhost:8080 to match +# the API server PORT below. Set when pointing the SPA at a remote API. +# VITE_API_TARGET=http://localhost:8080 # WebSocket Configuration WEBSOCKET_ENABLED=true diff --git a/README.md b/README.md index 2e7b802..bdc8b01 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ Behind a reverse proxy (e.g. nginx/Cloudflare), set `TRUSTED_PROXY_CIDRS` to the The Express server exposes a read-only JSON API under `/api`: - `GET /api/blocks`, `/api/block/:hashOrHeight`, `/api/blockcount` +- `GET /api/transactions` (paginated recent txs from mempool + recent blocks) - `GET /api/transaction/:txid`, `POST /api/tx/broadcast` - `GET /api/address/:address`, `/api/address/:address/txs?page=&limit=`, `/api/address/:address/utxos` (requires a node with `addressindex` for full data) - `GET /api/mempool`, `/api/masternodes`, `/api/peers`, `/api/stats`, `/api/network-info`, `/api/mining-info` @@ -138,6 +139,7 @@ Only key generation is done in-process (using the audited `@noble/curves` secp25 ## Features - Dashboard with live blocks, mempool, price and network stats +- Global recent transactions feed, charts/analytics, and tools (broadcast, address validator, API docs) - Block, transaction and address pages (with paginated address history when the node has `addressindex`) - Masternode list and reward stats, peers, network status - Universal search (height, hash, txid, address) diff --git a/index.html b/index.html index d5f6196..ce50112 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@
- +{t('overviewBody')}
+{t('networkNote')}
+{t('rateLimitNote')}
+{endpoint.path}
+ + {t(`endpoints.${endpoint.descriptionKey}`)} +
+{t('filterPageOnly')}
+ ) : null}{t('successBody')}
+ + {state.txid} + + +{state.message}
+{t('statsError')}
+ +{t('priceError')}
+ ) : priceLoading ? ( +{t('noPriceHistory')}
+ ) : ( +{hint}
: null} + {data.length < 2 ? ( +{empty}
+ ) : ( +- {this.state.error?.message ?? 'An unexpected error occurred.'} + {this.state.error?.message ?? t('errorBoundary.fallback')}
{t('githubReleased', { - when: formatDistanceToNow(new Date(release.publishedAt), { addSuffix: true }), + when: formatDistanceToNow(new Date(release.publishedAt), { + addSuffix: true, + locale: DATE_FNS_LOCALES[locale], + }), })}
diff --git a/src/components/home/latest-tx-list.tsx b/src/components/home/latest-tx-list.tsx index 6c6e0d4..90294b4 100644 --- a/src/components/home/latest-tx-list.tsx +++ b/src/components/home/latest-tx-list.tsx @@ -1,10 +1,10 @@ import { Link } from 'react-router-dom' -import { formatDistanceToNow } from 'date-fns' import { Receipt } from 'lucide-react' import { useTranslations } from '@/lib/i18n' import { useLatestTransactions, type RecentBlock } from '@/hooks/use-recent-blocks' import { Skeleton } from '@/components/ui/skeleton' import { CopyButton } from '@/components/copy-button' +import { RelativeTime } from '@/components/detail/relative-time' import { shortHash } from '@/lib/format' interface LatestTxListProps { @@ -55,7 +55,7 @@ export function LatestTxList({ blocks, isLoading, isError, max = 20 }: LatestTxL{t('emptyDescription')}
+ ) : ( +{t('feeHistogramHint')}
+