Own account domain templates (Phase 6)
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 41s

Auth templates moved from shared to account/templates/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-24 16:55:50 +00:00
parent daea61a481
commit ee2952e885
14 changed files with 365 additions and 1 deletions

2
shared

Submodule shared updated: 65c4989d08...322ae481ee

View File

@@ -0,0 +1,44 @@
<div class="w-full max-w-3xl mx-auto px-4 py-6">
<div class="bg-white/70 backdrop-blur rounded-2xl shadow border border-stone-200 p-6 sm:p-8 space-y-6">
<h1 class="text-xl font-semibold tracking-tight">Bookings</h1>
{% if bookings %}
<div class="divide-y divide-stone-100">
{% for booking in bookings %}
<div class="py-4 first:pt-0 last:pb-0">
<div class="flex items-start justify-between gap-4">
<div class="min-w-0 flex-1">
<p class="text-sm font-medium text-stone-800">{{ booking.name }}</p>
<div class="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-stone-500">
<span>{{ booking.start_at.strftime('%d %b %Y, %H:%M') }}</span>
{% if booking.end_at %}
<span>&ndash; {{ booking.end_at.strftime('%H:%M') }}</span>
{% endif %}
{% if booking.calendar_name %}
<span>&middot; {{ booking.calendar_name }}</span>
{% endif %}
{% if booking.cost %}
<span>&middot; &pound;{{ booking.cost }}</span>
{% endif %}
</div>
</div>
<div class="flex-shrink-0">
{% if booking.state == 'confirmed' %}
<span class="inline-flex items-center rounded-full bg-emerald-50 border border-emerald-200 px-2.5 py-0.5 text-xs font-medium text-emerald-700">confirmed</span>
{% elif booking.state == 'provisional' %}
<span class="inline-flex items-center rounded-full bg-amber-50 border border-amber-200 px-2.5 py-0.5 text-xs font-medium text-amber-700">provisional</span>
{% else %}
<span class="inline-flex items-center rounded-full bg-stone-50 border border-stone-200 px-2.5 py-0.5 text-xs font-medium text-stone-600">{{ booking.state }}</span>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-sm text-stone-500">No bookings yet.</p>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1 @@
{{ page_fragment_html | safe }}

View File

@@ -0,0 +1,49 @@
<div class="w-full max-w-3xl mx-auto px-4 py-6">
<div class="bg-white/70 backdrop-blur rounded-2xl shadow border border-stone-200 p-6 sm:p-8 space-y-8">
{% if error %}
<div class="rounded-lg border border-red-200 bg-red-50 text-red-800 px-4 py-3 text-sm">
{{ error }}
</div>
{% endif %}
{# Account header #}
<div class="flex items-center justify-between">
<div>
<h1 class="text-xl font-semibold tracking-tight">Account</h1>
{% if g.user %}
<p class="text-sm text-stone-500 mt-1">{{ g.user.email }}</p>
{% if g.user.name %}
<p class="text-sm text-stone-600">{{ g.user.name }}</p>
{% endif %}
{% endif %}
</div>
<form action="/auth/logout/" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button
type="submit"
class="inline-flex items-center gap-2 rounded-full border border-stone-300 px-4 py-2 text-sm font-medium text-stone-700 hover:bg-stone-50 transition"
>
<i class="fa-solid fa-right-from-bracket text-xs"></i>
Sign out
</button>
</form>
</div>
{# Labels #}
{% set labels = g.user.labels if g.user is defined and g.user.labels is defined else [] %}
{% if labels %}
<div>
<h2 class="text-base font-semibold tracking-tight mb-3">Labels</h2>
<div class="flex flex-wrap gap-2">
{% for label in labels %}
<span class="inline-flex items-center rounded-full border border-stone-200 px-3 py-1 text-xs font-medium bg-white/60">
{{ label.name }}
</span>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,7 @@
{% import 'macros/links.html' as links %}
{% call links.link(account_url('/newsletters/'), hx_select_search, select_colours, True, aclass=styles.nav_button) %}
newsletters
{% endcall %}
{% if account_nav_html %}
{{ account_nav_html | safe }}
{% endif %}

View File

@@ -0,0 +1,17 @@
<div id="nl-{{ un.newsletter_id }}" class="flex items-center">
<button
hx-post="{{ account_url('/newsletter/' ~ un.newsletter_id ~ '/toggle/') }}"
hx-headers='{"X-CSRFToken": "{{ csrf_token() }}"}'
hx-target="#nl-{{ un.newsletter_id }}"
hx-swap="outerHTML"
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2
{% if un.subscribed %}bg-emerald-500{% else %}bg-stone-300{% endif %}"
role="switch"
aria-checked="{{ 'true' if un.subscribed else 'false' }}"
>
<span
class="inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform
{% if un.subscribed %}translate-x-6{% else %}translate-x-1{% endif %}"
></span>
</button>
</div>

View File

@@ -0,0 +1,46 @@
<div class="w-full max-w-3xl mx-auto px-4 py-6">
<div class="bg-white/70 backdrop-blur rounded-2xl shadow border border-stone-200 p-6 sm:p-8 space-y-6">
<h1 class="text-xl font-semibold tracking-tight">Newsletters</h1>
{% if newsletter_list %}
<div class="divide-y divide-stone-100">
{% for item in newsletter_list %}
<div class="flex items-center justify-between py-4 first:pt-0 last:pb-0">
<div class="min-w-0 flex-1">
<p class="text-sm font-medium text-stone-800">{{ item.newsletter.name }}</p>
{% if item.newsletter.description %}
<p class="text-xs text-stone-500 mt-0.5 truncate">{{ item.newsletter.description }}</p>
{% endif %}
</div>
<div class="ml-4 flex-shrink-0">
{% if item.un %}
{% with un=item.un %}
{% include "_types/auth/_newsletter_toggle.html" %}
{% endwith %}
{% else %}
{# No subscription row yet — show an off toggle that will create one #}
<div id="nl-{{ item.newsletter.id }}" class="flex items-center">
<button
hx-post="{{ account_url('/newsletter/' ~ item.newsletter.id ~ '/toggle/') }}"
hx-headers='{"X-CSRFToken": "{{ csrf_token() }}"}'
hx-target="#nl-{{ item.newsletter.id }}"
hx-swap="outerHTML"
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 bg-stone-300"
role="switch"
aria-checked="false"
>
<span class="inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform translate-x-1"></span>
</button>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-sm text-stone-500">No newsletters available.</p>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,29 @@
{% extends 'oob_elements.html' %}
{# OOB elements for HTMX navigation - all elements that need updating #}
{# Import shared OOB macros #}
{% from '_types/root/_oob_menu.html' import mobile_menu with context %}
{# Header with app title - includes cart-mini, navigation, and market-specific header #}
{% block oobs %}
{% from '_types/root/_n/macros.html' import oob_header with context %}
{{oob_header('root-header-child', 'auth-header-child', '_types/auth/header/_header.html')}}
{% from '_types/root/header/_header.html' import header_row with context %}
{{ header_row(oob=True) }}
{% endblock %}
{% block mobile_menu %}
{% include '_types/auth/_nav.html' %}
{% endblock %}
{% block content %}
{% include oob.main %}
{% endblock %}

View File

@@ -0,0 +1,44 @@
<div class="w-full max-w-3xl mx-auto px-4 py-6">
<div class="bg-white/70 backdrop-blur rounded-2xl shadow border border-stone-200 p-6 sm:p-8 space-y-6">
<h1 class="text-xl font-semibold tracking-tight">Tickets</h1>
{% if tickets %}
<div class="divide-y divide-stone-100">
{% for ticket in tickets %}
<div class="py-4 first:pt-0 last:pb-0">
<div class="flex items-start justify-between gap-4">
<div class="min-w-0 flex-1">
<a href="{{ events_url('/tickets/' ~ ticket.code ~ '/') }}"
class="text-sm font-medium text-stone-800 hover:text-emerald-700 transition">
{{ ticket.entry_name }}
</a>
<div class="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-stone-500">
<span>{{ ticket.entry_start_at.strftime('%d %b %Y, %H:%M') }}</span>
{% if ticket.calendar_name %}
<span>&middot; {{ ticket.calendar_name }}</span>
{% endif %}
{% if ticket.ticket_type_name %}
<span>&middot; {{ ticket.ticket_type_name }}</span>
{% endif %}
</div>
</div>
<div class="flex-shrink-0">
{% if ticket.state == 'checked_in' %}
<span class="inline-flex items-center rounded-full bg-blue-50 border border-blue-200 px-2.5 py-0.5 text-xs font-medium text-blue-700">checked in</span>
{% elif ticket.state == 'confirmed' %}
<span class="inline-flex items-center rounded-full bg-emerald-50 border border-emerald-200 px-2.5 py-0.5 text-xs font-medium text-emerald-700">confirmed</span>
{% else %}
<span class="inline-flex items-center rounded-full bg-amber-50 border border-amber-200 px-2.5 py-0.5 text-xs font-medium text-amber-700">{{ ticket.state }}</span>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-sm text-stone-500">No tickets yet.</p>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,33 @@
{% extends "_types/root/index.html" %}
{% block content %}
<div class="w-full max-w-md">
<div class="bg-white/70 dark:bg-neutral-900/70 backdrop-blur rounded-2xl shadow p-6 sm:p-8 border border-neutral-200 dark:border-neutral-800">
<h1 class="text-2xl font-semibold tracking-tight">Check your email</h1>
<p class="text-base text-stone-700 dark:text-stone-300 mt-3">
If an account exists for
<strong class="text-stone-900 dark:text-white">{{ email }}</strong>,
youll receive a link to sign in. It expires in 15 minutes.
</p>
{% if email_error %}
<div
class="mt-4 rounded-lg border border-red-300 bg-red-50 text-red-700 text-sm px-3 py-2 flex items-start gap-2"
role="alert"
>
<span class="font-medium">Heads up:</span>
<span>{{ email_error }}</span>
</div>
{% endif %}
<p class="mt-6 text-sm">
<a
href="{{ blog_url('/auth/login/') }}"
class="text-stone-600 dark:text-stone-300 hover:underline"
>
← Back
</a>
</p>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% import 'macros/links.html' as links %}
{% macro header_row(oob=False) %}
{% call links.menu_row(id='auth-row', oob=oob) %}
{% call links.link(account_url('/'), hx_select_search ) %}
<i class="fa-solid fa-user"></i>
<div>account</div>
{% endcall %}
{% call links.desktop_nav() %}
{% include "_types/auth/_nav.html" %}
{% endcall %}
{% endcall %}
{% endmacro %}

View File

@@ -0,0 +1,18 @@
{% extends "_types/root/_index.html" %}
{% block root_header_child %}
{% from '_types/root/_n/macros.html' import index_row with context %}
{% call index_row('auth-header-child', '_types/auth/header/_header.html') %}
{% block auth_header_child %}
{% endblock %}
{% endcall %}
{% endblock %}
{% block _main_mobile_menu %}
{% include "_types/auth/_nav.html" %}
{% endblock %}
{% block content %}
{% include '_types/auth/_main_panel.html' %}
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends oob.extends %}
{% block root_header_child %}
{% from '_types/root/_n/macros.html' import index_row with context %}
{% call index_row(oob.child_id, oob.header) %}
{% block auth_header_child %}
{% endblock %}
{% endcall %}
{% endblock %}
{% block _main_mobile_menu %}
{% include oob.nav %}
{% endblock %}
{% block content %}
{% include oob.main %}
{% endblock %}

View File

@@ -0,0 +1,46 @@
{% extends "_types/root/index.html" %}
{% block content %}
<div class="w-full max-w-md">
<div class="bg-white/70 dark:bg-neutral-900/70 backdrop-blur rounded-2xl shadow p-6 sm:p-8 border border-neutral-200 dark:border-neutral-800">
<h1 class="text-2xl font-semibold tracking-tight">Sign in</h1>
<p class="mt-2 text-sm text-neutral-600 dark:text-neutral-400">
Enter your email and well email you a one-time sign-in link.
</p>
{% if error %}
<div class="mt-4 rounded-lg border border-red-200 bg-red-50 text-red-800 dark:border-red-900/40 dark:bg-red-950/40 dark:text-red-200 px-4 py-3 text-sm">
{{ error }}
</div>
{% endif %}
<form
method="post" action="{{ blog_url('/auth/start/') }}"
class="mt-6 space-y-5"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div>
<label for="email" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300">
Email
</label>
<input
type="email"
id="email"
name="email"
value="{{ email or '' }}"
required
class="mt-2 block w-full rounded-lg border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-2 text-neutral-900 dark:text-neutral-100 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-0 focus:ring-neutral-900 dark:focus:ring-neutral-200"
autocomplete="email"
inputmode="email"
>
</div>
<button
type="submit"
class="inline-flex w-full items-center justify-center rounded-lg bg-neutral-900 px-4 py-2.5 text-sm font-medium text-white hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-neutral-900 disabled:opacity-50 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-white"
>
Send link
</button>
</form>
</div>
</div>
{% endblock %}