Files
intotheeast-com/services/travel-memories/app/templates/phase5.html
T
m038 fade38e7a0 fix: enforce write phase completion gate and wire done endpoint
- GET /write now checks all groups are written/skipped before showing
  the completion screen; incomplete sessions are redirected to the first
  draft group
- POST /write/done now accepts form data (not JSON) and redirects to
  /export; wired up from the completion screen via a <form> POST button
- phase5.html extra_scripts block wrapped in {% if group %} to prevent
  Jinja errors when group is None on the completion screen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 16:58:03 +02:00

203 lines
7.7 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="p-4 max-w-6xl mx-auto">
<div class="flex items-center justify-between mb-4">
<h1 class="text-xl font-bold">Write</h1>
<span class="text-sm opacity-60">{{ done_count }} / {{ total }} done</span>
</div>
{% if not group %}
<div class="alert alert-success mb-4">All groups written or skipped.</div>
<form method="post" action="/write/done">
<input type="hidden" name="album_id" value="{{ album_id }}">
<button type="submit" class="btn btn-primary">Export →</button>
</form>
{% else %}
<div class="flex gap-4">
<!-- Photos panel -->
<div class="group-photos w-64 flex-shrink-0 space-y-2 overflow-y-auto max-h-[80vh]">
{% for photo in photos %}
<img src="/proxy/thumb/{{ photo.id }}"
id="photo-{{ photo.id }}"
class="w-full rounded cursor-pointer border-4 border-transparent transition"
onclick="setHero('{{ photo.id }}')"
alt="">
{% endfor %}
</div>
<!-- Form -->
<div class="flex-1 space-y-4">
<!-- Mode switch -->
<div class="tabs">
<button id="mode-journal" class="tab tab-bordered tab-active"
onclick="setMode('journal')">Journal</button>
<button id="mode-story" class="tab tab-bordered"
onclick="setMode('story')">Story</button>
</div>
<div class="form-control">
<label class="label text-sm">Title</label>
<input id="title-field" type="text" class="input input-bordered"
oninput="scheduleAutosave()"
value="{{ group.title | e }}">
</div>
<div class="form-control">
<label class="label text-sm">Date</label>
<input id="date-field" type="text" class="input input-bordered input-sm"
oninput="scheduleAutosave()"
value="{{ group.date | e }}">
</div>
<div class="grid grid-cols-2 gap-2">
<div class="form-control">
<label class="label text-sm">City</label>
<input id="city-field" type="text" class="input input-bordered input-sm"
oninput="scheduleAutosave()"
value="{{ group.location_city | e }}">
</div>
<div class="form-control">
<label class="label text-sm">Country</label>
<input id="country-field" type="text" class="input input-bordered input-sm"
oninput="scheduleAutosave()"
value="{{ group.location_country | e }}">
</div>
</div>
<div class="form-control" id="mode-journal-fields">
<label class="label text-sm">Body</label>
<textarea id="body-field" class="textarea textarea-bordered h-40"
oninput="scheduleAutosave()">{{ group.body | e }}</textarea>
</div>
<!-- Story-only fields (hidden by default if mode is journal) -->
<div id="hero-picker" class="form-control" style="display:{% if group.entry_type == 'story' %}block{% else %}none{% endif %}">
<label class="label text-sm">Hero photo: <span id="hero-label">{{ group.hero_photo_id or 'none' }}</span></label>
<p class="text-xs opacity-60">Click a photo on the left to set it as the hero.</p>
</div>
<div id="shortcode-field-wrap" class="form-control" style="display:{% if group.entry_type == 'story' %}block{% else %}none{% endif %}">
<label class="label text-sm">Shortcode hints</label>
<input id="shortcode-field" type="text" class="input input-bordered input-sm"
oninput="scheduleAutosave()"
placeholder="e.g. gallery block, pull quote"
value="{{ group.shortcode_hints | e }}">
</div>
<div class="flex gap-2 mt-4">
{% if group_idx > 0 %}
<a href="/write?album_id={{ album_id }}&group_idx={{ group_idx - 1 }}" class="btn btn-ghost btn-sm">← Prev</a>
{% endif %}
<button id="skip-btn" class="btn btn-ghost btn-sm" onclick="skipGroup()">Skip for now</button>
<button class="btn btn-primary btn-sm ml-auto" onclick="saveAndNext()">Save &amp; next</button>
</div>
</div>
<!-- Inline notes -->
<div id="inline-notes" class="w-64 flex-shrink-0 bg-base-100 rounded p-3">
<h3 class="font-semibold text-sm mb-2">Your notes</h3>
<p class="text-xs opacity-70 whitespace-pre-wrap">{{ state.notes or 'No notes yet.' }}</p>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_scripts %}
{% if group %}
<script>
(function() {
var albumId = {{ album_id | tojson }};
var groupId = {{ group.id | tojson }};
var mode = {{ group.entry_type | tojson }};
var heroId = {{ group.hero_photo_id | tojson }};
var autosaveTimer = null;
window.setMode = function(m) {
mode = m;
var storyFields = ['hero-picker', 'shortcode-field-wrap'];
storyFields.forEach(function(id) {
var el = document.getElementById(id);
if (el) el.style.display = (m === 'story') ? 'block' : 'none';
});
document.getElementById('mode-journal').classList.toggle('tab-active', m === 'journal');
document.getElementById('mode-story').classList.toggle('tab-active', m === 'story');
scheduleAutosave();
};
window.setHero = function(id) {
heroId = id;
// Update border highlight
document.querySelectorAll('.group-photos img').forEach(function(img) {
img.classList.remove('border-primary');
img.classList.add('border-transparent');
});
var el = document.getElementById('photo-' + id);
if (el) { el.classList.remove('border-transparent'); el.classList.add('border-primary'); }
var label = document.getElementById('hero-label');
if (label) label.textContent = id;
scheduleAutosave();
};
window.scheduleAutosave = function() {
clearTimeout(autosaveTimer);
autosaveTimer = setTimeout(doAutosave, 500);
};
function getFormData() {
return {
album_id: albumId,
group_id: groupId,
entry_type: mode,
hero_photo_id: heroId,
title: document.getElementById('title-field') ? document.getElementById('title-field').value : '',
body: document.getElementById('body-field') ? document.getElementById('body-field').value : '',
location_city: document.getElementById('city-field') ? document.getElementById('city-field').value : '',
location_country: document.getElementById('country-field') ? document.getElementById('country-field').value : '',
date: document.getElementById('date-field') ? document.getElementById('date-field').value : '',
shortcode_hints: document.getElementById('shortcode-field') ? document.getElementById('shortcode-field').value : '',
};
}
function doAutosave() {
fetch('/write/autosave', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(getFormData()),
});
}
window.skipGroup = function() {
fetch('/write/skip', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({album_id: albumId, group_id: groupId}),
}).then(function() {
window.location.reload();
});
};
window.saveAndNext = function() {
fetch('/write/save', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(getFormData()),
}).then(function() {
var url = new URL(window.location.href);
var idx = parseInt(url.searchParams.get('group_idx') || '0');
url.searchParams.set('group_idx', idx + 1);
window.location.href = url.toString();
});
};
// Initialize mode display
if (mode === 'story') {
document.getElementById('mode-story') && document.getElementById('mode-story').classList.add('tab-active');
document.getElementById('mode-journal') && document.getElementById('mode-journal').classList.remove('tab-active');
}
})();
</script>
{% endif %}
{% endblock %}