club-builder/admin_ui/index.html

253 lines
6.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<title>Club Builder Admin</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #111827;
color: #e5e7eb;
display: flex;
height: 100vh;
overflow: hidden;
}
.sidebar {
width: 220px;
border-right: 1px solid #1f2937;
background: #020617;
padding: 0.75rem;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.sidebar h1 {
font-size: 1rem;
margin: 0 0 0.5rem 0;
color: #f9fafb;
}
.page-list {
list-style: none;
padding: 0;
margin: 0;
flex: 1;
overflow-y: auto;
}
.page-list li {
margin-bottom: 0.25rem;
}
.page-btn {
width: 100%;
text-align: left;
background: #0f172a;
color: #e5e7eb;
border: 1px solid transparent;
border-radius: 0.4rem;
padding: 0.35rem 0.5rem;
font-size: 0.85rem;
cursor: pointer;
}
.page-btn.active {
background: #111827;
border-color: #4b5563;
}
.main {
flex: 1;
display: flex;
flex-direction: row;
min-width: 0;
}
.editor {
flex: 1;
display: flex;
flex-direction: column;
padding: 0.75rem;
box-sizing: border-box;
border-right: 1px solid #1f2937;
min-width: 0;
}
.editor-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.editor-header span {
font-size: 0.9rem;
color: #9ca3af;
}
.editor textarea {
flex: 1;
width: 100%;
resize: none;
border-radius: 0.4rem;
border: 1px solid #374151;
background: #020617;
color: #e5e7eb;
padding: 0.5rem;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.8rem;
box-sizing: border-box;
}
.editor button {
padding: 0.25rem 0.7rem;
border-radius: 999px;
border: none;
cursor: pointer;
font-size: 0.8rem;
}
.btn-primary {
background: #10b981;
color: #022c22;
}
.btn-primary:disabled {
opacity: 0.5;
cursor: default;
}
.status {
font-size: 0.75rem;
color: #9ca3af;
margin-left: auto;
}
.preview {
width: 45%;
min-width: 320px;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.preview-header {
padding: 0.5rem 0.75rem;
border-bottom: 1px solid #1f2937;
font-size: 0.85rem;
color: #9ca3af;
background: #020617;
}
.preview iframe {
flex: 1;
width: 100%;
border: none;
background: #000;
}
</style>
</head>
<body>
<aside class="sidebar">
<h1>Pages</h1>
<ul class="page-list" id="page-list"></ul>
</aside>
<main class="main">
<section class="editor">
<div class="editor-header">
<span id="editor-title">No page selected</span>
<button class="btn-primary" id="save-btn" disabled>Save</button>
<span class="status" id="status"></span>
</div>
<textarea id="json-editor" placeholder="Select a page from the left…"></textarea>
</section>
<section class="preview">
<div class="preview-header">
Preview: <span id="preview-label"></span>
</div>
<iframe id="preview-frame" src="about:blank"></iframe>
</section>
</main>
<script>
// Same origin: /api/... and /preview/... on builder-preview.clubdaguerre.de
const API_BASE = '';
const pageListEl = document.getElementById('page-list');
const editorTitleEl = document.getElementById('editor-title');
const previewLabelEl = document.getElementById('preview-label');
const previewFrameEl = document.getElementById('preview-frame');
const jsonEditorEl = document.getElementById('json-editor');
const saveBtnEl = document.getElementById('save-btn');
const statusEl = document.getElementById('status');
let currentSlug = null;
async function loadPages() {
const res = await fetch(API_BASE + '/api/pages');
const pages = await res.json();
pageListEl.innerHTML = '';
pages.forEach(page => {
const li = document.createElement('li');
const btn = document.createElement('button');
btn.className = 'page-btn';
btn.textContent = page.title || page.slug || '(untitled)';
btn.dataset.slug = page.slug;
btn.onclick = () => selectPage(page.slug, btn);
li.appendChild(btn);
pageListEl.appendChild(li);
});
}
async function selectPage(slug, buttonEl) {
currentSlug = slug;
statusEl.textContent = 'Loading…';
saveBtnEl.disabled = true;
document.querySelectorAll('.page-btn').forEach(b => b.classList.remove('active'));
if (buttonEl) buttonEl.classList.add('active');
const res = await fetch(API_BASE + '/api/pages/' + encodeURIComponent(slug));
const data = await res.json();
editorTitleEl.textContent = slug;
previewLabelEl.textContent = slug;
jsonEditorEl.value = JSON.stringify(data, null, 2);
saveBtnEl.disabled = false;
statusEl.textContent = 'Loaded';
previewFrameEl.src = API_BASE + '/preview/' + encodeURIComponent(slug) + '/';
}
async function saveCurrent() {
if (!currentSlug) return;
let parsed;
try {
parsed = JSON.parse(jsonEditorEl.value);
} catch (e) {
alert('JSON is invalid: ' + e.message);
return;
}
saveBtnEl.disabled = true;
statusEl.textContent = 'Saving…';
const res = await fetch(API_BASE + '/api/pages/' + encodeURIComponent(currentSlug), {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(parsed),
});
if (!res.ok) {
statusEl.textContent = 'Error: ' + res.status;
saveBtnEl.disabled = false;
return;
}
statusEl.textContent = 'Saved';
saveBtnEl.disabled = false;
// refresh preview
previewFrameEl.src = API_BASE + '/preview/' + encodeURIComponent(currentSlug) + '/';
}
jsonEditorEl.addEventListener('input', () => {
if (!currentSlug) return;
statusEl.textContent = 'Changed (not saved)';
});
saveBtnEl.addEventListener('click', saveCurrent);
loadPages().catch(err => {
console.error(err);
statusEl.textContent = 'Error loading pages';
});
</script>
</body>
</html>