This commit is contained in:
2025-06-10 15:21:42 +02:00
parent 4b6d3fd991
commit c6b4c716a8
5 changed files with 170 additions and 36 deletions

View File

@@ -274,21 +274,21 @@
</v-btn>
</div>
<!-- Selected Files Preview -->
<div v-if="selectedBrowserFiles.length > 0" class="mt-3">
<div class="text-caption mb-2">Selected from browser:</div>
<!-- All Attachments Preview -->
<div v-if="allAttachments.length > 0" class="mt-3">
<div class="text-caption mb-2">Attachments ({{ allAttachments.length }}):</div>
<v-chip
v-for="(file, i) in selectedBrowserFiles"
:key="i"
v-for="(attachment, i) in allAttachments"
:key="`${attachment.type}-${i}`"
size="small"
color="primary"
variant="tonal"
closable
@click:close="removeBrowserFile(i)"
@click:close="removeAttachment(attachment)"
class="mr-2 mb-2"
>
<v-icon start size="small">mdi-file</v-icon>
{{ file.name }}
<v-icon start size="small">{{ attachment.type === 'uploaded' ? 'mdi-upload' : 'mdi-file' }}</v-icon>
{{ attachment.name }}
</v-chip>
</div>
</div>
@@ -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);

View File

@@ -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');