backend-service/internal/templates/email_templates.go

404 lines
16 KiB
Go
Raw Normal View History

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 := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to {{.CompanyName}}</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background-color: #4a90e2; color: white; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; }
.content { background-color: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; }
.footer { text-align: center; margin-top: 20px; font-size: 12px; color: #666; }
.button { display: inline-block; background-color: #4a90e2; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; margin: 10px 0; }
</style>
</head>
<body>
<div class="header">
<h1>Welcome to {{.CompanyName}}!</h1>
</div>
<div class="content">
<h2>Hello {{.User.FirstName}}!</h2>
<p>Thank you for registering with us. We're excited to help you on your wellness journey.</p>
<p>You can now book video conference sessions with our therapists through our platform.</p>
<p>Here's what you can do next:</p>
<ul>
<li>Browse available appointment slots</li>
<li>Book your first therapy session</li>
<li>Complete your profile for a personalized experience</li>
</ul>
<p>If you have any questions, please don't hesitate to contact us at {{.SupportEmail}}.</p>
</div>
<div class="footer">
<p>Best regards,<br>The {{.CompanyName}} Team</p>
</div>
</body>
</html>`
// Payment success template
paymentSuccessTemplate := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Successful</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background-color: #28a745; color: white; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; }
.content { background-color: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; }
.footer { text-align: center; margin-top: 20px; font-size: 12px; color: #666; }
.success-icon { font-size: 48px; color: #28a745; text-align: center; margin: 20px 0; }
.booking-details { background-color: white; padding: 20px; border-radius: 4px; margin: 20px 0; }
.detail-row { display: flex; justify-content: space-between; margin: 10px 0; padding: 8px 0; border-bottom: 1px solid #eee; }
</style>
</head>
<body>
<div class="header">
<h1>Payment Successful!</h1>
</div>
<div class="content">
<div class="success-icon"></div>
<h2>Dear {{.User.FirstName}},</h2>
<p>Your payment has been successfully processed and your booking is confirmed.</p>
<div class="booking-details">
<h3>Booking Details:</h3>
<div class="detail-row">
<strong>Date & Time:</strong>
<span>{{.Booking.ScheduledAt.Format "January 2, 2006 at 3:04 PM"}}</span>
</div>
<div class="detail-row">
<strong>Duration:</strong>
<span>{{.Booking.Duration}} minutes</span>
</div>
<div class="detail-row">
<strong>Amount Paid:</strong>
<span>${{printf "%.2f" .Booking.Amount}}</span>
</div>
<div class="detail-row">
<strong>Payment ID:</strong>
<span>{{.Booking.PaymentID}}</span>
</div>
</div>
<p>You will receive meeting details closer to your appointment time.</p>
<p>If you need to reschedule or have any questions, please contact us at {{.SupportEmail}}.</p>
</div>
<div class="footer">
<p>Best regards,<br>The {{.CompanyName}} Team</p>
</div>
</body>
</html>`
// Payment failed template
paymentFailedTemplate := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Failed</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background-color: #dc3545; color: white; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; }
.content { background-color: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; }
.footer { text-align: center; margin-top: 20px; font-size: 12px; color: #666; }
.error-icon { font-size: 48px; color: #dc3545; text-align: center; margin: 20px 0; }
.booking-details { background-color: white; padding: 20px; border-radius: 4px; margin: 20px 0; }
.button { display: inline-block; background-color: #4a90e2; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; margin: 10px 0; }
</style>
</head>
<body>
<div class="header">
<h1>Payment Failed</h1>
</div>
<div class="content">
<div class="error-icon"></div>
<h2>Dear {{.User.FirstName}},</h2>
<p>Unfortunately, your payment could not be processed and your booking was not confirmed.</p>
<div class="booking-details">
<h3>Attempted Booking Details:</h3>
<ul>
<li><strong>Date & Time:</strong> {{.Booking.ScheduledAt.Format "January 2, 2006 at 3:04 PM"}}</li>
<li><strong>Duration:</strong> {{.Booking.Duration}} minutes</li>
<li><strong>Amount:</strong> ${{printf "%.2f" .Booking.Amount}}</li>
</ul>
</div>
<p>Please try booking again or contact us at {{.SupportEmail}} if you continue to experience issues.</p>
<p>Common reasons for payment failure:</p>
<ul>
<li>Insufficient funds</li>
<li>Incorrect card details</li>
<li>Card expired or blocked</li>
<li>Bank security restrictions</li>
</ul>
</div>
<div class="footer">
<p>Best regards,<br>The {{.CompanyName}} Team</p>
</div>
</body>
</html>`
// Meeting info template
meetingInfoTemplate := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meeting Information</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background-color: #17a2b8; color: white; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; }
.content { background-color: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; }
.footer { text-align: center; margin-top: 20px; font-size: 12px; color: #666; }
.meeting-details { background-color: white; padding: 20px; border-radius: 4px; margin: 20px 0; }
.join-button { display: inline-block; background-color: #28a745; color: white; padding: 15px 30px; text-decoration: none; border-radius: 4px; margin: 15px 0; font-weight: bold; }
.checklist { background-color: #e9f7ef; padding: 15px; border-radius: 4px; margin: 15px 0; }
</style>
</head>
<body>
<div class="header">
<h1>Your Therapy Session Details</h1>
</div>
<div class="content">
<h2>Dear {{.User.FirstName}},</h2>
<p>Here are the details for your upcoming therapy session:</p>
<div class="meeting-details">
<h3>Meeting Information:</h3>
<ul>
<li><strong>Date & Time:</strong> {{.Booking.ScheduledAt.Format "January 2, 2006 at 3:04 PM"}}</li>
<li><strong>Duration:</strong> {{.Booking.Duration}} minutes</li>
<li><strong>Meeting Room:</strong> {{.Booking.JitsiRoomID}}</li>
</ul>
<div style="text-align: center;">
<a href="{{.Booking.JitsiRoomURL}}" class="join-button">Join Meeting</a>
</div>
</div>
<div class="checklist">
<h3>Important Notes:</h3>
<ul>
<li> Please join the meeting 5 minutes before the scheduled time</li>
<li> Ensure you have a stable internet connection</li>
<li> Test your camera and microphone beforehand</li>
<li> Find a quiet, private space for the session</li>
</ul>
</div>
<p>If you need to reschedule or have any questions, please contact us at {{.SupportEmail}} as soon as possible.</p>
</div>
<div class="footer">
<p>Best regards,<br>The {{.CompanyName}} Team</p>
</div>
</body>
</html>`
// Reminder template
reminderTemplate := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Session Reminder</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background-color: #ffc107; color: #333; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; }
.content { background-color: #f9f9f9; padding: 30px; border-radius: 0 0 8px 8px; }
.footer { text-align: center; margin-top: 20px; font-size: 12px; color: #666; }
.reminder-icon { font-size: 48px; text-align: center; margin: 20px 0; }
.session-details { background-color: white; padding: 20px; border-radius: 4px; margin: 20px 0; }
.join-button { display: inline-block; background-color: #28a745; color: white; padding: 15px 30px; text-decoration: none; border-radius: 4px; margin: 15px 0; font-weight: bold; }
.checklist { background-color: #fff3cd; padding: 15px; border-radius: 4px; margin: 15px 0; }
</style>
</head>
<body>
<div class="header">
<h1>Session Reminder</h1>
</div>
<div class="content">
<div class="reminder-icon"></div>
<h2>Dear {{.User.FirstName}},</h2>
<p>This is a friendly reminder that you have a therapy session scheduled {{.ReminderText}}.</p>
<div class="session-details">
<h3>Session Details:</h3>
<ul>
<li><strong>Date & Time:</strong> {{.Booking.ScheduledAt.Format "January 2, 2006 at 3:04 PM"}}</li>
<li><strong>Duration:</strong> {{.Booking.Duration}} minutes</li>
</ul>
<div style="text-align: center;">
<a href="{{.Booking.JitsiRoomURL}}" class="join-button">Join Meeting</a>
</div>
</div>
<div class="checklist">
<h3>Preparation Checklist:</h3>
<ul>
<li> Test your camera and microphone</li>
<li> Ensure stable internet connection</li>
<li> Find a quiet, private space</li>
<li> Have any notes or questions ready</li>
</ul>
</div>
<p>We look forward to seeing you soon!</p>
</div>
<div class="footer">
<p>Best regards,<br>The {{.CompanyName}} Team</p>
</div>
</body>
</html>`
// 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)
}
}