From ea234bc6a17c0dd52d6f6f69d86bf9ffc725e42e Mon Sep 17 00:00:00 2001 From: Dionisio Date: Fri, 13 Feb 2026 12:29:21 +0100 Subject: [PATCH] fix: share dialog not opening + connect to backend API - Fix showShareDialog: add try-catch, null checks, prevent textContent from destroying header icon (use span child instead) - Capture file/folder target before closeContextMenu to prevent race - createSharedLink now calls real backend POST /api/shares instead of localStorage-only mock (still caches locally for offline compat) - Fix share_handler.rs: use OptionalAuthUser instead of AuthUser to prevent 401 when auth is disabled (same pattern as delete/trash) - Add null-safety to closeShareDialog - Reset new-share-section on dialog open --- Cargo.lock | 2 +- src/interfaces/api/handlers/share_handler.rs | 10 +- static/js/contextMenus.js | 149 +++++++++++++------ static/js/ui.js | 4 +- 4 files changed, 112 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c5ad114..baf0c6c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1686,7 +1686,7 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oxicloud" -version = "0.3.2" +version = "0.3.3" dependencies = [ "anyhow", "argon2", diff --git a/src/interfaces/api/handlers/share_handler.rs b/src/interfaces/api/handlers/share_handler.rs index e4bb589c..e25597a3 100644 --- a/src/interfaces/api/handlers/share_handler.rs +++ b/src/interfaces/api/handlers/share_handler.rs @@ -15,7 +15,7 @@ use crate::{ ports::share_ports::ShareUseCase }, common::errors::ErrorKind, - interfaces::middleware::auth::AuthUser, + interfaces::middleware::auth::OptionalAuthUser, }; #[derive(Debug, Deserialize)] @@ -32,10 +32,10 @@ pub struct VerifyPasswordRequest { /// Create a new shared link pub async fn create_shared_link( State(share_use_case): State>, - auth_user: AuthUser, + auth_user: OptionalAuthUser, Json(dto): Json, ) -> impl IntoResponse { - let user_id = &auth_user.id; + let user_id = auth_user.0.map(|u| u.id).unwrap_or_else(|| "anonymous".to_string()); match share_use_case.create_shared_link(&user_id, dto).await { Ok(share) => (StatusCode::CREATED, Json(share)).into_response(), Err(err) => { @@ -69,10 +69,10 @@ pub async fn get_shared_link( /// Get all shared links created by the current user pub async fn get_user_shares( State(share_use_case): State>, - auth_user: AuthUser, + auth_user: OptionalAuthUser, Query(query): Query, ) -> impl IntoResponse { - let user_id = &auth_user.id; + let user_id = auth_user.0.map(|u| u.id).unwrap_or_else(|| "anonymous".to_string()); let page = query.page.unwrap_or(1); let per_page = query.per_page.unwrap_or(20); diff --git a/static/js/contextMenus.js b/static/js/contextMenus.js index bf53cef3..e8e64fe5 100644 --- a/static/js/contextMenus.js +++ b/static/js/contextMenus.js @@ -62,8 +62,9 @@ const contextMenus = { }); document.getElementById('share-folder-option').addEventListener('click', () => { - if (window.app.contextMenuTargetFolder) { - this.showShareDialog(window.app.contextMenuTargetFolder, 'folder'); + const folder = window.app.contextMenuTargetFolder; + if (folder) { + this.showShareDialog(folder, 'folder'); } window.ui.closeContextMenu(); }); @@ -165,8 +166,9 @@ const contextMenus = { }); document.getElementById('share-file-option').addEventListener('click', () => { - if (window.app.contextMenuTargetFile) { - this.showShareDialog(window.app.contextMenuTargetFile, 'file'); + const file = window.app.contextMenuTargetFile; + if (file) { + this.showShareDialog(file, 'file'); } window.ui.closeFileContextMenu(); }); @@ -433,23 +435,42 @@ const contextMenus = { * @param {string} itemType - 'file' or 'folder' */ showShareDialog(item, itemType) { - // Update dialog title based on item type - const dialogHeader = document.getElementById('share-dialog').querySelector('.share-dialog-header'); + try { + const shareDialog = document.getElementById('share-dialog'); + if (!shareDialog) { + console.error('Share dialog element not found in DOM'); + window.ui.showNotification('Error', 'Share dialog not available'); + return; + } + + // Update dialog title — use the inside header to preserve icon + const dialogHeader = shareDialog.querySelector('.share-dialog-header'); + if (dialogHeader) { + const headerSpan = dialogHeader.querySelector('span'); + const titleText = itemType === 'file' ? + (window.i18n ? window.i18n.t('dialogs.share_file') : 'Share file') : + (window.i18n ? window.i18n.t('dialogs.share_folder') : 'Share folder'); + if (headerSpan) { + headerSpan.textContent = titleText; + } else { + dialogHeader.textContent = titleText; + } + } + const itemName = document.getElementById('shared-item-name'); - - // Update dialog content - dialogHeader.textContent = itemType === 'file' ? - (window.i18n ? window.i18n.t('dialogs.share_file') : 'Share file') : - (window.i18n ? window.i18n.t('dialogs.share_folder') : 'Share folder'); - - itemName.textContent = item.name; + if (itemName) itemName.textContent = item.name; // Reset form - document.getElementById('share-password').value = ''; - document.getElementById('share-expiration').value = ''; - document.getElementById('share-permission-read').checked = true; - document.getElementById('share-permission-write').checked = false; - document.getElementById('share-permission-reshare').checked = false; + const pwField = document.getElementById('share-password'); + const expField = document.getElementById('share-expiration'); + if (pwField) pwField.value = ''; + if (expField) expField.value = ''; + const permRead = document.getElementById('share-permission-read'); + const permWrite = document.getElementById('share-permission-write'); + const permReshare = document.getElementById('share-permission-reshare'); + if (permRead) permRead.checked = true; + if (permWrite) permWrite.checked = false; + if (permReshare) permReshare.checked = false; // Store the current item and type for use when creating the share window.app.shareDialogItem = item; @@ -526,14 +547,23 @@ const contextMenus = { document.getElementById('existing-shares-section').style.display = 'none'; } + // Hide new-share section from previous use + const newShareSection = document.getElementById('new-share-section'); + if (newShareSection) newShareSection.style.display = 'none'; + // Show dialog - document.getElementById('share-dialog').style.display = 'flex'; + shareDialog.style.display = 'flex'; + console.log('Share dialog opened for', itemType, item.name); + } catch (error) { + console.error('Error opening share dialog:', error); + window.ui.showNotification('Error', 'Could not open share dialog'); + } }, /** * Create a shared link with the configured options */ - createSharedLink() { + async createSharedLink() { if (!window.app.shareDialogItem || !window.app.shareDialogItemType) { window.ui.showNotification('Error', 'Could not share the item'); return; @@ -546,46 +576,74 @@ const contextMenus = { const permissionWrite = document.getElementById('share-permission-write').checked; const permissionReshare = document.getElementById('share-permission-reshare').checked; - // Prepare options - const options = { + const item = window.app.shareDialogItem; + const itemType = window.app.shareDialogItemType; + + // Build DTO for backend API + const createDto = { + item_id: item.id, + item_type: itemType, password: password || null, - expirationDate: expirationDate || null, + expires_at: expirationDate ? Math.floor(new Date(expirationDate).getTime() / 1000) : null, permissions: { read: permissionRead, write: permissionWrite, reshare: permissionReshare } }; - + try { - const item = window.app.shareDialogItem; - const itemType = window.app.shareDialogItemType; - - // Create share - const shareInfo = window.fileSharing.generateSharedLink( - item.id, - itemType, - options - ); + const token = localStorage.getItem('oxicloud_token'); + const headers = { 'Content-Type': 'application/json' }; + if (token) headers['Authorization'] = `Bearer ${token}`; + + const response = await fetch('/api/shares', { + method: 'POST', + headers, + body: JSON.stringify(createDto) + }); + + if (!response.ok) { + const errBody = await response.json().catch(() => ({})); + throw new Error(errBody.error || `Server error ${response.status}`); + } + + const shareInfo = await response.json(); + // Also save to localStorage for offline / shared-view compatibility + window.fileSharing.saveSharedLink({ + id: shareInfo.id, + type: shareInfo.item_type, + itemId: shareInfo.item_id, + url: shareInfo.url, + token: shareInfo.token, + password_protected: shareInfo.has_password, + expires_at: shareInfo.expires_at ? new Date(shareInfo.expires_at * 1000).toISOString() : null, + permissions: shareInfo.permissions, + created_at: new Date(shareInfo.created_at * 1000).toISOString(), + access_count: shareInfo.access_count || 0, + name: item.name, + dateShared: new Date().toISOString() + }); + // Update UI with new share const shareUrl = document.getElementById('generated-share-url'); - shareUrl.value = shareInfo.url; - document.getElementById('new-share-section').style.display = 'block'; - - // Focus and select for easy copying - shareUrl.focus(); - shareUrl.select(); + if (shareUrl) { + shareUrl.value = shareInfo.url; + document.getElementById('new-share-section').style.display = 'block'; + shareUrl.focus(); + shareUrl.select(); + } // Show success message - window.ui.showNotification('Link created', 'Shared link created successfully'); - - // Reload existing shares - this.showShareDialog(item, itemType); + window.ui.showNotification( + window.i18n ? window.i18n.t('notifications.link_created') : 'Link created', + window.i18n ? window.i18n.t('notifications.share_success') : 'Shared link created successfully' + ); } catch (error) { console.error('Error creating shared link:', error); - window.ui.showNotification('Error', 'Could not create shared link'); + window.ui.showNotification('Error', error.message || 'Could not create shared link'); } }, @@ -639,7 +697,8 @@ const contextMenus = { * Close share dialog */ closeShareDialog() { - document.getElementById('share-dialog').style.display = 'none'; + const dialog = document.getElementById('share-dialog'); + if (dialog) dialog.style.display = 'none'; window.app.shareDialogItem = null; window.app.shareDialogItemType = null; }, diff --git a/static/js/ui.js b/static/js/ui.js index 2645c3d2..4e1c7418 100644 --- a/static/js/ui.js +++ b/static/js/ui.js @@ -204,8 +204,8 @@ const ui = { contextMenus.closeShareDialog(); }); - document.getElementById('share-confirm-btn').addEventListener('click', () => { - contextMenus.createSharedLink(); + document.getElementById('share-confirm-btn').addEventListener('click', async () => { + await contextMenus.createSharedLink(); }); document.getElementById('copy-share-btn').addEventListener('click', async () => {