feat: SIWE auth, auto-pin, pin search/share, explorer improvements

- SIWE server-side auth (siwe + jose JWT)
- Wallet provider fallback (MAOS -> MetaMask)
- Auto-pin after upload (quick + crypto)
- Pin search/filter in explorer
- Share button (gateway link) per pin
- Copy CID button per pin
- Breadcrumb root navigation fix
- Hydration mismatch fix (mounted state)
- Defensive truncateCid for empty values
- CSP fix (template literal)
- Error boundaries (upload, profile, usage)
- 191 tests passing
This commit is contained in:
maikrolf
2026-09-19 12:25:36 +02:00
parent f72b775379
commit 7c7854e08f
24 changed files with 1349 additions and 125 deletions
+41 -29
View File
@@ -3,6 +3,7 @@
import { useState, useRef, type DragEvent, useCallback, useMemo } from 'react';
import { uploadFile } from '@/lib/api/files';
import { addToHistory, getSettings } from '@/lib/storage';
import { addPin } from '@/lib/api/pins';
import PortalLayout from '@/app/layout-portal';
import { checkUploadAllowed } from '@/lib/limits';
import { useNotify } from '@/lib/notifications';
@@ -12,6 +13,7 @@ import QuickUpload, { type FileEntry } from './components/QuickUpload';
import { PaymentProvider } from '@/lib/payment';
import CryptoUpload from './components/CryptoUpload';
import type { TabMode } from './helpers';
import ErrorBoundary from '@/components/ErrorBoundary';
/* ════════════════════════════ Page ════════════════════════════ */
@@ -143,6 +145,10 @@ export default function UploadPage() {
const result = await uploadFile(entry.file);
const date = new Date().toISOString();
addToHistory({ cid: result.cid, name: entry.file.name, size: result.size, date, method: 'quick' });
// Auto-pin na succesvolle upload
addPin(result.cid, entry.file.name).catch((err) => {
console.warn('[UploadPage] Auto-pin mislukt:', err);
});
setFiles(prev => prev.map(f =>
f.id === entry.id ? { ...f, status: 'done' as const, result, date } : f
));
@@ -177,6 +183,10 @@ export default function UploadPage() {
const r = await uploadFile(cryptoFile);
setCryptoResult(r);
addToHistory({ cid: r.cid, name: cryptoFile.name, size: r.size, date: new Date().toISOString(), method: 'eth' });
// Auto-pin na succesvolle crypto upload
addPin(r.cid, cryptoFile.name).catch((err) => {
console.warn('[UploadPage] Auto-pin crypto mislukt:', err);
});
return r;
}
function onCryptoPaid(_info: { cid: string; txHash?: string; method: string }) {
@@ -201,36 +211,37 @@ export default function UploadPage() {
return (
<PortalLayout>
<div className="mb-8">
<h1 className="text-2xl font-bold text-white">Upload</h1>
<p className="text-sm text-surface-400 mt-1">Upload files to IPFS &mdash; quick or with crypto payment</p>
</div>
<ErrorBoundary label="Upload">
<div className="mb-8">
<h1 className="text-2xl font-bold text-white">Upload</h1>
<p className="text-sm text-surface-400 mt-1">Upload files to IPFS &mdash; quick or with crypto payment</p>
</div>
{/* ── Tab switcher ── */}
<div className="flex gap-1 mb-6 p-1 rounded-xl bg-surface-900/70 border border-surface-800 w-fit">
<button
onClick={() => setTab('quick')}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all ${
tab === 'quick'
? 'bg-brand-500/15 text-brand-400 shadow-sm'
: 'text-surface-400 hover:text-surface-200'
}`}
>
<Zap className="w-4 h-4" />
Quick Upload
</button>
<button
onClick={() => setTab('crypto')}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all ${
tab === 'crypto'
? 'bg-brand-500/15 text-brand-400 shadow-sm'
: 'text-surface-400 hover:text-surface-200'
}`}
>
<ArrowUp className="w-4 h-4" />
Crypto Payment
</button>
</div>
{/* ── Tab switcher ── */}
<div className="flex gap-1 mb-6 p-1 rounded-xl bg-surface-900/70 border border-surface-800 w-fit">
<button
onClick={() => setTab('quick')}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all ${
tab === 'quick'
? 'bg-brand-500/15 text-brand-400 shadow-sm'
: 'text-surface-400 hover:text-surface-200'
}`}
>
<Zap className="w-4 h-4" />
Quick Upload
</button>
<button
onClick={() => setTab('crypto')}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all ${
tab === 'crypto'
? 'bg-brand-500/15 text-brand-400 shadow-sm'
: 'text-surface-400 hover:text-surface-200'
}`}
>
<ArrowUp className="w-4 h-4" />
Crypto Payment
</button>
</div>
{/* Quick Upload tab */}
{tab === 'quick' && (
@@ -275,6 +286,7 @@ export default function UploadPage() {
/>
</PaymentProvider>
)}
</ErrorBoundary>
</PortalLayout>
);
}