mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-03 20:13:00 +00:00
cleanup
This commit is contained in:
@@ -0,0 +1,354 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"bookra/apps/backend/internal/db"
|
||||
)
|
||||
|
||||
type EmailType string
|
||||
|
||||
const (
|
||||
EmailTypeConfirmation EmailType = "confirmation"
|
||||
EmailTypeReminder EmailType = "reminder"
|
||||
EmailTypeReschedule EmailType = "reschedule"
|
||||
EmailTypeCancellation EmailType = "cancellation"
|
||||
EmailTypeBusinessNotify EmailType = "business_notify"
|
||||
)
|
||||
|
||||
type BookingEmailData struct {
|
||||
Type EmailType
|
||||
TenantName string
|
||||
TenantSlug string
|
||||
BusinessEmail string
|
||||
BusinessPhone string
|
||||
BusinessAddress string
|
||||
BrandColor string
|
||||
CustomerName string
|
||||
CustomerEmail string
|
||||
Service string
|
||||
Location string
|
||||
Reference string
|
||||
StartsAt time.Time
|
||||
EndsAt time.Time
|
||||
Timezone string
|
||||
Locale string
|
||||
Notes string
|
||||
ManagementURL string
|
||||
AddToCalendarURL string
|
||||
}
|
||||
|
||||
func RenderEmailMessage(data BookingEmailData) EmailMessage {
|
||||
subject := renderSubject(data)
|
||||
htmlBody := renderHTMLBody(data)
|
||||
textBody := renderTextBody(data)
|
||||
|
||||
return EmailMessage{
|
||||
From: data.BusinessEmail,
|
||||
To: data.CustomerEmail,
|
||||
Subject: subject,
|
||||
Text: textBody,
|
||||
HTML: htmlBody,
|
||||
}
|
||||
}
|
||||
|
||||
func renderSubject(data BookingEmailData) string {
|
||||
localizedTime := formatLocalizedTime(data.StartsAt, data.Timezone, data.Locale)
|
||||
|
||||
switch data.Type {
|
||||
case EmailTypeConfirmation:
|
||||
if data.Locale == "cs" {
|
||||
return fmt.Sprintf("Potvrzení rezervace %s - %s", data.Reference, data.TenantName)
|
||||
}
|
||||
return fmt.Sprintf("Booking Confirmation %s - %s", data.Reference, data.TenantName)
|
||||
case EmailTypeReminder:
|
||||
if data.Locale == "cs" {
|
||||
return fmt.Sprintf("Připomínka: Máte rezervaci zítra v %s", localizedTime)
|
||||
}
|
||||
return fmt.Sprintf("Reminder: You have a booking tomorrow at %s", localizedTime)
|
||||
case EmailTypeReschedule:
|
||||
if data.Locale == "cs" {
|
||||
return fmt.Sprintf("Vaše rezervace byla přesunuta - %s", data.Reference)
|
||||
}
|
||||
return fmt.Sprintf("Your booking has been rescheduled - %s", data.Reference)
|
||||
case EmailTypeCancellation:
|
||||
if data.Locale == "cs" {
|
||||
return fmt.Sprintf("Vaše rezervace byla zrušena - %s", data.Reference)
|
||||
}
|
||||
return fmt.Sprintf("Your booking has been cancelled - %s", data.Reference)
|
||||
case EmailTypeBusinessNotify:
|
||||
if data.Locale == "cs" {
|
||||
return fmt.Sprintf("Nová rezervace od %s - %s", data.CustomerName, data.Reference)
|
||||
}
|
||||
return fmt.Sprintf("New booking from %s - %s", data.CustomerName, data.Reference)
|
||||
default:
|
||||
return "Booking Update"
|
||||
}
|
||||
}
|
||||
|
||||
func renderTextBody(data BookingEmailData) string {
|
||||
localizedTime := formatLocalizedDateTime(data.StartsAt, data.Timezone, data.Locale)
|
||||
|
||||
switch data.Type {
|
||||
case EmailTypeConfirmation:
|
||||
if data.Locale == "cs" {
|
||||
return fmt.Sprintf(`Dobrý den %s,
|
||||
|
||||
vaše rezervace byla potvrzena.
|
||||
|
||||
Detaily rezervace:
|
||||
- Služba: %s
|
||||
- Datum a čas: %s
|
||||
- Místo: %s
|
||||
- Reference: %s
|
||||
|
||||
Pro správu rezervace navštivte: %s
|
||||
|
||||
Děkujeme,
|
||||
%s
|
||||
%s`, data.CustomerName, data.Service, localizedTime, data.Location, data.Reference, data.ManagementURL, data.TenantName, data.BusinessEmail)
|
||||
}
|
||||
return fmt.Sprintf(`Hello %s,
|
||||
|
||||
Your booking has been confirmed.
|
||||
|
||||
Booking Details:
|
||||
- Service: %s
|
||||
- Date & Time: %s
|
||||
- Location: %s
|
||||
- Reference: %s
|
||||
|
||||
Manage your booking at: %s
|
||||
|
||||
Thank you,
|
||||
%s
|
||||
%s`, data.CustomerName, data.Service, localizedTime, data.Location, data.Reference, data.ManagementURL, data.TenantName, data.BusinessEmail)
|
||||
|
||||
case EmailTypeReminder:
|
||||
if data.Locale == "cs" {
|
||||
return fmt.Sprintf(`Dobrý den %s,
|
||||
|
||||
připomínáme vám zítřejší rezervaci.
|
||||
|
||||
- Služba: %s
|
||||
- Čas: %s
|
||||
- Místo: %s
|
||||
- Reference: %s
|
||||
|
||||
Pro správu rezervace: %s
|
||||
|
||||
%s`, data.CustomerName, data.Service, localizedTime, data.Location, data.Reference, data.ManagementURL, data.TenantName)
|
||||
}
|
||||
return fmt.Sprintf(`Hello %s,
|
||||
|
||||
This is a reminder for your booking tomorrow.
|
||||
|
||||
- Service: %s
|
||||
- Time: %s
|
||||
- Location: %s
|
||||
- Reference: %s
|
||||
|
||||
Manage booking: %s
|
||||
|
||||
%s`, data.CustomerName, data.Service, localizedTime, data.Location, data.Reference, data.ManagementURL, data.TenantName)
|
||||
|
||||
case EmailTypeReschedule:
|
||||
if data.Locale == "cs" {
|
||||
return fmt.Sprintf(`Dobrý den %s,
|
||||
|
||||
vaše rezervace byla přesunuta na nový termín.
|
||||
|
||||
Nové detaily:
|
||||
- Služba: %s
|
||||
- Datum a čas: %s
|
||||
- Místo: %s
|
||||
- Reference: %s
|
||||
|
||||
Pro správu rezervace: %s
|
||||
|
||||
%s`, data.CustomerName, data.Service, localizedTime, data.Location, data.Reference, data.ManagementURL, data.TenantName)
|
||||
}
|
||||
return fmt.Sprintf(`Hello %s,
|
||||
|
||||
Your booking has been rescheduled.
|
||||
|
||||
New details:
|
||||
- Service: %s
|
||||
- Date & Time: %s
|
||||
- Location: %s
|
||||
- Reference: %s
|
||||
|
||||
Manage booking: %s
|
||||
|
||||
%s`, data.CustomerName, data.Service, localizedTime, data.Location, data.Reference, data.ManagementURL, data.TenantName)
|
||||
|
||||
case EmailTypeCancellation:
|
||||
if data.Locale == "cs" {
|
||||
return fmt.Sprintf(`Dobrý den %s,
|
||||
|
||||
vaše rezervace byla zrušena.
|
||||
|
||||
Zrušená rezervace:
|
||||
- Služba: %s
|
||||
- Datum a čas: %s
|
||||
- Reference: %s
|
||||
|
||||
Pokud jste rezervaci nezrušili vy, kontaktujte nás: %s
|
||||
|
||||
%s`, data.CustomerName, data.Service, localizedTime, data.Reference, data.BusinessEmail, data.TenantName)
|
||||
}
|
||||
return fmt.Sprintf(`Hello %s,
|
||||
|
||||
Your booking has been cancelled.
|
||||
|
||||
Cancelled booking:
|
||||
- Service: %s
|
||||
- Date & Time: %s
|
||||
- Reference: %s
|
||||
|
||||
If you didn't cancel this, please contact us: %s
|
||||
|
||||
%s`, data.CustomerName, data.Service, localizedTime, data.Reference, data.BusinessEmail, data.TenantName)
|
||||
|
||||
case EmailTypeBusinessNotify:
|
||||
if data.Locale == "cs" {
|
||||
return fmt.Sprintf(`Nová rezervace od %s
|
||||
|
||||
Detaily:
|
||||
- Služba: %s
|
||||
- Datum a čas: %s
|
||||
- Reference: %s
|
||||
- Email: %s
|
||||
|
||||
Spravovat v administraci: https://bookra.eu/dashboard`, data.CustomerName, data.Service, localizedTime, data.Reference, data.CustomerEmail)
|
||||
}
|
||||
return fmt.Sprintf(`New booking from %s
|
||||
|
||||
Details:
|
||||
- Service: %s
|
||||
- Date & Time: %s
|
||||
- Reference: %s
|
||||
- Email: %s
|
||||
|
||||
Manage in dashboard: https://bookra.eu/dashboard`, data.CustomerName, data.Service, localizedTime, data.Reference, data.CustomerEmail)
|
||||
|
||||
default:
|
||||
return "Booking update"
|
||||
}
|
||||
}
|
||||
|
||||
func renderHTMLBody(data BookingEmailData) string {
|
||||
// For now, return simple HTML version. In production, this would use proper HTML templates
|
||||
textBody := renderTextBody(data)
|
||||
// Simple conversion: wrap paragraphs in <p> tags and preserve line breaks
|
||||
html := "<html><body style='font-family: Arial, sans-serif; line-height: 1.6; color: #333;'>"
|
||||
html += "<div style='max-width: 600px; margin: 0 auto; padding: 20px;'>"
|
||||
html += fmt.Sprintf("<h2 style='color: %s; margin-bottom: 20px;'>%s</h2>", data.BrandColor, data.TenantName)
|
||||
|
||||
// Convert text to simple HTML
|
||||
paragraphs := splitParagraphs(textBody)
|
||||
for _, p := range paragraphs {
|
||||
if len(p) > 0 {
|
||||
html += fmt.Sprintf("<p style='margin-bottom: 10px;'>%s</p>", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Add management button
|
||||
if data.ManagementURL != "" {
|
||||
html += fmt.Sprintf("<div style='margin-top: 30px;'><a href='%s' style='display: inline-block; background: %s; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px;'>Manage Booking</a></div>", data.ManagementURL, data.BrandColor)
|
||||
}
|
||||
|
||||
html += "</div></body></html>"
|
||||
return html
|
||||
}
|
||||
|
||||
func formatLocalizedTime(t time.Time, timezone, locale string) string {
|
||||
loc, err := time.LoadLocation(timezone)
|
||||
if err != nil {
|
||||
loc = time.UTC
|
||||
}
|
||||
localTime := t.In(loc)
|
||||
if locale == "cs" {
|
||||
return localTime.Format("15:04")
|
||||
}
|
||||
return localTime.Format("3:04 PM")
|
||||
}
|
||||
|
||||
func formatLocalizedDateTime(t time.Time, timezone, locale string) string {
|
||||
loc, err := time.LoadLocation(timezone)
|
||||
if err != nil {
|
||||
loc = time.UTC
|
||||
}
|
||||
localTime := t.In(loc)
|
||||
if locale == "cs" {
|
||||
return localTime.Format("02.01.2006 15:04")
|
||||
}
|
||||
return localTime.Format("Jan 02, 2006 3:04 PM")
|
||||
}
|
||||
|
||||
func splitParagraphs(text string) []string {
|
||||
var paragraphs []string
|
||||
current := ""
|
||||
for _, line := range splitLines(text) {
|
||||
trimmed := trimSpace(line)
|
||||
if trimmed == "" {
|
||||
if current != "" {
|
||||
paragraphs = append(paragraphs, current)
|
||||
current = ""
|
||||
}
|
||||
} else {
|
||||
if current != "" {
|
||||
current += " "
|
||||
}
|
||||
current += trimmed
|
||||
}
|
||||
}
|
||||
if current != "" {
|
||||
paragraphs = append(paragraphs, current)
|
||||
}
|
||||
return paragraphs
|
||||
}
|
||||
|
||||
func splitLines(s string) []string {
|
||||
var lines []string
|
||||
start := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '\n' {
|
||||
lines = append(lines, s[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
lines = append(lines, s[start:])
|
||||
return lines
|
||||
}
|
||||
|
||||
func trimSpace(s string) string {
|
||||
start := 0
|
||||
end := len(s)
|
||||
for start < end && (s[start] == ' ' || s[start] == '\t' || s[start] == '\r' || s[start] == '\n') {
|
||||
start++
|
||||
}
|
||||
for end > start && (s[end-1] == ' ' || s[end-1] == '\t' || s[end-1] == '\r' || s[end-1] == '\n') {
|
||||
end--
|
||||
}
|
||||
return s[start:end]
|
||||
}
|
||||
|
||||
// RenderReminderEmail renders the legacy reminder email from a job record
|
||||
func RenderReminderEmail(from string, job db.ReminderJobRecord) EmailMessage {
|
||||
data := BookingEmailData{
|
||||
Type: EmailTypeReminder,
|
||||
TenantName: job.TenantName,
|
||||
TenantSlug: "", // Not available in job record
|
||||
CustomerName: job.CustomerName,
|
||||
CustomerEmail: job.CustomerEmail,
|
||||
Reference: job.Reference,
|
||||
StartsAt: job.StartsAt,
|
||||
Timezone: job.Timezone,
|
||||
Locale: job.Locale,
|
||||
Service: "Service", // Legacy
|
||||
Location: "Location", // Legacy
|
||||
}
|
||||
return RenderEmailMessage(data)
|
||||
}
|
||||
Reference in New Issue
Block a user