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 (

OPENBUREAU CMS

setEmail(e.target.value)} autoFocus /> setPassword(e.target.value)} /> {err &&

{err}

}
); } 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 (