updates
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user