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:
+41
-29
@@ -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 — 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 — 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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user