SIWE auth, API proxy fixes, dashboard, login, usage pages

- SIWE auth flow: challenge/login/logout/me API routes + JWT middleware
- Fixed [...path] catch-all: Kubo proxy with matchRoute() + mapToKuboAPI()
- Fixed session_id → nonce field mismatch in AuthProvider
- Fixed dashboard pins crash: Kubo returns {Keys: {...}}, not array
- Added middleware route guard (307 redirect to /login)
- Added login, register, profile, usage, upload pages
- Added dashboard components (StorageGauge, FreeTierProgress)
- Added SWR hooks + AuthGuard + Providers + Toast/ErrorBoundary
- Added payment integration (smart contract ABI, tiers, tokens)
- Fixed IPNS key listing: Name/Id → name/id mapping
- Added PWA support (manifest, service worker, icons)
- Added tests for helpers, limits, search-index, wallet
This commit is contained in:
maikrolf
2026-06-28 18:31:05 +02:00
parent 1ddc89479c
commit c9432a16ba
84 changed files with 6679 additions and 1426 deletions
+67 -2
View File
@@ -2,7 +2,7 @@
import { useState, useEffect, useCallback } from 'react';
import PortalLayout from '@/app/layout-portal';
import { explorerLs, explorerCat } from '@/lib/api';
import { explorerLs, explorerCat, listPins } from '@/lib/api';
import type { IPFSEntry } from '@/lib/api';
import CIDInput from './components/CIDInput';
import DirectoryListing from './components/DirectoryListing';
@@ -10,7 +10,10 @@ import FilePreview from './components/FilePreview';
import Breadcrumbs from './components/Breadcrumbs';
import PinBadge from './components/PinBadge';
import GatewayLink from './components/GatewayLink';
import { Search, AlertCircle, RefreshCw } from 'lucide-react';
import SearchBar from '@/components/SearchBar';
import type { SearchResult } from '@/lib/search-index';
import { isTextFile, extractText, addToIndex, clearIndex, getIndexStats } from '@/lib/search-index';
import { Search, AlertCircle, RefreshCw, Database, Loader2 } from 'lucide-react';
const RECENT_STORAGE_KEY = 'ipfs-explorer-recent-cids';
@@ -52,6 +55,8 @@ export default function ExplorerPage() {
const [error, setError] = useState('');
const [pins, setPins] = useState<string[]>([]);
const [recentCids] = useState<string[]>(loadRecentCids);
const [indexStats, setIndexStats] = useState(getIndexStats);
const [rebuilding, setRebuilding] = useState(false);
// Load pins from the pins page (shared localStorage or just track in-memory)
function handlePinToggle(cid: string) {
@@ -122,6 +127,46 @@ export default function ExplorerPage() {
if (cid) navigateTo(cid);
}
/* ── Search ── */
function handleSearchResult(result: SearchResult) {
navigateTo(result.entry.cid);
}
async function rebuildSearchIndex() {
setRebuilding(true);
try {
const pinsList = await listPins();
clearIndex();
let indexed = 0;
let failed = 0;
for (const pin of pinsList) {
if (isTextFile(pin.name) || !pin.name) {
try {
const text = await explorerCat(pin.cid);
const extracted = extractText(text);
if (extracted.length > 0) {
addToIndex({
cid: pin.cid,
name: pin.name || pin.cid.slice(0, 20),
type: 'file',
text: extracted,
size: text.length,
indexedAt: Date.now(),
});
indexed++;
}
} catch {
failed++;
}
}
}
setIndexStats({ entries: indexed, totalChars: 0, lastIndexed: Date.now() });
} finally {
setRebuilding(false);
}
}
const isPinned = previewCid ? pins.includes(previewCid) : false;
return (
@@ -134,6 +179,26 @@ export default function ExplorerPage() {
</p>
</div>
{/* Search + Index rebuild */}
<div className="mb-4 flex items-start gap-3">
<div className="flex-1">
<SearchBar onSelect={handleSearchResult} placeholder="Search indexed files…" />
</div>
<button
onClick={rebuildSearchIndex}
disabled={rebuilding}
className="flex items-center gap-1.5 px-3 py-2.5 rounded-xl bg-surface-900 border border-surface-700 text-xs text-surface-400 hover:text-surface-200 hover:border-surface-600 transition-colors disabled:opacity-50 shrink-0"
title={`${indexStats.entries} entries indexed`}
>
{rebuilding ? (
<Loader2 className="w-3.5 h-3.5 animate-spin" />
) : (
<Database className="w-3.5 h-3.5" />
)}
<span className="hidden sm:inline">Index</span>
</button>
</div>
{/* CID input */}
<div className="mb-6">
<CIDInput onNavigate={navigateTo} />