scan-flow: geolocation scan, location-aware SMS throttle (log sender), finder contact, sms_enabled toggle, branding — 19/19 phase-2 scenarios pass

This commit is contained in:
2026-08-05 18:23:34 +10:00
parent 4666751e92
commit c2fa04afd0
26 changed files with 698 additions and 43 deletions

View File

@@ -4,7 +4,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{.Title}} · WhereWoof</title>
<title>{{.Title}} · Where Woof !</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
@@ -12,7 +12,7 @@
<body class="min-h-full bg-stone-100 text-stone-900 antialiased">
<nav class="bg-stone-900 text-white">
<div class="mx-auto flex max-w-4xl items-center justify-between px-4 py-3">
<a href="/" class="text-lg font-bold tracking-tight">🐕 WhereWoof</a>
<a href="/" class="text-lg font-bold tracking-tight">🐕 Where Woof</a>
<div class="flex items-center gap-4 text-sm">
{{if .CurrentUser}}
<span class="text-stone-300">Hi, {{.CurrentUser.Name}}</span>

View File

@@ -2,7 +2,7 @@
<div class="mx-auto max-w-2xl text-center">
<h1 class="text-4xl font-extrabold tracking-tight">Lost something?<br><span class="text-amber-600">The tag brings it home.</span></h1>
<p class="mt-4 text-lg text-stone-600">
WhereWoof tags carry the return details for your dog, baggage, skis — anything you care about.
Where Woof tags carry the return details for your dog, baggage, skis — anything you care about.
A finder scans the QR code or taps the NFC tag and sees exactly how to get it back to you.
</p>

View File

@@ -1,7 +1,7 @@
{{define "content"}}
<div class="mx-auto max-w-md">
<h1 class="text-2xl font-bold">Log in</h1>
<p class="mt-1 text-sm text-stone-600">Welcome back to WhereWoof.</p>
<p class="mt-1 text-sm text-stone-600">Welcome back to Where Woof.</p>
{{if .Error}}<p class="mt-4 rounded bg-red-50 px-4 py-2 text-sm text-red-700 border border-red-200">{{.Error}}</p>{{end}}
@@ -17,6 +17,6 @@
<button type="submit" class="w-full rounded bg-stone-900 px-4 py-2 font-semibold text-white hover:bg-stone-700">Log in</button>
</form>
<p class="mt-4 text-center text-sm text-stone-600">New to WhereWoof? <a href="/register" class="text-amber-600 hover:underline">Create an account</a></p>
<p class="mt-4 text-center text-sm text-stone-600">New to Where Woof? <a href="/register" class="text-amber-600 hover:underline">Create an account</a></p>
</div>
{{end}}

View File

@@ -2,7 +2,7 @@
<div class="mx-auto max-w-md text-center">
<div class="text-5xl">🔎</div>
<h1 class="mt-4 text-2xl font-bold">Tag not found</h1>
<p class="mt-2 text-stone-600">We couldn't find a WhereWoof tag with that code. Check the code on the tag and try again.</p>
<p class="mt-2 text-stone-600">We couldn't find a Where Woof tag with that code. Check the code on the tag and try again.</p>
<a href="/" class="mt-6 inline-block rounded bg-stone-900 px-4 py-2 text-sm font-semibold text-white hover:bg-stone-700">Go home</a>
</div>
{{end}}

View File

@@ -1,7 +1,7 @@
{{define "content"}}
<div class="mx-auto max-w-md">
<h1 class="text-2xl font-bold">Create your account</h1>
<p class="mt-1 text-sm text-stone-600">Register to claim your WhereWoof tag.</p>
<p class="mt-1 text-sm text-stone-600">Register to claim your Where Woof tag.</p>
{{if .Error}}<p class="mt-4 rounded bg-red-50 px-4 py-2 text-sm text-red-700 border border-red-200">{{.Error}}</p>{{end}}

View File

@@ -40,6 +40,10 @@
<label class="block text-sm font-medium">Notes</label>
<textarea name="notes" rows="2" class="mt-1 w-full rounded border border-stone-300 px-3 py-2">{{if .Data.Notes.Valid}}{{.Data.Notes.String}}{{end}}</textarea>
</div>
<label class="flex items-center gap-2 text-sm">
<input type="checkbox" name="sms_enabled" value="on" {{if .Data.SmsEnabled}}checked{{end}} class="h-4 w-4">
Send me SMS alerts when this tag is scanned
</label>
<div class="flex gap-3">
<button type="submit" class="rounded bg-stone-900 px-4 py-2 font-semibold text-white hover:bg-stone-700">Save details</button>
<a href="/account" class="rounded border border-stone-300 px-4 py-2 text-sm font-medium hover:bg-stone-50">Cancel</a>

View File

@@ -4,7 +4,7 @@
<div class="rounded-xl border border-stone-200 bg-white p-8 text-center shadow-sm">
<div class="text-5xl">🏷️</div>
<h1 class="mt-4 text-2xl font-bold">This tag isn't set up yet</h1>
<p class="mt-2 text-stone-600">This WhereWoof tag hasn't been claimed. If this is your tag, log in and add it to your account to set up the return details.</p>
<p class="mt-2 text-stone-600">This Where Woof tag hasn't been claimed. If this is your tag, log in and add it to your account to set up the return details.</p>
<div class="mt-6 flex justify-center gap-3">
<a href="/login" class="rounded bg-stone-900 px-4 py-2 text-sm font-semibold text-white hover:bg-stone-700">Log in</a>
<a href="/register" class="rounded bg-amber-500 px-4 py-2 text-sm font-semibold text-stone-900 hover:bg-amber-400">Create account</a>
@@ -22,7 +22,7 @@
<h1 class="text-2xl font-bold">
{{if .Data.ItemType.Valid}}{{.Data.ItemType.String | title}}{{else}}Item{{end}} — found!
</h1>
<p class="mt-1 text-sm text-stone-300">This item has a WhereWoof tag. Here's how to return it:</p>
<p class="mt-1 text-sm text-stone-300">This item has a Where Woof tag. Here's how to return it:</p>
</div>
<div class="p-6">
{{if .Data.PhotoUrl.Valid}}
@@ -37,6 +37,10 @@
<dt class="font-medium">Call the owner</dt>
<dd><a href="tel:{{.Data.Phone.String}}" class="font-semibold text-amber-600 hover:underline">{{.Data.Phone.String}}</a></dd>
</div>
<div class="flex items-center justify-between rounded bg-stone-50 px-4 py-3">
<dt class="font-medium">Text the owner</dt>
<dd><a href="sms:{{.Data.Phone.String}}?&body=Hi! I found your {{if .Data.ItemType.Valid}}{{.Data.ItemType.String}}{{else}}item{{end}}{{if .Data.Description.Valid}}, {{.Data.Description.String}}{{end}}. My phone number is: " class="font-semibold text-amber-600 hover:underline">Send an SMS</a></dd>
</div>
{{end}}
{{if .Data.Address.Valid}}
<div class="flex items-center justify-between rounded bg-stone-50 px-4 py-3">
@@ -51,14 +55,51 @@
</div>
{{end}}
</dl>
<p class="mt-6 rounded bg-amber-50 px-4 py-3 text-sm text-amber-800 border border-amber-200">
Found this item? Please contact the owner to arrange the return. Thank you for helping!
</p>
<div class="mt-4 rounded border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">
<p><strong>Help the owner find it:</strong> sharing your location sends them a map link. No location? No problem — the page still works.</p>
<button id="recheck-btn" type="button" class="mt-2 rounded bg-amber-500 px-3 py-1.5 text-xs font-semibold text-stone-900 hover:bg-amber-400">📍 Re-check my location</button>
</div>
<div id="contact-area" class="mt-4">
<form hx-post="/t/{{.Data.TagCode}}/contact" hx-target="#contact-area" hx-swap="innerHTML" class="flex gap-2">
<input name="phone" type="tel" placeholder="Leave your number for the owner (optional)" class="flex-1 rounded border border-stone-300 px-3 py-2 text-sm" pattern="[0-9+ ]{8,16}">
<button type="submit" class="rounded bg-stone-900 px-4 py-2 text-sm font-semibold text-white hover:bg-stone-700">Send number</button>
</form>
</div>
{{if .Data.IsOwner}}
<p class="mt-4 text-center text-sm"><a href="/account/tags/{{.Data.ID}}/edit" class="text-amber-600 hover:underline">✏️ Edit this tag's details</a></p>
{{end}}
</div>
</div>
<script>
(function () {
var code = {{.Data.TagCode | printf "%q"}};
var btn = document.getElementById("recheck-btn");
function sendScan(lat, lng) {
fetch("/t/" + code + "/scan", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify(lat != null ? {lat: lat, lng: lng} : {})
});
}
function tryLocation() {
if (!navigator.geolocation) { sendScan(null, null); return; }
navigator.geolocation.getCurrentPosition(
function (pos) {
if (btn) btn.classList.add("hidden");
sendScan(pos.coords.latitude, pos.coords.longitude);
},
function () { sendScan(null, null); },
{timeout: 8000, maximumAge: 60000}
);
}
if (btn) btn.addEventListener("click", tryLocation);
tryLocation();
})();
</script>
{{end}}
</div>
{{end}}