From 95c6e9b57b7044e6cb9371010fc256e06ff77116 Mon Sep 17 00:00:00 2001
From: Dan Bryan
Date: Wed, 16 Jul 2025 00:58:10 -0400
Subject: [PATCH 01/21] feat: major UI/UX improvements and API enhancements
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add programmatic URL retrieval API (GET /api/v1/chains/{chainId}/snapshots/latest)
- Add chain metadata API (GET /api/v1/chains/{chainId}/info)
- Implement JWT authentication for API access
- Fix chain card display with real-time countdown timers
- Remove incorrect block height display
- Make cards more compact and functional
- Add chain logos to detail pages
- Fix dark mode toggle for Tailwind v4
- Reduce homepage padding for better information density
- Add enhanced time display (days, hours, minutes)
- Create comprehensive documentation (architecture.md, enhancement.md)
- Add volume snapshot lifecycle management plan
🤖 Generated with Claude Code
Co-Authored-By: Claude
---
API_ROUTES.md | 30 +
CLAUDE.md | 2 +-
Dockerfile | 2 +
README.md | 2 +-
app/(public)/chains/[chainId]/not-found.tsx | 13 +-
app/(public)/chains/[chainId]/page.tsx | 120 +++-
app/api/admin/downloads/route.ts | 56 ++
app/api/debug-snapshots/route.ts | 43 ++
app/api/debug/route.ts | 69 +++
app/api/test-download/route.ts | 55 ++
app/api/test-minio-direct/route.ts | 66 ++
app/api/v1/auth/login/route.ts | 96 +--
app/api/v1/auth/token/route.ts | 52 ++
app/api/v1/chains/[chainId]/download/route.ts | 112 ++--
app/api/v1/chains/[chainId]/info/route.ts | 134 ++++
.../[chainId]/snapshots/latest/route.ts | 184 ++++++
.../v1/chains/[chainId]/snapshots/route.ts | 98 +--
app/api/v1/chains/route.ts | 116 +++-
app/api/v1/downloads/status/route.ts | 52 ++
app/globals.css | 14 +
app/layout.tsx | 16 +
app/page.tsx | 18 +-
architecture.md | 208 +++++++
components/auth/LoginForm.tsx | 18 +-
components/chains/ChainCard.tsx | 46 +-
components/chains/ChainListServer.tsx | 7 +-
components/chains/CountdownTimer.tsx | 53 ++
components/common/ThemeToggle.tsx | 18 +-
components/snapshots/DownloadButton.tsx | 91 ++-
docs/api-reference/authentication.md | 94 ++-
docs/api-reference/endpoints.md | 37 ++
docs/api/latest-snapshot.md | 180 ++++++
enhancement.md | 577 ++++++++++++++++++
lib/auth/jwt.ts | 87 +++
lib/bandwidth/manager.ts | 4 +-
lib/config/index.ts | 4 +
lib/download/tracker.ts | 192 ++++++
lib/minio/client.ts | 32 +-
lib/minio/operations.ts | 86 ++-
lib/types/index.ts | 7 +
lib/utils.ts | 45 ++
logs/combined.log | 0
logs/error.log | 0
package-lock.json | 99 ++-
package.json | 2 +
public/chains/cosmos.png | Bin 242 -> 34886 bytes
public/chains/kujira.png | Bin 0 -> 31155 bytes
public/chains/noble.png | Bin 0 -> 58117 bytes
public/chains/osmosis.png | Bin 242 -> 35293 bytes
public/chains/terra.png | Bin 0 -> 1572 bytes
public/chains/terra2.png | Bin 0 -> 3072 bytes
public/chains/thorchain.png | Bin 0 -> 13760 bytes
scripts/test-api.sh | 77 +++
...c9b-9e6b-ac8658bf64e6_20250715_212740.json | 75 +++
usage_tracking2.json | 4 +
55 files changed, 3070 insertions(+), 323 deletions(-)
create mode 100644 app/api/admin/downloads/route.ts
create mode 100644 app/api/debug-snapshots/route.ts
create mode 100644 app/api/debug/route.ts
create mode 100644 app/api/test-download/route.ts
create mode 100644 app/api/test-minio-direct/route.ts
create mode 100644 app/api/v1/auth/token/route.ts
create mode 100644 app/api/v1/chains/[chainId]/info/route.ts
create mode 100644 app/api/v1/chains/[chainId]/snapshots/latest/route.ts
create mode 100644 app/api/v1/downloads/status/route.ts
create mode 100644 architecture.md
create mode 100644 components/chains/CountdownTimer.tsx
create mode 100644 docs/api/latest-snapshot.md
create mode 100644 enhancement.md
create mode 100644 lib/auth/jwt.ts
create mode 100644 lib/download/tracker.ts
create mode 100644 logs/combined.log
create mode 100644 logs/error.log
create mode 100644 public/chains/kujira.png
create mode 100644 public/chains/noble.png
create mode 100644 public/chains/terra.png
create mode 100644 public/chains/terra2.png
create mode 100644 public/chains/thorchain.png
create mode 100755 scripts/test-api.sh
create mode 100644 uploads/XXXNEW_pro_Europe_Warsaw_4a068ded-8b5b-4c9b-9e6b-ac8658bf64e6_20250715_212740.json
create mode 100644 usage_tracking2.json
diff --git a/API_ROUTES.md b/API_ROUTES.md
index 3279186..e65ca4d 100644
--- a/API_ROUTES.md
+++ b/API_ROUTES.md
@@ -138,6 +138,36 @@ Get available snapshots for a specific chain.
}
```
+### GET /v1/chains/[chainId]/info
+Get metadata and statistics for a specific chain.
+
+**Response:**
+```json
+{
+ "success": true,
+ "data": {
+ "chain_id": "cosmoshub-4",
+ "latest_snapshot": {
+ "height": 19234567,
+ "size": 483183820800,
+ "age_hours": 6
+ },
+ "snapshot_schedule": "every 6 hours",
+ "average_size": 450000000000,
+ "compression_ratio": 0.35
+ }
+}
+```
+
+**Error Response (404):**
+```json
+{
+ "success": false,
+ "error": "Chain not found",
+ "message": "No snapshots found for chain ID invalid-chain"
+}
+```
+
### POST /v1/chains/[chainId]/download
Generate a presigned download URL for a snapshot.
diff --git a/CLAUDE.md b/CLAUDE.md
index b3e5edd..bf0896a 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -213,7 +213,7 @@ mc ls myminio/
## Important Notes
-1. **No Polkachu API** - This replaces the prototype. All data comes from MinIO
+1. **MinIO Storage** - All snapshot data comes from MinIO object storage
2. **BryanLabs Style** - Maintain professional design aesthetic
3. **Performance First** - Optimize for speed and reliability
4. **Security Critical** - Properly implement auth and access controls
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 94219d6..16aac9a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -22,6 +22,8 @@ RUN npm run build
# Production stage
FROM node:20-alpine AS runner
+LABEL org.opencontainers.image.source=https://github.com/bryanlabs/snapshots
+
WORKDIR /app
# Add non-root user
diff --git a/README.md b/README.md
index 5a928e1..7495a14 100644
--- a/README.md
+++ b/README.md
@@ -393,7 +393,7 @@ This project is licensed under the MIT License - see the [LICENSE](./LICENSE) fi
## 🙏 Acknowledgments
- BryanLabs team for infrastructure support
-- Polkachu for snapshot data integration
+- MinIO for object storage and snapshot hosting
- Cosmos ecosystem for blockchain technology
- Open source contributors
diff --git a/app/(public)/chains/[chainId]/not-found.tsx b/app/(public)/chains/[chainId]/not-found.tsx
index 21b19d7..52ee265 100644
--- a/app/(public)/chains/[chainId]/not-found.tsx
+++ b/app/(public)/chains/[chainId]/not-found.tsx
@@ -22,17 +22,8 @@ export default function NotFound() {
We currently support snapshots for these popular Cosmos ecosystem
chains:
-
-
- • Juno
-
• Stargaze
-
-
- • Persistence
-
- • Secret Network
-
• Injective
-
+
+ • Noble
diff --git a/app/(public)/chains/[chainId]/page.tsx b/app/(public)/chains/[chainId]/page.tsx
index 61dcf96..f2d2e1f 100644
--- a/app/(public)/chains/[chainId]/page.tsx
+++ b/app/(public)/chains/[chainId]/page.tsx
@@ -1,8 +1,92 @@
import { notFound } from 'next/navigation';
import Link from 'next/link';
-import { mockChains, mockSnapshots } from '@/lib/mock-data';
+import Image from 'next/image';
import { SnapshotListClient } from '@/components/snapshots/SnapshotListClient';
import type { Metadata } from 'next';
+import { Chain, Snapshot } from '@/lib/types';
+
+// Chain metadata mapping - same as in the API route
+const chainMetadata: Record = {
+ 'noble-1': {
+ name: 'Noble',
+ logoUrl: '/chains/noble.png',
+ },
+ 'cosmoshub-4': {
+ name: 'Cosmos Hub',
+ logoUrl: '/chains/cosmos.png',
+ },
+ 'osmosis-1': {
+ name: 'Osmosis',
+ logoUrl: '/chains/osmosis.png',
+ },
+ 'juno-1': {
+ name: 'Juno',
+ logoUrl: '/chains/juno.png',
+ },
+ 'kaiyo-1': {
+ name: 'Kujira',
+ logoUrl: '/chains/kujira.png',
+ },
+ 'columbus-5': {
+ name: 'Terra Classic',
+ logoUrl: '/chains/terra.png',
+ },
+ 'phoenix-1': {
+ name: 'Terra',
+ logoUrl: '/chains/terra2.png',
+ },
+ 'thorchain-1': {
+ name: 'THORChain',
+ logoUrl: '/chains/thorchain.png',
+ },
+};
+
+async function getChain(chainId: string): Promise {
+ try {
+ // For server-side requests, use internal URL
+ const apiUrl = process.env.NODE_ENV === 'production'
+ ? 'http://webapp:3000' // Internal Kubernetes service URL
+ : (process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000');
+
+ const response = await fetch(`${apiUrl}/api/v1/chains`, {
+ next: { revalidate: 60 }
+ });
+
+ if (!response.ok) {
+ return null;
+ }
+
+ const data = await response.json();
+ const chains = data.success ? data.data : [];
+ return chains.find((chain: Chain) => chain.id === chainId) || null;
+ } catch (error) {
+ console.error('Failed to fetch chain:', error);
+ return null;
+ }
+}
+
+async function getSnapshots(chainId: string): Promise {
+ try {
+ // For server-side requests, use internal URL
+ const apiUrl = process.env.NODE_ENV === 'production'
+ ? 'http://webapp:3000' // Internal Kubernetes service URL
+ : (process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000');
+
+ const response = await fetch(`${apiUrl}/api/v1/chains/${chainId}/snapshots`, {
+ next: { revalidate: 60 }
+ });
+
+ if (!response.ok) {
+ return [];
+ }
+
+ const data = await response.json();
+ return data.success ? data.data : [];
+ } catch (error) {
+ console.error('Failed to fetch snapshots:', error);
+ return [];
+ }
+}
export async function generateMetadata({
params,
@@ -10,7 +94,7 @@ export async function generateMetadata({
params: Promise<{ chainId: string }>;
}): Promise {
const { chainId } = await params;
- const chain = mockChains[chainId as keyof typeof mockChains];
+ const chain = await getChain(chainId);
if (!chain) {
return {
@@ -30,8 +114,8 @@ export default async function ChainDetailPage({
params: Promise<{ chainId: string }>;
}) {
const { chainId } = await params;
- const chain = mockChains[chainId as keyof typeof mockChains];
- const snapshots = mockSnapshots[chainId as keyof typeof mockSnapshots] || [];
+ const chain = await getChain(chainId);
+ const snapshots = await getSnapshots(chainId);
if (!chain) {
notFound();
@@ -55,9 +139,33 @@ export default async function ChainDetailPage({
{/* Header */}
-
-
+
+ {/* Background watermark logo */}
+
+
+
+
+