package templates import ( "bytes" "fmt" "html/template" "time" "attune-heart-therapy/internal/models" ) // EmailTemplate represents an email template with subject and body type EmailTemplate struct { Subject string Body string } // TemplateData contains data for email template rendering type TemplateData struct { User *models.User Booking *models.Booking Amount float64 PaymentID string JoinURL string ReminderText string CompanyName string SupportEmail string } // EmailTemplateService handles email template rendering type EmailTemplateService struct { templates map[models.NotificationType]*template.Template baseData TemplateData } // NewEmailTemplateService creates a new email template service func NewEmailTemplateService() *EmailTemplateService { service := &EmailTemplateService{ templates: make(map[models.NotificationType]*template.Template), baseData: TemplateData{ CompanyName: "Attune Heart Therapy", SupportEmail: "hello@attunehearttherapy.com", }, } service.initializeTemplates() return service } // initializeTemplates initializes all email templates func (s *EmailTemplateService) initializeTemplates() { // Welcome email template welcomeTemplate := ` Welcome to {{.CompanyName}}

Welcome to {{.CompanyName}}!

Hello {{.User.FirstName}}!

Thank you for registering with us. We're excited to help you on your wellness journey.

You can now book video conference sessions with our therapists through our platform.

Here's what you can do next:

If you have any questions, please don't hesitate to contact us at {{.SupportEmail}}.

` // Payment success template paymentSuccessTemplate := ` Payment Successful

Payment Successful!

Dear {{.User.FirstName}},

Your payment has been successfully processed and your booking is confirmed.

Booking Details:

Date & Time: {{.Booking.ScheduledAt.Format "January 2, 2006 at 3:04 PM"}}
Duration: {{.Booking.Duration}} minutes
Amount Paid: ${{printf "%.2f" .Booking.Amount}}
Payment ID: {{.Booking.PaymentID}}

You will receive meeting details closer to your appointment time.

If you need to reschedule or have any questions, please contact us at {{.SupportEmail}}.

` // Payment failed template paymentFailedTemplate := ` Payment Failed

Payment Failed

Dear {{.User.FirstName}},

Unfortunately, your payment could not be processed and your booking was not confirmed.

Attempted Booking Details:

Please try booking again or contact us at {{.SupportEmail}} if you continue to experience issues.

Common reasons for payment failure:

` // Meeting info template meetingInfoTemplate := ` Meeting Information

Your Therapy Session Details

Dear {{.User.FirstName}},

Here are the details for your upcoming therapy session:

Meeting Information:

Join Meeting

Important Notes:

If you need to reschedule or have any questions, please contact us at {{.SupportEmail}} as soon as possible.

` // Reminder template reminderTemplate := ` Session Reminder

Session Reminder

Dear {{.User.FirstName}},

This is a friendly reminder that you have a therapy session scheduled {{.ReminderText}}.

Session Details:

Join Meeting

Preparation Checklist:

We look forward to seeing you soon!

` // Parse and store templates s.templates[models.NotificationTypeWelcome] = template.Must(template.New("welcome").Parse(welcomeTemplate)) s.templates[models.NotificationTypePaymentSuccess] = template.Must(template.New("payment_success").Parse(paymentSuccessTemplate)) s.templates[models.NotificationTypePaymentFailed] = template.Must(template.New("payment_failed").Parse(paymentFailedTemplate)) s.templates[models.NotificationTypeMeetingInfo] = template.Must(template.New("meeting_info").Parse(meetingInfoTemplate)) s.templates[models.NotificationTypeReminder] = template.Must(template.New("reminder").Parse(reminderTemplate)) } // RenderTemplate renders an email template with the provided data func (s *EmailTemplateService) RenderTemplate(notificationType models.NotificationType, data TemplateData) (*EmailTemplate, error) { tmpl, exists := s.templates[notificationType] if !exists { return nil, fmt.Errorf("template not found for notification type: %s", notificationType) } // Merge base data with provided data mergedData := s.baseData if data.User != nil { mergedData.User = data.User } if data.Booking != nil { mergedData.Booking = data.Booking } mergedData.Amount = data.Amount mergedData.PaymentID = data.PaymentID mergedData.JoinURL = data.JoinURL mergedData.ReminderText = data.ReminderText var buf bytes.Buffer if err := tmpl.Execute(&buf, mergedData); err != nil { return nil, fmt.Errorf("failed to render template: %w", err) } // Generate subject based on notification type subject := s.getSubjectForType(notificationType, mergedData) return &EmailTemplate{ Subject: subject, Body: buf.String(), }, nil } // getSubjectForType returns the appropriate subject line for each notification type func (s *EmailTemplateService) getSubjectForType(notificationType models.NotificationType, data TemplateData) string { switch notificationType { case models.NotificationTypeWelcome: return fmt.Sprintf("Welcome to %s!", data.CompanyName) case models.NotificationTypePaymentSuccess: return "Payment Successful - Booking Confirmed" case models.NotificationTypePaymentFailed: return "Payment Failed - Booking Not Confirmed" case models.NotificationTypeMeetingInfo: return "Meeting Information - Your Therapy Session" case models.NotificationTypeReminder: if data.Booking != nil { timeUntil := time.Until(data.Booking.ScheduledAt) if timeUntil > 24*time.Hour { return "Reminder: Your Therapy Session is Tomorrow" } else if timeUntil > time.Hour { return "Reminder: Your Therapy Session is Today" } else { return "Reminder: Your Therapy Session Starts Soon" } } return "Reminder: Your Therapy Session is Coming Up" case models.NotificationTypeCancellation: return "Booking Cancelled - Confirmation" case models.NotificationTypeReschedule: return "Booking Rescheduled - New Time Confirmed" default: return "Notification from " + data.CompanyName } } // GetReminderText generates appropriate reminder text based on time until meeting func GetReminderText(scheduledAt time.Time) string { timeUntil := time.Until(scheduledAt) if timeUntil > 24*time.Hour { return "tomorrow" } else if timeUntil > time.Hour { hours := int(timeUntil.Hours()) if hours == 1 { return "in 1 hour" } return fmt.Sprintf("in %d hours", hours) } else { minutes := int(timeUntil.Minutes()) if minutes <= 1 { return "now" } return fmt.Sprintf("in %d minutes", minutes) } }