From 00869bbf1731e6948a302f4353a894dd66ca38b7 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Tue, 6 Jan 2026 19:18:01 +0530 Subject: [PATCH 1/8] feat: qol esigner and file manager improvements --- .../src/services/NotificationService.ts | 12 +- .../.svelte-kit/generated/client/app.js | 10 +- .../.svelte-kit/generated/client/nodes/5.js | 2 +- .../.svelte-kit/generated/client/nodes/6.js | 2 +- .../.svelte-kit/generated/client/nodes/7.js | 2 +- .../.svelte-kit/generated/client/nodes/8.js | 1 + .../esigner/.svelte-kit/non-ambient.d.ts | 5 +- .../.svelte-kit/types/route_meta_data.json | 1 + .../.svelte-kit/types/src/routes/$types.d.ts | 2 +- .../routes/(auth)/deeplink-login/$types.d.ts | 18 ++ .../routes/(auth)/deeplink-login/+page.svelte | 123 ++++++++++++ .../src/controllers/FileController.ts | 36 ++++ platforms/file-manager-api/src/index.ts | 3 + .../src/services/FileService.ts | 13 ++ .../src/services/NotificationService.ts | 6 +- .../lib/components/UserMenuDropdown.svelte | 64 ++++-- .../routes/(auth)/deeplink-login/+page.svelte | 123 ++++++++++++ .../src/routes/(protected)/files/+page.svelte | 182 ++++++++++++++++- .../(protected)/files/[id]/+page.svelte | 67 +++++++ .../routes/(protected)/storage/+page.svelte | 188 ++++++++++++++++++ 20 files changed, 813 insertions(+), 47 deletions(-) create mode 100644 platforms/esigner/.svelte-kit/generated/client/nodes/8.js create mode 100644 platforms/esigner/.svelte-kit/types/src/routes/(auth)/deeplink-login/$types.d.ts create mode 100644 platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte create mode 100644 platforms/file-manager/src/routes/(auth)/deeplink-login/+page.svelte create mode 100644 platforms/file-manager/src/routes/(protected)/storage/+page.svelte diff --git a/platforms/esigner-api/src/services/NotificationService.ts b/platforms/esigner-api/src/services/NotificationService.ts index f746faba1..560e73cda 100644 --- a/platforms/esigner-api/src/services/NotificationService.ts +++ b/platforms/esigner-api/src/services/NotificationService.ts @@ -285,6 +285,8 @@ export class NotificationService { const inviterText = inviterName ? ` from ${inviterName}` : ''; const containerName = file.displayName || file.name; const descriptionText = file.description ? `\nDescription: ${file.description}` : ''; + const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004'; + const fileLink = `${esignerUrl}/files/${file.id}`; return `📝 Signature Invitation @@ -296,7 +298,7 @@ Size: ${this.formatFileSize(file.size)} Type: ${file.mimeType} Time: ${formattedTime} -Please review and sign the signature container when ready.`; +View Signature Container in eSigner`; } /** @@ -314,6 +316,8 @@ Please review and sign the signature container when ready.`; const signerText = signerName ? ` by ${signerName}` : ''; const containerName = file.displayName || file.name; const descriptionText = file.description ? `\nDescription: ${file.description}` : ''; + const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004'; + const fileLink = `${esignerUrl}/files/${file.id}`; return `✅ Signature Completed @@ -323,7 +327,7 @@ Signature Container: ${containerName}${descriptionText} File: ${file.name} Time: ${formattedTime} -The signature has been recorded and verified.`; +View Signature Container in eSigner`; } /** @@ -340,6 +344,8 @@ The signature has been recorded and verified.`; const containerName = file.displayName || file.name; const descriptionText = file.description ? `\nDescription: ${file.description}` : ''; + const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004'; + const fileLink = `${esignerUrl}/files/${file.id}`; return `🎉 Signature Container Fully Signed @@ -349,7 +355,7 @@ Signature Container: ${containerName}${descriptionText} File: ${file.name} Time: ${formattedTime} -The signature container is now complete. You can download the proof from the eSigner platform.`; +Download Proof from eSigner`; } /** diff --git a/platforms/esigner/.svelte-kit/generated/client/app.js b/platforms/esigner/.svelte-kit/generated/client/app.js index b03d8aada..dee17bec0 100644 --- a/platforms/esigner/.svelte-kit/generated/client/app.js +++ b/platforms/esigner/.svelte-kit/generated/client/app.js @@ -8,7 +8,8 @@ export const nodes = [ () => import('./nodes/4'), () => import('./nodes/5'), () => import('./nodes/6'), - () => import('./nodes/7') + () => import('./nodes/7'), + () => import('./nodes/8') ]; export const server_loads = []; @@ -16,9 +17,10 @@ export const server_loads = []; export const dictionary = { "/": [3], "/(auth)/auth": [4], - "/(protected)/files": [5,[2]], - "/(protected)/files/new": [7,[2]], - "/(protected)/files/[id]": [6,[2]] + "/(auth)/deeplink-login": [5], + "/(protected)/files": [6,[2]], + "/(protected)/files/new": [8,[2]], + "/(protected)/files/[id]": [7,[2]] }; export const hooks = { diff --git a/platforms/esigner/.svelte-kit/generated/client/nodes/5.js b/platforms/esigner/.svelte-kit/generated/client/nodes/5.js index 5362e6dbe..2b85113b9 100644 --- a/platforms/esigner/.svelte-kit/generated/client/nodes/5.js +++ b/platforms/esigner/.svelte-kit/generated/client/nodes/5.js @@ -1 +1 @@ -export { default as component } from "../../../../src/routes/(protected)/files/+page.svelte"; \ No newline at end of file +export { default as component } from "../../../../src/routes/(auth)/deeplink-login/+page.svelte"; \ No newline at end of file diff --git a/platforms/esigner/.svelte-kit/generated/client/nodes/6.js b/platforms/esigner/.svelte-kit/generated/client/nodes/6.js index 8bd5dab8a..5362e6dbe 100644 --- a/platforms/esigner/.svelte-kit/generated/client/nodes/6.js +++ b/platforms/esigner/.svelte-kit/generated/client/nodes/6.js @@ -1 +1 @@ -export { default as component } from "../../../../src/routes/(protected)/files/[id]/+page.svelte"; \ No newline at end of file +export { default as component } from "../../../../src/routes/(protected)/files/+page.svelte"; \ No newline at end of file diff --git a/platforms/esigner/.svelte-kit/generated/client/nodes/7.js b/platforms/esigner/.svelte-kit/generated/client/nodes/7.js index 86a2cd32a..8bd5dab8a 100644 --- a/platforms/esigner/.svelte-kit/generated/client/nodes/7.js +++ b/platforms/esigner/.svelte-kit/generated/client/nodes/7.js @@ -1 +1 @@ -export { default as component } from "../../../../src/routes/(protected)/files/new/+page.svelte"; \ No newline at end of file +export { default as component } from "../../../../src/routes/(protected)/files/[id]/+page.svelte"; \ No newline at end of file diff --git a/platforms/esigner/.svelte-kit/generated/client/nodes/8.js b/platforms/esigner/.svelte-kit/generated/client/nodes/8.js new file mode 100644 index 000000000..86a2cd32a --- /dev/null +++ b/platforms/esigner/.svelte-kit/generated/client/nodes/8.js @@ -0,0 +1 @@ +export { default as component } from "../../../../src/routes/(protected)/files/new/+page.svelte"; \ No newline at end of file diff --git a/platforms/esigner/.svelte-kit/non-ambient.d.ts b/platforms/esigner/.svelte-kit/non-ambient.d.ts index 2d6d6dde0..8dca0d202 100644 --- a/platforms/esigner/.svelte-kit/non-ambient.d.ts +++ b/platforms/esigner/.svelte-kit/non-ambient.d.ts @@ -27,7 +27,7 @@ export {}; declare module "$app/types" { export interface AppTypes { - RouteId(): "/(protected)" | "/(auth)" | "/" | "/(auth)/auth" | "/(protected)/files" | "/(protected)/files/new" | "/(protected)/files/[id]"; + RouteId(): "/(protected)" | "/(auth)" | "/" | "/(auth)/auth" | "/(auth)/deeplink-login" | "/(protected)/files" | "/(protected)/files/new" | "/(protected)/files/[id]"; RouteParams(): { "/(protected)/files/[id]": { id: string } }; @@ -36,11 +36,12 @@ declare module "$app/types" { "/(auth)": Record; "/": { id?: string }; "/(auth)/auth": Record; + "/(auth)/deeplink-login": Record; "/(protected)/files": { id?: string }; "/(protected)/files/new": Record; "/(protected)/files/[id]": { id: string } }; - Pathname(): "/" | "/auth" | "/auth/" | "/files" | "/files/" | "/files/new" | "/files/new/" | `/files/${string}` & {} | `/files/${string}/` & {}; + Pathname(): "/" | "/auth" | "/auth/" | "/deeplink-login" | "/deeplink-login/" | "/files" | "/files/" | "/files/new" | "/files/new/" | `/files/${string}` & {} | `/files/${string}/` & {}; ResolvedPathname(): `${"" | `/${string}`}${ReturnType}`; Asset(): string & {}; } diff --git a/platforms/esigner/.svelte-kit/types/route_meta_data.json b/platforms/esigner/.svelte-kit/types/route_meta_data.json index d45ad958e..bffb4f4fb 100644 --- a/platforms/esigner/.svelte-kit/types/route_meta_data.json +++ b/platforms/esigner/.svelte-kit/types/route_meta_data.json @@ -2,6 +2,7 @@ "/(protected)": [], "/": [], "/(auth)/auth": [], + "/(auth)/deeplink-login": [], "/(protected)/files": [], "/(protected)/files/new": [], "/(protected)/files/[id]": [] diff --git a/platforms/esigner/.svelte-kit/types/src/routes/$types.d.ts b/platforms/esigner/.svelte-kit/types/src/routes/$types.d.ts index 32f48a484..68302399c 100644 --- a/platforms/esigner/.svelte-kit/types/src/routes/$types.d.ts +++ b/platforms/esigner/.svelte-kit/types/src/routes/$types.d.ts @@ -12,7 +12,7 @@ type EnsureDefined = T extends null | undefined ? {} : T; type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; export type Snapshot = Kit.Snapshot; type PageParentData = EnsureDefined; -type LayoutRouteId = RouteId | "/" | "/(auth)/auth" | "/(protected)/files" | "/(protected)/files/[id]" | "/(protected)/files/new" | null +type LayoutRouteId = RouteId | "/" | "/(auth)/auth" | "/(auth)/deeplink-login" | "/(protected)/files" | "/(protected)/files/[id]" | "/(protected)/files/new" | null type LayoutParams = RouteParams & { id?: string } type LayoutParentData = EnsureDefined<{}>; diff --git a/platforms/esigner/.svelte-kit/types/src/routes/(auth)/deeplink-login/$types.d.ts b/platforms/esigner/.svelte-kit/types/src/routes/(auth)/deeplink-login/$types.d.ts new file mode 100644 index 000000000..d6aa873d8 --- /dev/null +++ b/platforms/esigner/.svelte-kit/types/src/routes/(auth)/deeplink-login/$types.d.ts @@ -0,0 +1,18 @@ +import type * as Kit from '@sveltejs/kit'; + +type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; +// @ts-ignore +type MatcherParam = M extends (param : string) => param is infer U ? U extends string ? U : string : string; +type RouteParams = { }; +type RouteId = '/(auth)/deeplink-login'; +type MaybeWithVoid = {} extends T ? T | void : T; +export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T]; +type OutputDataShape = MaybeWithVoid> & Partial> & Record> +type EnsureDefined = T extends null | undefined ? {} : T; +type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; +export type Snapshot = Kit.Snapshot; +type PageParentData = EnsureDefined; + +export type PageServerData = null; +export type PageData = Expand; +export type PageProps = { params: RouteParams; data: PageData } \ No newline at end of file diff --git a/platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte b/platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte new file mode 100644 index 000000000..2448c608a --- /dev/null +++ b/platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte @@ -0,0 +1,123 @@ + + +{#if isLoading} +
+
+
+

Authenticating...

+
+
+{:else if error} +
+
+
{error}
+ +
+
+{/if} diff --git a/platforms/file-manager-api/src/controllers/FileController.ts b/platforms/file-manager-api/src/controllers/FileController.ts index a481aab27..06be92a4e 100644 --- a/platforms/file-manager-api/src/controllers/FileController.ts +++ b/platforms/file-manager-api/src/controllers/FileController.ts @@ -26,6 +26,28 @@ export class FileController { return res.status(401).json({ error: "Authentication required" }); } + // Check file size limit (5MB) + const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB in bytes + if (req.file.size > MAX_FILE_SIZE) { + return res.status(413).json({ + error: "File size exceeds 5MB limit", + maxSize: MAX_FILE_SIZE, + fileSize: req.file.size + }); + } + + // Check user's storage quota (1GB total) + const { used, limit } = await this.fileService.getUserStorageUsage(req.user.id); + if (used + req.file.size > limit) { + return res.status(413).json({ + error: "Storage quota exceeded", + used, + limit, + fileSize: req.file.size, + available: limit - used + }); + } + const { displayName, description, folderId } = req.body; // Normalize folderId - convert string "null" to actual null @@ -338,5 +360,19 @@ export class FileController { res.status(500).json({ error: "Failed to move file" }); } }; + + getStorageUsage = async (req: Request, res: Response) => { + try { + if (!req.user) { + return res.status(401).json({ error: "Authentication required" }); + } + + const usage = await this.fileService.getUserStorageUsage(req.user.id); + res.json(usage); + } catch (error) { + console.error("Error getting storage usage:", error); + res.status(500).json({ error: "Failed to get storage usage" }); + } + }; } diff --git a/platforms/file-manager-api/src/index.ts b/platforms/file-manager-api/src/index.ts index 7dc78839a..e1a3372a6 100644 --- a/platforms/file-manager-api/src/index.ts +++ b/platforms/file-manager-api/src/index.ts @@ -95,6 +95,9 @@ app.patch("/api/files/:id", authGuard, fileController.updateFile); app.delete("/api/files/:id", authGuard, fileController.deleteFile); app.post("/api/files/:id/move", authGuard, fileController.moveFile); +// Storage routes +app.get("/api/storage", authGuard, fileController.getStorageUsage); + // Folder routes app.post("/api/folders", authGuard, folderController.createFolder); app.get("/api/folders", authGuard, folderController.getFolders); diff --git a/platforms/file-manager-api/src/services/FileService.ts b/platforms/file-manager-api/src/services/FileService.ts index 45d07698b..9356f9385 100644 --- a/platforms/file-manager-api/src/services/FileService.ts +++ b/platforms/file-manager-api/src/services/FileService.ts @@ -317,5 +317,18 @@ export class FileService { return false; } + + async getUserStorageUsage(userId: string): Promise<{ used: number; limit: number }> { + const result = await this.fileRepository + .createQueryBuilder('file') + .select('SUM(file.size)', 'total') + .where('file.ownerId = :userId', { userId }) + .getRawOne(); + + const used = parseInt(result?.total || '0', 10); + const limit = 1073741824; // 1GB in bytes + + return { used, limit }; + } } diff --git a/platforms/file-manager-api/src/services/NotificationService.ts b/platforms/file-manager-api/src/services/NotificationService.ts index 8efe5e38b..44df96b2e 100644 --- a/platforms/file-manager-api/src/services/NotificationService.ts +++ b/platforms/file-manager-api/src/services/NotificationService.ts @@ -235,7 +235,7 @@ export class NotificationService { const fileName = file.displayName || file.name; const descriptionText = file.description ? `\nDescription: ${file.description}` : ''; const fileManagerUrl = process.env.PUBLIC_FILE_MANAGER_BASE_URL || 'http://localhost:3005'; - const fileLink = `${fileManagerUrl}/files/${file.id}`; + const fileLink = `${fileManagerUrl}/files/${file.id}?view=shared`; return `📁 File Shared @@ -264,9 +264,7 @@ Time: ${formattedTime} const sharerText = sharerName ? ` from ${sharerName}` : ''; const fileManagerUrl = process.env.PUBLIC_FILE_MANAGER_BASE_URL || 'http://localhost:3005'; - const folderLink = folder.parentFolderId - ? `${fileManagerUrl}/files?folderId=${folder.id}` - : `${fileManagerUrl}/files?folderId=${folder.id}`; + const folderLink = `${fileManagerUrl}/files?view=shared&folderId=${folder.id}`; return `📂 Folder Shared diff --git a/platforms/file-manager/src/lib/components/UserMenuDropdown.svelte b/platforms/file-manager/src/lib/components/UserMenuDropdown.svelte index f3b059125..d8df429da 100644 --- a/platforms/file-manager/src/lib/components/UserMenuDropdown.svelte +++ b/platforms/file-manager/src/lib/components/UserMenuDropdown.svelte @@ -59,29 +59,49 @@ {/if} - -
- -
+ + + Storage + + + {/if} diff --git a/platforms/file-manager/src/routes/(auth)/deeplink-login/+page.svelte b/platforms/file-manager/src/routes/(auth)/deeplink-login/+page.svelte new file mode 100644 index 000000000..058fec572 --- /dev/null +++ b/platforms/file-manager/src/routes/(auth)/deeplink-login/+page.svelte @@ -0,0 +1,123 @@ + + +{#if isLoading} +
+
+
+

Authenticating...

+
+
+{:else if error} +
+
+
{error}
+ +
+
+{/if} diff --git a/platforms/file-manager/src/routes/(protected)/files/+page.svelte b/platforms/file-manager/src/routes/(protected)/files/+page.svelte index 8b7b9d779..90b2dfd92 100644 --- a/platforms/file-manager/src/routes/(protected)/files/+page.svelte +++ b/platforms/file-manager/src/routes/(protected)/files/+page.svelte @@ -79,7 +79,10 @@ let showMoveModal = $state(false); let showDeleteModal = $state(false); let showShareModal = $state(false); + let showRenameModal = $state(false); let itemToShare = $state<{ type: 'file' | 'folder'; id: string; name: string } | null>(null); + let itemToRename = $state<{ type: 'file' | 'folder'; id: string; name: string; displayName?: string } | null>(null); + let newName = $state(''); let selectedFile = $state(null); let itemToMove = $state(null); let itemToDelete = $state<{ type: 'file' | 'folder'; id: string; name: string } | null>(null); @@ -96,6 +99,8 @@ let previewFile = $state(null); let previewUrl = $state(null); let breadcrumbs = $state>([{ id: null, name: 'My Files' }]); + let uploadProgress = $state(0); + let isUploading = $state(false); // Subscribe to stores at top level to make them reactive let user = $state(get(currentUser)); @@ -121,6 +126,21 @@ // Load data asynchronously (async () => { + // Check for query params to handle navigation from notifications + const searchParams = new URLSearchParams(window.location.search); + const viewParam = searchParams.get('view'); + const folderIdParam = searchParams.get('folderId'); + + // First, switch view if specified + if (viewParam === 'shared') { + currentView = 'shared'; + } + + // Then navigate to folder if specified + if (folderIdParam) { + currentFolderId = folderIdParam; + } + await fetchFolderTree(); await loadFiles(); await updateBreadcrumbs(); @@ -158,21 +178,58 @@ } async function handleFileUpload(file: globalThis.File) { + // Client-side validation + const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB + if (file.size > MAX_FILE_SIZE) { + toast.error(`File size exceeds 5MB limit. Your file is ${(file.size / 1024 / 1024).toFixed(2)}MB`); + return; + } + try { - isLoading = true; - await uploadFile(file, currentFolderId); + isUploading = true; + uploadProgress = 0; + + const formData = new FormData(); + formData.append('file', file); + if (currentFolderId !== undefined) { + formData.append('folderId', currentFolderId || 'null'); + } + + const response = await apiClient.post('/api/files', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + onUploadProgress: (progressEvent) => { + if (progressEvent.total) { + uploadProgress = Math.round((progressEvent.loaded / progressEvent.total) * 100); + } + } + }); + toast.success('File uploaded successfully'); showUploadModal = false; selectedFile = null; + uploadProgress = 0; + // Refresh files and folder tree after upload await Promise.all([ loadFiles(), fetchFolderTree() ]); - } catch (error) { + } catch (error: any) { console.error('Upload failed:', error); - toast.error('Failed to upload file'); + if (error.response?.status === 413) { + const errorData = error.response.data; + if (errorData.error?.includes('Storage quota')) { + toast.error(`Storage quota exceeded. You have ${(errorData.available / 1024 / 1024).toFixed(2)}MB available.`); + } else { + toast.error('File size exceeds 5MB limit'); + } + } else { + toast.error('Failed to upload file'); + } } finally { + isUploading = false; isLoading = false; } } @@ -257,6 +314,47 @@ } } + function openRenameModal(item: any, type: 'file' | 'folder') { + itemToRename = { + type, + id: item.id, + name: item.name, + displayName: item.displayName || item.name + }; + newName = item.displayName || item.name; + showRenameModal = true; + openDropdownId = null; + } + + async function handleRename() { + if (!itemToRename || !newName.trim()) { + toast.error('Please enter a name'); + return; + } + + try { + isLoading = true; + if (itemToRename.type === 'file') { + await updateFile(itemToRename.id, newName.trim()); + toast.success('File renamed successfully'); + } else { + // For folders, we'll need to add updateFolder to the store + await apiClient.patch(`/api/folders/${itemToRename.id}`, { name: newName.trim() }); + toast.success('Folder renamed successfully'); + } + showRenameModal = false; + itemToRename = null; + newName = ''; + await loadFiles(); + await fetchFolderTree(); + } catch (error) { + console.error('Failed to rename:', error); + toast.error(`Failed to rename ${itemToRename.type}`); + } finally { + isLoading = false; + } + } + async function openMoveModal(item: any, type: 'file' | 'folder') { itemToMove = { ...item, _type: type }; moveModalFolderId = null; @@ -786,6 +884,19 @@ >
{#if currentView === 'my-files' && item.ownerId === user?.id} +
+ {#if isUploading} +
+
+ Uploading... + {uploadProgress}% +
+
+
+
+
+ {/if} +
@@ -1151,6 +1278,45 @@ {/if} + +{#if showRenameModal && itemToRename} +
+
+

Rename {itemToRename.type === 'file' ? 'File' : 'Folder'}

+ { + if (e.key === 'Enter' && newName.trim()) { + handleRename(); + } + }} + /> +
+ + +
+
+
+{/if} + {#if previewFile && previewUrl}
diff --git a/platforms/file-manager/src/routes/(protected)/files/[id]/+page.svelte b/platforms/file-manager/src/routes/(protected)/files/[id]/+page.svelte index 79b1ec5ed..0fb5826a7 100644 --- a/platforms/file-manager/src/routes/(protected)/files/[id]/+page.svelte +++ b/platforms/file-manager/src/routes/(protected)/files/[id]/+page.svelte @@ -16,6 +16,7 @@ let previewUrl = $state(null); let showAccessModal = $state(false); let showTagModal = $state(false); + let showRenameModal = $state(false); let searchQuery = $state(''); let searchResults = $state([]); let selectedUsers = $state([]); @@ -23,6 +24,7 @@ let tagInput = $state(''); let filteredTags = $state([]); let breadcrumbs = $state>([{ id: null, name: 'My Files' }]); + let newDisplayName = $state(''); onMount(async () => { isAuthenticated.subscribe((auth) => { @@ -37,6 +39,15 @@ goto('/files'); return; } + + // Check for view param from notification link + const searchParams = new URLSearchParams(window.location.search); + const viewParam = searchParams.get('view'); + if (viewParam === 'shared') { + // Store flag for potential back navigation + // Could be used to return to shared view instead of my files + } + await loadFile(fileId); // Only fetch access and tags if user is the owner @@ -278,6 +289,28 @@ } } + async function handleRenameFile() { + if (!newDisplayName.trim()) { + toast.error('Please enter a name'); + return; + } + + try { + isLoading = true; + await apiClient.patch(`/api/files/${file.id}`, { + displayName: newDisplayName.trim() + }); + toast.success('File renamed successfully'); + showRenameModal = false; + await loadFile(file.id); + } catch (error) { + console.error('Failed to rename file:', error); + toast.error('Failed to rename file'); + } finally { + isLoading = false; + } + } + function formatFileSize(bytes: number): string { if (bytes === 0) return '0 Bytes'; const k = 1024; @@ -656,3 +689,37 @@
{/if} + +{#if showRenameModal} +
+
+

Rename File

+ { + if (e.key === 'Enter' && newDisplayName.trim()) { + handleRenameFile(); + } + }} + /> +
+ + +
+
+
+{/if} diff --git a/platforms/file-manager/src/routes/(protected)/storage/+page.svelte b/platforms/file-manager/src/routes/(protected)/storage/+page.svelte new file mode 100644 index 000000000..54e746753 --- /dev/null +++ b/platforms/file-manager/src/routes/(protected)/storage/+page.svelte @@ -0,0 +1,188 @@ + + +
+
+

Storage

+

Manage your file storage usage

+
+ + {#if isLoading} +
+
+

Loading storage information...

+
+ {:else} +
+
+
+ Storage Used + + {formatBytes(used)} / {formatBytes(limit)} + +
+
+
+
+

+ {percentage.toFixed(1)}% of your total storage +

+
+ +
+
+
+
+ + + +
+
+

Used

+

{formatBytes(used)}

+
+
+
+ +
+
+
+ + + +
+
+

Available

+

{formatBytes(limit - used)}

+
+
+
+
+ + {#if percentage >= 90} +
+
+ + + +
+

Storage Almost Full

+

+ You're running low on storage space. Consider deleting some files to free up space. +

+
+
+
+ {:else if percentage >= 75} +
+
+ + + +
+

Storage Getting Full

+

+ You've used {percentage.toFixed(1)}% of your storage. You may want to clean up some files soon. +

+
+
+
+ {/if} + +
+ + +
+
+ +
+
+ + + +
+

Storage Limits

+
    +
  • • Maximum file size: 5 MB per file
  • +
  • • Total storage quota: 1 GB
  • +
  • • Deleted files are permanently removed and cannot be recovered
  • +
+
+
+
+ {/if} +
From 8443cb131e8600bc49cd639cccb99b7dee725894 Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Tue, 6 Jan 2026 19:27:06 +0530 Subject: [PATCH 2/8] fix: login issues --- .../routes/(auth)/deeplink-login/+page.svelte | 43 ++-- .../routes/(auth)/deeplink-login/+page.svelte | 212 +++++++++--------- 2 files changed, 124 insertions(+), 131 deletions(-) diff --git a/platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte b/platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte index 2448c608a..af0fd5858 100644 --- a/platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte +++ b/platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte @@ -1,6 +1,8 @@ {#if isLoading} -
-
-
-

Authenticating...

-
-
+
+
+
+

Authenticating...

+
+
{:else if error} -
-
-
{error}
- -
-
+
+
+
{error}
+ +
+
{/if} From ffe2e120211415c56009a808f41dce0ba33272de Mon Sep 17 00:00:00 2001 From: Merul Dhiman Date: Tue, 6 Jan 2026 19:38:10 +0530 Subject: [PATCH 3/8] style: file manager and signer --- .../esigner/src/lib/utils/mobile-detection.ts | 10 ++ .../(protected)/files/[id]/+page.svelte | 129 ++++++++++++------ .../src/routes/(protected)/files/+page.svelte | 41 +++--- 3 files changed, 119 insertions(+), 61 deletions(-) create mode 100644 platforms/esigner/src/lib/utils/mobile-detection.ts diff --git a/platforms/esigner/src/lib/utils/mobile-detection.ts b/platforms/esigner/src/lib/utils/mobile-detection.ts new file mode 100644 index 000000000..e99efff39 --- /dev/null +++ b/platforms/esigner/src/lib/utils/mobile-detection.ts @@ -0,0 +1,10 @@ +export function isMobileDevice(): boolean { + if (typeof window === 'undefined') return false; + + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || + (window.innerWidth <= 768); +} + +export function getDeepLinkUrl(qrData: string): string { + return qrData; +} diff --git a/platforms/esigner/src/routes/(protected)/files/[id]/+page.svelte b/platforms/esigner/src/routes/(protected)/files/[id]/+page.svelte index 06b17c36d..1f5a39c12 100644 --- a/platforms/esigner/src/routes/(protected)/files/[id]/+page.svelte +++ b/platforms/esigner/src/routes/(protected)/files/[id]/+page.svelte @@ -8,6 +8,7 @@ import { qrcode } from 'svelte-qrcode-action'; import { PUBLIC_ESIGNER_BASE_URL } from '$env/static/public'; import { toast } from '$lib/stores/toast'; + import { isMobileDevice, getDeepLinkUrl } from '$lib/utils/mobile-detection'; let file = $state(null); let invitations = $state([]); @@ -282,22 +283,22 @@ -
+
-
+
Back to Signature Containers -

{file?.displayName || file?.name || 'Signature Container'}

+

{file?.displayName || file?.name || 'Signature Container'}

{#if file?.description}

{file.description}

{/if}
-
+
{#if isLoading}
@@ -306,8 +307,8 @@
{:else if file} - -
+ +