import type { APIRoute } from 'astro'; import { initializeEmailServices, authenticatePocketBase, getStatusColor, getStatusText, getNextStepsText } from '../../../scripts/email/EmailHelpers'; // Add function to generate status image URL (now SVG-based) function getStatusImageUrl(status: string, baseUrl: string = ''): string { return `${baseUrl}/api/generate-status-image?status=${status}&width=500&height=150`; } export const POST: APIRoute = async ({ request }) => { try { console.log('๐Ÿ“จ Reimbursement email API called'); const { type, reimbursementId, previousStatus, newStatus, changedByUserId, comment, commentByUserId, isPrivate, additionalContext, authData, useImageProgress = true // New option to use image instead of HTML progress (default: true for better email compatibility) } = 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 (skip for test emails) if (type !== 'test') { 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, useImageProgress }); 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 }); // Check if this is a test scenario const isTestData = data.reimbursementId?.includes('test') || data.reimbursementId === 'test-id'; let reimbursement, user; if (isTestData) { console.log('๐Ÿงช Using test data for demonstration'); // Use mock data for testing reimbursement = { id: data.reimbursementId, title: 'Test Reimbursement Request', total_amount: 125.50, date_of_purchase: new Date().toISOString(), department: 'general', payment_method: 'Personal Card', status: data.previousStatus || 'submitted', submitted_by: 'test-user-id', audit_notes: '' }; user = { id: 'test-user-id', name: 'Test User', email: data.additionalContext?.testEmail || 'test@example.com' }; console.log('โœ… Test data prepared:', { reimbursementTitle: reimbursement.title, userEmail: user.email }); } else { // Get real reimbursement details console.log('๐Ÿ” Fetching reimbursement details for:', data.reimbursementId); 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); 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 }); // Add audit note when reimbursement is declined (skip for test data) if (data.newStatus === 'rejected' && !isTestData) { try { console.log('๐Ÿ“ Adding audit note for declined reimbursement...'); // Prepare audit note content let auditNote = `Status changed to REJECTED by ${changedByName}`; if (data.additionalContext?.rejectionReason) { auditNote += `\nRejection Reason: ${data.additionalContext.rejectionReason}`; } auditNote += `\nDate: ${new Date().toLocaleString()}`; // Get existing audit notes or initialize empty string const existingNotes = reimbursement.audit_notes || ''; const updatedNotes = existingNotes ? `${existingNotes}\n\n--- DECLINE RECORD ---\n${auditNote}` : `--- DECLINE RECORD ---\n${auditNote}`; // Update the reimbursement record with the new audit notes await pb.collection('reimbursement').update(data.reimbursementId, { audit_notes: updatedNotes }); console.log('โœ… Audit note added successfully for declined reimbursement'); } catch (auditError) { console.error('โŒ Failed to add audit note for declined reimbursement:', auditError); // Don't fail the entire email process if audit note fails } } else if (data.newStatus === 'rejected' && isTestData) { console.log('๐Ÿงช Skipping audit note update for test data'); } // Helper function to generate status progress bar HTML (email-compatible) function generateStatusProgressBar(currentStatus: string): string { const statusOrder = ['submitted', 'under_review', 'approved', 'in_progress', 'paid']; const rejectedStatus = ['submitted', 'under_review', 'rejected']; const isRejected = currentStatus === 'rejected'; const statuses = isRejected ? rejectedStatus : statusOrder; const statusIcons: Record = { submitted: 'โ†’', under_review: '?', approved: 'โœ“', rejected: 'โœ—', in_progress: 'โ—‹', paid: '$' }; const statusLabels: Record = { submitted: 'Submitted', under_review: 'Under Review', approved: 'Approved', rejected: 'Rejected', in_progress: 'In Progress', paid: 'Paid' }; const currentIndex = statuses.indexOf(currentStatus); let progressBarHtml = `

Request Progress

`; statuses.forEach((status, index) => { const isActive = index <= currentIndex; const isCurrent = status === currentStatus; let backgroundColor, textColor, lineColor; if (isCurrent) { if (status === 'rejected') { backgroundColor = '#ef4444'; textColor = 'white'; lineColor = '#ef4444'; } else if (status === 'paid') { backgroundColor = '#10b981'; textColor = 'white'; lineColor = '#10b981'; } else if (status === 'in_progress') { backgroundColor = '#f59e0b'; textColor = 'white'; lineColor = '#f59e0b'; } else { backgroundColor = '#3b82f6'; textColor = 'white'; lineColor = '#3b82f6'; } } else if (isActive) { backgroundColor = '#e2e8f0'; textColor = '#475569'; lineColor = '#cbd5e1'; } else { backgroundColor = '#f8fafc'; textColor = '#94a3b8'; lineColor = '#e2e8f0'; } // Status circle progressBarHtml += ` `; // Connecting line (except for the last status) if (index < statuses.length - 1) { const nextIsActive = (index + 1) <= currentIndex; const connectionColor = nextIsActive ? lineColor : '#e2e8f0'; progressBarHtml += ` `; } }); progressBarHtml += `
${statusIcons[status]}
${statusLabels[status]}
`; return progressBarHtml; } const html = ` ${subject}

IEEE UCSD Reimbursement Update

Status Update

Hello ${user.name},

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

${data.useImageProgress ? `
Request Progress
` : generateStatusProgressBar(data.newStatus) }
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; } // Send confirmation email to submitter const submitterSubject = `Reimbursement Submitted: ${reimbursement.title}`; const submitterHtml = ` ${submitterSubject}

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}

`; // Send notification email to treasurer const treasurerSubject = `New Reimbursement Request: ${reimbursement.title} - $${reimbursement.total_amount.toFixed(2)}`; const treasurerHtml = ` ${treasurerSubject}

New Reimbursement Request

Action Required

Hello Treasurer,

A new reimbursement request has been submitted and is awaiting review.

Reimbursement Details

Submitted by: ${user.name} (${user.email})
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}
Submitted: ${new Date(reimbursement.created).toLocaleString()}
Status: Submitted - Awaiting Review
${reimbursement.additional_info ? `

Additional Information:

${reimbursement.additional_info}
` : ''}

Next Steps:

  • Review the submitted receipts and documentation
  • Log into the reimbursement portal to approve or request changes
  • The submitter will be notified of any status updates

This is an automated notification from IEEE UCSD Reimbursement System.

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

`; // Send both emails const submitterResult = await resend.emails.send({ from: fromEmail, to: [user.email], replyTo: replyToEmail, subject: submitterSubject, html: submitterHtml, }); const treasurerResult = await resend.emails.send({ from: fromEmail, to: ['treasurer@ieeeatucsd.org'], replyTo: user.email, // Set reply-to as the submitter for treasurer's convenience subject: treasurerSubject, html: treasurerHtml, }); console.log('Submission confirmation email sent successfully:', submitterResult); console.log('Treasurer notification email sent successfully:', treasurerResult); // Return true if at least one email was sent successfully return !!(submitterResult && treasurerResult); } catch (error) { console.error('Failed to send submission emails:', 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; } }