diff --git a/components/ClientEmailSection.vue b/components/ClientEmailSection.vue index f84850d..856b735 100644 --- a/components/ClientEmailSection.vue +++ b/components/ClientEmailSection.vue @@ -274,21 +274,21 @@ - -
-
Selected from browser:
+ +
+
Attachments ({{ allAttachments.length }}):
- mdi-file - {{ file.name }} + {{ attachment.type === 'uploaded' ? 'mdi-upload' : 'mdi-file' }} + {{ attachment.name }}
@@ -387,6 +387,25 @@ const emailDraft = ref<{ attachments: [] }); +// Combined attachments from both upload and browser +const allAttachments = computed(() => { + const uploaded = emailDraft.value.attachments.map((file, index) => ({ + type: 'uploaded', + file, + name: file.name, + index + })); + + const browser = selectedBrowserFiles.value.map((file, index) => ({ + type: 'browser', + file, + name: file.displayName || file.name, + index + })); + + return [...uploaded, ...browser]; +}); + // Check for stored session on mount onMounted(() => { const storedSession = localStorage.getItem('emailSessionId'); @@ -444,6 +463,7 @@ const loadEmailThread = async () => { const response = await $fetch<{ success: boolean; emails: any[]; + threads?: any[]; }>('/api/email/fetch-thread', { method: 'POST', headers: { @@ -459,6 +479,12 @@ const loadEmailThread = async () => { if (response.success) { console.log('[ClientEmailSection] Successfully loaded', response.emails?.length || 0, 'emails'); emailThreads.value = response.emails || []; + + // Check if we have threads from the API response + if (response.threads) { + console.log('[ClientEmailSection] Threads available:', response.threads.length); + // For now, still use emails until we implement thread UI + } } } catch (error) { console.error('[ClientEmailSection] Failed to load email thread:', error); @@ -538,6 +564,8 @@ const sendEmail = async () => { if (emailDraft.value.attachments && emailDraft.value.attachments.length > 0) { for (const file of emailDraft.value.attachments) { try { + console.log('[ClientEmailSection] Uploading file:', file.name, 'size:', file.size); + const formData = new FormData(); formData.append('file', file); @@ -548,6 +576,8 @@ const sendEmail = async () => { .toLowerCase() .replace(/[^a-z0-9-]/g, ''); + console.log('[ClientEmailSection] Upload path:', `${username}-attachments/`, 'bucket: client-emails'); + const uploadResponse = await $fetch<{ success: boolean; path: string; @@ -564,15 +594,25 @@ const sendEmail = async () => { body: formData }); + console.log('[ClientEmailSection] Upload response:', uploadResponse); + if (uploadResponse.success) { + const attachmentPath = uploadResponse.path || uploadResponse.fileName; + console.log('[ClientEmailSection] Successfully uploaded, path:', attachmentPath); + uploadedAttachments.push({ name: file.name, - path: uploadResponse.path + path: attachmentPath, + bucket: 'client-emails' }); + } else { + console.error('[ClientEmailSection] Upload failed, response:', uploadResponse); + toast.error(`Failed to upload ${file.name}`); } - } catch (uploadError) { - console.error('Failed to upload attachment:', uploadError); - toast.error(`Failed to upload ${file.name}`); + } catch (uploadError: any) { + console.error('[ClientEmailSection] Failed to upload attachment:', file.name, uploadError); + console.error('[ClientEmailSection] Upload error details:', uploadError.data || uploadError.message); + toast.error(`Failed to upload ${file.name}: ${uploadError.data?.statusMessage || uploadError.message || 'Unknown error'}`); } } } @@ -622,14 +662,19 @@ const sendEmail = async () => { localStorage.setItem('emailSignature', JSON.stringify(signatureConfig.value)); } - // Add to thread + // Add to thread with all attachments info + const allAttachmentNames = [ + ...emailDraft.value.attachments.map((f: File) => ({ name: f.name })), + ...selectedBrowserFiles.value.map((f: any) => ({ name: f.displayName || f.name })) + ]; + emailThreads.value.unshift({ direction: 'sent', to: props.interest['Email Address'], subject: emailDraft.value.subject, content: emailDraft.value.content, timestamp: new Date().toISOString(), - attachments: emailDraft.value.attachments.map((f: File) => ({ name: f.name })) + attachments: allAttachmentNames }); // Close composer and reset @@ -661,6 +706,16 @@ const removeBrowserFile = (index: number) => { selectedBrowserFiles.value = selectedBrowserFiles.value.filter((_, i) => i !== index); }; +const removeAttachment = (attachment: any) => { + if (attachment.type === 'uploaded') { + // Remove from uploaded files + emailDraft.value.attachments = emailDraft.value.attachments.filter((_, i) => i !== attachment.index); + } else if (attachment.type === 'browser') { + // Remove from browser-selected files + selectedBrowserFiles.value = selectedBrowserFiles.value.filter((_, i) => i !== attachment.index); + } +}; + const onCredentialsSaved = (data: { sessionId: string }) => { sessionId.value = data.sessionId; localStorage.setItem('emailSessionId', data.sessionId); diff --git a/components/FilePreviewModal.vue b/components/FilePreviewModal.vue index cc8712d..cc23498 100644 --- a/components/FilePreviewModal.vue +++ b/components/FilePreviewModal.vue @@ -94,6 +94,7 @@ interface FileItem { icon: string; displayName: string; isFolder: boolean; + bucket?: string; } interface Props { @@ -163,8 +164,9 @@ const loadPreview = async () => { try { // For images and PDFs, fetch as blob and create object URL if (isImage.value || isPdf.value) { - // Fetch the file as a blob - const response = await fetch(`/api/files/proxy-preview?fileName=${encodeURIComponent(props.file.name)}`); + // Fetch the file as a blob, including bucket if specified + const bucket = props.file.bucket || 'client-portal'; + const response = await fetch(`/api/files/proxy-preview?fileName=${encodeURIComponent(props.file.name)}&bucket=${bucket}`); if (!response.ok) { throw new Error('Failed to fetch file'); @@ -213,13 +215,15 @@ const downloadFile = async () => { // Check if Safari (iOS or desktop) const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + const bucket = props.file.bucket || 'client-portal'; + if (isSafari) { // For Safari, use location.href to force proper filename handling - const downloadUrl = `/api/files/proxy-download?fileName=${encodeURIComponent(props.file.name)}`; + const downloadUrl = `/api/files/proxy-download?fileName=${encodeURIComponent(props.file.name)}&bucket=${bucket}`; window.location.href = downloadUrl; } else { // For other browsers, use blob approach - const response = await fetch(`/api/files/proxy-download?fileName=${encodeURIComponent(props.file.name)}`); + const response = await fetch(`/api/files/proxy-download?fileName=${encodeURIComponent(props.file.name)}&bucket=${bucket}`); if (!response.ok) { throw new Error('Failed to download file'); diff --git a/pages/dashboard/file-browser.vue b/pages/dashboard/file-browser.vue index 0a58b53..e4cd93b 100644 --- a/pages/dashboard/file-browser.vue +++ b/pages/dashboard/file-browser.vue @@ -113,17 +113,29 @@ + + +