Compare commits

..

7 Commits

Author SHA1 Message Date
giles
5053448ee2 fix: remove existing bp dir before symlinking in Dockerfile
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 50s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 01:08:05 +00:00
giles
7b384bd335 feat: add markets and payments nav buttons, remove features panel
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 40s
- Add cross-app nav buttons for markets and payments (events app)
- Remove page features panel and markets panel from blog admin
  (calendars, markets, and payments are now managed in the events app)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 23:45:00 +00:00
giles
8fad1ecc7d fix: auto-create PageConfig when enabling page features
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 36s
When toggling market/calendar features or saving SumUp settings,
create a PageConfig row if one doesn't exist yet instead of returning
a 404 error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 23:34:18 +00:00
giles
af260872d1 fix: build editor in Docker and restore admin panel content
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 49s
The editor.js/editor.css were gitignored and never built during docker
build, causing 404s on the edit page. Add a Node multi-stage build step
to compile them. Also replace "nowt" placeholder in admin templates with
the actual main panel include.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 23:31:35 +00:00
giles
f90d0f2e21 chore: move repo to ~/rose-ash/ and add configurable CI paths
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 44s
REPO_DIR points to /root/rose-ash/blog, COOP_DIR to /root/coop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 22:05:41 +00:00
giles
218849552d chore: update shared_lib submodule to Phase 4
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 39s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 21:47:44 +00:00
giles
6bb520cb0a feat: show page cart badge in blog post header (Phase 4)
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 41s
Fetches page-scoped cart count from /internal/cart/summary?page_slug=
for page posts and displays a cart icon with count in the post header
that links to the page cart.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 21:45:41 +00:00
10 changed files with 65 additions and 20 deletions

View File

@@ -7,7 +7,8 @@ on:
env: env:
REGISTRY: registry.rose-ash.com:5000 REGISTRY: registry.rose-ash.com:5000
IMAGE: blog IMAGE: blog
REPO_DIR: /root/blog REPO_DIR: /root/rose-ash/blog
COOP_DIR: /root/coop
jobs: jobs:
build-and-deploy: build-and-deploy:
@@ -58,7 +59,7 @@ jobs:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
run: | run: |
ssh "root@$DEPLOY_HOST" " ssh "root@$DEPLOY_HOST" "
cd /root/coop cd ${{ env.COOP_DIR }}
source .env source .env
docker stack deploy -c docker-compose.yml coop docker stack deploy -c docker-compose.yml coop
echo 'Waiting for services to update...' echo 'Waiting for services to update...'

View File

@@ -1,5 +1,14 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
# ---------- Stage 1: Build editor JS/CSS ----------
FROM node:20-slim AS editor-build
WORKDIR /build
COPY shared_lib/editor/package.json shared_lib/editor/package-lock.json* ./
RUN npm ci --ignore-scripts 2>/dev/null || npm install
COPY shared_lib/editor/ ./
RUN NODE_ENV=production node build.mjs
# ---------- Stage 2: Python runtime ----------
FROM python:3.11-slim AS base FROM python:3.11-slim AS base
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
@@ -21,8 +30,11 @@ RUN pip install -r requirements.txt
COPY . . COPY . .
# Copy built editor assets from stage 1
COPY --from=editor-build /static/scripts/editor.js /static/scripts/editor.css shared_lib/static/scripts/
# Link app blueprints into the shared library's namespace # Link app blueprints into the shared library's namespace
RUN ln -s /app/bp /app/shared_lib/suma_browser/app/bp RUN rm -rf /app/shared_lib/suma_browser/app/bp && ln -s /app/bp /app/shared_lib/suma_browser/app/bp
# ---------- Runtime setup ---------- # ---------- Runtime setup ----------
COPY entrypoint.sh /usr/local/bin/entrypoint.sh COPY entrypoint.sh /usr/local/bin/entrypoint.sh

View File

@@ -74,12 +74,14 @@ def register():
post_id = post["id"] post_id = post["id"]
# Load PageConfig # Load or create PageConfig
pc = (await g.s.execute( pc = (await g.s.execute(
sa_select(PageConfig).where(PageConfig.post_id == post_id) sa_select(PageConfig).where(PageConfig.post_id == post_id)
)).scalar_one_or_none() )).scalar_one_or_none()
if pc is None: if pc is None:
return jsonify({"error": "PageConfig not found for this page."}), 404 pc = PageConfig(post_id=post_id, features={})
g.s.add(pc)
await g.s.flush()
# Parse request body # Parse request body
body = await request.get_json() body = await request.get_json()
@@ -139,7 +141,9 @@ def register():
sa_select(PageConfig).where(PageConfig.post_id == post_id) sa_select(PageConfig).where(PageConfig.post_id == post_id)
)).scalar_one_or_none() )).scalar_one_or_none()
if pc is None: if pc is None:
return jsonify({"error": "PageConfig not found for this page."}), 404 pc = PageConfig(post_id=post_id, features={})
g.s.add(pc)
await g.s.flush()
form = await request.form form = await request.form
merchant_code = (form.get("merchant_code") or "").strip() merchant_code = (form.get("merchant_code") or "").strip()

View File

@@ -65,6 +65,7 @@ def register():
p_data = getattr(g, "post_data", None) p_data = getattr(g, "post_data", None)
if p_data: if p_data:
from .services.entry_associations import get_associated_entries from .services.entry_associations import get_associated_entries
from shared.internal_api import get as api_get
db_post_id = (g.post_data.get("post") or {}).get("id") # <-- integer db_post_id = (g.post_data.get("post") or {}).get("id") # <-- integer
calendars = ( calendars = (
@@ -86,13 +87,30 @@ def register():
# Fetch associated entries for nav display # Fetch associated entries for nav display
associated_entries = await get_associated_entries(g.s, db_post_id) associated_entries = await get_associated_entries(g.s, db_post_id)
return { ctx = {
**p_data, **p_data,
"base_title": f"{config()['title']} {p_data['post']['title']}", "base_title": f"{config()['title']} {p_data['post']['title']}",
"calendars": calendars, "calendars": calendars,
"markets": markets, "markets": markets,
"associated_entries": associated_entries, "associated_entries": associated_entries,
} }
# Page cart badge: fetch page-scoped cart count for pages
post_dict = p_data.get("post") or {}
if post_dict.get("is_page"):
page_cart = await api_get(
"cart",
f"/internal/cart/summary?page_slug={post_dict['slug']}",
forward_session=True,
)
if page_cart:
ctx["page_cart_count"] = page_cart.get("count", 0) + page_cart.get("calendar_count", 0)
ctx["page_cart_total"] = page_cart.get("total", 0) + page_cart.get("calendar_total", 0)
else:
ctx["page_cart_count"] = 0
ctx["page_cart_total"] = 0
return ctx
else: else:
return {} return {}

View File

@@ -3,14 +3,5 @@
id="main-panel" id="main-panel"
class="flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport" class="flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"
> >
{% if post and post.is_page %}
<div class="max-w-lg mx-auto mt-6 px-4 space-y-6">
{% include "_types/post/admin/_features_panel.html" %}
{% if features.get('market') %}
{% include "_types/post/admin/_markets_panel.html" %}
{% endif %}
</div>
{% endif %}
<div class="pb-8"></div> <div class="pb-8"></div>
</section> </section>

View File

@@ -4,6 +4,16 @@
calendars calendars
</a> </a>
</div> </div>
<div class="relative nav-group">
<a href="{{ events_url('/' + post.slug + '/markets/') }}" class="{{styles.nav_button}}">
markets
</a>
</div>
<div class="relative nav-group">
<a href="{{ events_url('/' + post.slug + '/payments/') }}" class="{{styles.nav_button}}">
payments
</a>
</div>
{% call links.link(url_for('blog.post.admin.entries', slug=post.slug), hx_select_search, select_colours, True, aclass=styles.nav_button) %} {% call links.link(url_for('blog.post.admin.entries', slug=post.slug), hx_select_search, select_colours, True, aclass=styles.nav_button) %}
entries entries
{% endcall %} {% endcall %}

View File

@@ -18,5 +18,5 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
nowt {% include '_types/post/admin/_main_panel.html' %}
{% endblock %} {% endblock %}

View File

@@ -14,5 +14,5 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
nowt {% include '_types/post/admin/_main_panel.html' %}
{% endblock %} {% endblock %}

View File

@@ -13,6 +13,15 @@
</span> </span>
{% endcall %} {% endcall %}
{% call links.desktop_nav() %} {% call links.desktop_nav() %}
{% if page_cart_count is defined and page_cart_count > 0 %}
<a
href="{{ cart_url('/' + post.slug + '/') }}"
class="relative inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-full border border-emerald-300 bg-emerald-50 text-emerald-800 hover:bg-emerald-100 transition"
>
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
<span>{{ page_cart_count }}</span>
</a>
{% endif %}
{% include '_types/post/_nav.html' %} {% include '_types/post/_nav.html' %}
{% endcall %} {% endcall %}
{% endcall %} {% endcall %}