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:
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user