From e4917874be67de24f934e069b53e1726599c6cc5 Mon Sep 17 00:00:00 2001 From: diogo464 Date: Thu, 14 Aug 2025 21:11:48 +0100 Subject: frontend: improvements --- frontend/app/api/logout/route.ts | 4 +- frontend/app/api/upload/route.ts | 4 +- frontend/components/drive/DriveDirectoryClient.tsx | 88 +++++++++------------- frontend/components/drive/DriveDirectoryView.tsx | 7 +- frontend/components/drive/StorageUsage.tsx | 9 +-- frontend/components/history/HistoryView.tsx | 9 +-- frontend/lib/constants.ts | 37 --------- frontend/lib/drive_server.ts | 83 ++++++-------------- frontend/package.json | 5 +- 9 files changed, 67 insertions(+), 179 deletions(-) diff --git a/frontend/app/api/logout/route.ts b/frontend/app/api/logout/route.ts index 51de324..202a8b7 100644 --- a/frontend/app/api/logout/route.ts +++ b/frontend/app/api/logout/route.ts @@ -10,7 +10,7 @@ export async function POST(request: NextRequest) { if (sessionCookie) { // Call tinyauth logout endpoint to invalidate the session - const logoutResponse = await fetch(`${Auth_tinyauth_endpoint()}/auth/logout`, { + await fetch(`${Auth_tinyauth_endpoint()}/auth/logout`, { method: 'POST', headers: { 'Cookie': `${sessionCookie.name}=${sessionCookie.value}` @@ -29,4 +29,4 @@ export async function POST(request: NextRequest) { // Even if logout fails, redirect to home return redirect('/'); } -} \ No newline at end of file +} diff --git a/frontend/app/api/upload/route.ts b/frontend/app/api/upload/route.ts index 5ce0640..ba2d151 100644 --- a/frontend/app/api/upload/route.ts +++ b/frontend/app/api/upload/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from 'next/server' -import { writeFile, unlink } from 'fs/promises' +import { writeFile } from 'fs/promises' import { tmpdir } from 'os' import { join } from 'path' import { randomUUID } from 'crypto' @@ -25,7 +25,6 @@ export async function POST(request: NextRequest) { const url = new URL(request.url) const rawPath = url.searchParams.get('path') || '/' const targetPath = decodeURIComponent(rawPath) - console.log(`targetPath = ${targetPath}`) // Handle multipart form data const formData = await request.formData() @@ -53,7 +52,6 @@ export async function POST(request: NextRequest) { // Construct the final drive path const finalPath = `${targetPath}/${file.name}` - console.log(`finalPath = ${finalPath}`) // Import file using Drive_import (uses --mode move, so temp file is automatically deleted) await Drive_import(tempFilePath, finalPath, user.email) diff --git a/frontend/components/drive/DriveDirectoryClient.tsx b/frontend/components/drive/DriveDirectoryClient.tsx index f523e4f..d238065 100644 --- a/frontend/components/drive/DriveDirectoryClient.tsx +++ b/frontend/components/drive/DriveDirectoryClient.tsx @@ -32,18 +32,10 @@ import { Checkbox } from "@/components/ui/checkbox" import { toast } from "@/hooks/use-toast" import { DriveLsEntry } from "@/lib/drive_types" import { UPLOAD_MAX_FILE_SIZE, UPLOAD_MAX_FILES } from "@/lib/constants" +import { formatFileSize } from "@/lib/utils" import { DriveMoveDialog } from "./DriveMoveDialog" import { StorageUsage } from "./StorageUsage" import type { StorageData } from "@/lib/storage" -import type { UserAuth } from "@/lib/auth_types" - -function formatFileSize(bytes: number): string { - if (bytes === 0) return "0 Bytes" - const k = 1024 - const sizes = ["Bytes", "KB", "MB", "GB"] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i] -} function formatDate(timestamp: number): string { return new Date(timestamp * 1000).toISOString().split('T')[0] @@ -52,9 +44,9 @@ function formatDate(timestamp: number): string { function formatDateTime(timestamp: number): string { const date = new Date(timestamp * 1000) const dateStr = date.toISOString().split('T')[0] - const timeStr = date.toLocaleTimeString('en-US', { - hour12: false, - hour: '2-digit', + const timeStr = date.toLocaleTimeString('en-US', { + hour12: false, + hour: '2-digit', minute: '2-digit', second: '2-digit' }) @@ -72,10 +64,9 @@ interface DriveDirectoryClientProps { files: DriveLsEntry[] breadcrumbs: Breadcrumb[] storageData: StorageData - user: UserAuth } -export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, user }: DriveDirectoryClientProps) { +export function DriveDirectoryClient({ path, files, breadcrumbs, storageData }: DriveDirectoryClientProps) { const [selectedFiles, setSelectedFiles] = useState>(new Set()) const [renameDialogOpen, setRenameDialogOpen] = useState(false) const [infoDialogOpen, setInfoDialogOpen] = useState(false) @@ -98,10 +89,6 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us setSelectedFiles(newSelected) } - const selectAll = () => { - setSelectedFiles(new Set(files.map(file => file.path))) - } - const deselectAll = () => { setSelectedFiles(new Set()) } @@ -130,7 +117,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us }) return } - + const filename = item.path.split('/').pop() || 'download' const permalink = `${window.location.origin}/blob/${item.blob}?filename=${encodeURIComponent(filename)}` navigator.clipboard.writeText(permalink).then(() => { @@ -171,8 +158,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us title: "Renamed successfully", description: result.message, }) - - // Refresh page to show changes + window.location.reload() } else { throw new Error(result.error || `Rename failed with status ${response.status}`) @@ -222,7 +208,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us try { const formData = new FormData() formData.append('file', file) - + // Use the new simple upload endpoint with path as query parameter const response = await fetch(`/api/upload?path=${encodeURIComponent(path)}`, { method: 'POST', @@ -247,8 +233,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us title: "Upload successful", description: `${successCount} file(s) uploaded successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}` }) - - // Refresh page to show changes + window.location.reload() } @@ -292,8 +277,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us title: "Deleted successfully", description: result.message, }) - - // Refresh page to show changes + window.location.reload() } else { throw new Error(result.error || `Delete failed with status ${response.status}`) @@ -322,7 +306,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us // Extract filename from the full path const fileName = filePath.split('/').pop() || '' // Construct new path: destination + filename - const newPath = destinationPath.endsWith('/') + const newPath = destinationPath.endsWith('/') ? `${destinationPath}${fileName}` : `${destinationPath}/${fileName}` @@ -360,14 +344,13 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us title: "Move completed", description: `${successCount} item(s) moved successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}`, }) - - // Refresh page to show changes + window.location.reload() } if (errorCount > 0 && successCount === 0) { toast({ - title: "Move failed", + title: "Move failed", description: `All ${errorCount} item(s) failed to move. ${errors[0] || ''}`, variant: "destructive" }) @@ -416,8 +399,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us title: "Folder created", description: result.message, }) - - // Refresh page to show changes + window.location.reload() } else { throw new Error(result.error || `Failed to create folder`) @@ -447,7 +429,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us {index === breadcrumbs.length - 1 ? ( {crumb.name} ) : ( - @@ -457,9 +439,9 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us ))} - +
- -
@@ -749,13 +731,13 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us -
) -} \ No newline at end of file +} diff --git a/frontend/components/drive/DriveDirectoryView.tsx b/frontend/components/drive/DriveDirectoryView.tsx index d2f30ee..de9d70a 100644 --- a/frontend/components/drive/DriveDirectoryView.tsx +++ b/frontend/components/drive/DriveDirectoryView.tsx @@ -2,7 +2,6 @@ import { DriveLsEntry } from "@/lib/drive_types" import { DriveDirectoryClient } from "./DriveDirectoryClient" import { DriveHeader } from "./DriveHeader" import type { StorageData } from "@/lib/storage" -import { Auth_get_user } from "@/lib/auth" interface DriveDirectoryViewProps { path: string @@ -20,7 +19,7 @@ function generateBreadcrumbs(currentPath: string) { const breadcrumbs = [{ name: 'Root', path: '/drive' }] let accumulatedPath = '' - parts.forEach((part, index) => { + parts.forEach((part, _index) => { accumulatedPath += '/' + part breadcrumbs.push({ name: decodeURIComponent(part), // Decode URL encoded characters @@ -48,7 +47,6 @@ function sortFiles(files: DriveLsEntry[]): DriveLsEntry[] { export async function DriveDirectoryView({ path, files, storageData }: DriveDirectoryViewProps) { const sortedFiles = sortFiles(files) const breadcrumbs = generateBreadcrumbs(path) - const user = await Auth_get_user() return (
@@ -58,8 +56,7 @@ export async function DriveDirectoryView({ path, files, storageData }: DriveDire files={sortedFiles} breadcrumbs={breadcrumbs} storageData={storageData} - user={user} />
) -} \ No newline at end of file +} diff --git a/frontend/components/drive/StorageUsage.tsx b/frontend/components/drive/StorageUsage.tsx index 2cb5d5d..39be4ba 100644 --- a/frontend/components/drive/StorageUsage.tsx +++ b/frontend/components/drive/StorageUsage.tsx @@ -1,17 +1,10 @@ import type { StorageData } from "@/lib/storage" +import { formatFileSize } from "@/lib/utils" interface StorageUsageProps { data: StorageData } -function formatFileSize(bytes: number): string { - if (bytes === 0) return "0 Bytes" - const k = 1024 - const sizes = ["Bytes", "KB", "MB", "GB", "TB"] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i] -} - export function StorageUsage({ data }: StorageUsageProps) { // Safety check for undefined data if (!data) { diff --git a/frontend/components/history/HistoryView.tsx b/frontend/components/history/HistoryView.tsx index c459275..e6d1cd1 100644 --- a/frontend/components/history/HistoryView.tsx +++ b/frontend/components/history/HistoryView.tsx @@ -3,6 +3,7 @@ import { DriveHeader } from "@/components/drive/DriveHeader" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { Button } from "@/components/ui/button" import { ChevronLeft, ChevronRight } from "lucide-react" +import { formatFileSize } from "@/lib/utils" import Link from "next/link" interface HistoryViewProps { @@ -13,14 +14,6 @@ interface HistoryViewProps { totalEntries: number } -function formatFileSize(bytes: number): string { - if (bytes === 0) return "0 Bytes" - const k = 1024 - const sizes = ["Bytes", "KB", "MB", "GB"] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i] -} - function formatDateTime(timestamp: number): string { return new Date(timestamp * 1000).toLocaleString() } diff --git a/frontend/lib/constants.ts b/frontend/lib/constants.ts index 173e90d..e01251f 100644 --- a/frontend/lib/constants.ts +++ b/frontend/lib/constants.ts @@ -1,39 +1,2 @@ -// Upload configuration constants export const UPLOAD_MAX_FILE_SIZE = 4096 * 1024 * 1024; // 100MB export const UPLOAD_MAX_FILES = 10; // Maximum files per upload -export const UPLOAD_ALLOWED_TYPES = [ - // Documents - 'application/pdf', - 'application/msword', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.ms-excel', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'application/vnd.ms-powerpoint', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'text/plain', - 'text/csv', - // Images - 'image/jpeg', - 'image/png', - 'image/gif', - 'image/webp', - 'image/svg+xml', - // Videos - 'video/mp4', - 'video/webm', - 'video/ogg', - // Audio - 'audio/mpeg', - 'audio/wav', - 'audio/ogg', - // Archives - 'application/zip', - 'application/x-rar-compressed', - 'application/x-7z-compressed', - // Code/Text - 'application/json', - 'text/javascript', - 'text/html', - 'text/css', - 'application/xml' -]; // Empty array means all types allowed diff --git a/frontend/lib/drive_server.ts b/frontend/lib/drive_server.ts index e7c88e8..1462f6e 100644 --- a/frontend/lib/drive_server.ts +++ b/frontend/lib/drive_server.ts @@ -1,11 +1,21 @@ import { spawnSync } from 'child_process' import { DriveLsEntry, DriveLogEntry } from './drive_types' -import { Drive_split_path, Drive_basename } from './drive_shared' /** * Server-only drive functions that use Node.js APIs */ +function executeFctDriveCommand(args: string[]): string { + const result = spawnSync('fctdrive', args, { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }) + if (result.error) { + throw new Error(`Failed to execute fctdrive: ${result.error.message}`) + } + if (result.status !== 0) { + throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`) + } + return result.stdout +} + /// lists the given path on the drive export async function Drive_ls(path: string, recursive: boolean): Promise { const args = ['ls'] @@ -16,14 +26,7 @@ export async function Drive_ls(path: string, recursive: boolean): Promise { - const result = spawnSync('fctdrive', ['blob', blob], { encoding: 'utf-8' }) - if (result.error) { - throw new Error(`Failed to execute fctdrive: ${result.error.message}`) - } - if (result.status !== 0) { - throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`) - } - return result.stdout.trim(); + const stdout = executeFctDriveCommand(['blob', blob]) + return stdout.trim(); } /// removes the file or directory at the given path export async function Drive_remove(path: string, email: string) { - const result = spawnSync('fctdrive', ['remove', '--email', email, path], { encoding: 'utf-8' }); - if (result.error) { - throw new Error(`Failed to execute fctdrive: ${result.error.message}`); - } - if (result.status !== 0) { - throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`); - } + executeFctDriveCommand(['remove', '--email', email, path]); } /// creates a directory at the given path export async function Drive_mkdir(path: string, email: string) { - const result = spawnSync('fctdrive', ['mkdir', '--email', email, '--path', path], { encoding: 'utf-8' }); - if (result.error) { - throw new Error(`Failed to execute fctdrive: ${result.error.message}`); - } - if (result.status !== 0) { - throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`); - } + executeFctDriveCommand(['mkdir', '--email', email, '--path', path]); } /// renames a file or directory from old path to new path export async function Drive_rename(oldPath: string, newPath: string, email: string) { - const result = spawnSync('fctdrive', ['rename', '--email', email, '--old', oldPath, '--new', newPath], { encoding: 'utf-8' }); - if (result.error) { - throw new Error(`Failed to execute fctdrive: ${result.error.message}`); - } - if (result.status !== 0) { - throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`); - } + executeFctDriveCommand(['rename', '--email', email, '--old', oldPath, '--new', newPath]); } /// gets the blob ID for a given path export async function Drive_stat(path: string): Promise { - const result = spawnSync('fctdrive', ['stat', path], { encoding: 'utf-8' }); - if (result.error) { - throw new Error(`Failed to execute fctdrive: ${result.error.message}`); - } - if (result.status !== 0) { - throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`); - } - return result.stdout.trim(); + const stdout = executeFctDriveCommand(['stat', path]); + return stdout.trim(); } @@ -130,15 +97,7 @@ export async function Drive_ls_directories(path: string = '/'): Promise { - const result = spawnSync('fctdrive', ['log'], { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }) - if (result.error) { - throw new Error(`Failed to execute fctdrive: ${result.error.message}`) - } - if (result.status !== 0) { - throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`) - } - - const stdout = result.stdout + const stdout = executeFctDriveCommand(['log']) const entries: DriveLogEntry[] = [] for (const line of stdout.split('\n')) { if (line.trim() === "") diff --git a/frontend/package.json b/frontend/package.json index d3762a2..3bd5610 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,10 @@ "dev": "next dev --turbopack", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "lint:fix": "next lint --fix", + "type-check": "tsc --noEmit", + "analyze": "ANALYZE=true next build" }, "dependencies": { "@radix-ui/react-checkbox": "^1.3.2", -- cgit