style: reformat checkin and home page templates

This commit is contained in:
Jon Seager 2026-02-07 09:48:37 +00:00
parent 3747ab1e93
commit f9adc849c2
No known key found for this signature in database
2 changed files with 183 additions and 90 deletions

View file

@ -1,12 +1,14 @@
{% extends "base.html" %} {% import "partials/icons.html" as icons %} {% import "partials/scan_input.html" as scan %} {% import "partials/location_search.html" as location %} {% block title %}Brewlog · Check In{% endblock %}
{% block head %}
{% extends "base.html" %} {% import "partials/icons.html" as icons %} {% import
"partials/scan_input.html" as scan %} {% import "partials/location_search.html" as location %} {%
block title %}Brewlog · Check In{% endblock %} {% block head %}
<script>
{{ location::location_search_js() }}
{
{
location::location_search_js()
}
}
</script>
{% endblock %}
{% block content %}
{% endblock %} {% block content %}
<section
id="checkin-root"
data-signals:_step="1"
@ -35,9 +37,7 @@
>
<header class="flex flex-col gap-2">
<h1 class="text-3xl font-semibold">Check In</h1>
<p class="max-w-2xl text-sm text-text-secondary">
Record a cup of coffee at a cafe.
</p>
<p class="max-w-2xl text-sm text-text-secondary">Record a cup of coffee at a cafe.</p>
</header>
<!-- Step progress -->
@ -45,38 +45,51 @@
<!-- Background track (continuous line through dot centers) -->
<div class="absolute bg-border" style="height: 3px; top: 8px; left: 10px; right: 10px"></div>
<!-- Accent overlay: first segment -->
<div class="absolute bg-accent" style="height: 3px; top: 8px; left: 10px; right: 50%; display: none"
<div
class="absolute bg-accent"
style="height: 3px; top: 8px; left: 10px; right: 50%; display: none"
data-show="$_step > 1"
></div>
<!-- Accent overlay: second segment -->
<div class="absolute bg-accent" style="height: 3px; top: 8px; left: 50%; right: 10px; display: none"
<div
class="absolute bg-accent"
style="height: 3px; top: 8px; left: 50%; right: 10px; display: none"
data-show="$_step > 2"
></div>
<div class="relative flex justify-between">
<div class="flex flex-col items-center">
<div class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
<div
class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
data-class="{'border-accent': $_step >= 1, 'border-border': $_step < 1}"
></div>
<span class="mt-2 text-xs font-medium transition-colors"
<span
class="mt-2 text-xs font-medium transition-colors"
data-class="{'text-accent': $_step === 1, 'text-text': $_step > 1}"
>Cafe</span>
>Cafe</span
>
</div>
<div class="flex flex-col items-center">
<div class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
<div
class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
data-class="{'border-accent': $_step >= 2, 'border-border': $_step < 2}"
></div>
<span class="mt-2 text-xs font-medium transition-colors"
<span
class="mt-2 text-xs font-medium transition-colors"
data-class="{'text-accent': $_step === 2, 'text-text': $_step > 2, 'text-text-muted': $_step < 2}"
>Coffee</span>
>Coffee</span
>
</div>
<div class="flex flex-col items-center">
<div class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
<div
class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
data-class="{'border-accent': $_step >= 3, 'border-border': $_step < 3}"
></div>
<span class="mt-2 text-xs font-medium transition-colors"
<span
class="mt-2 text-xs font-medium transition-colors"
data-class="{'text-accent': $_step === 3, 'text-text-muted': $_step < 3}"
>Submit</span>
>Submit</span
>
</div>
</div>
</div>
@ -90,13 +103,20 @@
<div class="flex items-center gap-2">
{{ icons::location("h-4 w-4 text-accent shrink-0") }}
<span class="font-medium text-text" data-text="$_cafeName"></span>
<span class="text-xs text-text-muted" data-show="$_cafeCity" data-text="$_cafeCity" style="display: none"></span>
<span
class="text-xs text-text-muted"
data-show="$_cafeCity"
data-text="$_cafeCity"
style="display: none"
></span>
</div>
<button
type="button"
data-on:click="$_cafeId = ''; $_cafeName = ''; $_step = 1"
class="text-xs text-text-muted hover:text-text"
>Change</button>
>
Change
</button>
</div>
<!-- Selected roast summary (shown after selection) -->
@ -108,13 +128,20 @@
<div class="flex items-center gap-2">
{{ icons::bag("h-4 w-4 text-accent shrink-0") }}
<span class="font-medium text-text" data-text="$_roastName"></span>
<span class="text-xs text-text-muted" data-show="$_roasterName" data-text="$_roasterName" style="display: none"></span>
<span
class="text-xs text-text-muted"
data-show="$_roasterName"
data-text="$_roasterName"
style="display: none"
></span>
</div>
<button
type="button"
data-on:click="$_roastId = ''; $_roastName = ''; $_roasterName = ''; $_scanSuccess = ''; $_step = 2"
class="text-xs text-text-muted hover:text-text"
>Change</button>
>
Change
</button>
</div>
<!-- Error display -->
@ -128,15 +155,10 @@
<!-- Step 1: Cafe selection -->
<div class="mt-4" data-show="$_step === 1" style="display: none">
<div class="rounded-lg border bg-surface p-5">
<div data-show="!$_reviewingCafe">
{{ location::location_search("$_error") }}
</div>
<div data-show="!$_reviewingCafe">{{ location::location_search("$_error") }}</div>
<!-- Review form for new cafe from Foursquare -->
<div
data-show="$_reviewingCafe"
style="display: none"
>
<div data-show="$_reviewingCafe" style="display: none">
<h3 class="text-base font-semibold text-text mb-1">Confirm cafe details</h3>
<p class="text-sm text-text-secondary mb-3">Review and edit the details before saving.</p>
<div class="grid gap-4 sm:grid-cols-2">
@ -154,7 +176,12 @@
</label>
<label class="flex flex-col gap-1 text-sm">
<span class="text-text">Website</span>
<input type="url" data-bind:_cafe-website class="input-field" placeholder="https://&hellip;" />
<input
type="url"
data-bind:_cafe-website
class="input-field"
placeholder="https://&hellip;"
/>
</label>
</div>
<div class="mt-4 flex items-center justify-end gap-2">
@ -179,13 +206,19 @@
{% if !cafe_options.is_empty() %}
<div class="mt-3 border-t pt-3" data-show="!$_reviewingCafe">
<p class="mb-2 text-xs text-text-muted">Or choose a saved cafe:</p>
<searchable-select name="saved_cafe_id" placeholder="Type to search saved cafes&hellip;"
<searchable-select
name="saved_cafe_id"
placeholder="Type to search saved cafes&hellip;"
data-on:change="$_cafeId = evt.detail.value; $_cafeName = evt.detail.display; $_cafeCity = evt.detail.data.city; $_cafeCountry = ''; $_cafeLat = 0; $_cafeLng = 0; $_cafeWebsite = ''; $_step = 2"
>
{% for cafe in cafe_options %}
<button type="button" value="{{ cafe.id }}" data-display="{{ cafe.name }}"
<button
type="button"
value="{{ cafe.id }}"
data-display="{{ cafe.name }}"
data-city="{{ cafe.city }}"
class="w-full px-3 py-2 text-left text-sm hover:bg-surface-alt transition">
class="w-full px-3 py-2 text-left text-sm hover:bg-surface-alt transition"
>
<span class="font-medium text-text">{{ cafe.name }}</span>
<span class="ml-2 text-xs text-text-muted">{{ cafe.city }}</span>
</button>
@ -202,13 +235,18 @@
<h3 class="text-base font-semibold text-text mb-3">What are you drinking?</h3>
<div data-show="!$_scanSuccess" class="mb-4">
<p class="text-sm text-text-secondary mb-3" data-show="!$_extracting">Scan a bag to identify the coffee, or select from your existing roasts below.</p>
<p class="text-sm text-text-secondary mb-3" data-show="!$_extracting">
Scan a bag to identify the coffee, or select from your existing roasts below.
</p>
<form id="checkin-scan-form"
<form
id="checkin-scan-form"
data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/scan', {contentType: 'form'})"
data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_roastName = $_scanSuccess; $_step = 3 } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Scan failed. Please try again.' }"
>
{{ scan::scan_input("checkin-scan-form", "checkin-image", "$_extracting", "$_extractError", "Describe the coffee…", "Scanning…") }}
{{ scan::scan_input("checkin-scan-form", "checkin-image", "$_extracting",
"$_extractError", "Describe a coffee bag to fetch details automatically…", "Scanning…")
}}
</form>
</div>
@ -223,13 +261,19 @@
{% if !roast_options.is_empty() %}
<div class="border-t pt-3">
<p class="text-xs text-text-muted mb-2">Or select an existing roast:</p>
<searchable-select name="roast_id" placeholder="Type to search existing roasts&hellip;"
<searchable-select
name="roast_id"
placeholder="Type to search existing roasts&hellip;"
data-on:change="$_roastId = evt.detail.value; $_roastName = evt.detail.display; $_roasterName = evt.detail.data.roaster; $_step = 3"
>
{% for roast in roast_options %}
<button type="button" value="{{ roast.id }}" data-display="{{ roast.name }}"
<button
type="button"
value="{{ roast.id }}"
data-display="{{ roast.name }}"
data-roaster="{{ roast.roaster_name }}"
class="w-full px-3 py-2 text-left text-sm hover:bg-surface-alt transition">
class="w-full px-3 py-2 text-left text-sm hover:bg-surface-alt transition"
>
<span class="font-medium text-text">{{ roast.name }}</span>
<span class="ml-2 text-xs text-text-muted">{{ roast.roaster_name }}</span>
</button>
@ -247,11 +291,17 @@
<dl class="flex flex-col gap-3 text-sm mb-6">
<div class="flex justify-between gap-2">
<dt class="font-medium text-text-muted">Cafe</dt>
<dd class="text-right text-text" data-text="$_cafeCity ? $_cafeName + ', ' + $_cafeCity : $_cafeName"></dd>
<dd
class="text-right text-text"
data-text="$_cafeCity ? $_cafeName + ', ' + $_cafeCity : $_cafeName"
></dd>
</div>
<div class="flex justify-between gap-2">
<dt class="font-medium text-text-muted">Coffee</dt>
<dd class="text-right text-text" data-text="$_roasterName ? $_roastName + ' (' + $_roasterName + ')' : $_roastName"></dd>
<dd
class="text-right text-text"
data-text="$_roasterName ? $_roastName + ' (' + $_roasterName + ')' : $_roastName"
></dd>
</div>
</dl>

View file

@ -1,33 +1,29 @@
{% extends "base.html" %} {% import "partials/bag_card.html" as bag_card %} {% import "partials/icons.html" as icons %} {% import "partials/scan_input.html" as scan %} {% block title %}Brewlog{% endblock %}
{% block head %}
{% if is_authenticated %}
{% extends "base.html" %} {% import "partials/bag_card.html" as bag_card %} {% import
"partials/icons.html" as icons %} {% import "partials/scan_input.html" as scan %} {% block title
%}Brewlog{% endblock %} {% block head %} {% if is_authenticated %}
<script>
const closeBag = async (bagId, cardEl) => {
if (!confirm('Close this bag? This will mark it as finished.')) return;
if (!confirm("Close this bag? This will mark it as finished.")) return
try {
const today = new Date().toISOString().split('T')[0];
const today = new Date().toISOString().split("T")[0]
const resp = await fetch(`/api/v1/bags/${bagId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
method: "PUT",
headers: { "Content-Type": "application/json" },
credentials: "same-origin",
body: JSON.stringify({ closed: true, remaining: 0.0, finished_at: today }),
});
if (!resp.ok) throw new Error(`Server returned ${resp.status}`);
cardEl.remove();
const grid = document.getElementById('open-bags-grid');
})
if (!resp.ok) throw new Error(`Server returned ${resp.status}`)
cardEl.remove()
const grid = document.getElementById("open-bags-grid")
if (grid && grid.children.length === 0) {
document.getElementById('open-bags-section')?.remove();
document.getElementById("open-bags-section")?.remove()
}
} catch (e) {
alert(`Failed to close bag: ${e.message}`);
alert(`Failed to close bag: ${e.message}`)
}
}
};
</script>
{% endif %}
{% endblock %}
{% block content %}
{% endif %} {% endblock %} {% block content %}
<!-- Scan Bag -->
{% if is_authenticated %}
<section
@ -53,11 +49,13 @@
>
<!-- Input: photo or text (shown when not yet extracted) -->
<div data-show="!$_scanExtracted" class="rounded-lg border bg-surface p-5">
<form id="scan-extract-form"
<form
id="scan-extract-form"
data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/extract-bag-scan', {contentType: 'form'})"
data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_scanExtracted = true; document.getElementById('scan-extract-form').reset() } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Extraction failed. Please try again.' }"
>
{{ scan::scan_input("scan-extract-form", "scan-image", "$_extracting", "$_extractError", "Describe the coffee bag…", "Waiting for response…") }}
{{ scan::scan_input("scan-extract-form", "scan-image", "$_extracting", "$_extractError",
"Describe a coffee bag to fetch details automatically…", "Waiting for response…") }}
</form>
</div>
@ -79,7 +77,9 @@
<section data-signals:_brewing="false">
<div class="flex items-center justify-between mb-3">
<h2 class="text-lg font-semibold text-text">Recent Brews</h2>
<a href="/data?type=brews" class="text-sm text-accent hover:text-accent-hover font-medium">View all brews &rarr;</a>
<a href="/data?type=brews" class="text-sm text-accent hover:text-accent-hover font-medium"
>View all brews &rarr;</a
>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 items-start gap-4">
{% for brew in recent_brews %}
@ -89,20 +89,31 @@
<span class="block truncate font-semibold text-text">{{ brew.roast_name }}</span>
<p class="truncate text-sm text-text-muted">{{ brew.roaster_name }}</p>
</div>
<time class="text-xs text-text-muted whitespace-nowrap shrink-0">{{ brew.relative_date_label }}</time>
<time class="text-xs text-text-muted whitespace-nowrap shrink-0"
>{{ brew.relative_date_label }}</time
>
</div>
<div class="mt-2 space-y-0.5 text-sm">
<p class="truncate"><span class="font-medium text-text">{{ brew.coffee_weight }} · {{ brew.water_volume }} · {{ brew.water_temp }}</span> <span class="text-text-muted">· {{ brew.ratio }}</span></p>
<p class="truncate"><span class="font-medium text-text">{{ brew.grind_setting }}</span> <span class="text-text-muted">· {{ brew.grinder_name }}</span></p>
<p class="truncate"><span class="font-medium text-text">{{ brew.brewer_name }}</span>{% if let Some(fp) = brew.filter_paper_name %} <span class="text-text-muted">· {{ fp }}</span>{% endif %}</p>
<p class="truncate">
<span class="font-medium text-text"
>{{ brew.coffee_weight }} · {{ brew.water_volume }} · {{ brew.water_temp }}</span
>
<span class="text-text-muted">· {{ brew.ratio }}</span>
</p>
<p class="truncate">
<span class="font-medium text-text">{{ brew.grind_setting }}</span>
<span class="text-text-muted">· {{ brew.grinder_name }}</span>
</p>
<p class="truncate">
<span class="font-medium text-text">{{ brew.brewer_name }}</span>{% if let Some(fp) =
brew.filter_paper_name %} <span class="text-text-muted">· {{ fp }}</span>{% endif %}
</p>
</div>
<div class="mt-3 pt-3 border-t flex items-center justify-between gap-3">
<div class="flex flex-wrap gap-1">
{% if !brew.quick_notes.is_empty() %}
{% for qn in brew.quick_notes %}
{% if !brew.quick_notes.is_empty() %} {% for qn in brew.quick_notes %}
<span class="{{ qn.pill_class }}">{{ qn.label }}</span>
{% endfor %}
{% else %}
{% endfor %} {% else %}
<span class="pill pill-muted">No Notes</span>
{% endif %}
</div>
@ -144,12 +155,12 @@
<section id="open-bags-section">
<div class="flex items-center justify-between mb-3">
<h2 class="text-lg font-semibold text-text">Open Bags</h2>
<a href="/data?type=bags" class="text-sm text-accent hover:text-accent-hover font-medium">View all bags &rarr;</a>
<a href="/data?type=bags" class="text-sm text-accent hover:text-accent-hover font-medium"
>View all bags &rarr;</a
>
</div>
<div id="open-bags-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for bag in open_bags %}
{{ bag_card::card(bag, is_authenticated) }}
{% endfor %}
{% for bag in open_bags %} {{ bag_card::card(bag, is_authenticated) }} {% endfor %}
</div>
</section>
{% endif %}
@ -159,16 +170,27 @@
<section>
<div class="flex items-center justify-between mb-3">
<h2 class="text-lg font-semibold text-text">Recent Activity</h2>
<a href="/timeline" class="text-sm text-accent hover:text-accent-hover font-medium">View full timeline &rarr;</a>
<a href="/timeline" class="text-sm text-accent hover:text-accent-hover font-medium"
>View full timeline &rarr;</a
>
</div>
<div class="space-y-2">
{% for event in recent_events %}
<div class="rounded-lg border bg-surface px-4 py-3 flex items-center justify-between gap-3">
<div class="flex items-center gap-3 min-w-0">
<span class="pill pill-muted w-28 shrink-0 justify-center whitespace-nowrap">{{ event.kind_label }}</span>
<p class="min-w-0 truncate text-sm"><a href="{{ event.link }}" class="font-medium text-text hover:text-accent">{{ event.title }}</a>{% if let Some(sub) = event.subtitle %} <span class="text-xs text-text-muted italic">· {{ sub }}</span>{% endif %}</p>
<span class="pill pill-muted w-28 shrink-0 justify-center whitespace-nowrap"
>{{ event.kind_label }}</span
>
<p class="min-w-0 truncate text-sm">
<a href="{{ event.link }}" class="font-medium text-text hover:text-accent"
>{{ event.title }}</a
>{% if let Some(sub) = event.subtitle %}
<span class="text-xs text-text-muted italic">· {{ sub }}</span>{% endif %}
</p>
</div>
<time class="text-xs text-text-muted whitespace-nowrap shrink-0">{{ event.relative_date_label }}</time>
<time class="text-xs text-text-muted whitespace-nowrap shrink-0"
>{{ event.relative_date_label }}</time
>
</div>
{% endfor %}
</div>
@ -181,37 +203,58 @@
<h2 class="text-lg font-semibold text-text">Stats</h2>
</div>
<div class="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-7">
<a href="/data?type=brews" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
<a
href="/data?type=brews"
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
>
{{ icons::beaker("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.brews }}</span>
<span class="text-xs text-text-muted">Brews</span>
</a>
<a href="/data?type=roasts" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
<a
href="/data?type=roasts"
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
>
{{ icons::fire("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.roasts }}</span>
<span class="text-xs text-text-muted">Roasts</span>
</a>
<a href="/data?type=roasters" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
<a
href="/data?type=roasters"
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
>
{{ icons::building("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.roasters }}</span>
<span class="text-xs text-text-muted">Roasters</span>
</a>
<a href="/data?type=bags" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
<a
href="/data?type=bags"
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
>
{{ icons::bag("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.bags }}</span>
<span class="text-xs text-text-muted">Bags</span>
</a>
<a href="/data?type=cups" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
<a
href="/data?type=cups"
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
>
{{ icons::cup("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.cups }}</span>
<span class="text-xs text-text-muted">Cups</span>
</a>
<a href="/data?type=cafes" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
<a
href="/data?type=cafes"
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
>
{{ icons::location("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.cafes }}</span>
<span class="text-xs text-text-muted">Cafes</span>
</a>
<a href="/data?type=gear" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
<a
href="/data?type=gear"
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
>
{{ icons::wrench("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.gear }}</span>
<span class="text-xs text-text-muted">Gear</span>