diff --git a/src/pages/api/email/send-event-request-email.ts b/src/pages/api/email/send-event-request-email.ts new file mode 100644 index 0000000..b79d31c --- /dev/null +++ b/src/pages/api/email/send-event-request-email.ts @@ -0,0 +1,120 @@ +import type { APIRoute } from 'astro'; +import { initializeEmailServices, authenticatePocketBase } from '../../../scripts/email/EmailHelpers'; +import { + sendEventRequestSubmissionEmail, + sendEventRequestStatusChangeEmail, + sendPRCompletedEmail, + sendDesignPRNotificationEmail +} from '../../../scripts/email/EventRequestEmailFunctions'; + +export const POST: APIRoute = async ({ request }) => { + try { + console.log('๐Ÿ“จ Event request email API called'); + + const { + type, + eventRequestId, + previousStatus, + newStatus, + changedByUserId, + declinedReason, + additionalContext, + authData + } = await request.json(); + + console.log('๐Ÿ“‹ Request data:', { + type, + eventRequestId, + hasAuthData: !!authData, + authDataHasToken: !!(authData?.token), + authDataHasModel: !!(authData?.model), + newStatus, + previousStatus + }); + + if (!type || !eventRequestId) { + console.error('โŒ Missing required parameters'); + return new Response( + JSON.stringify({ error: 'Missing required parameters: type and eventRequestId' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); + } + + // Initialize services + const { pb, resend, fromEmail, replyToEmail } = await initializeEmailServices(); + + // Authenticate with PocketBase if auth data is provided + authenticatePocketBase(pb, authData); + + let success = false; + + console.log(`๐ŸŽฏ Processing event request email type: ${type}`); + + switch (type) { + case 'event_request_submission': + success = await sendEventRequestSubmissionEmail(pb, resend, fromEmail, replyToEmail, { + eventRequestId + }); + break; + + case 'event_request_status_change': + if (!newStatus) { + return new Response( + JSON.stringify({ error: 'Missing newStatus for event request status change notification' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); + } + success = await sendEventRequestStatusChangeEmail(pb, resend, fromEmail, replyToEmail, { + eventRequestId, + newStatus, + previousStatus, + changedByUserId, + declinedReason: declinedReason || additionalContext?.declinedReason + }); + break; + + case 'pr_completed': + success = await sendPRCompletedEmail(pb, resend, fromEmail, replyToEmail, { + eventRequestId + }); + break; + + case 'design_pr_notification': + success = await sendDesignPRNotificationEmail(pb, resend, fromEmail, replyToEmail, { + eventRequestId, + action: additionalContext?.action || 'unknown' + }); + break; + + default: + console.error('โŒ Unknown event request notification type:', type); + return new Response( + JSON.stringify({ error: `Unknown event request notification type: ${type}` }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); + } + + console.log(`๐Ÿ“Š Event request email operation result: ${success ? 'SUCCESS' : 'FAILED'}`); + + return new Response( + JSON.stringify({ + success, + message: success ? 'Event request email notification sent successfully' : 'Failed to send event request email notification' + }), + { + status: success ? 200 : 500, + headers: { 'Content-Type': 'application/json' } + } + ); + + } catch (error) { + console.error('โŒ Error in event request email notification API:', error); + return new Response( + JSON.stringify({ + error: 'Internal server error', + details: error instanceof Error ? error.message : 'Unknown error' + }), + { status: 500, headers: { 'Content-Type': 'application/json' } } + ); + } +}; diff --git a/src/pages/api/email/send-reimbursement-email.ts b/src/pages/api/email/send-reimbursement-email.ts new file mode 100644 index 0000000..de73154 --- /dev/null +++ b/src/pages/api/email/send-reimbursement-email.ts @@ -0,0 +1,592 @@ +import type { APIRoute } from 'astro'; +import { initializeEmailServices, authenticatePocketBase, getStatusColor, getStatusText, getNextStepsText } from '../../../scripts/email/EmailHelpers'; + +export const POST: APIRoute = async ({ request }) => { + try { + console.log('๐Ÿ“จ Reimbursement email API called'); + + const { + type, + reimbursementId, + previousStatus, + newStatus, + changedByUserId, + comment, + commentByUserId, + isPrivate, + additionalContext, + authData + } = await request.json(); + + console.log('๐Ÿ“‹ Request data:', { + type, + reimbursementId, + hasAuthData: !!authData, + authDataHasToken: !!(authData?.token), + authDataHasModel: !!(authData?.model), + commentLength: comment?.length || 0, + commentByUserId, + isPrivate + }); + + if (!type || !reimbursementId) { + console.error('โŒ Missing required parameters'); + return new Response( + JSON.stringify({ error: 'Missing required parameters: type and reimbursementId' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); + } + + // Initialize services + const { pb, resend, fromEmail, replyToEmail } = await initializeEmailServices(); + + // Authenticate with PocketBase if auth data is provided + authenticatePocketBase(pb, authData); + + let success = false; + + console.log(`๐ŸŽฏ Processing reimbursement email type: ${type}`); + + switch (type) { + case 'status_change': + if (!newStatus) { + return new Response( + JSON.stringify({ error: 'Missing newStatus for status_change notification' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); + } + success = await sendStatusChangeEmail(pb, resend, fromEmail, replyToEmail, { + reimbursementId, + newStatus, + previousStatus, + changedByUserId, + additionalContext + }); + break; + + case 'comment': + if (!comment || !commentByUserId) { + console.error('โŒ Missing comment or commentByUserId for comment notification'); + return new Response( + JSON.stringify({ error: 'Missing comment or commentByUserId for comment notification' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); + } + success = await sendCommentEmail(pb, resend, fromEmail, replyToEmail, { + reimbursementId, + comment, + commentByUserId, + isPrivate: isPrivate || false + }); + break; + + case 'submission': + success = await sendSubmissionEmail(pb, resend, fromEmail, replyToEmail, { + reimbursementId + }); + break; + + case 'test': + const { email } = additionalContext || {}; + if (!email) { + return new Response( + JSON.stringify({ error: 'Missing email for test notification' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); + } + success = await sendTestEmail(resend, fromEmail, replyToEmail, email); + break; + + default: + console.error('โŒ Unknown reimbursement notification type:', type); + return new Response( + JSON.stringify({ error: `Unknown reimbursement notification type: ${type}` }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); + } + + console.log(`๐Ÿ“Š Reimbursement email operation result: ${success ? 'SUCCESS' : 'FAILED'}`); + + return new Response( + JSON.stringify({ + success, + message: success ? 'Reimbursement email notification sent successfully' : 'Failed to send reimbursement email notification' + }), + { + status: success ? 200 : 500, + headers: { 'Content-Type': 'application/json' } + } + ); + + } catch (error) { + console.error('โŒ Error in reimbursement email notification API:', error); + return new Response( + JSON.stringify({ + error: 'Internal server error', + details: error instanceof Error ? error.message : 'Unknown error' + }), + { status: 500, headers: { 'Content-Type': 'application/json' } } + ); + } +}; + +// Helper functions for reimbursement email types +async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { + try { + console.log('๐Ÿ“ง Starting reimbursement status change email process...'); + console.log('Environment check:', { + hasResendKey: !!import.meta.env.RESEND_API_KEY, + fromEmail, + replyToEmail, + pocketbaseUrl: import.meta.env.POCKETBASE_URL + }); + + // Get reimbursement details + console.log('๐Ÿ” Fetching reimbursement details for:', data.reimbursementId); + const reimbursement = await pb.collection('reimbursement').getOne(data.reimbursementId); + console.log('โœ… Reimbursement fetched:', { id: reimbursement.id, title: reimbursement.title }); + + // Get submitter user details + console.log('๐Ÿ‘ค Fetching user details for:', reimbursement.submitted_by); + const user = await pb.collection('users').getOne(reimbursement.submitted_by); + if (!user || !user.email) { + console.error('โŒ User not found or no email:', reimbursement.submitted_by); + return false; + } + console.log('โœ… User fetched:', { id: user.id, name: user.name, email: user.email }); + + // Get changed by user name if provided + let changedByName = 'System'; + if (data.changedByUserId) { + try { + const changedByUser = await pb.collection('users').getOne(data.changedByUserId); + changedByName = changedByUser?.name || 'Unknown User'; + console.log('๐Ÿ‘ค Changed by user:', changedByName); + } catch (error) { + console.warn('โš ๏ธ Could not get changed by user name:', error); + } + } + + const subject = `Reimbursement Status Updated: ${reimbursement.title}`; + const statusColor = getStatusColor(data.newStatus); + const statusText = getStatusText(data.newStatus); + + console.log('๐Ÿ“ Email details:', { + to: user.email, + subject, + status: data.newStatus + }); + + const html = ` + + + + + + ${subject} + + +
+

IEEE UCSD Reimbursement Update

+
+ +
+

Status Update

+

Hello ${user.name},

+

Your reimbursement request "${reimbursement.title}" has been updated.

+ +
+
+ Status: + ${statusText} +
+ + ${data.previousStatus && data.previousStatus !== data.newStatus ? ` +
+ Changed from: ${getStatusText(data.previousStatus)} โ†’ ${statusText} +
+ ` : ''} + + ${changedByName !== 'System' ? ` +
+ Updated by: ${changedByName} +
+ ` : ''} + + ${data.newStatus === 'rejected' && data.additionalContext?.rejectionReason ? ` +
+
Rejection Reason:
+
${data.additionalContext.rejectionReason}
+
+ ` : ''} +
+ +
+

Reimbursement Details

+ + + + + + + + + + + + + + + + + +
Amount:$${reimbursement.total_amount.toFixed(2)}
Date of Purchase:${new Date(reimbursement.date_of_purchase).toLocaleDateString()}
Department:${reimbursement.department.charAt(0).toUpperCase() + reimbursement.department.slice(1)}
Payment Method:${reimbursement.payment_method}
+
+ +
+

Next Steps:

+

+ ${getNextStepsText(data.newStatus)} +

+
+
+ +
+

This is an automated notification from IEEE UCSD Reimbursement System.

+

If you have any questions, please contact us at ${replyToEmail}

+
+ + + `; + + console.log('๐Ÿ“ค Attempting to send email via Resend...'); + const result = await resend.emails.send({ + from: fromEmail, + to: [user.email], + replyTo: replyToEmail, + subject, + html, + }); + + console.log('โœ… Resend response:', result); + console.log('๐ŸŽ‰ Status change email sent successfully!'); + return true; + } catch (error) { + console.error('โŒ Failed to send status change email:', error); + console.error('Error details:', { + name: error instanceof Error ? error.name : 'Unknown', + message: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined + }); + return false; + } +} + +async function sendCommentEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { + try { + console.log('๐Ÿ’ฌ Starting comment email process...'); + console.log('Comment data received:', { + reimbursementId: data.reimbursementId, + commentByUserId: data.commentByUserId, + isPrivate: data.isPrivate, + commentLength: data.comment?.length || 0 + }); + + // Don't send emails for private comments + if (data.isPrivate) { + console.log('๐Ÿ”’ Comment is private, skipping email notification'); + return true; + } + + // Get reimbursement details + console.log('๐Ÿ” Fetching reimbursement details for:', data.reimbursementId); + const reimbursement = await pb.collection('reimbursement').getOne(data.reimbursementId); + console.log('โœ… Reimbursement fetched:', { + id: reimbursement.id, + title: reimbursement.title, + submitted_by: reimbursement.submitted_by + }); + + // Get submitter user details + console.log('๐Ÿ‘ค Fetching submitter user details for:', reimbursement.submitted_by); + const user = await pb.collection('users').getOne(reimbursement.submitted_by); + if (!user || !user.email) { + console.error('โŒ User not found or no email:', reimbursement.submitted_by); + return false; + } + console.log('โœ… Submitter user fetched:', { + id: user.id, + name: user.name, + email: user.email + }); + + // Get commenter user name + console.log('๐Ÿ‘ค Fetching commenter user details for:', data.commentByUserId); + let commentByName = 'Unknown User'; + try { + const commentByUser = await pb.collection('users').getOne(data.commentByUserId); + commentByName = commentByUser?.name || 'Unknown User'; + console.log('โœ… Commenter user fetched:', { + id: commentByUser?.id, + name: commentByName + }); + } catch (error) { + console.warn('โš ๏ธ Could not get commenter user name:', error); + } + + const subject = `New Comment on Reimbursement: ${reimbursement.title}`; + + console.log('๐Ÿ“ Comment email details:', { + to: user.email, + subject, + commentBy: commentByName, + commentPreview: data.comment.substring(0, 50) + (data.comment.length > 50 ? '...' : '') + }); + + const html = ` + + + + + + ${subject} + + +
+

IEEE UCSD Reimbursement Comment

+
+ +
+

New Comment Added

+

Hello ${user.name},

+

A new comment has been added to your reimbursement request "${reimbursement.title}".

+ +
+
+ Comment by: ${commentByName} +
+
+

${data.comment}

+
+
+ +
+

Reimbursement Details

+ + + + + + + + + + + + + +
Status: + + ${getStatusText(reimbursement.status)} + +
Amount:$${reimbursement.total_amount.toFixed(2)}
Date of Purchase:${new Date(reimbursement.date_of_purchase).toLocaleDateString()}
+
+
+ +
+

This is an automated notification from IEEE UCSD Reimbursement System.

+

If you have any questions, please contact us at ${replyToEmail}

+
+ + + `; + + console.log('๐Ÿ“ค Attempting to send comment email via Resend...'); + const result = await resend.emails.send({ + from: fromEmail, + to: [user.email], + replyTo: replyToEmail, + subject, + html, + }); + + console.log('โœ… Resend comment email response:', result); + console.log('๐ŸŽ‰ Comment email sent successfully!'); + return true; + } catch (error) { + console.error('โŒ Failed to send comment email:', error); + console.error('Comment email error details:', { + name: error instanceof Error ? error.name : 'Unknown', + message: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined + }); + return false; + } +} + +async function sendSubmissionEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { + try { + // Get reimbursement details + const reimbursement = await pb.collection('reimbursement').getOne(data.reimbursementId); + + // Get submitter user details + const user = await pb.collection('users').getOne(reimbursement.submitted_by); + if (!user || !user.email) { + console.error('User not found or no email:', reimbursement.submitted_by); + return false; + } + + const subject = `Reimbursement Submitted: ${reimbursement.title}`; + + const html = ` + + + + + + ${subject} + + +
+

โœ… Reimbursement Submitted Successfully

+
+ +
+

Submission Confirmed

+

Hello ${user.name},

+

Your reimbursement request has been successfully submitted and is now under review.

+ +
+

Reimbursement Details

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Title:${reimbursement.title}
Amount:$${reimbursement.total_amount.toFixed(2)}
Date of Purchase:${new Date(reimbursement.date_of_purchase).toLocaleDateString()}
Department:${reimbursement.department.charAt(0).toUpperCase() + reimbursement.department.slice(1)}
Payment Method:${reimbursement.payment_method}
Status: + + Submitted + +
+
+ +
+

What happens next?

+
    +
  • Your receipts will be reviewed by our team
  • +
  • You'll receive email updates as the status changes
  • +
  • Once approved, payment will be processed
  • +
  • Typical processing time is 1-2 weeks
  • +
+
+
+ +
+

This is an automated notification from IEEE UCSD Reimbursement System.

+

If you have any questions, please contact us at ${replyToEmail}

+
+ + + `; + + const result = await resend.emails.send({ + from: fromEmail, + to: [user.email], + replyTo: replyToEmail, + subject, + html, + }); + + console.log('Submission confirmation email sent successfully:', result); + return true; + } catch (error) { + console.error('Failed to send submission confirmation email:', error); + return false; + } +} + +async function sendTestEmail(resend: any, fromEmail: string, replyToEmail: string, email: string): Promise { + try { + console.log('๐Ÿงช Starting test email process...'); + console.log('Test email configuration:', { + fromEmail, + replyToEmail, + toEmail: email, + hasResend: !!resend + }); + + const subject = 'Test Email from IEEE UCSD Reimbursement System'; + + const html = ` + + + + + + ${subject} + + +
+

๐Ÿงช Test Email

+
+ +
+

Email System Test

+

This is a test email from the IEEE UCSD Reimbursement System.

+

If you receive this email, the notification system is working correctly!

+ +
+

โœ… Email delivery successful

+
+
+ +
+

This is a test notification from IEEE UCSD Reimbursement System.

+

If you have any questions, please contact us at ${replyToEmail}

+
+ + + `; + + console.log('๐Ÿ“ค Sending test email via Resend...'); + const result = await resend.emails.send({ + from: fromEmail, + to: [email], + replyTo: replyToEmail, + subject, + html, + }); + + console.log('โœ… Resend test email response:', result); + console.log('๐ŸŽ‰ Test email sent successfully!'); + return true; + } catch (error) { + console.error('โŒ Failed to send test email:', error); + console.error('Test email error details:', { + name: error instanceof Error ? error.name : 'Unknown', + message: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined + }); + return false; + } +} \ No newline at end of file diff --git a/src/pages/api/email/send-reimbursement-notification.ts b/src/pages/api/email/send-reimbursement-notification.ts index 937d9ac..148a697 100644 --- a/src/pages/api/email/send-reimbursement-notification.ts +++ b/src/pages/api/email/send-reimbursement-notification.ts @@ -2,1454 +2,87 @@ import type { APIRoute } from 'astro'; export const POST: APIRoute = async ({ request }) => { try { - console.log('๐Ÿ“จ Reimbursement email API called'); + console.log('๐Ÿ“จ Email notification API called (legacy endpoint)'); - const { - type, - reimbursementId, - eventRequestId, - previousStatus, - newStatus, - changedByUserId, - comment, - commentByUserId, - isPrivate, - declinedReason, - additionalContext, - authData // Change to authData containing token and model - } = await request.json(); + const requestData = await request.json(); + const { type, reimbursementId, eventRequestId } = requestData; console.log('๐Ÿ“‹ Request data:', { type, reimbursementId, - eventRequestId, - hasAuthData: !!authData, - authDataHasToken: !!(authData?.token), - authDataHasModel: !!(authData?.model), - commentLength: comment?.length || 0, - commentByUserId, - isPrivate + eventRequestId }); - if (!type || (!reimbursementId && !eventRequestId)) { - console.error('โŒ Missing required parameters'); + if (!type) { + console.error('โŒ Missing required parameter: type'); return new Response( - JSON.stringify({ error: 'Missing required parameters: type and (reimbursementId or eventRequestId)' }), + JSON.stringify({ error: 'Missing required parameter: type' }), { status: 400, headers: { 'Content-Type': 'application/json' } } ); } - // Import Resend and create direct PocketBase connection for server-side use - const { Resend } = await import('resend'); - const PocketBase = await import('pocketbase').then(module => module.default); + // Determine which endpoint to redirect to based on email type + const reimbursementTypes = ['status_change', 'comment', 'submission', 'test']; + const eventRequestTypes = ['event_request_submission', 'event_request_status_change', 'pr_completed', 'design_pr_notification']; + + let targetEndpoint = ''; - // Initialize services - const pb = new PocketBase(import.meta.env.POCKETBASE_URL || 'http://127.0.0.1:8090'); - const resend = new Resend(import.meta.env.RESEND_API_KEY); - - if (!import.meta.env.RESEND_API_KEY) { - throw new Error('RESEND_API_KEY environment variable is required'); - } - - // Authenticate with PocketBase if auth data is provided - if (authData && authData.token && authData.model) { - console.log('๐Ÿ” Authenticating with PocketBase using provided auth data'); - pb.authStore.save(authData.token, authData.model); - console.log('โœ… PocketBase authentication successful'); - } else { - console.warn('โš ๏ธ No auth data provided, proceeding without authentication'); - } - - const fromEmail = import.meta.env.FROM_EMAIL || 'IEEE UCSD '; - const replyToEmail = import.meta.env.REPLY_TO_EMAIL || 'treasurer@ieeeucsd.org'; - - let success = false; - - console.log(`๐ŸŽฏ Processing email type: ${type}`); - - switch (type) { - case 'status_change': - if (!newStatus || !reimbursementId) { - return new Response( - JSON.stringify({ error: 'Missing newStatus or reimbursementId for status_change notification' }), - { status: 400, headers: { 'Content-Type': 'application/json' } } - ); - } - success = await sendStatusChangeEmail(pb, resend, fromEmail, replyToEmail, { - reimbursementId, - newStatus, - previousStatus, - changedByUserId, - additionalContext - }); - break; - - case 'comment': - if (!comment || !commentByUserId || !reimbursementId) { - console.error('โŒ Missing comment, commentByUserId, or reimbursementId for comment notification'); - return new Response( - JSON.stringify({ error: 'Missing comment, commentByUserId, or reimbursementId for comment notification' }), - { status: 400, headers: { 'Content-Type': 'application/json' } } - ); - } - success = await sendCommentEmail(pb, resend, fromEmail, replyToEmail, { - reimbursementId, - comment, - commentByUserId, - isPrivate: isPrivate || false - }); - break; - - case 'submission': - if (!reimbursementId) { - return new Response( - JSON.stringify({ error: 'Missing reimbursementId for submission notification' }), - { status: 400, headers: { 'Content-Type': 'application/json' } } - ); - } - success = await sendSubmissionEmail(pb, resend, fromEmail, replyToEmail, { - reimbursementId - }); - break; - - case 'event_request_submission': - if (!eventRequestId) { - return new Response( - JSON.stringify({ error: 'Missing eventRequestId for event request submission notification' }), - { status: 400, headers: { 'Content-Type': 'application/json' } } - ); - } - success = await sendEventRequestSubmissionEmail(pb, resend, fromEmail, replyToEmail, { - eventRequestId - }); - break; - - case 'event_request_status_change': - if (!eventRequestId || !newStatus) { - return new Response( - JSON.stringify({ error: 'Missing eventRequestId or newStatus for event request status change notification' }), - { status: 400, headers: { 'Content-Type': 'application/json' } } - ); - } - success = await sendEventRequestStatusChangeEmail(pb, resend, fromEmail, replyToEmail, { - eventRequestId, - newStatus, - previousStatus, - changedByUserId, - declinedReason: declinedReason || additionalContext?.declinedReason - }); - break; - - case 'pr_completed': - if (!eventRequestId) { - return new Response( - JSON.stringify({ error: 'Missing eventRequestId for PR completed notification' }), - { status: 400, headers: { 'Content-Type': 'application/json' } } - ); - } - success = await sendPRCompletedEmail(pb, resend, fromEmail, replyToEmail, { - eventRequestId - }); - break; - - case 'design_pr_notification': - if (!eventRequestId) { - return new Response( - JSON.stringify({ error: 'Missing eventRequestId for design PR notification' }), - { status: 400, headers: { 'Content-Type': 'application/json' } } - ); - } - success = await sendDesignPRNotificationEmail(pb, resend, fromEmail, replyToEmail, { - eventRequestId, - action: additionalContext?.action || 'unknown' - }); - break; - - case 'test': - const { email } = additionalContext || {}; - if (!email) { - return new Response( - JSON.stringify({ error: 'Missing email for test notification' }), - { status: 400, headers: { 'Content-Type': 'application/json' } } - ); - } - success = await sendTestEmail(resend, fromEmail, replyToEmail, email); - break; - - default: - console.error('โŒ Unknown notification type:', type); + if (reimbursementTypes.includes(type)) { + if (!reimbursementId && type !== 'test') { return new Response( - JSON.stringify({ error: `Unknown notification type: ${type}` }), + JSON.stringify({ error: 'Missing reimbursementId for reimbursement notification' }), { status: 400, headers: { 'Content-Type': 'application/json' } } ); + } + targetEndpoint = '/api/email/send-reimbursement-email'; + } else if (eventRequestTypes.includes(type)) { + if (!eventRequestId) { + return new Response( + JSON.stringify({ error: 'Missing eventRequestId for event request notification' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); + } + targetEndpoint = '/api/email/send-event-request-email'; + } else { + console.error('โŒ Unknown notification type:', type); + return new Response( + JSON.stringify({ error: `Unknown notification type: ${type}` }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); } - console.log(`๐Ÿ“Š Email operation result: ${success ? 'SUCCESS' : 'FAILED'}`); + console.log(`๐Ÿ”„ Redirecting ${type} to ${targetEndpoint}`); + + // Forward the request to the appropriate endpoint + const baseUrl = new URL(request.url).origin; + const response = await fetch(`${baseUrl}${targetEndpoint}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestData), + }); + + const result = await response.json(); + + console.log(`๐Ÿ“Š Forwarded request result: ${result.success ? 'SUCCESS' : 'FAILED'}`); return new Response( - JSON.stringify({ - success, - message: success ? 'Email notification sent successfully' : 'Failed to send email notification' - }), - { - status: success ? 200 : 500, - headers: { 'Content-Type': 'application/json' } + JSON.stringify(result), + { + status: response.status, + headers: { 'Content-Type': 'application/json' } } ); } catch (error) { console.error('โŒ Error in email notification API:', error); return new Response( - JSON.stringify({ - error: 'Internal server error', - details: error instanceof Error ? error.message : 'Unknown error' + JSON.stringify({ + error: 'Internal server error', + details: error instanceof Error ? error.message : 'Unknown error' }), { status: 500, headers: { 'Content-Type': 'application/json' } } ); } -}; - -// Helper functions for different email types -async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { - try { - console.log('๐Ÿ“ง Starting status change email process...'); - console.log('Environment check:', { - hasResendKey: !!import.meta.env.RESEND_API_KEY, - fromEmail, - replyToEmail, - pocketbaseUrl: import.meta.env.POCKETBASE_URL - }); - - // Get reimbursement details - console.log('๐Ÿ” Fetching reimbursement details for:', data.reimbursementId); - const reimbursement = await pb.collection('reimbursement').getOne(data.reimbursementId); - console.log('โœ… Reimbursement fetched:', { id: reimbursement.id, title: reimbursement.title }); - - // Get submitter user details - console.log('๐Ÿ‘ค Fetching user details for:', reimbursement.submitted_by); - const user = await pb.collection('users').getOne(reimbursement.submitted_by); - if (!user || !user.email) { - console.error('โŒ User not found or no email:', reimbursement.submitted_by); - return false; - } - console.log('โœ… User fetched:', { id: user.id, name: user.name, email: user.email }); - - // Get changed by user name if provided - let changedByName = 'System'; - if (data.changedByUserId) { - try { - const changedByUser = await pb.collection('users').getOne(data.changedByUserId); - changedByName = changedByUser?.name || 'Unknown User'; - console.log('๐Ÿ‘ค Changed by user:', changedByName); - } catch (error) { - console.warn('โš ๏ธ Could not get changed by user name:', error); - } - } - - const subject = `Reimbursement Status Updated: ${reimbursement.title}`; - const statusColor = getStatusColor(data.newStatus); - const statusText = getStatusText(data.newStatus); - - console.log('๐Ÿ“ Email details:', { - to: user.email, - subject, - status: data.newStatus - }); - - const html = ` - - - - - - ${subject} - - -
-

IEEE UCSD Reimbursement Update

-
- -
-

Status Update

-

Hello ${user.name},

-

Your reimbursement request "${reimbursement.title}" has been updated.

- -
-
- Status: - ${statusText} -
- - ${data.previousStatus && data.previousStatus !== data.newStatus ? ` -
- Changed from: ${getStatusText(data.previousStatus)} โ†’ ${statusText} -
- ` : ''} - - ${changedByName !== 'System' ? ` -
- Updated by: ${changedByName} -
- ` : ''} - - ${data.newStatus === 'rejected' && data.additionalContext?.rejectionReason ? ` -
-
Rejection Reason:
-
${data.additionalContext.rejectionReason}
-
- ` : ''} -
- -
-

Reimbursement Details

- - - - - - - - - - - - - - - - - -
Amount:$${reimbursement.total_amount.toFixed(2)}
Date of Purchase:${new Date(reimbursement.date_of_purchase).toLocaleDateString()}
Department:${reimbursement.department.charAt(0).toUpperCase() + reimbursement.department.slice(1)}
Payment Method:${reimbursement.payment_method}
-
- -
-

Next Steps:

-

- ${getNextStepsText(data.newStatus)} -

-
-
- -
-

This is an automated notification from IEEE UCSD Reimbursement System.

-

If you have any questions, please contact us at ${replyToEmail}

-
- - - `; - - console.log('๐Ÿ“ค Attempting to send email via Resend...'); - const result = await resend.emails.send({ - from: fromEmail, - to: [user.email], - replyTo: replyToEmail, - subject, - html, - }); - - console.log('โœ… Resend response:', result); - console.log('๐ŸŽ‰ Status change email sent successfully!'); - return true; - } catch (error) { - console.error('โŒ Failed to send status change email:', error); - console.error('Error details:', { - name: error instanceof Error ? error.name : 'Unknown', - message: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined - }); - return false; - } -} - -async function sendCommentEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { - try { - console.log('๐Ÿ’ฌ Starting comment email process...'); - console.log('Comment data received:', { - reimbursementId: data.reimbursementId, - commentByUserId: data.commentByUserId, - isPrivate: data.isPrivate, - commentLength: data.comment?.length || 0 - }); - - // Don't send emails for private comments - if (data.isPrivate) { - console.log('๐Ÿ”’ Comment is private, skipping email notification'); - return true; - } - - // Get reimbursement details - console.log('๐Ÿ” Fetching reimbursement details for:', data.reimbursementId); - const reimbursement = await pb.collection('reimbursement').getOne(data.reimbursementId); - console.log('โœ… Reimbursement fetched:', { - id: reimbursement.id, - title: reimbursement.title, - submitted_by: reimbursement.submitted_by - }); - - // Get submitter user details - console.log('๐Ÿ‘ค Fetching submitter user details for:', reimbursement.submitted_by); - const user = await pb.collection('users').getOne(reimbursement.submitted_by); - if (!user || !user.email) { - console.error('โŒ User not found or no email:', reimbursement.submitted_by); - return false; - } - console.log('โœ… Submitter user fetched:', { - id: user.id, - name: user.name, - email: user.email - }); - - // Get commenter user name - console.log('๐Ÿ‘ค Fetching commenter user details for:', data.commentByUserId); - let commentByName = 'Unknown User'; - try { - const commentByUser = await pb.collection('users').getOne(data.commentByUserId); - commentByName = commentByUser?.name || 'Unknown User'; - console.log('โœ… Commenter user fetched:', { - id: commentByUser?.id, - name: commentByName - }); - } catch (error) { - console.warn('โš ๏ธ Could not get commenter user name:', error); - } - - const subject = `New Comment on Reimbursement: ${reimbursement.title}`; - - console.log('๐Ÿ“ Comment email details:', { - to: user.email, - subject, - commentBy: commentByName, - commentPreview: data.comment.substring(0, 50) + (data.comment.length > 50 ? '...' : '') - }); - - const html = ` - - - - - - ${subject} - - -
-

IEEE UCSD Reimbursement Comment

-
- -
-

New Comment Added

-

Hello ${user.name},

-

A new comment has been added to your reimbursement request "${reimbursement.title}".

- -
-
- Comment by: ${commentByName} -
-
-

${data.comment}

-
-
- -
-

Reimbursement Details

- - - - - - - - - - - - - -
Status: - - ${getStatusText(reimbursement.status)} - -
Amount:$${reimbursement.total_amount.toFixed(2)}
Date of Purchase:${new Date(reimbursement.date_of_purchase).toLocaleDateString()}
-
-
- -
-

This is an automated notification from IEEE UCSD Reimbursement System.

-

If you have any questions, please contact us at ${replyToEmail}

-
- - - `; - - console.log('๐Ÿ“ค Attempting to send comment email via Resend...'); - const result = await resend.emails.send({ - from: fromEmail, - to: [user.email], - replyTo: replyToEmail, - subject, - html, - }); - - console.log('โœ… Resend comment email response:', result); - console.log('๐ŸŽ‰ Comment email sent successfully!'); - return true; - } catch (error) { - console.error('โŒ Failed to send comment email:', error); - console.error('Comment email error details:', { - name: error instanceof Error ? error.name : 'Unknown', - message: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined - }); - return false; - } -} - -async function sendSubmissionEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { - try { - // Get reimbursement details - const reimbursement = await pb.collection('reimbursement').getOne(data.reimbursementId); - - // Get submitter user details - const user = await pb.collection('users').getOne(reimbursement.submitted_by); - if (!user || !user.email) { - console.error('User not found or no email:', reimbursement.submitted_by); - return false; - } - - const subject = `Reimbursement Submitted: ${reimbursement.title}`; - - const html = ` - - - - - - ${subject} - - -
-

โœ… Reimbursement Submitted Successfully

-
- -
-

Submission Confirmed

-

Hello ${user.name},

-

Your reimbursement request has been successfully submitted and is now under review.

- -
-

Reimbursement Details

- - - - - - - - - - - - - - - - - - - - - - - - - -
Title:${reimbursement.title}
Amount:$${reimbursement.total_amount.toFixed(2)}
Date of Purchase:${new Date(reimbursement.date_of_purchase).toLocaleDateString()}
Department:${reimbursement.department.charAt(0).toUpperCase() + reimbursement.department.slice(1)}
Payment Method:${reimbursement.payment_method}
Status: - - Submitted - -
-
- -
-

What happens next?

-
    -
  • Your receipts will be reviewed by our team
  • -
  • You'll receive email updates as the status changes
  • -
  • Once approved, payment will be processed
  • -
  • Typical processing time is 1-2 weeks
  • -
-
-
- -
-

This is an automated notification from IEEE UCSD Reimbursement System.

-

If you have any questions, please contact us at ${replyToEmail}

-
- - - `; - - const result = await resend.emails.send({ - from: fromEmail, - to: [user.email], - replyTo: replyToEmail, - subject, - html, - }); - - console.log('Submission confirmation email sent successfully:', result); - return true; - } catch (error) { - console.error('Failed to send submission confirmation email:', error); - return false; - } -} - -async function sendTestEmail(resend: any, fromEmail: string, replyToEmail: string, email: string): Promise { - try { - console.log('๐Ÿงช Starting test email process...'); - console.log('Test email configuration:', { - fromEmail, - replyToEmail, - toEmail: email, - hasResend: !!resend - }); - - const subject = 'Test Email from IEEE UCSD Reimbursement System'; - - const html = ` - - - - - - ${subject} - - -
-

๐Ÿงช Test Email

-
- -
-

Email System Test

-

This is a test email from the IEEE UCSD Reimbursement System.

-

If you receive this email, the notification system is working correctly!

- -
-

โœ… Email delivery successful

-
-
- -
-

This is a test notification from IEEE UCSD Reimbursement System.

-

If you have any questions, please contact us at ${replyToEmail}

-
- - - `; - - console.log('๐Ÿ“ค Sending test email via Resend...'); - const result = await resend.emails.send({ - from: fromEmail, - to: [email], - replyTo: replyToEmail, - subject, - html, - }); - - console.log('โœ… Resend test email response:', result); - console.log('๐ŸŽ‰ Test email sent successfully!'); - return true; - } catch (error) { - console.error('โŒ Failed to send test email:', error); - console.error('Test email error details:', { - name: error instanceof Error ? error.name : 'Unknown', - message: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined - }); - return false; - } -} - -async function sendEventRequestSubmissionEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { - try { - console.log('๐ŸŽช Starting event request submission email process...'); - console.log('Environment check:', { - hasResendKey: !!import.meta.env.RESEND_API_KEY, - fromEmail, - replyToEmail, - pocketbaseUrl: import.meta.env.POCKETBASE_URL - }); - - // Get event request details - console.log('๐Ÿ” Fetching event request details for:', data.eventRequestId); - const eventRequest = await pb.collection('event_request').getOne(data.eventRequestId); - console.log('โœ… Event request fetched:', { id: eventRequest.id, name: eventRequest.name }); - - // Get submitter user details - console.log('๐Ÿ‘ค Fetching user details for:', eventRequest.requested_user); - const user = await pb.collection('users').getOne(eventRequest.requested_user); - if (!user) { - console.error('โŒ User not found:', eventRequest.requested_user); - return false; - } - console.log('โœ… User fetched:', { id: user.id, name: user.name, email: user.email }); - - const coordinatorsEmail = 'coordinators@ieeeatucsd.org'; - const subject = `New Event Request Submitted: ${eventRequest.name}`; - - console.log('๐Ÿ“ Email details:', { - to: coordinatorsEmail, - subject, - submittedBy: user.name - }); - - // Format date/time for display - const formatDateTime = (dateString: string) => { - try { - const date = new Date(dateString); - return date.toLocaleString('en-US', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: '2-digit', - timeZoneName: 'short' - }); - } catch (e) { - return dateString; - } - }; - - // Format flyer types for display - const formatFlyerTypes = (flyerTypes: string[]) => { - if (!flyerTypes || flyerTypes.length === 0) return 'None specified'; - - const typeMap: Record = { - 'digital_with_social': 'Digital with Social Media', - 'digital_no_social': 'Digital without Social Media', - 'physical_with_advertising': 'Physical with Advertising', - 'physical_no_advertising': 'Physical without Advertising', - 'newsletter': 'Newsletter', - 'other': 'Other' - }; - - return flyerTypes.map(type => typeMap[type] || type).join(', '); - }; - - // Format required logos for display - const formatLogos = (logos: string[]) => { - if (!logos || logos.length === 0) return 'None specified'; - return logos.join(', '); - }; - - const html = ` - - - - - - ${subject} - - -
-

๐ŸŽช New Event Request Submitted

-
- -
-

Event Request Details

-

Hello Coordinators,

-

A new event request has been submitted by ${user.name} and requires your review.

- -
-

Basic Information

- - - - - - - - - - - - - - - - - - - - - - - - - -
Event Name:${eventRequest.name}
Location:${eventRequest.location}
Start Date & Time:${formatDateTime(eventRequest.start_date_time)}
End Date & Time:${formatDateTime(eventRequest.end_date_time)}
Expected Attendance:${eventRequest.expected_attendance || 'Not specified'}
Submitted By:${user.name} (${user.email})
-
- -
-

Event Description

-
-

${eventRequest.event_description || 'No description provided'}

-
-
- -
-

PR & Marketing Requirements

- - - - - - ${eventRequest.flyers_needed ? ` - - - - - ${eventRequest.flyer_advertising_start_date ? ` - - - - - ` : ''} - ${eventRequest.required_logos ? ` - - - - - ` : ''} - ` : ''} - - - - -
Flyers Needed: - - ${eventRequest.flyers_needed ? 'Yes' : 'No'} - -
Flyer Types:${formatFlyerTypes(eventRequest.flyer_type)}
Advertising Start:${formatDateTime(eventRequest.flyer_advertising_start_date)}
Required Logos:${formatLogos(eventRequest.required_logos)}
Photography Needed: - - ${eventRequest.photography_needed ? 'Yes' : 'No'} - -
-
- -
-

Logistics & Funding

- - - - - - - - - - - - - -
AS Funding Required: - - ${eventRequest.as_funding_required ? 'Yes' : 'No'} - -
Food/Drinks Served: - - ${eventRequest.food_drinks_being_served ? 'Yes' : 'No'} - -
Room Booking: - - ${eventRequest.will_or_have_room_booking ? 'Has Booking' : 'No Booking'} - -
-
- -
-

Next Steps

-
    -
  • Review the event request details in the dashboard
  • -
  • Coordinate with the submitter if clarification is needed
  • -
  • Assign tasks to appropriate team members (Internal, Events, Projects, etc)
  • -
  • Update the event request status once processed
  • -
-
-
- -
-

This is an automated notification from IEEE UCSD Event Management System.

-

Event Request ID: ${eventRequest.id}

-

If you have any questions, please contact the submitter at ${user.email}

-
- - - `; - - console.log('๐Ÿ“ค Attempting to send event request notification email via Resend...'); - const result = await resend.emails.send({ - from: fromEmail, - to: [coordinatorsEmail], - replyTo: user.email, // Set reply-to as the submitter for easy communication - subject, - html, - }); - - console.log('โœ… Resend event request notification response:', result); - console.log('๐ŸŽ‰ Event request notification email sent successfully!'); - return true; - } catch (error) { - console.error('โŒ Failed to send event request notification email:', error); - console.error('Event request email error details:', { - name: error instanceof Error ? error.name : 'Unknown', - message: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined - }); - return false; - } -} - -async function sendEventRequestStatusChangeEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { - try { - console.log('๐ŸŽฏ Starting event request status change email process...'); - console.log('Environment check:', { - hasResendKey: !!import.meta.env.RESEND_API_KEY, - fromEmail, - replyToEmail, - pocketbaseUrl: import.meta.env.POCKETBASE_URL - }); - - // Get event request details - console.log('๐Ÿ” Fetching event request details for:', data.eventRequestId); - const eventRequest = await pb.collection('event_request').getOne(data.eventRequestId); - console.log('โœ… Event request fetched:', { id: eventRequest.id, name: eventRequest.name }); - - // Get submitter user details - console.log('๐Ÿ‘ค Fetching user details for:', eventRequest.requested_user); - const user = await pb.collection('users').getOne(eventRequest.requested_user); - if (!user) { - console.error('โŒ User not found:', eventRequest.requested_user); - return false; - } - console.log('โœ… User fetched:', { id: user.id, name: user.name, email: user.email }); - - const coordinatorsEmail = 'coordinators@ieeeatucsd.org'; - - // Format date/time for display - const formatDateTime = (dateString: string) => { - try { - const date = new Date(dateString); - return date.toLocaleString('en-US', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: '2-digit', - timeZoneName: 'short' - }); - } catch (e) { - return dateString; - } - }; - - // Email 1: Send to User (Submitter) - const userSubject = `Your Event Request Status Updated: ${eventRequest.name}`; - const userHtml = ` - - - - - - ${userSubject} - - -
-

IEEE UCSD Event Request Update

-
- -
-

Status Update

-

Hello ${user.name},

-

Your event request "${eventRequest.name}" has been updated.

- -
-
- Status: - ${getStatusText(data.newStatus)} -
- - ${data.previousStatus && data.previousStatus !== data.newStatus ? ` -
- Changed from: ${getStatusText(data.previousStatus)} โ†’ ${getStatusText(data.newStatus)} -
- ` : ''} - - ${data.newStatus === 'declined' && data.declinedReason ? ` -
-

Decline Reason:

-

${data.declinedReason}

-
-
-

Next Steps: Please address the concerns mentioned above and resubmit your event request with the proper information.

-
- ` : ''} -
- -
-

Your Event Request Details

- - - - - - - - - - - - - - - - - -
Event Name:${eventRequest.name}
Status:${getStatusText(data.newStatus)}
Location:${eventRequest.location}
Event Date:${formatDateTime(eventRequest.start_date_time)}
-
-
- -
-

This is an automated notification from IEEE UCSD Event Management System.

-

Event Request ID: ${eventRequest.id}

-

If you have any questions, please contact us at coordinators@ieeeatucsd.org

-
- - - `; - - // Email 2: Send to Coordinators - const coordinatorSubject = `Event Request Status Updated: ${eventRequest.name}`; - const coordinatorHtml = ` - - - - - - ${coordinatorSubject} - - -
-

IEEE UCSD Event Request Update

-
- -
-

Event Request Status Updated

-

Hello Coordinators,

-

The status of the event request "${eventRequest.name}" has been updated.

- -
-
- Status: - ${getStatusText(data.newStatus)} -
- - ${data.previousStatus && data.previousStatus !== data.newStatus ? ` -
- Changed from: ${getStatusText(data.previousStatus)} โ†’ ${getStatusText(data.newStatus)} -
- ` : ''} - - ${data.newStatus === 'declined' && data.declinedReason ? ` -
-

Decline Reason Provided:

-

${data.declinedReason}

-
- ` : ''} -
- -
-

Event Request Details

- - - - - - - - - - - - - -
Event Name:${eventRequest.name}
Status:${getStatusText(data.newStatus)}
Submitted By:${user.name} (${user.email})
-
-
- -
-

This is an automated notification from IEEE UCSD Event Management System.

-

Event Request ID: ${eventRequest.id}

-

If you have any questions, please contact the submitter at ${user.email}

-
- - - `; - - console.log('๐Ÿ“ค Attempting to send event request status change emails via Resend...'); - - // Send email to user - console.log('๐Ÿ“ง Sending to user:', user.email); - const userResult = await resend.emails.send({ - from: fromEmail, - to: [user.email], - replyTo: replyToEmail, - subject: userSubject, - html: userHtml, - }); - - // Send email to coordinators - console.log('๐Ÿ“ง Sending to coordinators:', coordinatorsEmail); - const coordinatorResult = await resend.emails.send({ - from: fromEmail, - to: [coordinatorsEmail], - replyTo: user.email, // Set reply-to as the submitter for easy communication - subject: coordinatorSubject, - html: coordinatorHtml, - }); - - console.log('โœ… Resend user email response:', userResult); - console.log('โœ… Resend coordinator email response:', coordinatorResult); - console.log('๐ŸŽ‰ Event request status change emails sent successfully!'); - return true; - } catch (error) { - console.error('โŒ Failed to send event request status change email:', error); - console.error('Event request status change email error details:', { - name: error instanceof Error ? error.name : 'Unknown', - message: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined - }); - return false; - } -} - -async function sendPRCompletedEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { - try { - console.log('๐ŸŽจ Starting PR completed email process...'); - console.log('Environment check:', { - hasResendKey: !!import.meta.env.RESEND_API_KEY, - fromEmail, - replyToEmail, - pocketbaseUrl: import.meta.env.POCKETBASE_URL - }); - - // Get event request details - console.log('๐Ÿ” Fetching event request details for:', data.eventRequestId); - const eventRequest = await pb.collection('event_request').getOne(data.eventRequestId); - console.log('โœ… Event request fetched:', { id: eventRequest.id, name: eventRequest.name }); - - // Get submitter user details - console.log('๐Ÿ‘ค Fetching user details for:', eventRequest.requested_user); - const user = await pb.collection('users').getOne(eventRequest.requested_user); - if (!user || !user.email) { - console.error('โŒ User not found or no email:', eventRequest.requested_user); - return false; - } - console.log('โœ… User fetched:', { id: user.id, name: user.name, email: user.email }); - - const subject = `PR Materials Completed for Your Event: ${eventRequest.name}`; - - console.log('๐Ÿ“ Email details:', { - to: user.email, - subject, - eventName: eventRequest.name - }); - - // Format date/time for display - const formatDateTime = (dateString: string) => { - try { - const date = new Date(dateString); - return date.toLocaleString('en-US', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: '2-digit', - timeZoneName: 'short' - }); - } catch (e) { - return dateString; - } - }; - - const html = ` - - - - - - ${subject} - - -
-

๐ŸŽจ PR Materials Completed!

-
- -
-

Great News!

-

Hello ${user.name},

-

The PR materials for your event "${eventRequest.name}" have been completed by our PR team!

- -
-
- - โœ… PR Materials Completed - -
- -

Event Details

- - - - - - - - - - - - - - - - - -
Event Name:${eventRequest.name}
Location:${eventRequest.location}
Event Date:${formatDateTime(eventRequest.start_date_time)}
Flyers Needed:${eventRequest.flyers_needed ? 'Yes' : 'No'}
-
- -
-

๐Ÿ“ž Next Steps

-

- Important: Please reach out to the Internal team to coordinate any remaining logistics for your event. - They will help ensure everything is ready for your event date. -

-

- Contact: internal@ieeeatucsd.org -

-
-
- -
-

This is an automated notification from IEEE UCSD Event Management System.

-

Event Request ID: ${eventRequest.id}

-

If you have any questions about your PR materials, please contact us at ${replyToEmail}

-
- - - `; - - console.log('๐Ÿ“ค Attempting to send PR completed email via Resend...'); - const result = await resend.emails.send({ - from: fromEmail, - to: [user.email], - replyTo: replyToEmail, - subject, - html, - }); - - console.log('โœ… Resend PR completed response:', result); - console.log('๐ŸŽ‰ PR completed email sent successfully!'); - return true; - } catch (error) { - console.error('โŒ Failed to send PR completed email:', error); - console.error('PR completed email error details:', { - name: error instanceof Error ? error.name : 'Unknown', - message: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined - }); - return false; - } -} - -async function sendDesignPRNotificationEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { - try { - console.log('๐ŸŽจ Starting design PR notification email process...'); - console.log('Environment check:', { - hasResendKey: !!import.meta.env.RESEND_API_KEY, - fromEmail, - replyToEmail, - pocketbaseUrl: import.meta.env.POCKETBASE_URL - }); - - // Get event request details - console.log('๐Ÿ” Fetching event request details for:', data.eventRequestId); - const eventRequest = await pb.collection('event_request').getOne(data.eventRequestId); - console.log('โœ… Event request fetched:', { id: eventRequest.id, name: eventRequest.name }); - - // Get submitter user details - console.log('๐Ÿ‘ค Fetching user details for:', eventRequest.requested_user); - const user = await pb.collection('users').getOne(eventRequest.requested_user); - if (!user) { - console.error('โŒ User not found:', eventRequest.requested_user); - return false; - } - console.log('โœ… User fetched:', { id: user.id, name: user.name, email: user.email }); - - const designEmail = 'design@ieeeatucsd.org'; - let subject = ''; - let actionMessage = ''; - - switch (data.action) { - case 'submission': - subject = `New Event Request with PR Materials: ${eventRequest.name}`; - actionMessage = 'A new event request has been submitted that requires PR materials.'; - break; - case 'pr_update': - subject = `PR Materials Updated: ${eventRequest.name}`; - actionMessage = 'The PR materials for this event request have been updated.'; - break; - case 'declined': - subject = `Event Request Declined - PR Work Cancelled: ${eventRequest.name}`; - actionMessage = 'This event request has been declined. Please ignore any pending PR work for this event.'; - break; - default: - subject = `Event Request PR Notification: ${eventRequest.name}`; - actionMessage = 'There has been an update to an event request requiring PR materials.'; - } - - console.log('๐Ÿ“ Email details:', { - to: designEmail, - subject, - action: data.action - }); - - const html = ` - - - - - - ${subject} - - -
-

๐ŸŽจ IEEE UCSD Design Team Notification

-
- -
-

PR Materials ${data.action === 'declined' ? 'Cancelled' : 'Required'}

-

Hello Design Team,

-

${actionMessage}

- -
-

Event Request Details

- - - - - - - - - - - - - - - - - -
Event Name:${eventRequest.name}
Action:${data.action.charAt(0).toUpperCase() + data.action.slice(1)}
Submitted By:${user.name} (${user.email})
Event Description:${eventRequest.event_description}
-
- - ${data.action !== 'declined' ? ` -
-

Next Steps: Please coordinate with the internal team for PR material creation and timeline.

-
- ` : ` -
-

Note: This event has been declined. No further PR work is needed.

-
- `} -
- -
-

This is an automated notification from IEEE UCSD Event Management System.

-

Event Request ID: ${eventRequest.id}

-

If you have any questions, please contact internal@ieeeatucsd.org

-
- - - `; - - console.log('๐Ÿ“ค Attempting to send design PR notification email via Resend...'); - const result = await resend.emails.send({ - from: fromEmail, - to: [designEmail], - replyTo: replyToEmail, - subject, - html, - }); - - console.log('โœ… Resend design PR notification response:', result); - console.log('๐ŸŽ‰ Design PR notification email sent successfully!'); - return true; - } catch (error) { - console.error('โŒ Failed to send design PR notification email:', error); - console.error('Design PR notification email error details:', { - name: error instanceof Error ? error.name : 'Unknown', - message: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined - }); - return false; - } -} - -// Helper functions -function getStatusColor(status: string): string { - switch (status) { - case 'submitted': return '#ffc107'; - case 'under_review': return '#17a2b8'; - case 'approved': return '#28a745'; - case 'rejected': return '#dc3545'; - case 'in_progress': return '#6f42c1'; - case 'paid': return '#20c997'; - default: return '#6c757d'; - } -} - -function getStatusText(status: string): string { - switch (status) { - case 'submitted': return 'Submitted'; - case 'under_review': return 'Under Review'; - case 'approved': return 'Approved'; - case 'rejected': return 'Rejected'; - case 'in_progress': return 'In Progress'; - case 'paid': return 'Paid'; - default: return status.charAt(0).toUpperCase() + status.slice(1); - } -} - -function getNextStepsText(status: string): string { - switch (status) { - case 'submitted': - return 'Your reimbursement is in the queue for review. We\'ll notify you once it\'s being processed.'; - case 'under_review': - return 'Our team is currently reviewing your receipts and documentation. No action needed from you.'; - case 'approved': - return 'Your reimbursement has been approved! Payment processing will begin shortly.'; - case 'rejected': - return 'Your reimbursement has been rejected. Please review the rejection reason above and reach out to our treasurer if you have questions or need to resubmit with corrections.'; - case 'in_progress': - return 'Payment is being processed. You should receive your reimbursement within 1-2 business days.'; - case 'paid': - return 'Your reimbursement has been completed! Please check your account for the payment.'; - default: - return 'Check your dashboard for more details about your reimbursement status.'; - } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/scripts/email/EmailHelpers.ts b/src/scripts/email/EmailHelpers.ts new file mode 100644 index 0000000..c0a040a --- /dev/null +++ b/src/scripts/email/EmailHelpers.ts @@ -0,0 +1,112 @@ +// Shared email helper functions and utilities + +export function getStatusColor(status: string): string { + switch (status) { + case 'submitted': return '#ffc107'; + case 'under_review': return '#17a2b8'; + case 'approved': return '#28a745'; + case 'rejected': return '#dc3545'; + case 'in_progress': return '#6f42c1'; + case 'paid': return '#20c997'; + case 'declined': return '#dc3545'; + default: return '#6c757d'; + } +} + +export function getStatusText(status: string): string { + switch (status) { + case 'submitted': return 'Submitted'; + case 'under_review': return 'Under Review'; + case 'approved': return 'Approved'; + case 'rejected': return 'Rejected'; + case 'in_progress': return 'In Progress'; + case 'paid': return 'Paid'; + case 'declined': return 'Declined'; + default: return status.charAt(0).toUpperCase() + status.slice(1); + } +} + +export function getNextStepsText(status: string): string { + switch (status) { + case 'submitted': + return 'Your reimbursement is in the queue for review. We\'ll notify you once it\'s being processed.'; + case 'under_review': + return 'Our team is currently reviewing your receipts and documentation. No action needed from you.'; + case 'approved': + return 'Your reimbursement has been approved! Payment processing will begin shortly.'; + case 'rejected': + return 'Your reimbursement has been rejected. Please review the rejection reason above and reach out to our treasurer if you have questions or need to resubmit with corrections.'; + case 'in_progress': + return 'Payment is being processed. You should receive your reimbursement within 1-2 business days.'; + case 'paid': + return 'Your reimbursement has been completed! Please check your account for the payment.'; + default: + return 'Check your dashboard for more details about your reimbursement status.'; + } +} + +export async function initializeEmailServices() { + // Import Resend and create direct PocketBase connection for server-side use + const { Resend } = await import('resend'); + const PocketBase = await import('pocketbase').then(module => module.default); + + // Initialize services + const pb = new PocketBase(import.meta.env.POCKETBASE_URL || 'http://127.0.0.1:8090'); + const resend = new Resend(import.meta.env.RESEND_API_KEY); + + if (!import.meta.env.RESEND_API_KEY) { + throw new Error('RESEND_API_KEY environment variable is required'); + } + + const fromEmail = import.meta.env.FROM_EMAIL || 'IEEE UCSD '; + const replyToEmail = import.meta.env.REPLY_TO_EMAIL || 'treasurer@ieeeucsd.org'; + + return { pb, resend, fromEmail, replyToEmail }; +} + +export function authenticatePocketBase(pb: any, authData: any) { + if (authData && authData.token && authData.model) { + console.log('๐Ÿ” Authenticating with PocketBase using provided auth data'); + pb.authStore.save(authData.token, authData.model); + console.log('โœ… PocketBase authentication successful'); + } else { + console.warn('โš ๏ธ No auth data provided, proceeding without authentication'); + } +} + +export function formatDateTime(dateString: string): string { + try { + const date = new Date(dateString); + return date.toLocaleString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: '2-digit', + timeZoneName: 'short' + }); + } catch (e) { + return dateString; + } +} + +export function formatFlyerTypes(flyerTypes: string[]): string { + if (!flyerTypes || flyerTypes.length === 0) return 'None specified'; + + const typeMap: Record = { + 'digital_with_social': 'Digital with Social Media', + 'digital_no_social': 'Digital without Social Media', + 'physical_with_advertising': 'Physical with Advertising', + 'physical_no_advertising': 'Physical without Advertising', + 'newsletter': 'Newsletter', + 'other': 'Other' + }; + + return flyerTypes.map(type => typeMap[type] || type).join(', '); +} + +export function formatLogos(logos: string[]): string { + if (!logos || logos.length === 0) return 'None specified'; + return logos.join(', '); +} \ No newline at end of file diff --git a/src/scripts/email/EventRequestEmailFunctions.ts b/src/scripts/email/EventRequestEmailFunctions.ts new file mode 100644 index 0000000..6168df0 --- /dev/null +++ b/src/scripts/email/EventRequestEmailFunctions.ts @@ -0,0 +1,429 @@ +import { getStatusColor, getStatusText, formatDateTime, formatFlyerTypes, formatLogos } from './EmailHelpers'; + +export async function sendEventRequestSubmissionEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { + try { + console.log('๐ŸŽช Starting event request submission email process...'); + + // Get event request details + const eventRequest = await pb.collection('event_request').getOne(data.eventRequestId); + const user = await pb.collection('users').getOne(eventRequest.requested_user); + + if (!user) { + console.error('โŒ User not found:', eventRequest.requested_user); + return false; + } + + const coordinatorsEmail = 'coordinators@ieeeatucsd.org'; + const subject = `New Event Request Submitted: ${eventRequest.name}`; + + const html = ` + + + + + + ${subject} + + +
+

๐ŸŽช New Event Request Submitted

+
+ +
+

Event Request Details

+

Hello Coordinators,

+

A new event request has been submitted by ${user.name} and requires your review.

+ +
+

Basic Information

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Event Name:${eventRequest.name}
Location:${eventRequest.location}
Start Date & Time:${formatDateTime(eventRequest.start_date_time)}
End Date & Time:${formatDateTime(eventRequest.end_date_time)}
Expected Attendance:${eventRequest.expected_attendance || 'Not specified'}
Submitted By:${user.name} (${user.email})
+
+ +
+

Event Description

+
+

${eventRequest.event_description || 'No description provided'}

+
+
+ +
+

Next Steps

+
    +
  • Review the event request details in the dashboard
  • +
  • Coordinate with the submitter if clarification is needed
  • +
  • Assign tasks to appropriate team members
  • +
  • Update the event request status once processed
  • +
+
+
+ +
+

This is an automated notification from IEEE UCSD Event Management System.

+

Event Request ID: ${eventRequest.id}

+

If you have any questions, please contact the submitter at ${user.email}

+
+ + + `; + + const result = await resend.emails.send({ + from: fromEmail, + to: [coordinatorsEmail], + replyTo: user.email, + subject, + html, + }); + + console.log('โœ… Event request notification email sent successfully!'); + return true; + } catch (error) { + console.error('โŒ Failed to send event request notification email:', error); + return false; + } +} + +export async function sendEventRequestStatusChangeEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { + try { + console.log('๐ŸŽฏ Starting event request status change email process...'); + + // Get event request details + const eventRequest = await pb.collection('event_request').getOne(data.eventRequestId); + const user = await pb.collection('users').getOne(eventRequest.requested_user); + + if (!user) { + console.error('โŒ User not found:', eventRequest.requested_user); + return false; + } + + const coordinatorsEmail = 'coordinators@ieeeatucsd.org'; + const userSubject = `Your Event Request Status Updated: ${eventRequest.name}`; + + const userHtml = ` + + + + + + ${userSubject} + + +
+

IEEE UCSD Event Request Update

+
+ +
+

Status Update

+

Hello ${user.name},

+

Your event request "${eventRequest.name}" has been updated.

+ +
+
+ Status: + ${getStatusText(data.newStatus)} +
+ + ${data.previousStatus && data.previousStatus !== data.newStatus ? ` +
+ Changed from: ${getStatusText(data.previousStatus)} โ†’ ${getStatusText(data.newStatus)} +
+ ` : ''} + + ${data.newStatus === 'declined' && data.declinedReason ? ` +
+

Decline Reason:

+

${data.declinedReason}

+
+ ` : ''} +
+ +
+

Your Event Request Details

+ + + + + + + + + + + + + + + + + +
Event Name:${eventRequest.name}
Status:${getStatusText(data.newStatus)}
Location:${eventRequest.location}
Event Date:${formatDateTime(eventRequest.start_date_time)}
+
+
+ +
+

This is an automated notification from IEEE UCSD Event Management System.

+

Event Request ID: ${eventRequest.id}

+

If you have any questions, please contact us at coordinators@ieeeatucsd.org

+
+ + + `; + + // Send email to user + await resend.emails.send({ + from: fromEmail, + to: [user.email], + replyTo: replyToEmail, + subject: userSubject, + html: userHtml, + }); + + // Send email to coordinators + const coordinatorSubject = `Event Request Status Updated: ${eventRequest.name}`; + await resend.emails.send({ + from: fromEmail, + to: [coordinatorsEmail], + replyTo: user.email, + subject: coordinatorSubject, + html: userHtml.replace(user.name, 'Coordinators').replace('Your event request', `Event request by ${user.name}`), + }); + + console.log('โœ… Event request status change emails sent successfully!'); + return true; + } catch (error) { + console.error('โŒ Failed to send event request status change email:', error); + return false; + } +} + +export async function sendPRCompletedEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { + try { + console.log('๐ŸŽจ Starting PR completed email process...'); + + // Get event request details + const eventRequest = await pb.collection('event_request').getOne(data.eventRequestId); + const user = await pb.collection('users').getOne(eventRequest.requested_user); + + if (!user || !user.email) { + console.error('โŒ User not found or no email:', eventRequest.requested_user); + return false; + } + + const subject = `PR Materials Completed for Your Event: ${eventRequest.name}`; + + const html = ` + + + + + + ${subject} + + +
+

๐ŸŽจ PR Materials Completed!

+
+ +
+

Great News!

+

Hello ${user.name},

+

The PR materials for your event "${eventRequest.name}" have been completed by our PR team!

+ +
+
+ + โœ… PR Materials Completed + +
+ +

Event Details

+ + + + + + + + + + + + + + + + + +
Event Name:${eventRequest.name}
Location:${eventRequest.location}
Event Date:${formatDateTime(eventRequest.start_date_time)}
Flyers Needed:${eventRequest.flyers_needed ? 'Yes' : 'No'}
+
+ +
+

๐Ÿ“ž Next Steps

+

+ Important: Please reach out to the Internal team to coordinate any remaining logistics for your event. +

+

+ Contact: internal@ieeeatucsd.org +

+
+
+ +
+

This is an automated notification from IEEE UCSD Event Management System.

+

Event Request ID: ${eventRequest.id}

+

If you have any questions about your PR materials, please contact us at ${replyToEmail}

+
+ + + `; + + await resend.emails.send({ + from: fromEmail, + to: [user.email], + replyTo: replyToEmail, + subject, + html, + }); + + console.log('โœ… PR completed email sent successfully!'); + return true; + } catch (error) { + console.error('โŒ Failed to send PR completed email:', error); + return false; + } +} + +export async function sendDesignPRNotificationEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise { + try { + console.log('๐ŸŽจ Starting design PR notification email process...'); + + // Get event request details + const eventRequest = await pb.collection('event_request').getOne(data.eventRequestId); + const user = await pb.collection('users').getOne(eventRequest.requested_user); + + if (!user) { + console.error('โŒ User not found:', eventRequest.requested_user); + return false; + } + + const designEmail = 'design@ieeeatucsd.org'; + let subject = ''; + let actionMessage = ''; + + switch (data.action) { + case 'submission': + subject = `New Event Request with PR Materials: ${eventRequest.name}`; + actionMessage = 'A new event request has been submitted that requires PR materials.'; + break; + case 'pr_update': + subject = `PR Materials Updated: ${eventRequest.name}`; + actionMessage = 'The PR materials for this event request have been updated.'; + break; + case 'declined': + subject = `Event Request Declined - PR Work Cancelled: ${eventRequest.name}`; + actionMessage = 'This event request has been declined. Please ignore any pending PR work for this event.'; + break; + default: + subject = `Event Request PR Notification: ${eventRequest.name}`; + actionMessage = 'There has been an update to an event request requiring PR materials.'; + } + + const html = ` + + + + + + ${subject} + + +
+

๐ŸŽจ IEEE UCSD Design Team Notification

+
+ +
+

PR Materials ${data.action === 'declined' ? 'Cancelled' : 'Required'}

+

Hello Design Team,

+

${actionMessage}

+ +
+

Event Request Details

+ + + + + + + + + + + + + + + + + +
Event Name:${eventRequest.name}
Action:${data.action.charAt(0).toUpperCase() + data.action.slice(1)}
Submitted By:${user.name} (${user.email})
Event Description:${eventRequest.event_description}
+
+ + ${data.action !== 'declined' ? ` +
+

Next Steps: Please coordinate with the internal team for PR material creation and timeline.

+
+ ` : ` +
+

Note: This event has been declined. No further PR work is needed.

+
+ `} +
+ +
+

This is an automated notification from IEEE UCSD Event Management System.

+

Event Request ID: ${eventRequest.id}

+

If you have any questions, please contact internal@ieeeatucsd.org

+
+ + + `; + + await resend.emails.send({ + from: fromEmail, + to: [designEmail], + replyTo: replyToEmail, + subject, + html, + }); + + console.log('โœ… Design PR notification email sent successfully!'); + return true; + } catch (error) { + console.error('โŒ Failed to send design PR notification email:', error); + return false; + } +} \ No newline at end of file