diff --git a/admin/app/app/Models/User.php b/admin/app/app/Models/User.php index 5d35445..90aa9d1 100644 --- a/admin/app/app/Models/User.php +++ b/admin/app/app/Models/User.php @@ -15,6 +15,8 @@ class User extends Authenticatable implements FilamentUser use HasFactory, Notifiable, Billable; protected $table = 'users'; + // Shared schema uses password_hash, not Laravel's default 'password'. + protected $authPasswordName = 'password_hash'; public $timestamps = false; // shared schema: created_at only, set by DB default protected $fillable = ['email', 'password_hash', 'name', 'phone', 'is_admin', 'stripe_id', 'sms_credits']; protected $hidden = ['password_hash']; diff --git a/frontend/internal/handlers/handlers.go b/frontend/internal/handlers/handlers.go index 3a1e761..35703f5 100644 --- a/frontend/internal/handlers/handlers.go +++ b/frontend/internal/handlers/handlers.go @@ -58,6 +58,7 @@ func LoadTemplates() (Templates, error) { "about": {dir + "/about.html"}, "what": {dir + "/what.html"}, "contact": {dir + "/contact.html"}, + "order": {dir + "/order.html"}, "register": {dir + "/register.html"}, "login": {dir + "/login.html"}, "account": {dir + "/account.html", dir + "/account-panel.html", dir + "/tag-list.html", dir + "/tag-edit-inline.html"}, diff --git a/frontend/internal/handlers/pages.go b/frontend/internal/handlers/pages.go index d74dba1..1aefb5b 100644 --- a/frontend/internal/handlers/pages.go +++ b/frontend/internal/handlers/pages.go @@ -14,5 +14,11 @@ func (a *App) WhatIsThis(w http.ResponseWriter, r *http.Request) { // Contact renders the contact page. func (a *App) Contact(w http.ResponseWriter, r *http.Request) { - a.render(w, r, "contact", "Contact", nil, "") + a.render(w, r, "contact", "Contact", contactData{}, "") } + +// contactData passed to the contact page (form submission state). +type contactData struct { + Sent bool + Error string +} \ No newline at end of file diff --git a/frontend/internal/handlers/promo.go b/frontend/internal/handlers/promo.go new file mode 100644 index 0000000..a34b2e5 --- /dev/null +++ b/frontend/internal/handlers/promo.go @@ -0,0 +1,92 @@ +package handlers + +import ( + "fmt" + "net/http" + "net/smtp" + "os" + "strings" +) + +// orderData passes the Stripe Payment Links (env-configured) to the order page. +// Links are bought via Stripe-hosted checkout; the buyer chooses quantity there +// (single 1-25, 10-pack 1-2). Prices: $5.00 + GST, $20.00 + GST (fee handled +// by Stripe Managed Payments). +type orderData struct { + SingleLink string + PackLink string + Thanks bool +} + +// Order renders the public ordering page. +func (a *App) Order(w http.ResponseWriter, r *http.Request) { + a.render(w, r, "order", "Order tags", + orderData{ + SingleLink: os.Getenv("ORDER_SINGLE_LINK"), + PackLink: os.Getenv("ORDER_PACK_LINK"), + Thanks: r.URL.Query().Get("thanks") == "1", + }, "") +} + +// ContactSubmit receives the contact form and emails the team via SMTP +// (env: SMTP_HOST/SMTP_PORT/SMTP_USER/SMTP_PASS, CONTACT_TO). +// Returns the contact page with a success/error notice. +func (a *App) ContactSubmit(w http.ResponseWriter, r *http.Request) { + name := strings.TrimSpace(r.FormValue("name")) + email := strings.TrimSpace(r.FormValue("email")) + subject := strings.TrimSpace(r.FormValue("subject")) + message := strings.TrimSpace(r.FormValue("message")) + + errMsg := "" + if name == "" || email == "" || message == "" { + errMsg = "Please fill in your name, email and message." + } else if !strings.Contains(email, "@") { + errMsg = "Please enter a valid email address." + } + + if errMsg == "" { + if err := sendContactEmail(name, email, subject, message); err != nil { + fmt.Println("contact email failed:", err) + errMsg = "Sorry β we couldn't send your message right now. Please email us directly at hello@where-woof.com." + } else { + // Success: show a confirmation instead of re-rendering the form. + a.render(w, r, "contact", "Get in touch", contactData{Sent: true}, "") + return + } + } + + a.render(w, r, "contact", "Get in touch", contactData{Error: errMsg}, errMsg) +} + +// sendContactEmail sends the form contents to CONTACT_TO via Gmail SMTP +// (ENV: SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, CONTACT_TO). +func sendContactEmail(name, email, subject, message string) error { + host := os.Getenv("SMTP_HOST") + port := os.Getenv("SMTP_PORT") + user := os.Getenv("SMTP_USER") + pass := os.Getenv("SMTP_PASS") + to := os.Getenv("CONTACT_TO") + if host == "" || user == "" || pass == "" || to == "" { + return fmt.Errorf("SMTP not configured") + } + if port == "" { + port = "587" + } + + subj := subject + if subj == "" { + subj = "Where Woof contact form" + } + body := fmt.Sprintf("From: %s <%s>\nSubject: %s\n\n%s", name, email, subj, message) + + msg := []byte("To: " + to + "\r\n" + + "From: " + user + "\r\n" + + "Subject: " + subj + "\r\n" + + "MIME-Version: 1.0\r\n" + + "Content-Type: text/plain; charset=utf-8\r\n" + + "\r\n" + body + "\r\n") + + addr := host + ":" + port + auth := smtp.PlainAuth("", user, pass, host) + return smtp.SendMail(addr, auth, user, []string{to}, msg) +} \ No newline at end of file diff --git a/frontend/main.go b/frontend/main.go index 3793166..1591475 100644 --- a/frontend/main.go +++ b/frontend/main.go @@ -70,6 +70,8 @@ func main() { mux.HandleFunc("GET /about", app.About) mux.HandleFunc("GET /what-is-this", app.WhatIsThis) mux.HandleFunc("GET /contact", app.Contact) + mux.HandleFunc("POST /contact", app.ContactSubmit) + mux.HandleFunc("GET /order", app.Order) mux.HandleFunc("/{path...}", app.LegacyNotFound) mux.HandleFunc("GET /t/{tag_code}", app.PublicTag) mux.HandleFunc("GET /s/{code}", app.ServeShort) diff --git a/frontend/templates/base.html b/frontend/templates/base.html index 089f41d..f87535f 100644 --- a/frontend/templates/base.html +++ b/frontend/templates/base.html @@ -40,9 +40,25 @@ + +