feat: add markets and payments management pages
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 45s

- New markets blueprint at /<slug>/markets/ with create/delete
- New payments blueprint at /<slug>/payments/ with SumUp config
- Register both in events app with context processor for markets
- Remove PageConfig feature flag check from calendar creation
  (feature toggles replaced by direct management pages)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-10 23:45:07 +00:00
parent 0255c937dd
commit e0679f8100
20 changed files with 502 additions and 8 deletions

View File

@@ -0,0 +1,25 @@
<section class="p-4">
{% if has_access('markets.create_market') %}
<div id="market-create-errors" class="mt-2 text-sm text-red-600"></div>
<form
class="mt-4 flex gap-2 items-end"
hx-post="{{ url_for('markets.create_market') }}"
hx-target="#markets-list"
hx-select="#markets-list"
hx-swap="outerHTML"
hx-on::before-request="document.querySelector('#market-create-errors').textContent='';"
hx-on::response-error="document.querySelector('#market-create-errors').innerHTML = event.detail.xhr.responseText;"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="flex-1">
<label class="block text-sm text-gray-600">Name</label>
<input name="name" type="text" required class="w-full border rounded px-3 py-2" placeholder="e.g. Farm Shop, Bakery" />
</div>
<button type="submit" class="border rounded px-3 py-2">Add market</button>
</form>
{% endif %}
<div id="markets-list" class="mt-6">
{% include "_types/markets/_markets_list.html" %}
</div>
</section>

View File

@@ -0,0 +1,37 @@
{% for m in markets %}
<div class="mt-6 border rounded-lg p-4">
<div class="flex items-center justify-between gap-3">
{% set market_href = market_url('/' + post.slug + '/' + m.slug + '/') %}
<a
class="flex items-baseline gap-3"
href="{{ market_href }}"
>
<h3 class="font-semibold">{{ m.name }}</h3>
<h4 class="text-gray-500">/{{ m.slug }}/</h4>
</a>
<button
class="text-sm border rounded px-3 py-1 hover:bg-red-50 hover:border-red-400"
data-confirm
data-confirm-title="Delete market?"
data-confirm-text="Products will be hidden (soft delete)"
data-confirm-icon="warning"
data-confirm-confirm-text="Yes, delete it"
data-confirm-cancel-text="Cancel"
data-confirm-event="confirmed"
hx-delete="{{ url_for('markets.delete_market', market_slug=m.slug) }}"
hx-trigger="confirmed"
hx-target="#markets-list"
hx-select="#markets-list"
hx-swap="outerHTML"
hx-headers='{"X-CSRFToken":"{{ csrf_token() }}"}'
>
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
{% else %}
<p class="text-gray-500 mt-4">No markets yet. Create one above.</p>
{% endfor %}

View File

@@ -0,0 +1,2 @@
{% from 'macros/admin_nav.html' import placeholder_nav %}
{{ placeholder_nav() }}

View File

@@ -0,0 +1,19 @@
{% extends 'oob_elements.html' %}
{% from '_types/root/_oob_menu.html' import mobile_menu with context %}
{% block oobs %}
{% from '_types/root/_n/macros.html' import oob_header with context %}
{{oob_header('post-admin-header-child', 'markets-header-child', '_types/markets/header/_header.html')}}
{% from '_types/post/admin/header/_header.html' import header_row with context %}
{{ header_row(oob=True) }}
{% endblock %}
{% block mobile_menu %}
{% include '_types/markets/_nav.html' %}
{% endblock %}
{% block content %}
{% include "_types/markets/_main_panel.html" %}
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% import 'macros/links.html' as links %}
{% macro header_row(oob=False) %}
{% call links.menu_row(id='markets-row', oob=oob) %}
{% call links.link(url_for('markets.home'), hx_select_search) %}
<i class="fa fa-shopping-bag" aria-hidden="true"></i>
<div>
Markets
</div>
{% endcall %}
{% call links.desktop_nav() %}
{% include '_types/markets/_nav.html' %}
{% endcall %}
{% endcall %}
{% endmacro %}

View File

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

View File

@@ -0,0 +1,70 @@
<section class="p-4 max-w-lg mx-auto">
<div id="payments-panel" class="space-y-4 p-4 bg-white rounded-lg border border-stone-200">
<h3 class="text-lg font-semibold text-stone-800">
<i class="fa fa-credit-card text-purple-600 mr-1"></i>
SumUp Payment
</h3>
<p class="text-xs text-stone-400">
Configure per-page SumUp credentials. Leave blank to use the global merchant account.
</p>
<form
hx-put="{{ url_for('payments.update_sumup') }}"
hx-target="#payments-panel"
hx-swap="outerHTML"
hx-select="#payments-panel"
class="space-y-3"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div>
<label class="block text-xs font-medium text-stone-600 mb-1">Merchant Code</label>
<input
type="text"
name="merchant_code"
value="{{ sumup_merchant_code }}"
placeholder="e.g. ME4J6100"
class="w-full px-3 py-1.5 text-sm border border-stone-300 rounded focus:ring-purple-500 focus:border-purple-500"
>
</div>
<div>
<label class="block text-xs font-medium text-stone-600 mb-1">API Key</label>
<input
type="password"
name="api_key"
value=""
placeholder="{{ '--------' if sumup_configured else 'sup_sk_...' }}"
class="w-full px-3 py-1.5 text-sm border border-stone-300 rounded focus:ring-purple-500 focus:border-purple-500"
>
{% if sumup_configured %}
<p class="text-xs text-stone-400 mt-0.5">Key is set. Leave blank to keep current key.</p>
{% endif %}
</div>
<div>
<label class="block text-xs font-medium text-stone-600 mb-1">Checkout Reference Prefix</label>
<input
type="text"
name="checkout_prefix"
value="{{ sumup_checkout_prefix }}"
placeholder="e.g. ROSE-"
class="w-full px-3 py-1.5 text-sm border border-stone-300 rounded focus:ring-purple-500 focus:border-purple-500"
>
</div>
<button
type="submit"
class="px-4 py-1.5 text-sm font-medium text-white bg-purple-600 rounded hover:bg-purple-700 focus:ring-2 focus:ring-purple-500"
>
Save SumUp Settings
</button>
{% if sumup_configured %}
<span class="ml-2 text-xs text-green-600">
<i class="fa fa-check-circle"></i> Connected
</span>
{% endif %}
</form>
</div>
</section>

View File

@@ -0,0 +1,2 @@
{% from 'macros/admin_nav.html' import placeholder_nav %}
{{ placeholder_nav() }}

View File

@@ -0,0 +1,19 @@
{% extends 'oob_elements.html' %}
{% from '_types/root/_oob_menu.html' import mobile_menu with context %}
{% block oobs %}
{% from '_types/root/_n/macros.html' import oob_header with context %}
{{oob_header('post-admin-header-child', 'payments-header-child', '_types/payments/header/_header.html')}}
{% from '_types/post/admin/header/_header.html' import header_row with context %}
{{ header_row(oob=True) }}
{% endblock %}
{% block mobile_menu %}
{% include '_types/payments/_nav.html' %}
{% endblock %}
{% block content %}
{% include "_types/payments/_main_panel.html" %}
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% import 'macros/links.html' as links %}
{% macro header_row(oob=False) %}
{% call links.menu_row(id='payments-row', oob=oob) %}
{% call links.link(url_for('payments.home'), hx_select_search) %}
<i class="fa fa-credit-card" aria-hidden="true"></i>
<div>
Payments
</div>
{% endcall %}
{% call links.desktop_nav() %}
{% include '_types/payments/_nav.html' %}
{% endcall %}
{% endcall %}
{% endmacro %}

View File

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