From 2a101e9a6283a74270b96219bfa4c8bbbc515a42 Mon Sep 17 00:00:00 2001 From: Sam Rolfe Date: Tue, 1 Sep 2026 12:53:53 +1000 Subject: [PATCH] Promo: order page + red banner + contact form; fix admin login rehash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /order page: single tag $5.50 / 10-pack $22.00 (GST-incl), qty via Stripe Payment Links (single 1-25, pack 1-2), thanks state - Red 'Protect & recover your valuables β€” ORDER NOW' banner in base layout (every page incl. found-dog tag page) + footer promo links - Contact page: form -> Gmail SMTP (env SMTP_*), hello@where-woof.com - Fix admin login 500: User.getAuthPasswordName() = password_hash so Laravel 13 rehash-on-login writes the correct column (was trying UPDATE users SET password = ...) --- admin/app/app/Models/User.php | 2 + frontend/internal/handlers/handlers.go | 1 + frontend/internal/handlers/pages.go | 8 ++- frontend/internal/handlers/promo.go | 92 ++++++++++++++++++++++++++ frontend/main.go | 2 + frontend/templates/base.html | 16 +++++ frontend/templates/contact.html | 41 +++++++++++- frontend/templates/order.html | 60 +++++++++++++++++ todo.txt | 4 ++ 9 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 frontend/internal/handlers/promo.go create mode 100644 frontend/templates/order.html 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 @@ + +
+ + πŸ›‘οΈ Protect & recover your valuables + ORDER NOW β€” tags from $5 + +
{{template "content" .}}
+ {{end}} diff --git a/frontend/templates/contact.html b/frontend/templates/contact.html index 19d0582..6d35647 100644 --- a/frontend/templates/contact.html +++ b/frontend/templates/contact.html @@ -2,10 +2,47 @@

Get in touch

Questions, lost-tag help, or bulk orders? We'd love to hear from you.

+ + {{if .Data.Sent}} +
+
πŸ“¨
+

Message sent!

+

Thanks for reaching out β€” we'll get back to you shortly (usually within a day).

+
+ {{else}} +
+ {{if .Data.Error}} +

{{.Data.Error}}

+ {{end}} +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ +
+
+ {{end}} +
-

Email: info@where-woof.com

+

Email: hello@where-woof.com

Website: where-woof.com

+

Order tags: yeah, take my money β€” from $5 + GST.

For bulk tags (parks, trails, campgrounds) mention your use case and volume.

-{{end}} +{{end}} \ No newline at end of file diff --git a/frontend/templates/order.html b/frontend/templates/order.html new file mode 100644 index 0000000..d1b919d --- /dev/null +++ b/frontend/templates/order.html @@ -0,0 +1,60 @@ +{{define "content"}} +
+ {{if .Data.Thanks}} +
+
βœ…
+

Thanks for your order!

+

We've received your payment and will ship your tags soon. Watch your inbox for the email from hello@where-woof.com with activation details.

+
+ {{end}} + +

Order tags

+

Stick a Where Woof tag on anything you don't want to lose β€” a dog collar, a school bag, ski gear, a toolbox. If it's found, whoever finds it scans the tag and you get an SMS with its location, instantly.

+ +
+ +
+
🏷️
+

Single tag

+

One tag for one item. Plastic laminate, QR + NFC.

+

$5.00 + GST ($5.50)

+

+ $10/yr subscription (50 SMS alerts included)

+
+ + Order now + +

Need 2, 3 … 7? Pick the quantity at checkout (up to 25).

+
+
+ + +
+
+
πŸŽ’πŸΎ
+ BEST VALUE +
+

10-pack

+

Tag the whole family β€” collars, bags, keys, skis.

+

$20.00 + GST ($22.00)

+

$2 per tag Β· + $10/yr subscription (50 SMS alerts included)

+
+ + Order the 10-pack + +

Up to 2 packs per account (20 tags, fits the 25-tag limit).

+
+
+
+ +
+

How it works

+
    +
  1. Order your tags β€” they arrive ready to scan (QR + NFC).
  2. +
  3. When they arrive, create a free account and add each tag.
  4. +
  5. Set the item's name and your phone number.
  6. +
  7. If it's ever lost and found, you get an SMS with a map link β€” instantly.
  8. +
+

Questions? Get in touch β€” hello@where-woof.com

+
+
+{{end}} \ No newline at end of file diff --git a/todo.txt b/todo.txt index ee68746..18020f3 100644 --- a/todo.txt +++ b/todo.txt @@ -26,3 +26,7 @@ x 2026-08-08 2026-08-08 Billing phase + (A) Do we tie this all into an established Open Source inventory payment system - keep qick dash for now? +area:CONCEPT (A) Order-tags. Tie in the inventory system? +area:Where-woof (A) General webiste pages - what is this? Get in touch? About Us. +area:Where-woof +(A) Order tags page + promotion banner (Protect & Recover Valuables β€” Order now) +project:where-woof +agent:where_woof +(A) Red promo banner on tag/about/contact/what-is-this pages +project:where-woof +agent:where_woof +(A) Get in touch page: hello@where-woof.com + contact form +project:where-woof +agent:where_woof +(C) Verify coordinates in scan/location flow +project:where-woof +agent:where_woof