120 lines
3.6 KiB
Go
120 lines
3.6 KiB
Go
package campaigns
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TemplateKey is a seasonal or custom campaign template identifier.
|
||
|
|
type TemplateKey string
|
||
|
|
|
||
|
|
const (
|
||
|
|
TemplateChristmas TemplateKey = "christmas"
|
||
|
|
TemplateBlackFriday TemplateKey = "black_friday"
|
||
|
|
TemplateSpring TemplateKey = "spring"
|
||
|
|
TemplateCustom TemplateKey = "custom"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Template struct {
|
||
|
|
Key string `json:"key"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
Season string `json:"season"`
|
||
|
|
DefaultSubject string `json:"default_subject"`
|
||
|
|
DefaultPrompt string `json:"default_prompt"`
|
||
|
|
Description string `json:"description"`
|
||
|
|
}
|
||
|
|
|
||
|
|
var builtInTemplates = []Template{
|
||
|
|
{
|
||
|
|
Key: string(TemplateChristmas),
|
||
|
|
Name: "Christmas",
|
||
|
|
Season: "christmas",
|
||
|
|
DefaultSubject: "Holiday picks from {{brand}}",
|
||
|
|
DefaultPrompt: "Warm Christmas email for these products. Festive, concise, clear CTA. JSON only.",
|
||
|
|
Description: "Festive seasonal campaign for holiday shoppers.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Key: string(TemplateBlackFriday),
|
||
|
|
Name: "Black Friday",
|
||
|
|
Season: "black_friday",
|
||
|
|
DefaultSubject: "Black Friday deals from {{brand}}",
|
||
|
|
DefaultPrompt: "Urgent Black Friday email for these products. Limited-time value, no false claims, strong CTA. JSON only.",
|
||
|
|
Description: "Deal-focused Black Friday / Cyber Week campaign.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Key: string(TemplateSpring),
|
||
|
|
Name: "Spring",
|
||
|
|
Season: "spring",
|
||
|
|
DefaultSubject: "Fresh for spring — {{brand}}",
|
||
|
|
DefaultPrompt: "Light spring email for these products. Renewal + practical benefits, clear CTA. JSON only.",
|
||
|
|
Description: "Seasonal spring refresh campaign.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Key: string(TemplateCustom),
|
||
|
|
Name: "Custom",
|
||
|
|
Season: "custom",
|
||
|
|
DefaultSubject: "News from {{brand}}",
|
||
|
|
DefaultPrompt: "Clear marketing email for these products. Short subject, scannable body, CTA. JSON only.",
|
||
|
|
Description: "Blank slate with sensible defaults.",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
func ListTemplates() []Template {
|
||
|
|
out := make([]Template, len(builtInTemplates))
|
||
|
|
copy(out, builtInTemplates)
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
func GetTemplate(key string) (Template, error) {
|
||
|
|
key = strings.TrimSpace(strings.ToLower(key))
|
||
|
|
if key == "" {
|
||
|
|
key = string(TemplateCustom)
|
||
|
|
}
|
||
|
|
for _, t := range builtInTemplates {
|
||
|
|
if t.Key == key {
|
||
|
|
return t, nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return Template{}, ErrInvalidTemplate
|
||
|
|
}
|
||
|
|
|
||
|
|
func ValidTemplateKey(key string) bool {
|
||
|
|
_, err := GetTemplate(key)
|
||
|
|
return err == nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func renderSubject(tpl Template, brand string) string {
|
||
|
|
if brand == "" {
|
||
|
|
brand = "our store"
|
||
|
|
}
|
||
|
|
return strings.ReplaceAll(tpl.DefaultSubject, "{{brand}}", brand)
|
||
|
|
}
|
||
|
|
|
||
|
|
func templateHTML(subject, intro, productBlock, ctaURL, logoURL string) string {
|
||
|
|
if ctaURL == "" {
|
||
|
|
ctaURL = "#"
|
||
|
|
}
|
||
|
|
logoBlock := ""
|
||
|
|
if strings.TrimSpace(logoURL) != "" {
|
||
|
|
logoBlock = fmt.Sprintf(
|
||
|
|
`<p style="margin:0 0 16px"><img src="%s" alt="" width="120" style="max-width:160px;height:auto;border:0" /></p>`,
|
||
|
|
escapeAttr(logoURL),
|
||
|
|
)
|
||
|
|
}
|
||
|
|
return fmt.Sprintf(`<!DOCTYPE html><html><body style="font-family:Arial,sans-serif;color:#222;line-height:1.5">
|
||
|
|
%s<h1 style="font-size:22px;margin:0 0 12px">%s</h1>
|
||
|
|
<p>%s</p>
|
||
|
|
%s
|
||
|
|
<p style="margin:24px 0"><a href="%s" style="background:#2d1b4e;color:#fff;padding:10px 16px;text-decoration:none;border-radius:4px">Shop now</a></p>
|
||
|
|
</body></html>`, logoBlock, escapeHTML(subject), escapeHTML(intro), productBlock, escapeAttr(ctaURL))
|
||
|
|
}
|
||
|
|
|
||
|
|
func escapeHTML(s string) string {
|
||
|
|
r := strings.NewReplacer("&", "&", "<", "<", ">", ">", `"`, """)
|
||
|
|
return r.Replace(s)
|
||
|
|
}
|
||
|
|
|
||
|
|
func escapeAttr(s string) string {
|
||
|
|
return escapeHTML(s)
|
||
|
|
}
|