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: "support@attuneheart.com",
},
}
service.initializeTemplates()
return service
}
// initializeTemplates initializes all email templates
func (s *EmailTemplateService) initializeTemplates() {
// Welcome email template
welcomeTemplate := `
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:
- Browse available appointment slots
- Book your first therapy session
- Complete your profile for a personalized experience
If you have any questions, please don't hesitate to contact us at {{.SupportEmail}}.
`
// Payment success template
paymentSuccessTemplate := `
✓
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 := `
✗
Dear {{.User.FirstName}},
Unfortunately, your payment could not be processed and your booking was not confirmed.
Attempted Booking Details:
- Date & Time: {{.Booking.ScheduledAt.Format "January 2, 2006 at 3:04 PM"}}
- Duration: {{.Booking.Duration}} minutes
- Amount: ${{printf "%.2f" .Booking.Amount}}
Please try booking again or contact us at {{.SupportEmail}} if you continue to experience issues.
Common reasons for payment failure:
- Insufficient funds
- Incorrect card details
- Card expired or blocked
- Bank security restrictions
`
// Meeting info template
meetingInfoTemplate := `
Dear {{.User.FirstName}},
Here are the details for your upcoming therapy session:
Meeting Information:
- Date & Time: {{.Booking.ScheduledAt.Format "January 2, 2006 at 3:04 PM"}}
- Duration: {{.Booking.Duration}} minutes
- Meeting Room: {{.Booking.JitsiRoomID}}
Important Notes:
- ✓ Please join the meeting 5 minutes before the scheduled time
- ✓ Ensure you have a stable internet connection
- ✓ Test your camera and microphone beforehand
- ✓ Find a quiet, private space for the session
If you need to reschedule or have any questions, please contact us at {{.SupportEmail}} as soon as possible.
`
// Reminder template
reminderTemplate := `
⏰
Dear {{.User.FirstName}},
This is a friendly reminder that you have a therapy session scheduled {{.ReminderText}}.
Session Details:
- Date & Time: {{.Booking.ScheduledAt.Format "January 2, 2006 at 3:04 PM"}}
- Duration: {{.Booking.Duration}} minutes
Preparation Checklist:
- ✓ Test your camera and microphone
- ✓ Ensure stable internet connection
- ✓ Find a quiet, private space
- ✓ Have any notes or questions ready
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)
}
}