diff --git a/components/EOISection.vue b/components/EOISection.vue
index 560b914..69e366f 100644
--- a/components/EOISection.vue
+++ b/components/EOISection.vue
@@ -28,8 +28,8 @@
-
-
+
+
+
+
+
+
+ Uploaded EOI documents found. Remove uploaded documents to generate a new EOI.
+
+
+ Upload Additional EOI
+
+
-
-
+
+
-
+
-
-
-
-
-
- mdi-account
-
-
- Client Signature Link
- {{ interest['Full Name'] }}
-
-
-
-
+
+
+
+ Signature Status
+
+
+
+
+
+
+
+
+ {{ getSignatureStatusIcon('client') }}
+
+
+
+
+ Client Signature
+
+ {{ getSignatureStatusText('client') }}
+
+
+ {{ interest['Full Name'] }}
+
+
+
+
-
-
-
- mdi-account-check
-
-
- CC Signature Link
- Oscar Faragher
-
-
-
-
+
+
+
+
+ {{ getSignatureStatusIcon('cc') }}
+
+
+
+
+ CC Signature
+
+ {{ getSignatureStatusText('cc') }}
+
+
+ Oscar Faragher
+
+
+
+
-
-
-
- mdi-account-tie
-
-
- Developer Signature Link
- David Mizrahi
-
-
-
-
-
+
+
+
+
+ {{ getSignatureStatusIcon('developer') }}
+
+
+
+
+ Developer Signature
+
+ {{ getSignatureStatusText('developer') }}
+
+
+ David Mizrahi
+
+
+
+
+
+
-
-
+
+
Regenerate EOI
+
+
+ Delete Generated EOI
+
@@ -162,7 +247,7 @@
size="small"
prepend-icon="mdi-delete"
>
- Delete EOI
+ Delete Uploaded EOI
@@ -231,7 +316,7 @@
-
+
- Are you sure you want to delete the EOI document? This will:
+ Are you sure you want to delete the uploaded EOI document? This will:
- Remove the uploaded EOI document
- Reset the Sales Process Level to "Specific Qualified Interest"
@@ -265,7 +350,7 @@
Delete EOI
@@ -273,6 +358,27 @@
+
+
+
@@ -296,12 +402,17 @@ const showUploadDialog = ref(false);
const isUploading = ref(false);
const selectedFile = ref(null);
const showDeleteConfirmDialog = ref(false);
+const showDeleteGeneratedConfirmDialog = ref(false);
const isDeleting = ref(false);
+const isDeletingGenerated = ref(false);
+const signatureStatus = ref(null);
+const isCheckingSignatures = ref(false);
-const hasEOI = computed(() => {
+const hasGeneratedEOI = computed(() => {
return !!(props.interest['Signature Link Client'] ||
props.interest['Signature Link CC'] ||
- props.interest['Signature Link Developer']);
+ props.interest['Signature Link Developer'] ||
+ props.interest['documensoID']);
});
const eoiDocuments = computed(() => {
@@ -342,6 +453,11 @@ const generateEOI = async (retryCount = 0) => {
emit('eoi-generated', { signingLinks: response.signingLinks });
emit('update'); // Trigger parent to refresh data
+
+ // Check signature status after generation
+ if (response.documentId !== 'existing') {
+ setTimeout(() => checkSignatureStatus(), 1000);
+ }
} else {
throw new Error('EOI generation failed');
}
@@ -362,6 +478,70 @@ const generateEOI = async (retryCount = 0) => {
}
};
+const checkSignatureStatus = async () => {
+ if (!props.interest['documensoID']) return;
+
+ isCheckingSignatures.value = true;
+
+ try {
+ const response = await $fetch('/api/eoi/check-signature-status', {
+ headers: {
+ 'x-tag': '094ut234'
+ },
+ params: {
+ interestId: props.interest.Id.toString()
+ }
+ });
+
+ if (response.success) {
+ signatureStatus.value = response;
+ }
+ } catch (error: any) {
+ console.error('Failed to check signature status:', error);
+ } finally {
+ isCheckingSignatures.value = false;
+ }
+};
+
+const getSignatureStatusColor = (role: string) => {
+ if (!signatureStatus.value) return 'grey';
+
+ const isSigned = signatureStatus.value.signedRecipients?.some((r: any) => {
+ if (role === 'client') return r.email === props.interest['Email Address'];
+ if (role === 'cc') return r.email === 'sales@portnimara.com';
+ if (role === 'developer') return r.email === 'dm@portnimara.com';
+ return false;
+ });
+
+ return isSigned ? 'success' : 'warning';
+};
+
+const getSignatureStatusIcon = (role: string) => {
+ if (!signatureStatus.value) return 'mdi-clock-outline';
+
+ const isSigned = signatureStatus.value.signedRecipients?.some((r: any) => {
+ if (role === 'client') return r.email === props.interest['Email Address'];
+ if (role === 'cc') return r.email === 'sales@portnimara.com';
+ if (role === 'developer') return r.email === 'dm@portnimara.com';
+ return false;
+ });
+
+ return isSigned ? 'mdi-check' : 'mdi-clock-outline';
+};
+
+const getSignatureStatusText = (role: string) => {
+ if (!signatureStatus.value) return 'Checking...';
+
+ const isSigned = signatureStatus.value.signedRecipients?.some((r: any) => {
+ if (role === 'client') return r.email === props.interest['Email Address'];
+ if (role === 'cc') return r.email === 'sales@portnimara.com';
+ if (role === 'developer') return r.email === 'dm@portnimara.com';
+ return false;
+ });
+
+ return isSigned ? 'Signed' : 'Pending';
+};
+
const copyLink = async (link: string | undefined) => {
if (!link) return;
@@ -473,13 +653,12 @@ const handleUpload = () => {
}
};
-
const closeUploadDialog = () => {
showUploadDialog.value = false;
selectedFile.value = null;
};
-const deleteEOI = async () => {
+const deleteUploadedEOI = async () => {
isDeleting.value = true;
try {
@@ -494,15 +673,50 @@ const deleteEOI = async () => {
});
if (response.success) {
- toast.success('EOI deleted successfully');
+ toast.success('Uploaded EOI deleted successfully');
showDeleteConfirmDialog.value = false;
emit('update'); // Refresh parent data
}
} catch (error: any) {
- console.error('Failed to delete EOI:', error);
- toast.error(error.data?.statusMessage || 'Failed to delete EOI');
+ console.error('Failed to delete uploaded EOI:', error);
+ toast.error(error.data?.statusMessage || 'Failed to delete uploaded EOI');
} finally {
isDeleting.value = false;
}
};
+
+const deleteGeneratedEOI = async () => {
+ isDeletingGenerated.value = true;
+
+ try {
+ const response = await $fetch<{ success: boolean; message: string }>('/api/eoi/delete-generated-document', {
+ method: 'POST',
+ headers: {
+ 'x-tag': '094ut234'
+ },
+ body: {
+ interestId: props.interest.Id.toString()
+ }
+ });
+
+ if (response.success) {
+ toast.success('Generated EOI deleted successfully');
+ showDeleteGeneratedConfirmDialog.value = false;
+ signatureStatus.value = null; // Reset signature status
+ emit('update'); // Refresh parent data
+ }
+ } catch (error: any) {
+ console.error('Failed to delete generated EOI:', error);
+ toast.error(error.data?.statusMessage || 'Failed to delete generated EOI');
+ } finally {
+ isDeletingGenerated.value = false;
+ }
+};
+
+// Auto-check signature status when component mounts if there's a generated EOI
+onMounted(() => {
+ if (hasGeneratedEOI.value && !isEOISigned.value) {
+ checkSignatureStatus();
+ }
+});
diff --git a/components/SliderConfirmation.vue b/components/SliderConfirmation.vue
new file mode 100644
index 0000000..c3865ea
--- /dev/null
+++ b/components/SliderConfirmation.vue
@@ -0,0 +1,251 @@
+
+
+
+
+ {{ icon }}
+ {{ title }}
+
+
+
+
+
+
+
+
+ {{ message }}
+
+
+
+
+
+ {{ warningText }}
+
+
+
+
+
+
+
+
+ {{ sliderProgress >= 100 ? 'mdi-check' : 'mdi-chevron-right' }}
+
+
+
+
+ {{ sliderProgress >= 100 ? 'Ready to delete!' : sliderText }}
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ {{ confirmButtonText }}
+
+
+
+
+
+
+
+
+
diff --git a/server/api/email/generate-eoi-document.ts b/server/api/email/generate-eoi-document.ts
index b401fa3..dd67899 100644
--- a/server/api/email/generate-eoi-document.ts
+++ b/server/api/email/generate-eoi-document.ts
@@ -43,6 +43,27 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 404, statusMessage: "Interest not found" });
}
+ // Documenso API configuration - moved to top for use throughout
+ const documensoApiKey = process.env.NUXT_DOCUMENSO_API_KEY;
+ const documensoBaseUrl = process.env.NUXT_DOCUMENSO_BASE_URL;
+ const templateId = '9';
+
+ if (!documensoApiKey || !documensoBaseUrl) {
+ throw createError({
+ statusCode: 500,
+ statusMessage: "Documenso configuration missing. Please check NUXT_DOCUMENSO_API_KEY and NUXT_DOCUMENSO_BASE_URL environment variables."
+ });
+ }
+
+ // Check if uploaded EOI documents exist - prevent generation if they do
+ const eoiDocuments = interest['EOI Document'] || [];
+ if (eoiDocuments.length > 0) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: "Cannot generate EOI - uploaded documents already exist. Please remove uploaded documents first."
+ });
+ }
+
// Check if EOI already exists (has signature links)
if (interest['Signature Link Client'] && interest['Signature Link CC'] && interest['Signature Link Developer']) {
console.log('EOI already exists, returning existing links');
@@ -58,6 +79,29 @@ export default defineEventHandler(async (event) => {
};
}
+ // If there's an existing generated document, delete it from Documenso first
+ if (interest['documensoID']) {
+ console.log('Existing generated document found, deleting from Documenso first');
+ try {
+ const deleteResponse = await fetch(`${documensoBaseUrl}/api/v1/documents/${interest['documensoID']}`, {
+ method: 'DELETE',
+ headers: {
+ 'Authorization': `Bearer ${documensoApiKey}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ if (deleteResponse.ok) {
+ console.log('Successfully deleted old document from Documenso');
+ } else {
+ console.warn('Failed to delete old document from Documenso, continuing with new generation');
+ }
+ } catch (error) {
+ console.warn('Error deleting old document from Documenso:', error);
+ // Continue with generation even if deletion fails
+ }
+ }
+
// Validate required fields
const requiredFields = [
{ field: 'Full Name', value: interest['Full Name'] },
@@ -104,18 +148,6 @@ export default defineEventHandler(async (event) => {
// Concatenate berth numbers
const berthNumbers = berths.map(b => b['Mooring Number']).join(', ');
- // Documenso API configuration
- const documensoApiKey = process.env.NUXT_DOCUMENSO_API_KEY;
- const documensoBaseUrl = process.env.NUXT_DOCUMENSO_BASE_URL;
- const templateId = '9';
-
- if (!documensoApiKey || !documensoBaseUrl) {
- throw createError({
- statusCode: 500,
- statusMessage: "Documenso configuration missing. Please check NUXT_DOCUMENSO_API_KEY and NUXT_DOCUMENSO_BASE_URL environment variables."
- });
- }
-
// 1. Get template (optional - just to verify it exists)
try {
const templateResponse = await fetch(`${documensoBaseUrl}/api/v1/templates/${templateId}`, {
@@ -272,7 +304,8 @@ export default defineEventHandler(async (event) => {
'EOI Status': 'Waiting for Signatures',
'Sales Process Level': 'LOI and NDA Sent',
// Don't set EOI Time Sent here - only when email is sent or link is copied
- 'Extra Comments': updatedComments
+ 'Extra Comments': updatedComments,
+ 'documensoID': documentResponse.documentId.toString()
};
// Add signing links to update data with new column names
diff --git a/server/api/eoi/delete-document.ts b/server/api/eoi/delete-document.ts
index a778073..564ccaa 100644
--- a/server/api/eoi/delete-document.ts
+++ b/server/api/eoi/delete-document.ts
@@ -64,6 +64,7 @@ export default defineEventHandler(async (event) => {
'Signature Link CC': null,
'Signature Link Developer': null,
'Documeso ID': null,
+ 'documensoID': null,
'reminder_enabled': false
};
diff --git a/server/api/eoi/delete-generated-document.ts b/server/api/eoi/delete-generated-document.ts
new file mode 100644
index 0000000..e51ebf7
--- /dev/null
+++ b/server/api/eoi/delete-generated-document.ts
@@ -0,0 +1,131 @@
+import { getInterestById, updateInterest } from '~/server/utils/nocodb';
+import { checkDocumentSignatureStatus } from '~/server/utils/documeso';
+import type { InterestSalesProcessLevel, EOIStatus } from '~/utils/types';
+
+export default defineEventHandler(async (event) => {
+ const xTagHeader = getRequestHeader(event, "x-tag");
+
+ console.log('[Delete Generated EOI] Request received with x-tag:', xTagHeader);
+
+ if (!xTagHeader || (xTagHeader !== "094ut234" && xTagHeader !== "pjnvü1230")) {
+ console.error('[Delete Generated EOI] Authentication failed - invalid x-tag');
+ throw createError({ statusCode: 401, statusMessage: "unauthenticated" });
+ }
+
+ try {
+ const body = await readBody(event);
+ const { interestId } = body;
+
+ console.log('[Delete Generated EOI] Interest ID:', interestId);
+
+ if (!interestId) {
+ console.error('[Delete Generated EOI] No interest ID provided');
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'Interest ID is required',
+ });
+ }
+
+ // Get current interest data
+ const interest = await getInterestById(interestId);
+ if (!interest) {
+ throw createError({
+ statusCode: 404,
+ statusMessage: 'Interest not found',
+ });
+ }
+
+ const documensoID = interest['documensoID'];
+ console.log('[Delete Generated EOI] Documenso ID:', documensoID);
+
+ if (!documensoID) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'No generated document found to delete',
+ });
+ }
+
+ // Check if document is fully signed - prevent deletion if it is
+ try {
+ const signatureStatus = await checkDocumentSignatureStatus(parseInt(documensoID));
+ console.log('[Delete Generated EOI] Signature status:', signatureStatus);
+
+ if (signatureStatus.allSigned) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'Cannot delete a fully signed document. All parties have already signed.',
+ });
+ }
+ } catch (error: any) {
+ console.error('[Delete Generated EOI] Failed to check signature status:', error);
+ // If we can't check status, we'll proceed with deletion but warn
+ console.warn('[Delete Generated EOI] Proceeding with deletion despite signature status check failure');
+ }
+
+ // Delete document from Documenso
+ const documensoApiKey = process.env.NUXT_DOCUMENSO_API_KEY;
+ const documensoBaseUrl = process.env.NUXT_DOCUMENSO_BASE_URL;
+
+ if (!documensoApiKey || !documensoBaseUrl) {
+ throw createError({
+ statusCode: 500,
+ statusMessage: "Documenso configuration missing. Please check NUXT_DOCUMENSO_API_KEY and NUXT_DOCUMENSO_BASE_URL environment variables."
+ });
+ }
+
+ console.log('[Delete Generated EOI] Deleting document from Documenso');
+ try {
+ const deleteResponse = await fetch(`${documensoBaseUrl}/api/v1/documents/${documensoID}`, {
+ method: 'DELETE',
+ headers: {
+ 'Authorization': `Bearer ${documensoApiKey}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ if (!deleteResponse.ok) {
+ const errorText = await deleteResponse.text();
+ console.error('[Delete Generated EOI] Documenso deletion failed:', errorText);
+ throw new Error(`Failed to delete document from Documenso: ${deleteResponse.statusText}`);
+ }
+
+ console.log('[Delete Generated EOI] Successfully deleted document from Documenso');
+ } catch (error: any) {
+ console.error('[Delete Generated EOI] Documenso deletion error:', error);
+ throw createError({
+ statusCode: 500,
+ statusMessage: `Failed to delete document from Documenso: ${error.message}`,
+ });
+ }
+
+ // Reset interest fields
+ const updateData = {
+ 'EOI Status': 'Awaiting Further Details' as EOIStatus,
+ 'Sales Process Level': 'Specific Qualified Interest' as InterestSalesProcessLevel,
+ 'EOI Time Sent': undefined,
+ 'Signature Link Client': undefined,
+ 'Signature Link CC': undefined,
+ 'Signature Link Developer': undefined,
+ 'documensoID': undefined,
+ 'reminder_enabled': false
+ };
+
+ console.log('[Delete Generated EOI] Resetting interest fields');
+
+ // Update the interest
+ await updateInterest(interestId, updateData);
+
+ console.log('[Delete Generated EOI] Delete completed successfully');
+ return {
+ success: true,
+ message: 'Generated EOI document deleted successfully',
+ };
+ } catch (error: any) {
+ console.error('[Delete Generated EOI] Failed to delete generated EOI document:', error);
+ console.error('[Delete Generated EOI] Error stack:', error.stack);
+ throw createError({
+ statusCode: error.statusCode || 500,
+ statusMessage: error.statusMessage || error.message || 'Failed to delete generated EOI document',
+ });
+ }
+});
diff --git a/utils/types.ts b/utils/types.ts
index 66ebcda..436ae13 100644
--- a/utils/types.ts
+++ b/utils/types.ts
@@ -127,6 +127,8 @@ export interface Interest {
"Signature Link Client"?: string;
"Signature Link CC"?: string;
"Signature Link Developer"?: string;
+ // Documenso document ID for generated documents
+ "documensoID"?: string;
}
export interface InterestsResponse {