add officer role update
This commit is contained in:
parent
40b2ea48c1
commit
a57a4e6889
5 changed files with 601 additions and 1 deletions
|
@ -5,6 +5,7 @@ import type { User, Officer } from '../../../schemas/pocketbase/schema';
|
|||
import { Button } from '../universal/Button';
|
||||
import toast from 'react-hot-toast';
|
||||
import { Toast } from '../universal/Toast';
|
||||
import { EmailClient } from '../../../scripts/email/EmailClient';
|
||||
|
||||
// Interface for officer with expanded user data
|
||||
interface OfficerWithUser extends Officer {
|
||||
|
@ -252,12 +253,34 @@ export default function OfficerManagement() {
|
|||
try {
|
||||
const pb = auth.getPocketBase();
|
||||
|
||||
// Store previous values for the email notification
|
||||
const previousRole = officerToReplace.existingOfficer.role;
|
||||
const previousType = officerToReplace.existingOfficer.type;
|
||||
const currentUserId = auth.getUserId();
|
||||
|
||||
// Update the existing officer record
|
||||
await pb.collection(Collections.OFFICERS).update(officerToReplace.existingOfficer.id, {
|
||||
role: officerToReplace.newRole,
|
||||
type: officerToReplace.newType
|
||||
});
|
||||
|
||||
// Send email notification (non-blocking)
|
||||
try {
|
||||
await EmailClient.notifyOfficerRoleChange(
|
||||
officerToReplace.existingOfficer.id,
|
||||
previousRole,
|
||||
previousType,
|
||||
officerToReplace.newRole,
|
||||
officerToReplace.newType,
|
||||
currentUserId || undefined,
|
||||
false // This is an update, not a new officer
|
||||
);
|
||||
console.log('Officer role change notification email sent successfully');
|
||||
} catch (emailError) {
|
||||
console.error('Failed to send officer role change notification email:', emailError);
|
||||
// Don't show error to user - email failure shouldn't disrupt the main operation
|
||||
}
|
||||
|
||||
// Show success message
|
||||
toast.success(`Officer role updated successfully for ${officerToReplace.existingOfficer.expand?.user.name}`);
|
||||
|
||||
|
@ -427,8 +450,13 @@ export default function OfficerManagement() {
|
|||
|
||||
// Check if user is already an officer
|
||||
const existingOfficer = officers.find(officer => officer.expand?.user.id === user.id);
|
||||
const currentUserId = auth.getUserId();
|
||||
|
||||
if (existingOfficer) {
|
||||
// Store previous values for the email notification
|
||||
const previousRole = existingOfficer.role;
|
||||
const previousType = existingOfficer.type;
|
||||
|
||||
// Update existing officer
|
||||
const updatedOfficer = await pb.collection(Collections.OFFICERS).update(existingOfficer.id, {
|
||||
role: newOfficerRole,
|
||||
|
@ -436,6 +464,23 @@ export default function OfficerManagement() {
|
|||
});
|
||||
|
||||
successfulUpdates.push(updatedOfficer);
|
||||
|
||||
// Send email notification for role update (non-blocking)
|
||||
try {
|
||||
await EmailClient.notifyOfficerRoleChange(
|
||||
existingOfficer.id,
|
||||
previousRole,
|
||||
previousType,
|
||||
newOfficerRole,
|
||||
validType,
|
||||
currentUserId || undefined,
|
||||
false // This is an update, not a new officer
|
||||
);
|
||||
console.log(`Officer role change notification sent for ${user.name}`);
|
||||
} catch (emailError) {
|
||||
console.error(`Failed to send officer role change notification for ${user.name}:`, emailError);
|
||||
// Don't show error to user - email failure shouldn't disrupt the main operation
|
||||
}
|
||||
} else {
|
||||
// Create new officer record
|
||||
const createdOfficer = await pb.collection(Collections.OFFICERS).create({
|
||||
|
@ -445,6 +490,23 @@ export default function OfficerManagement() {
|
|||
});
|
||||
|
||||
successfulCreations.push(createdOfficer);
|
||||
|
||||
// Send email notification for new officer (non-blocking)
|
||||
try {
|
||||
await EmailClient.notifyOfficerRoleChange(
|
||||
createdOfficer.id,
|
||||
undefined, // No previous role for new officers
|
||||
undefined, // No previous type for new officers
|
||||
newOfficerRole,
|
||||
validType,
|
||||
currentUserId || undefined,
|
||||
true // This is a new officer
|
||||
);
|
||||
console.log(`New officer notification sent for ${user.name}`);
|
||||
} catch (emailError) {
|
||||
console.error(`Failed to send new officer notification for ${user.name}:`, emailError);
|
||||
// Don't show error to user - email failure shouldn't disrupt the main operation
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to process officer for user ${user.name}:`, error);
|
||||
|
@ -679,8 +741,28 @@ export default function OfficerManagement() {
|
|||
}
|
||||
|
||||
try {
|
||||
// Store previous values for the email notification
|
||||
const previousType = officerToEdit.type;
|
||||
|
||||
await updateService.updateField(Collections.OFFICERS, officerId, 'type', newType);
|
||||
|
||||
// Send email notification for role type change (non-blocking)
|
||||
try {
|
||||
await EmailClient.notifyOfficerRoleChange(
|
||||
officerId,
|
||||
officerToEdit.role, // Role stays the same
|
||||
previousType,
|
||||
officerToEdit.role, // Role stays the same
|
||||
newType,
|
||||
currentUserId || undefined,
|
||||
false // This is an update, not a new officer
|
||||
);
|
||||
console.log(`Officer type change notification sent for ${officerToEdit.expand?.user.name}`);
|
||||
} catch (emailError) {
|
||||
console.error(`Failed to send officer type change notification for ${officerToEdit.expand?.user.name}:`, emailError);
|
||||
// Don't show error to user - email failure shouldn't disrupt the main operation
|
||||
}
|
||||
|
||||
toast.success('Officer updated successfully');
|
||||
|
||||
// Refresh officers list
|
||||
|
|
155
src/pages/api/email/send-officer-notification.ts
Normal file
155
src/pages/api/email/send-officer-notification.ts
Normal file
|
@ -0,0 +1,155 @@
|
|||
import type { APIRoute } from 'astro';
|
||||
import { OfficerEmailNotifications } from '../../../scripts/email/OfficerEmailNotifications';
|
||||
import type { OfficerRoleChangeEmailData } from '../../../scripts/email/OfficerEmailNotifications';
|
||||
import { initializeEmailServices, authenticatePocketBase } from '../../../scripts/email/EmailHelpers';
|
||||
import { Collections } from '../../../schemas/pocketbase';
|
||||
import type { User, Officer } from '../../../schemas/pocketbase/schema';
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
console.log('📨 Officer notification email API called');
|
||||
|
||||
const requestData = await request.json();
|
||||
const {
|
||||
type,
|
||||
officerId,
|
||||
additionalContext,
|
||||
authData
|
||||
} = requestData;
|
||||
|
||||
console.log('📋 Request data:', {
|
||||
type,
|
||||
officerId,
|
||||
hasAdditionalContext: !!additionalContext,
|
||||
hasAuthData: !!authData
|
||||
});
|
||||
|
||||
if (type !== 'officer_role_change') {
|
||||
console.error('❌ Invalid notification type for officer endpoint:', type);
|
||||
return new Response(
|
||||
JSON.stringify({ error: `Invalid notification type: ${type}` }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
if (!officerId) {
|
||||
console.error('❌ Missing required parameter: officerId');
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Missing required parameter: officerId' }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize services - this creates a fresh PocketBase instance for server-side use
|
||||
const { pb } = await initializeEmailServices();
|
||||
|
||||
// Authenticate with PocketBase if auth data is provided
|
||||
authenticatePocketBase(pb, authData);
|
||||
|
||||
const emailService = OfficerEmailNotifications.getInstance();
|
||||
|
||||
// Get the officer record with user data
|
||||
console.log('🔍 Fetching officer data...');
|
||||
const officer = await pb.collection(Collections.OFFICERS).getOne(officerId, {
|
||||
expand: 'user'
|
||||
}) as Officer & { expand?: { user: User } };
|
||||
|
||||
if (!officer) {
|
||||
console.error('❌ Officer not found:', officerId);
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Officer not found' }),
|
||||
{ status: 404, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
// Get the user data from the expanded relation
|
||||
const user = officer.expand?.user;
|
||||
|
||||
if (!user) {
|
||||
console.error('❌ User data not found for officer:', officerId);
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'User data not found for officer' }),
|
||||
{ status: 404, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
// Extract additional context data
|
||||
const {
|
||||
previousRole,
|
||||
previousType,
|
||||
newRole,
|
||||
newType,
|
||||
changedByUserId,
|
||||
isNewOfficer
|
||||
} = additionalContext || {};
|
||||
|
||||
// Get the name of the person who made the change
|
||||
let changedByName = '';
|
||||
if (changedByUserId) {
|
||||
try {
|
||||
const changedByUser = await pb.collection(Collections.USERS).getOne(changedByUserId) as User;
|
||||
changedByName = changedByUser?.name || 'Unknown User';
|
||||
} catch (error) {
|
||||
console.warn('Could not fetch changed by user name:', error);
|
||||
changedByName = 'Unknown User';
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare email data
|
||||
const emailData: OfficerRoleChangeEmailData = {
|
||||
user,
|
||||
officer,
|
||||
previousRole,
|
||||
previousType,
|
||||
newRole: newRole || officer.role,
|
||||
newType: newType || officer.type,
|
||||
changedBy: changedByName,
|
||||
isNewOfficer: isNewOfficer || false
|
||||
};
|
||||
|
||||
console.log('📧 Sending officer role change notification...');
|
||||
console.log('📧 Email data:', {
|
||||
userName: user.name,
|
||||
userEmail: user.email,
|
||||
officerRole: emailData.newRole,
|
||||
officerType: emailData.newType,
|
||||
previousRole: emailData.previousRole,
|
||||
previousType: emailData.previousType,
|
||||
changedBy: emailData.changedBy,
|
||||
isNewOfficer: emailData.isNewOfficer
|
||||
});
|
||||
|
||||
const success = await emailService.sendRoleChangeNotification(emailData);
|
||||
|
||||
if (success) {
|
||||
console.log('✅ Officer role change notification sent successfully');
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: 'Officer role change notification sent successfully'
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
} else {
|
||||
console.error('❌ Failed to send officer role change notification');
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Failed to send officer role change notification'
|
||||
}),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error in officer notification API:', error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
details: error instanceof Error ? error.message : 'Unknown error'
|
||||
}),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
};
|
|
@ -24,6 +24,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||
// 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'];
|
||||
const officerTypes = ['officer_role_change'];
|
||||
|
||||
let targetEndpoint = '';
|
||||
|
||||
|
@ -43,6 +44,15 @@ export const POST: APIRoute = async ({ request }) => {
|
|||
);
|
||||
}
|
||||
targetEndpoint = '/api/email/send-event-request-email';
|
||||
} else if (officerTypes.includes(type)) {
|
||||
const { officerId } = requestData;
|
||||
if (!officerId) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Missing officerId for officer notification' }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
targetEndpoint = '/api/email/send-officer-notification';
|
||||
} else {
|
||||
console.error('❌ Unknown notification type:', type);
|
||||
return new Response(
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
import { Authentication } from '../pocketbase/Authentication';
|
||||
|
||||
interface EmailNotificationRequest {
|
||||
type: 'status_change' | 'comment' | 'submission' | 'test' | 'event_request_submission' | 'event_request_status_change' | 'pr_completed' | 'design_pr_notification';
|
||||
type: 'status_change' | 'comment' | 'submission' | 'test' | 'event_request_submission' | 'event_request_status_change' | 'pr_completed' | 'design_pr_notification' | 'officer_role_change';
|
||||
reimbursementId?: string;
|
||||
eventRequestId?: string;
|
||||
officerId?: string;
|
||||
previousStatus?: string;
|
||||
newStatus?: string;
|
||||
changedByUserId?: string;
|
||||
|
@ -74,6 +75,36 @@ export class EmailClient {
|
|||
}
|
||||
}
|
||||
|
||||
private static async sendOfficerNotification(request: EmailNotificationRequest): Promise<boolean> {
|
||||
try {
|
||||
const authData = this.getAuthData();
|
||||
const requestWithAuth = {
|
||||
...request,
|
||||
authData
|
||||
};
|
||||
|
||||
const response = await fetch('/api/email/send-officer-notification', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestWithAuth),
|
||||
});
|
||||
|
||||
const result: EmailNotificationResponse = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Officer notification API error:', result.error || result.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return result.success;
|
||||
} catch (error) {
|
||||
console.error('Failed to send officer notification:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send status change notification
|
||||
*/
|
||||
|
@ -204,4 +235,30 @@ export class EmailClient {
|
|||
additionalContext: { action }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send officer role change notification
|
||||
*/
|
||||
static async notifyOfficerRoleChange(
|
||||
officerId: string,
|
||||
previousRole?: string,
|
||||
previousType?: string,
|
||||
newRole?: string,
|
||||
newType?: string,
|
||||
changedByUserId?: string,
|
||||
isNewOfficer?: boolean
|
||||
): Promise<boolean> {
|
||||
return this.sendOfficerNotification({
|
||||
type: 'officer_role_change',
|
||||
officerId,
|
||||
additionalContext: {
|
||||
previousRole,
|
||||
previousType,
|
||||
newRole,
|
||||
newType,
|
||||
changedByUserId,
|
||||
isNewOfficer
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
296
src/scripts/email/OfficerEmailNotifications.ts
Normal file
296
src/scripts/email/OfficerEmailNotifications.ts
Normal file
|
@ -0,0 +1,296 @@
|
|||
import { Resend } from 'resend';
|
||||
import type { User, Officer } from '../../schemas/pocketbase/schema';
|
||||
import { OfficerTypes } from '../../schemas/pocketbase';
|
||||
|
||||
// Email template data interfaces
|
||||
export interface OfficerRoleChangeEmailData {
|
||||
user: User;
|
||||
officer: Officer;
|
||||
previousRole?: string;
|
||||
previousType?: string;
|
||||
newRole: string;
|
||||
newType: string;
|
||||
changedBy?: string;
|
||||
isNewOfficer?: boolean; // If this is a new officer appointment
|
||||
}
|
||||
|
||||
export class OfficerEmailNotifications {
|
||||
private resend: Resend;
|
||||
private fromEmail: string;
|
||||
private replyToEmail: string;
|
||||
|
||||
constructor() {
|
||||
// Initialize Resend with API key from environment
|
||||
const apiKey = import.meta.env.RESEND_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error('RESEND_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
this.resend = new Resend(apiKey);
|
||||
this.fromEmail = import.meta.env.FROM_EMAIL || 'IEEE UCSD <noreply@transactional.ieeeatucsd.org>';
|
||||
this.replyToEmail = import.meta.env.REPLY_TO_EMAIL || 'ieee@ucsd.edu';
|
||||
}
|
||||
|
||||
private static instance: OfficerEmailNotifications | null = null;
|
||||
|
||||
public static getInstance(): OfficerEmailNotifications {
|
||||
if (!OfficerEmailNotifications.instance) {
|
||||
OfficerEmailNotifications.instance = new OfficerEmailNotifications();
|
||||
}
|
||||
return OfficerEmailNotifications.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send officer role change notification email
|
||||
*/
|
||||
async sendRoleChangeNotification(data: OfficerRoleChangeEmailData): Promise<boolean> {
|
||||
try {
|
||||
const { user, officer, previousRole, previousType, newRole, newType, changedBy, isNewOfficer } = data;
|
||||
|
||||
const subject = isNewOfficer
|
||||
? `Welcome to IEEE UCSD Leadership - ${newRole}`
|
||||
: `Your IEEE UCSD Officer Role has been Updated`;
|
||||
|
||||
const typeColor = this.getOfficerTypeColor(newType);
|
||||
const typeText = this.getOfficerTypeDisplayName(newType);
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>${subject}</title>
|
||||
</head>
|
||||
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 10px; margin-bottom: 30px;">
|
||||
<h1 style="color: white; margin: 0; font-size: 24px;">IEEE UCSD Officer Update</h1>
|
||||
</div>
|
||||
|
||||
<div style="background: #f8f9fa; padding: 25px; border-radius: 10px; margin-bottom: 25px;">
|
||||
<h2 style="margin-top: 0; color: #2c3e50;">
|
||||
${isNewOfficer ? 'Welcome to the Team!' : 'Role Update'}
|
||||
</h2>
|
||||
<p>Hello ${user.name},</p>
|
||||
|
||||
${isNewOfficer ? `
|
||||
<p>Congratulations! You have been appointed as an officer for IEEE UCSD. We're excited to have you join our leadership team!</p>
|
||||
` : `
|
||||
<p>Your officer role has been updated in the IEEE UCSD system.</p>
|
||||
`}
|
||||
|
||||
<div style="background: white; padding: 20px; border-radius: 8px; border-left: 4px solid ${typeColor}; margin: 20px 0;">
|
||||
<div style="margin-bottom: 15px;">
|
||||
<h3 style="margin: 0 0 10px 0; color: #2c3e50;">Your Current Role</h3>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<span style="font-weight: bold; font-size: 18px; color: #2c3e50;">${newRole}</span>
|
||||
<span style="background: ${typeColor}; color: white; padding: 6px 12px; border-radius: 20px; font-size: 14px; font-weight: 500;">${typeText}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${!isNewOfficer && (previousRole || previousType) ? `
|
||||
<div style="color: #666; font-size: 14px; padding: 10px 0; border-top: 1px solid #eee;">
|
||||
<strong>Previous:</strong> ${previousRole || 'Unknown Role'} (${this.getOfficerTypeDisplayName(previousType || '')})
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${changedBy ? `
|
||||
<div style="color: #666; font-size: 14px; margin-top: 10px;">
|
||||
${isNewOfficer ? 'Appointed' : 'Updated'} by: ${changedBy}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div style="margin: 25px 0;">
|
||||
<h3 style="color: #2c3e50; margin-bottom: 15px;">Officer Information</h3>
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #eee; font-weight: bold; width: 30%;">Name:</td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #eee;">${user.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #eee; font-weight: bold;">Email:</td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #eee;">${user.email}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #eee; font-weight: bold;">Role:</td>
|
||||
<td style="padding: 8px 0; border-bottom: 1px solid #eee;">${newRole}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; font-weight: bold;">Officer Type:</td>
|
||||
<td style="padding: 8px 0;">${typeText}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
${this.getOfficerTypeDescription(newType)}
|
||||
|
||||
<div style="background: #e8f4fd; padding: 15px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #3498db;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #2980b9;">Next Steps:</h4>
|
||||
<ul style="margin: 0; padding-left: 20px;">
|
||||
<li>Check your access to the officer dashboard</li>
|
||||
<li>Familiarize yourself with your new responsibilities</li>
|
||||
<li>Reach out to other officers if you have questions</li>
|
||||
${isNewOfficer ? '<li>Attend the next officer meeting to get up to speed</li>' : ''}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; padding: 20px; border-top: 1px solid #eee; color: #666; font-size: 14px;">
|
||||
<p>This is an automated notification from IEEE UCSD Officer Management System.</p>
|
||||
<p>If you have any questions about your role, please contact us at <a href="mailto:${this.replyToEmail}" style="color: #667eea;">${this.replyToEmail}</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const result = await this.resend.emails.send({
|
||||
from: this.fromEmail,
|
||||
to: [user.email],
|
||||
replyTo: this.replyToEmail,
|
||||
subject,
|
||||
html,
|
||||
});
|
||||
|
||||
console.log('Officer role change email sent successfully:', result);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to send officer role change email:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color for officer type badge
|
||||
*/
|
||||
private getOfficerTypeColor(type: string): string {
|
||||
switch (type) {
|
||||
case OfficerTypes.ADMINISTRATOR:
|
||||
return '#dc3545'; // Red for admin
|
||||
case OfficerTypes.EXECUTIVE:
|
||||
return '#6f42c1'; // Purple for executive
|
||||
case OfficerTypes.GENERAL:
|
||||
return '#007bff'; // Blue for general
|
||||
case OfficerTypes.HONORARY:
|
||||
return '#fd7e14'; // Orange for honorary
|
||||
case OfficerTypes.PAST:
|
||||
return '#6c757d'; // Gray for past
|
||||
default:
|
||||
return '#28a745'; // Green as default
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display name for officer type
|
||||
*/
|
||||
private getOfficerTypeDisplayName(type: string): string {
|
||||
switch (type) {
|
||||
case OfficerTypes.ADMINISTRATOR:
|
||||
return 'Administrator';
|
||||
case OfficerTypes.EXECUTIVE:
|
||||
return 'Executive Officer';
|
||||
case OfficerTypes.GENERAL:
|
||||
return 'General Officer';
|
||||
case OfficerTypes.HONORARY:
|
||||
return 'Honorary Officer';
|
||||
case OfficerTypes.PAST:
|
||||
return 'Past Officer';
|
||||
default:
|
||||
return 'Officer';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description for officer type
|
||||
*/
|
||||
private getOfficerTypeDescription(type: string): string {
|
||||
const baseStyle = "background: #fff3cd; padding: 15px; border-radius: 8px; border-left: 4px solid #ffc107; margin: 20px 0;";
|
||||
|
||||
switch (type) {
|
||||
case OfficerTypes.ADMINISTRATOR:
|
||||
return `
|
||||
<div style="${baseStyle}">
|
||||
<p style="margin: 0; font-size: 14px;"><strong>Administrator Role:</strong></p>
|
||||
<p style="margin: 5px 0 0 0; font-size: 14px;">
|
||||
As an administrator, you have full access to manage officers, events, and system settings. You can add/remove other officers and access all administrative features.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
case OfficerTypes.EXECUTIVE:
|
||||
return `
|
||||
<div style="${baseStyle}">
|
||||
<p style="margin: 0; font-size: 14px;"><strong>Executive Officer Role:</strong></p>
|
||||
<p style="margin: 5px 0 0 0; font-size: 14px;">
|
||||
As an executive officer, you have leadership responsibilities and access to advanced features in the officer dashboard. You can manage events and participate in key decision-making.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
case OfficerTypes.GENERAL:
|
||||
return `
|
||||
<div style="${baseStyle}">
|
||||
<p style="margin: 0; font-size: 14px;"><strong>General Officer Role:</strong></p>
|
||||
<p style="margin: 5px 0 0 0; font-size: 14px;">
|
||||
As a general officer, you have access to the officer dashboard and can help with event management, member engagement, and other organizational activities.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
case OfficerTypes.HONORARY:
|
||||
return `
|
||||
<div style="${baseStyle}">
|
||||
<p style="margin: 0; font-size: 14px;"><strong>Honorary Officer Role:</strong></p>
|
||||
<p style="margin: 5px 0 0 0; font-size: 14px;">
|
||||
As an honorary officer, you are recognized for your contributions to IEEE UCSD. You have access to officer resources and are part of our leadership community.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
case OfficerTypes.PAST:
|
||||
return `
|
||||
<div style="${baseStyle}">
|
||||
<p style="margin: 0; font-size: 14px;"><strong>Past Officer Status:</strong></p>
|
||||
<p style="margin: 5px 0 0 0; font-size: 14px;">
|
||||
Thank you for your service to IEEE UCSD! As a past officer, you maintain access to alumni resources and remain part of our leadership community.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
default:
|
||||
return `
|
||||
<div style="${baseStyle}">
|
||||
<p style="margin: 0; font-size: 14px;"><strong>Officer Role:</strong></p>
|
||||
<p style="margin: 5px 0 0 0; font-size: 14px;">
|
||||
Welcome to the IEEE UCSD officer team! You now have access to officer resources and can contribute to our organization's activities.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch notify multiple officers (for bulk operations)
|
||||
*/
|
||||
async notifyBulkRoleChanges(
|
||||
notifications: OfficerRoleChangeEmailData[]
|
||||
): Promise<{ successful: number; failed: number }> {
|
||||
let successful = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const notification of notifications) {
|
||||
try {
|
||||
const result = await this.sendRoleChangeNotification(notification);
|
||||
if (result) {
|
||||
successful++;
|
||||
} else {
|
||||
failed++;
|
||||
}
|
||||
|
||||
// Add a small delay to avoid rate limiting
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
} catch (error) {
|
||||
console.error('Failed to send bulk notification:', error);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
return { successful, failed };
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue