This commit is contained in:
Matt 2025-06-10 20:56:56 +02:00
parent cf4af2cbff
commit 09c32ae6cb
3 changed files with 136 additions and 5 deletions

View File

@ -91,7 +91,10 @@
<v-icon :size="mobile ? 'small' : 'default'">mdi-account</v-icon> <v-icon :size="mobile ? 'small' : 'default'">mdi-account</v-icon>
</v-avatar> </v-avatar>
</template> </template>
<v-list-item-title :class="mobile ? 'text-body-2' : ''">Client Signature Link</v-list-item-title> <v-list-item-title :class="mobile ? 'text-body-2' : ''">
Client Signature Link
<v-icon v-if="signatureStatus.clientSigned" color="success" :size="mobile ? 'small' : 'default'" class="ml-1">mdi-check-circle</v-icon>
</v-list-item-title>
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">{{ interest['Full Name'] }}</v-list-item-subtitle> <v-list-item-subtitle :class="mobile ? 'text-caption' : ''">{{ interest['Full Name'] }}</v-list-item-subtitle>
<template v-slot:append> <template v-slot:append>
<v-btn-group variant="text" :size="mobile ? 'small' : 'default'"> <v-btn-group variant="text" :size="mobile ? 'small' : 'default'">
@ -114,7 +117,10 @@
<v-icon :size="mobile ? 'small' : 'default'">mdi-account-check</v-icon> <v-icon :size="mobile ? 'small' : 'default'">mdi-account-check</v-icon>
</v-avatar> </v-avatar>
</template> </template>
<v-list-item-title :class="mobile ? 'text-body-2' : ''">CC Signature Link</v-list-item-title> <v-list-item-title :class="mobile ? 'text-body-2' : ''">
CC Signature Link
<v-icon v-if="isSignedByEmail('sales@portnimara.com')" color="success" :size="mobile ? 'small' : 'default'" class="ml-1">mdi-check-circle</v-icon>
</v-list-item-title>
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">Oscar Faragher</v-list-item-subtitle> <v-list-item-subtitle :class="mobile ? 'text-caption' : ''">Oscar Faragher</v-list-item-subtitle>
<template v-slot:append> <template v-slot:append>
<v-btn-group variant="text" :size="mobile ? 'small' : 'default'"> <v-btn-group variant="text" :size="mobile ? 'small' : 'default'">
@ -137,7 +143,10 @@
<v-icon :size="mobile ? 'small' : 'default'">mdi-account-tie</v-icon> <v-icon :size="mobile ? 'small' : 'default'">mdi-account-tie</v-icon>
</v-avatar> </v-avatar>
</template> </template>
<v-list-item-title :class="mobile ? 'text-body-2' : ''">Developer Signature Link</v-list-item-title> <v-list-item-title :class="mobile ? 'text-body-2' : ''">
Developer Signature Link
<v-icon v-if="isSignedByEmail('dm@portnimara.com')" color="success" :size="mobile ? 'small' : 'default'" class="ml-1">mdi-check-circle</v-icon>
</v-list-item-title>
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">David Mizrahi</v-list-item-subtitle> <v-list-item-subtitle :class="mobile ? 'text-caption' : ''">David Mizrahi</v-list-item-subtitle>
<template v-slot:append> <template v-slot:append>
<v-btn-group variant="text" :size="mobile ? 'small' : 'default'"> <v-btn-group variant="text" :size="mobile ? 'small' : 'default'">
@ -313,6 +322,18 @@ const selectedFile = ref<File | null>(null);
const showDeleteConfirmDialog = ref(false); const showDeleteConfirmDialog = ref(false);
const isDeleting = ref(false); const isDeleting = ref(false);
// Signature status tracking
const signatureStatus = ref({
documentStatus: '',
clientSigned: false,
allSigned: false,
signedRecipients: [] as any[],
unsignedRecipients: [] as any[]
});
// Polling interval for signature status
let statusPollingInterval: NodeJS.Timeout | null = null;
const hasEOI = computed(() => { const hasEOI = computed(() => {
return !!(props.interest['Signature Link Client'] || return !!(props.interest['Signature Link Client'] ||
props.interest['Signature Link CC'] || props.interest['Signature Link CC'] ||
@ -520,4 +541,102 @@ const deleteEOI = async () => {
isDeleting.value = false; isDeleting.value = false;
} }
}; };
// Check signature status from Documenso
const checkSignatureStatus = async () => {
if (!hasEOI.value || isEOISigned.value) return;
try {
const response = await $fetch<{
success: boolean;
documentStatus: string;
clientSigned: boolean;
allSigned: boolean;
signedRecipients: any[];
unsignedRecipients: any[];
}>('/api/eoi/check-signature-status', {
headers: {
'x-tag': '094ut234'
},
query: {
interestId: props.interest.Id.toString()
}
});
if (response.success) {
signatureStatus.value = {
documentStatus: response.documentStatus,
clientSigned: response.clientSigned,
allSigned: response.allSigned,
signedRecipients: response.signedRecipients,
unsignedRecipients: response.unsignedRecipients
};
// If all signed, update the EOI status
if (response.allSigned && props.interest['EOI Status'] !== 'Signed') {
try {
await $fetch('/api/update-interest', {
method: 'POST',
headers: {
'x-tag': '094ut234'
},
body: {
id: props.interest.Id.toString(),
data: {
'EOI Status': 'Signed',
'Sales Process Level': 'Made Reservation'
}
}
});
emit('update'); // Refresh parent data
stopSignaturePolling(); // Stop polling once signed
} catch (updateError) {
console.error('Failed to update EOI status:', updateError);
}
}
}
} catch (error) {
console.error('Failed to check signature status:', error);
}
};
// Check if a specific email has signed
const isSignedByEmail = (email: string) => {
return signatureStatus.value.signedRecipients.some(
recipient => recipient.email === email
);
};
// Start polling for signature status
const startSignaturePolling = () => {
// Initial check
checkSignatureStatus();
// Poll every 30 seconds
statusPollingInterval = setInterval(() => {
checkSignatureStatus();
}, 30000);
};
// Stop polling
const stopSignaturePolling = () => {
if (statusPollingInterval) {
clearInterval(statusPollingInterval);
statusPollingInterval = null;
}
};
// Watch for changes in EOI status
watch(() => hasEOI.value, (newValue) => {
if (newValue && !isEOISigned.value) {
startSignaturePolling();
} else {
stopSignaturePolling();
}
}, { immediate: true });
// Cleanup on unmount
onUnmounted(() => {
stopSignaturePolling();
});
</script> </script>

View File

@ -13,6 +13,7 @@ interface EmailMessage {
timestamp: string; timestamp: string;
direction: 'sent' | 'received'; direction: 'sent' | 'received';
threadId?: string; threadId?: string;
attachments?: any[];
} }
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
@ -304,6 +305,15 @@ async function fetchImapEmails(
return; return;
} }
// Extract attachments
const attachments = parsed.attachments ? parsed.attachments.map((att: any) => ({
filename: att.filename || 'attachment',
name: att.filename || 'attachment',
size: att.size || 0,
type: att.contentType || 'application/octet-stream',
cid: att.cid || undefined
})) : [];
const email: EmailMessage = { const email: EmailMessage = {
id: parsed.messageId || `${Date.now()}-${seqno}`, id: parsed.messageId || `${Date.now()}-${seqno}`,
from: fromEmail, from: fromEmail,
@ -312,7 +322,8 @@ async function fetchImapEmails(
body: parsed.text || '', body: parsed.text || '',
html: parsed.html || undefined, html: parsed.html || undefined,
timestamp: parsed.date?.toISOString() || new Date().toISOString(), timestamp: parsed.date?.toISOString() || new Date().toISOString(),
direction: fromEmail.toLowerCase().includes(userEmail.toLowerCase()) ? 'sent' : 'received' direction: fromEmail.toLowerCase().includes(userEmail.toLowerCase()) ? 'sent' : 'received',
attachments: attachments
}; };
if (parsed.headers.has('in-reply-to')) { if (parsed.headers.has('in-reply-to')) {

View File

@ -181,7 +181,8 @@ export default defineEventHandler(async (event) => {
html: htmlBody, html: htmlBody,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
direction: 'sent', direction: 'sent',
interestId: interestId interestId: interestId,
attachments: attachments // Include attachment info
}; };
const objectName = `interest-${interestId}/${Date.now()}-sent.json`; const objectName = `interest-${interestId}/${Date.now()}-sent.json`;