Update create_brew, create_cup, and submit_checkin to navigate to the new detail pages instead of reloading or redirecting to home.
328 lines
13 KiB
HTML
328 lines
13 KiB
HTML
{% 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() }}
|
|
</script>
|
|
{% endblock %} {% block content %}
|
|
<section
|
|
id="checkin-root"
|
|
data-signals:_step="1"
|
|
data-signals:_cafe-id="''"
|
|
data-signals:_cafe-name="''"
|
|
data-signals:_cafe-city="''"
|
|
data-signals:_cafe-country="''"
|
|
data-signals:_cafe-lat="0"
|
|
data-signals:_cafe-lng="0"
|
|
data-signals:_cafe-website="''"
|
|
data-signals:_cafe-search="''"
|
|
data-signals:_roast-id="''"
|
|
data-signals:_roast-name="''"
|
|
data-signals:_roaster-name="''"
|
|
data-signals:_error="''"
|
|
data-signals:_submitting="false"
|
|
data-signals:_locating="false"
|
|
data-signals:_location-found="false"
|
|
data-signals:_user-lat="0"
|
|
data-signals:_user-lng="0"
|
|
data-signals:_city-name="''"
|
|
data-signals:_reviewing-cafe="false"
|
|
data-signals:_extracting="false"
|
|
data-signals:_extract-error="''"
|
|
data-signals:_scan-success="''"
|
|
>
|
|
<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>
|
|
</header>
|
|
|
|
<!-- Step progress -->
|
|
<div class="relative mx-auto mt-6" style="width: 80%">
|
|
<!-- 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"
|
|
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"
|
|
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"
|
|
data-class="{'border-accent': $_step >= 1, 'border-border': $_step < 1}"
|
|
></div>
|
|
<span
|
|
class="mt-2 text-xs font-medium transition-colors"
|
|
data-class="{'text-accent': $_step === 1, 'text-text': $_step > 1}"
|
|
>Cafe</span
|
|
>
|
|
</div>
|
|
<div class="flex flex-col items-center">
|
|
<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"
|
|
data-class="{'text-accent': $_step === 2, 'text-text': $_step > 2, 'text-text-muted': $_step < 2}"
|
|
>Coffee</span
|
|
>
|
|
</div>
|
|
<div class="flex flex-col items-center">
|
|
<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"
|
|
data-class="{'text-accent': $_step === 3, 'text-text-muted': $_step < 3}"
|
|
>Submit</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Selected cafe summary (shown after selection) -->
|
|
<div
|
|
class="mt-4 rounded-lg border bg-surface px-4 py-3 flex items-center justify-between cursor-pointer"
|
|
data-show="$_step > 1 && $_cafeName && !$_reviewingCafe"
|
|
style="display: none"
|
|
data-on:click="$_cafeId = ''; $_cafeName = ''; $_step = 1"
|
|
>
|
|
<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>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
data-on:click="$_cafeId = ''; $_cafeName = ''; $_step = 1"
|
|
class="inline-flex items-center gap-1 text-xs text-text-muted hover:text-text"
|
|
>
|
|
{{ icons::pencil("h-3 w-3") }} Change
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Selected roast summary (shown after selection) -->
|
|
<div
|
|
class="mt-2 rounded-lg border bg-surface px-4 py-3 flex items-center justify-between cursor-pointer"
|
|
data-show="$_roastName && $_step > 2"
|
|
style="display: none"
|
|
data-on:click="$_roastId = ''; $_roastName = ''; $_roasterName = ''; $_scanSuccess = ''; $_step = 2"
|
|
>
|
|
<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>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
data-on:click="$_roastId = ''; $_roastName = ''; $_roasterName = ''; $_scanSuccess = ''; $_step = 2"
|
|
class="inline-flex items-center gap-1 text-xs text-text-muted hover:text-text"
|
|
>
|
|
{{ icons::pencil("h-3 w-3") }} Change
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Error display -->
|
|
<p
|
|
class="mt-3 text-sm text-red-600"
|
|
data-show="$_error"
|
|
style="display: none"
|
|
data-text="$_error"
|
|
></p>
|
|
|
|
<!-- 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>
|
|
|
|
<!-- Review form for new cafe from Foursquare -->
|
|
<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">
|
|
<label class="flex flex-col gap-1 text-sm">
|
|
<span class="text-text">Name *</span>
|
|
<input type="text" data-bind:_cafe-name required class="input-field" />
|
|
</label>
|
|
<label class="flex flex-col gap-1 text-sm">
|
|
<span class="text-text">City</span>
|
|
<input type="text" data-bind:_cafe-city class="input-field" />
|
|
</label>
|
|
<label class="flex flex-col gap-1 text-sm">
|
|
<span class="text-text">Country</span>
|
|
<input type="text" data-bind:_cafe-country class="input-field" />
|
|
</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://…"
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div class="mt-4 flex items-center justify-end gap-2">
|
|
<button
|
|
type="button"
|
|
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-text transition hover:bg-surface-alt"
|
|
data-on:click="$_reviewingCafe = false; $_cafeName = ''; $_cafeCity = ''; $_cafeCountry = ''; $_cafeLat = 0; $_cafeLng = 0; $_cafeWebsite = ''"
|
|
>
|
|
Back
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="inline-flex items-center justify-center gap-2 rounded-md bg-accent px-4 py-2 text-sm font-semibold text-accent-text transition hover:bg-accent-hover"
|
|
data-on:click="$_reviewingCafe = false; $_step = 2"
|
|
>
|
|
Next {{ icons::chevron_right("h-4 w-4") }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Saved cafes -->
|
|
{% if !cafe_options.is_empty() %}
|
|
<div class="mt-3 border-t pt-3" data-show="!$_reviewingCafe">
|
|
<p class="mb-2 text-sm text-text-secondary">Or choose a saved cafe:</p>
|
|
<searchable-select
|
|
name="saved_cafe_id"
|
|
placeholder="Type to search saved cafes…"
|
|
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 }}"
|
|
data-city="{{ cafe.city }}"
|
|
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>
|
|
{% endfor %}
|
|
</searchable-select>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 2: Roast identification -->
|
|
<div class="mt-4" data-show="$_step === 2" style="display: none">
|
|
<div class="rounded-lg border bg-surface p-5">
|
|
<h3 class="text-base font-semibold text-text mb-3">Select a coffee</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 existing roasts below.
|
|
</p>
|
|
|
|
<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 a coffee bag…", "Scanning…") }}
|
|
</form>
|
|
</div>
|
|
|
|
<div
|
|
class="mb-4 rounded-md bg-green-50 border border-green-200 px-3 py-2 text-sm text-green-800"
|
|
data-show="$_scanSuccess"
|
|
style="display: none"
|
|
>
|
|
Scanned: <span class="font-medium" data-text="$_scanSuccess"></span>
|
|
</div>
|
|
|
|
{% if !roast_options.is_empty() %}
|
|
<div class="border-t pt-3">
|
|
<p class="text-sm text-text-secondary mb-2">Or select an existing roast:</p>
|
|
<searchable-select
|
|
name="roast_id"
|
|
placeholder="Type to search existing roasts…"
|
|
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 }}"
|
|
data-roaster="{{ roast.roaster_name }}"
|
|
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>
|
|
{% endfor %}
|
|
</searchable-select>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3: Review + submit -->
|
|
<div class="mt-4" data-show="$_step === 3" style="display: none">
|
|
<div class="rounded-lg border bg-surface p-5">
|
|
<h3 class="text-base font-semibold text-text mb-4">Review & Submit</h3>
|
|
<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>
|
|
</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>
|
|
</div>
|
|
</dl>
|
|
|
|
<form
|
|
data-on:submit="$_submitting = true; $_error = ''; @post('/api/v1/check-in', {contentType: 'form'})"
|
|
data-on:datastar-fetch="if (!$_submitting) return; if (evt.detail.type === 'error') { $_submitting = false; $_error = 'Check-in failed. Please try again.' }"
|
|
>
|
|
<input type="hidden" name="cafe_id" data-attr:value="$_cafeId" />
|
|
<input type="hidden" name="cafe_name" data-attr:value="$_cafeName" />
|
|
<input type="hidden" name="cafe_city" data-attr:value="$_cafeCity" />
|
|
<input type="hidden" name="cafe_country" data-attr:value="$_cafeCountry" />
|
|
<input type="hidden" name="cafe_lat" data-attr:value="$_cafeLat" />
|
|
<input type="hidden" name="cafe_lng" data-attr:value="$_cafeLng" />
|
|
<input type="hidden" name="cafe_website" data-attr:value="$_cafeWebsite" />
|
|
<input type="hidden" name="roast_id" data-attr:value="$_roastId" />
|
|
<button
|
|
type="submit"
|
|
class="inline-flex w-full items-center justify-center gap-2 rounded-md bg-accent px-4 py-3 text-sm font-semibold text-accent-text transition hover:bg-accent-hover disabled:opacity-50"
|
|
data-attr:disabled="$_submitting"
|
|
>
|
|
{{ icons::check("h-4 w-4") }} Check In
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
{% endblock %}
|