- Fix 98 TypeScript errors across 37 files: - Service layer double-unwrapping (subscriptionService, distributionService, gearService) - Self-referencing variables in SearchPageResults - FeedView/ExploreView .posts→.items alignment - useQueueSync Zustand subscribe API - AdminAuditLogsView missing interface fields - Toast proxy type, interceptor type narrowing - 22 unused imports/variables removed - 5 storybook mock data fixes - Align frontend API calls with backend endpoints: - Analytics: useAnalyticsView now calls /creator/analytics/dashboard (was /analytics) - Chat: chatService uses /conversations (was mock data), WS URL from backend token - Dashboard StatsSection: uses real /dashboard API data (was hardcoded zeros) - Settings: suppress 2FA toast error when endpoint unavailable - Fix marketplace products: seed uses 'active' status (was 'published') - Enrich seed: admin follows all creators (feed has content) - Optimize bundle: vendor catch-all 793KB→318KB gzip (-60%) Split into vendor-charts, vendor-emoji, vendor-swagger, vendor-media, etc. - Clean repo: remove ~100 orphaned screenshots, audit reports, logs from root Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
106 lines
3.6 KiB
TypeScript
106 lines
3.6 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { CloudFolderTree } from './CloudFolderTree';
|
|
import { CloudFileList } from './CloudFileList';
|
|
import { cloudService, type CloudFolder, type CloudFile, type CloudQuota } from '../services/cloudService';
|
|
|
|
interface CloudBrowserViewProps {
|
|
onUploadClick?: () => void;
|
|
}
|
|
|
|
export function CloudBrowserView({ onUploadClick: _onUploadClick }: CloudBrowserViewProps) {
|
|
const [folders, setFolders] = useState<CloudFolder[]>([]);
|
|
const [files, setFiles] = useState<CloudFile[]>([]);
|
|
const [selectedFolderId, setSelectedFolderId] = useState<string | null>(null);
|
|
const [quota, setQuota] = useState<CloudQuota | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, [selectedFolderId]);
|
|
|
|
async function loadData() {
|
|
setLoading(true);
|
|
try {
|
|
const [folderData, fileData, quotaData] = await Promise.all([
|
|
cloudService.listFolders(selectedFolderId ?? undefined),
|
|
cloudService.listFiles(selectedFolderId ?? undefined),
|
|
cloudService.getQuota(),
|
|
]);
|
|
setFolders((prev) => {
|
|
const otherFolders = prev.filter(
|
|
(f) => f.parent_id !== (selectedFolderId ?? null) || !selectedFolderId
|
|
);
|
|
return [...otherFolders, ...folderData].filter(
|
|
(f, i, arr) => arr.findIndex((x) => x.id === f.id) === i
|
|
);
|
|
});
|
|
setFiles(fileData);
|
|
setQuota(quotaData);
|
|
} catch {
|
|
// Errors handled silently
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-full gap-6">
|
|
<aside className="w-56 shrink-0 border-r border-border pr-4">
|
|
<CloudFolderTree
|
|
folders={folders}
|
|
selectedFolderId={selectedFolderId}
|
|
onSelectFolder={setSelectedFolderId}
|
|
onCreateFolder={async () => {
|
|
const name = prompt('Folder name:');
|
|
if (name) {
|
|
await cloudService.createFolder(name, selectedFolderId ?? undefined);
|
|
loadData();
|
|
}
|
|
}}
|
|
/>
|
|
{quota && (
|
|
<div className="mt-6 px-3">
|
|
<div className="text-xs text-muted-foreground mb-1">
|
|
Storage: {((quota.used_bytes / quota.max_bytes) * 100).toFixed(1)}%
|
|
</div>
|
|
<div className="h-1.5 bg-muted rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-primary rounded-full transition-all"
|
|
style={{ width: `${Math.min((quota.used_bytes / quota.max_bytes) * 100, 100)}%` }}
|
|
/>
|
|
</div>
|
|
<div className="text-xs text-muted-foreground mt-1">
|
|
{formatBytes(quota.used_bytes)} / {formatBytes(quota.max_bytes)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</aside>
|
|
|
|
<main className="flex-1 min-w-0">
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-16">
|
|
<div className="animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" />
|
|
</div>
|
|
) : (
|
|
<CloudFileList
|
|
files={files}
|
|
onDeleteFile={async (id) => {
|
|
await cloudService.deleteFile(id);
|
|
loadData();
|
|
}}
|
|
onPublishFile={async (id) => {
|
|
await cloudService.publishAsTrack(id);
|
|
}}
|
|
/>
|
|
)}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function formatBytes(bytes: number): string {
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
}
|