1
0
forked from maik/IPFS-portal

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
@@ -2,7 +2,9 @@
import type { IPFSEntry } from '@/lib/api';
import FileIcon from './FileIcon';
import { CheckCircle } from 'lucide-react';
import { CheckCircle, Download } from 'lucide-react';
import { truncateCid } from '@/lib/helpers';
import { downloadFile } from '@/lib/download';
interface DirectoryListingProps {
entries: IPFSEntry[];
@@ -20,11 +22,6 @@ function formatSize(bytes: number): string {
return `${s} ${units[i] ?? ''}`;
}
function truncateHash(hash: string): string {
if (hash.length <= 12) return hash;
return `${hash.slice(0, 6)}${hash.slice(-4)}`;
}
function SkeletonRow() {
return (
<div className="flex items-center gap-3 px-5 py-3 animate-pulse">
@@ -82,7 +79,7 @@ export default function DirectoryListing({
<div className="w-12 text-center">Type</div>
<div className="w-16 text-right hidden md:block">Size</div>
<div className="w-20 text-right hidden lg:block">Hash</div>
<div className="w-10 text-center" />
<div className="w-20 text-center" />
</div>
<div className="divide-y divide-surface-800/50">
@@ -113,10 +110,23 @@ export default function DirectoryListing({
</span>
<code className="text-xs text-surface-500 w-20 text-right shrink-0 hidden lg:block font-mono">
{truncateHash(entry.hash)}
{truncateCid(entry.hash, 6)}
</code>
<div className="w-10 flex justify-center shrink-0">
<div className="w-20 flex items-center justify-center gap-1 shrink-0">
{entry.type === 'file' && (
<button
onClick={(e) => {
e.stopPropagation();
downloadFile(entry.hash, entry.name);
}}
className="p-1 rounded-md hover:bg-surface-700/50 text-surface-500 hover:text-accent-blue transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-blue"
aria-label={`Download ${entry.name}`}
title={`Download ${entry.name}`}
>
<Download className="w-3.5 h-3.5" />
</button>
)}
{isPinned && (
<CheckCircle className="w-3.5 h-3.5 text-accent-green" />
)}
+10 -1
View File
@@ -19,8 +19,17 @@ function detectFileType(filename: string | undefined): 'image' | 'markdown' | 'j
return 'other';
}
function renderMarkdown(text: string): string {
function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
function renderMarkdown(text: string): string {
return escapeHtml(text)
.replace(/^### (.+)$/gm, '<h3 class="text-white font-semibold text-sm mt-3 mb-1">$1</h3>')
.replace(/^## (.+)$/gm, '<h2 class="text-white font-semibold text-base mt-4 mb-1">$1</h2>')
.replace(/^# (.+)$/gm, '<h1 class="text-white font-bold text-lg mt-4 mb-2">$1</h1>')