import { useEffect, useState } from 'react';
import { supabase } from './supabase.js';
import { api } from './api.js';
const SECTIONS = ['buerofuehrung', 'software', 'theorie'];
const LAYOUTS = ['', 'text', 'image'];
const EMPTY = {
section: 'software',
slug: '',
title: '',
date: new Date().toISOString().slice(0, 10),
weight: '',
tags: '',
summary: '',
cover_image: '',
layout: 'text',
external: '',
color: '',
body: '',
};
export default function App() {
const [session, setSession] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
supabase.auth.getSession().then(({ data }) => {
setSession(data.session);
setLoading(false);
});
const { data: sub } = supabase.auth.onAuthStateChange((_e, s) => setSession(s));
return () => sub.subscription.unsubscribe();
}, []);
if (loading) return
…
;
if (!session) return ;
return ;
}
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [err, setErr] = useState(null);
async function submit(e) {
e.preventDefault();
setErr(null);
const { error } = await supabase.auth.signInWithPassword({ email, password });
if (error) setErr(error.message);
}
return (
);
}
function Dashboard({ email }) {
const [posts, setPosts] = useState([]);
const [current, setCurrent] = useState(null); // post-Objekt oder null
const [msg, setMsg] = useState(null);
async function refresh() {
try {
setPosts(await api.listPosts());
} catch (e) {
setMsg({ type: 'err', text: e.message });
}
}
useEffect(() => { refresh(); }, []);
return (
OPENBUREAU CMS
{email}
{current
? { setCurrent(fromRow(row)); refresh(); }}
onMsg={setMsg} />
: Wähle links einen Post oder leg einen neuen an.
}
{msg &&
setMsg(null)}>{msg.text}
}
);
}
function Editor({ initial, onSaved, onMsg }) {
const [post, setPost] = useState(initial);
const [previewUrl, setPreviewUrl] = useState(null);
const [busy, setBusy] = useState(false);
const set = (k) => (e) => setPost({ ...post, [k]: e.target.value });
async function uploadCover(e) {
const file = e.target.files?.[0];
if (!file) return;
setBusy(true);
try {
const res = await api.upload(file);
setPost((p) => ({ ...p, cover_image: res.url }));
onMsg({ type: 'ok', text: `Hochgeladen: ${res.url}` });
} catch (err) {
onMsg({ type: 'err', text: err.message });
} finally {
setBusy(false);
}
}
async function save() {
setBusy(true);
try {
const payload = toRow(post);
const row = post.id
? await api.updatePost(post.id, payload)
: await api.createPost(payload);
onSaved(row);
onMsg({ type: 'ok', text: 'Gespeichert.' });
return row;
} catch (e) {
onMsg({ type: 'err', text: e.message });
} finally {
setBusy(false);
}
}
async function preview() {
const row = await save();
if (!row) return;
setBusy(true);
try {
const res = await api.preview(row.id);
// Cache-Buster, damit der frische Build geladen wird.
setPreviewUrl(`${res.url}?t=${Date.now()}`);
} catch (e) {
onMsg({ type: 'err', text: e.message });
} finally {
setBusy(false);
}
}
async function publish() {
const row = await save();
if (!row) return;
if (!confirm('Diesen Post live publizieren?')) return;
setBusy(true);
try {
const res = await api.publish(row.id);
onMsg({ type: 'ok', text: `Live: ${res.url}` });
} catch (e) {
onMsg({ type: 'err', text: e.message });
} finally {
setBusy(false);
}
}
return (
{previewUrl
?
:
Vorschau erscheint hier nach Klick auf „Vorschau".
}
);
}
// --- DB-Zeile <-> Formular ---
function fromRow(row) {
return {
...EMPTY, ...row,
weight: row.weight ?? '',
tags: Array.isArray(row.tags) ? row.tags.join(', ') : '',
date: row.date ? String(row.date).slice(0, 10) : EMPTY.date,
};
}
function toRow(post) {
return {
section: post.section,
slug: post.slug,
title: post.title,
date: post.date,
weight: post.weight === '' ? null : Number(post.weight),
tags: post.tags ? post.tags.split(',').map((t) => t.trim()).filter(Boolean) : [],
summary: post.summary || null,
cover_image: post.cover_image || null,
layout: post.layout || null,
external: post.external || null,
color: post.color || null,
body: post.body || '',
};
}