forked from maik/IPFS-portal
fix: bw/stats 404, gateway links, history crash
- swr.ts: remove useBandwidth(), drop bw from useDashboard() - events/route.ts: skip bandwidth SSE (Kubo 0.28.0 unsupported) - usage/page.tsx: remove getBWStats call + bw references - helpers.ts: gatewayLink -> ipfs.maos.dedyn.io (subdomain only) - upload/page.tsx: fix local gatewayLink (subdomain only) - storage.ts: update default gatewayUrl, add defensive getHistory()
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { type DragEvent, type RefObject } from 'react';
|
||||
import { useState, type DragEvent, type RefObject } from 'react';
|
||||
import {
|
||||
Upload, File, CheckCircle, XCircle, Loader2,
|
||||
Copy, ExternalLink, Trash2, Globe,
|
||||
Copy, ExternalLink, Trash2, Globe, AlertTriangle,
|
||||
} from 'lucide-react';
|
||||
|
||||
const MAX_FILES = 20;
|
||||
const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MB
|
||||
const ALLOWED_TYPES = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
|
||||
import { getSettings } from '@/lib/storage';
|
||||
|
||||
/* ── Helpers ── */
|
||||
function formatBytes(b: number): string {
|
||||
@@ -57,20 +54,80 @@ export default function QuickUpload({
|
||||
onBrowse, onInputChange, onStartUpload, onCopyCid, onCopyShareLink, onClearAll, gatewayLink,
|
||||
}: QuickUploadProps) {
|
||||
|
||||
const s = getSettings();
|
||||
const maxFiles = s.maxFiles;
|
||||
const maxFileSize = s.maxFileSize;
|
||||
|
||||
const allowedExts = s.allowedFileTypes
|
||||
? s.allowedFileTypes.split(',').map(e => e.trim().toLowerCase()).filter(Boolean)
|
||||
: null;
|
||||
|
||||
const [fileTypeError, setFileTypeError] = useState<string | null>(null);
|
||||
|
||||
const doneCount = files.filter(f => f.status === 'done').length;
|
||||
const errorCount = files.filter(f => f.status === 'error').length;
|
||||
const totalCount = files.length;
|
||||
const allDone = files.every(f => f.status === 'done' || f.status === 'error');
|
||||
|
||||
function filterAllowedFiles(fileList: File[]): { accepted: File[]; rejected: string[] } {
|
||||
if (!allowedExts) return { accepted: fileList, rejected: [] };
|
||||
const accepted: File[] = [];
|
||||
const rejected: string[] = [];
|
||||
for (const f of fileList) {
|
||||
const ext = f.name.split('.').pop()?.toLowerCase();
|
||||
if (ext && allowedExts.includes(ext)) {
|
||||
accepted.push(f);
|
||||
} else {
|
||||
rejected.push(f.name);
|
||||
}
|
||||
}
|
||||
return { accepted, rejected };
|
||||
}
|
||||
|
||||
function handleLocalDrop(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
onDragLeave();
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
const fileArr = Array.from(e.dataTransfer.files);
|
||||
const { accepted, rejected } = filterAllowedFiles(fileArr);
|
||||
if (rejected.length > 0) {
|
||||
setFileTypeError(`${rejected.length} bestand(en) overgeslagen (type niet toegestaan): ${rejected.join(', ')}`);
|
||||
setTimeout(() => setFileTypeError(null), 5000);
|
||||
}
|
||||
if (accepted.length > 0) onAddFiles(accepted);
|
||||
}
|
||||
}
|
||||
|
||||
function handleLocalInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
if (e.target.files && e.target.files.length > 0) {
|
||||
const fileArr = Array.from(e.target.files);
|
||||
const { accepted, rejected } = filterAllowedFiles(fileArr);
|
||||
if (rejected.length > 0) {
|
||||
setFileTypeError(`${rejected.length} bestand(en) overgeslagen (type niet toegestaan): ${rejected.join(', ')}`);
|
||||
setTimeout(() => setFileTypeError(null), 5000);
|
||||
}
|
||||
if (accepted.length > 0) onAddFiles(accepted);
|
||||
}
|
||||
e.target.value = '';
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* File type error warning */}
|
||||
{fileTypeError && (
|
||||
<div className="mb-4 flex items-center gap-2 p-3 rounded-xl bg-accent-rose/10 border border-accent-rose/30 text-sm text-accent-rose">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0" />
|
||||
<span>{fileTypeError}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Drop zone */}
|
||||
{!uploading && !uploadDone && (
|
||||
<div
|
||||
onDragOver={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
onDrop={files.length < MAX_FILES ? onDrop : undefined}
|
||||
onClick={files.length < MAX_FILES ? onBrowse : undefined}
|
||||
onDrop={files.length < maxFiles ? handleLocalDrop : undefined}
|
||||
onClick={files.length < maxFiles ? onBrowse : undefined}
|
||||
className={`relative border-2 border-dashed rounded-xl p-10 text-center transition-all duration-200 cursor-pointer ${
|
||||
dragOver
|
||||
? 'border-brand-400 bg-brand-500/10'
|
||||
@@ -82,8 +139,8 @@ export default function QuickUpload({
|
||||
type="file"
|
||||
multiple
|
||||
className="hidden"
|
||||
onChange={onInputChange}
|
||||
disabled={uploading || files.length >= MAX_FILES}
|
||||
onChange={files.length < maxFiles ? handleLocalInputChange : undefined}
|
||||
disabled={uploading || files.length >= maxFiles}
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<Upload className="w-10 h-10 text-surface-500" />
|
||||
@@ -92,7 +149,7 @@ export default function QuickUpload({
|
||||
<span className="text-brand-400 font-medium">Click to browse</span> or drag & drop files
|
||||
</p>
|
||||
<p className="text-xs text-surface-500 mt-1">
|
||||
Up to {MAX_FILES} files, max {formatBytes(MAX_FILE_SIZE)} each
|
||||
Up to {maxFiles} files, max {formatBytes(maxFileSize)} each
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,7 +219,7 @@ export default function QuickUpload({
|
||||
Uploading…
|
||||
</span>
|
||||
)}
|
||||
{entry.status === 'done' && entry.result && (
|
||||
{entry.status === 'done' && entry.result?.cid && (
|
||||
<span className="text-xs font-mono text-surface-400 truncate max-w-[160px]">
|
||||
{entry.result.cid.slice(0, 16)}…
|
||||
</span>
|
||||
@@ -257,12 +314,12 @@ export default function QuickUpload({
|
||||
</div>
|
||||
|
||||
{/* Per-file result details */}
|
||||
{files.filter(f => f.status === 'done').map(entry => entry.result && (
|
||||
{files.filter(f => f.status === 'done').map(entry => entry.result?.cid && (
|
||||
<div key={entry.id} className="flex items-center justify-between py-2 px-3 rounded-lg bg-surface-800/50 mb-1.5">
|
||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||
<span className="text-sm text-surface-300 truncate max-w-[150px]">{entry.file.name}</span>
|
||||
<span className="text-xs font-mono text-surface-500 truncate max-w-[120px]">{entry.result.cid.slice(0, 16)}…</span>
|
||||
<span className="text-xs text-surface-500">{formatBytes(entry.result.size)}</span>
|
||||
<span className="text-xs text-surface-500">{formatBytes(entry.result.size ?? 0)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user