Jul 22 - Initial code

This commit is contained in:
2026-07-22 14:50:16 -04:00
commit ade7c5b33c
21 changed files with 1588 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Admin{% endblock %} · JQC</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,700&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/admin.css') }}">
</head>
<body class="{% block body_class %}{% endblock %}">
{% if session.get('admin') %}
<nav class="anav">
<a class="anav__brand" href="{{ url_for('admin.dashboard') }}">JQC<span>admin</span></a>
<div class="anav__right">
<a class="anav__link" href="{{ url_for('index') }}" target="_blank" rel="noopener">View site ↗</a>
<span class="anav__user">{{ session.get('admin') }}</span>
<form method="post" action="{{ url_for('admin.logout') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn--ghost btn--sm" type="submit">Sign out</button>
</form>
</div>
</nav>
{% endif %}
<main class="awrap">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flashes">
{% for cat, msg in messages %}
<div class="flash flash--{{ cat }}">{{ msg }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
</body>
</html>
+64
View File
@@ -0,0 +1,64 @@
{% extends "admin/base.html" %}
{% block title %}Dashboard{% endblock %}
{% block content %}
<header class="page-head">
<div>
<h1>Content</h1>
<p class="muted">Edit the sections and topics shown on the public site.</p>
</div>
<div class="page-head__actions">
<a class="btn btn--ghost" href="{{ url_for('admin.section_form') }}">+ Section</a>
<a class="btn btn--primary" href="{{ url_for('admin.topic_form') }}">+ Topic</a>
</div>
</header>
{% if not sections %}
<div class="empty">No sections yet. Start by adding one.</div>
{% endif %}
{% for section in sections %}
<section class="sec-card">
<div class="sec-card__head">
<div class="sec-card__meta">
<span class="pill">§{{ '%02d' % section.num }}</span>
<h2>{{ section.title }}</h2>
{% if section.subtitle %}<span class="muted">{{ section.subtitle }}</span>{% endif %}
</div>
<div class="sec-card__actions">
<a class="btn btn--ghost btn--sm" href="{{ url_for('admin.section_form', section_id=section.id) }}">Edit</a>
<a class="btn btn--ghost btn--sm" href="{{ url_for('admin.topic_form') }}?section={{ section.id }}">+ Topic</a>
<form method="post" action="{{ url_for('admin.section_delete', section_id=section.id) }}"
onsubmit="return confirm('Delete section “{{ section.title }}” and ALL its topics?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn--danger btn--sm" type="submit">Delete</button>
</form>
</div>
</div>
{% if section.topics %}
<ul class="topic-rows">
{% for topic in section.topics %}
<li class="topic-row">
<div class="topic-row__main">
<span class="topic-row__order">{{ topic.sort_order }}</span>
<a class="topic-row__title" href="{{ url_for('admin.topic_form', topic_id=topic.id) }}">{{ topic.title | safe }}</a>
{% if topic.media_type and topic.media_type != 'none' %}<span class="chip">{{ topic.media_type }}</span>{% endif %}
{% if topic.link_url %}<span class="chip chip--link">link</span>{% endif %}
</div>
<div class="topic-row__actions">
<a class="btn btn--ghost btn--sm" href="{{ url_for('admin.topic_form', topic_id=topic.id) }}">Edit</a>
<form method="post" action="{{ url_for('admin.topic_delete', topic_id=topic.id) }}"
onsubmit="return confirm('Delete topic “{{ topic.title }}”?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn--danger btn--sm" type="submit">Delete</button>
</form>
</div>
</li>
{% endfor %}
</ul>
{% else %}
<p class="muted sec-card__empty">No topics in this section yet.</p>
{% endif %}
</section>
{% endfor %}
{% endblock %}
+21
View File
@@ -0,0 +1,21 @@
{% extends "admin/base.html" %}
{% block title %}Sign in{% endblock %}
{% block body_class %}login-body{% endblock %}
{% block content %}
<div class="login-card">
<div class="login-brand">JQC<span>admin</span></div>
<p class="login-hint">Sign in to edit site content.</p>
<form method="post" action="{{ url_for('admin.login') }}" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label class="field">
<span>Username</span>
<input type="text" name="username" autocomplete="username" autofocus required>
</label>
<label class="field">
<span>Password</span>
<input type="password" name="password" autocomplete="current-password" required>
</label>
<button class="btn btn--primary" type="submit">Sign in</button>
</form>
</div>
{% endblock %}
+49
View File
@@ -0,0 +1,49 @@
{% extends "admin/base.html" %}
{% block title %}{{ 'Edit section' if section else 'New section' }}{% endblock %}
{% macro val(field, default='') -%}
{%- if form is not none -%}{{ form.get(field, default) }}
{%- elif section is not none -%}{{ section[field] if section[field] is not none else default }}
{%- else -%}{{ default }}{%- endif -%}
{%- endmacro %}
{% block content %}
<header class="page-head">
<div>
<h1>{{ 'Edit section' if section else 'New section' }}</h1>
<p class="muted">{{ section.title if section else 'Sections group topics on the public page.' }}</p>
</div>
<a class="btn btn--ghost" href="{{ url_for('admin.dashboard') }}">← Back</a>
</header>
<form method="post" class="form-card stack"
action="{{ url_for('admin.section_form', section_id=section.id) if section else url_for('admin.section_form') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="grid-2">
<label class="field">
<span>Number * <small class="muted">(shown as §NN, must be unique)</small></span>
<input type="number" name="num" value="{{ val('num') }}" required>
</label>
<label class="field">
<span>Sort order <small class="muted">(defaults to number × 10)</small></span>
<input type="number" name="sort_order" value="{{ val('sort_order') }}" placeholder="auto">
</label>
</div>
<label class="field">
<span>Title *</span>
<input type="text" name="title" value="{{ val('title') }}" required>
</label>
<label class="field">
<span>Subtitle</span>
<input type="text" name="subtitle" value="{{ val('subtitle') }}">
</label>
<div class="form-actions">
<a class="btn btn--ghost" href="{{ url_for('admin.dashboard') }}">Cancel</a>
<button class="btn btn--primary" type="submit">Save section</button>
</div>
</form>
{% endblock %}
+101
View File
@@ -0,0 +1,101 @@
{% extends "admin/base.html" %}
{% block title %}{{ 'Edit topic' if topic else 'New topic' }}{% endblock %}
{% macro val(field, default='') -%}
{%- if form is not none -%}{{ form.get(field, default) }}
{%- elif topic is not none -%}{{ topic[field] if topic[field] is not none else default }}
{%- else -%}{{ default }}{%- endif -%}
{%- endmacro %}
{% block content %}
{% set current_section = (form.get('section_id') if form else (topic.section_id if topic else request.args.get('section'))) %}
{% set current_media = (form.get('media_type') if form else (topic.media_type if topic else 'none')) %}
<header class="page-head">
<div>
<h1>{{ 'Edit topic' if topic else 'New topic' }}</h1>
<p class="muted">{{ topic.title if topic else 'Add a new topic to a section.' }}</p>
</div>
<a class="btn btn--ghost" href="{{ url_for('admin.dashboard') }}">← Back</a>
</header>
<form method="post" class="form-card stack"
action="{{ url_for('admin.topic_form', topic_id=topic.id) if topic else url_for('admin.topic_form') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="grid-2">
<label class="field">
<span>Section *</span>
<select name="section_id" required>
{% for s in sections %}
<option value="{{ s.id }}" {{ 'selected' if current_section and s.id|string == current_section|string }}>
§{{ '%02d' % s.num }} — {{ s.title }}
</option>
{% endfor %}
</select>
</label>
<label class="field">
<span>Sort order</span>
<input type="number" name="sort_order" value="{{ val('sort_order', '10') }}">
</label>
</div>
<label class="field">
<span>Title *</span>
<input type="text" name="title" value="{{ val('title') }}" required>
</label>
<label class="field">
<span>Slug <small class="muted">(optional — auto-generated from the title, must be unique)</small></span>
<input type="text" name="slug" value="{{ val('slug') }}" placeholder="auto">
</label>
<label class="field">
<span>Body <small class="muted">(HTML — use &lt;p&gt;, &lt;strong&gt;, &lt;ul&gt;&lt;li&gt;)</small></span>
<textarea name="body_html" rows="7">{{ val('body_html') }}</textarea>
</label>
<fieldset class="fieldset">
<legend>Media</legend>
<div class="grid-2">
<label class="field">
<span>Type</span>
<select name="media_type">
{% for mt in media_types %}
<option value="{{ mt }}" {{ 'selected' if mt == current_media }}>{{ mt }}</option>
{% endfor %}
</select>
</label>
<label class="field">
<span>Caption</span>
<input type="text" name="media_caption" value="{{ val('media_caption') }}">
</label>
</div>
<label class="field">
<span>Media URL</span>
<input type="text" name="media_url" value="{{ val('media_url') }}"
placeholder="/static/img/photo.jpg · /static/vid/demo.mp4 · https://www.youtube.com/embed/ID">
</label>
<p class="hint muted">image → &lt;img&gt; · video → &lt;video&gt; · embed → &lt;iframe&gt; (use a YouTube <code>/embed/</code> URL)</p>
</fieldset>
<fieldset class="fieldset">
<legend>Link button (optional)</legend>
<div class="grid-2">
<label class="field">
<span>Link URL</span>
<input type="text" name="link_url" value="{{ val('link_url') }}" placeholder="https://…">
</label>
<label class="field">
<span>Link label</span>
<input type="text" name="link_label" value="{{ val('link_label') }}" placeholder="Learn more">
</label>
</div>
</fieldset>
<div class="form-actions">
<a class="btn btn--ghost" href="{{ url_for('admin.dashboard') }}">Cancel</a>
<button class="btn btn--primary" type="submit">Save topic</button>
</div>
</form>
{% endblock %}