/* ZEN SUSHI — Painel de blog funcional (/admin).
Login simples → gerenciar artigos (criar, editar, excluir) com upload
de imagem. Posts salvos no navegador (localStorage) via window.KZ_BLOG. */
const { Button: AB, Badge: ABd, Card: ACd, Eyebrow: AEy, KatanaDivider: AKD } = window.KAZUKIDesignSystem_63706c;
const adminSeo = window.KZ_SEO;
const BLOG = window.KZ_BLOG;
const CATEGORIAS = ["Novidades", "Cozinha", "Vinhos", "Confeitaria", "Experiências", "Promoções"];
const AUTH_KEY = "zenSushiAdminAuth";
/* Redimensiona a imagem enviada para no máx. 1280px e exporta JPEG (q .82),
mantendo o localStorage leve. Retorna data URL (base64). */
function fileToOptimizedDataURL(file, maxW) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const img = new Image();
img.onload = () => {
const scale = Math.min(1, maxW / img.width);
const w = Math.round(img.width * scale);
const h = Math.round(img.height * scale);
const c = document.createElement("canvas");
c.width = w; c.height = h;
c.getContext("2d").drawImage(img, 0, 0, w, h);
try { resolve(c.toDataURL("image/jpeg", 0.82)); }
catch (e) { resolve(reader.result); }
};
img.onerror = reject;
img.src = reader.result;
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
function emptyDraft() {
return {
titulo: "", resumo: "", autor: "Equipe ZEN SUSHI",
categoria: "Novidades", data: new Date().toISOString().slice(0, 10),
img: "", imgAlt: "",
topicos: [{ h2: "", p: "" }],
dicas: { h3: "", p: "" },
cta: "Reserve sua mesa no ZEN SUSHI",
slug: "",
};
}
function AdminPage() {
const [authed, setAuthed] = React.useState(() => sessionStorage.getItem(AUTH_KEY) === "1");
const [user, setUser] = React.useState("");
const [pass, setPass] = React.useState("");
const [err, setErr] = React.useState(false);
const [mode, setMode] = React.useState("list"); // list | edit
const [draft, setDraft] = React.useState(emptyDraft());
const [originalSlug, setOriginalSlug] = React.useState(null);
const [refresh, setRefresh] = React.useState(0);
const [toast, setToast] = React.useState("");
const [busy, setBusy] = React.useState(false);
React.useEffect(() => { adminSeo.track("admin_access", { autenticado: authed }); }, []);
React.useEffect(() => {
if (!toast) return;
const t = setTimeout(() => setToast(""), 2600);
return () => clearTimeout(t);
}, [toast]);
const login = (e) => {
e.preventDefault();
if (user.trim() && pass.trim()) {
setAuthed(true); setErr(false);
sessionStorage.setItem(AUTH_KEY, "1");
adminSeo.track("admin_access", { autenticado: true, usuario: user });
} else { setErr(true); }
};
const logout = () => { setAuthed(false); sessionStorage.removeItem(AUTH_KEY); };
/* ---------- LOGIN ---------- */
if (!authed) {
return (
🔒
Área Restrita
Painel de blog do ZEN SUSHI. Esta página é noindex, nofollow.
Acesso somente para a equipe.
);
}
/* ---------- helpers de edição ---------- */
const startNew = () => { setDraft(emptyDraft()); setOriginalSlug(null); setMode("edit"); };
const startEdit = (post) => {
setDraft({
titulo: post.titulo || "", resumo: post.resumo || "", autor: post.autor || "Equipe ZEN SUSHI",
categoria: post.categoria || "Novidades", data: post.data || new Date().toISOString().slice(0, 10),
img: post.img || "", imgAlt: post.imgAlt || "",
topicos: (post.topicos && post.topicos.length ? post.topicos.map((t) => ({ h2: t.h2 || "", p: t.p || "" })) : [{ h2: "", p: "" }]),
dicas: post.dicas ? { h3: post.dicas.h3 || "", p: post.dicas.p || "" } : { h3: "", p: "" },
cta: post.cta || "Reserve sua mesa no ZEN SUSHI",
slug: post.slug || "",
});
setOriginalSlug(post.slug);
setMode("edit");
};
const set = (k, v) => setDraft((d) => ({ ...d, [k]: v }));
const setTopico = (i, k, v) => setDraft((d) => { const t = d.topicos.slice(); t[i] = { ...t[i], [k]: v }; return { ...d, topicos: t }; });
const addTopico = () => setDraft((d) => ({ ...d, topicos: d.topicos.concat([{ h2: "", p: "" }]) }));
const removeTopico = (i) => setDraft((d) => ({ ...d, topicos: d.topicos.filter((_, j) => j !== i) }));
const onImage = async (file) => {
if (!file) return;
setBusy(true);
try {
const url = await fileToOptimizedDataURL(file, 1280);
set("img", url);
if (!draft.imgAlt) set("imgAlt", (draft.titulo || "Imagem") + " — ZEN SUSHI, restaurante em Cotia");
} catch (e) { setToast("Falha ao carregar a imagem."); }
setBusy(false);
};
const canSave = draft.titulo.trim() && draft.resumo.trim();
const save = (publishView) => {
if (!canSave) { setToast("Preencha pelo menos o título e o resumo."); return; }
let slug;
try {
slug = BLOG.save(draft, originalSlug);
} catch (e) { setToast("Não foi possível salvar (armazenamento cheio?)."); return; }
adminSeo.track("blog_post_save", { slug, novo: !originalSlug });
setRefresh((n) => n + 1);
if (publishView) { location.hash = "#/blog/" + slug; }
else { setMode("list"); setToast("Artigo salvo com sucesso."); }
};
const del = (post) => {
if (!window.confirm('Excluir o artigo "' + post.titulo + '"? Esta ação não pode ser desfeita.')) return;
BLOG.remove(post.slug);
adminSeo.track("blog_post_delete", { slug: post.slug });
setRefresh((n) => n + 1);
setToast("Artigo excluído.");
};
const userPosts = BLOG.listUser().slice().sort((a, b) => (b.data || "").localeCompare(a.data || ""));
const staticPosts = (window.KZ_DATA.BLOG || []);
/* ---------- EDITOR ---------- */
if (mode === "edit") {
return (
{toast &&
{toast}
}
{originalSlug ? "Editando artigo" : "Novo artigo"}
{originalSlug ? "Editar" : "Escrever"}
setMode("list")}>← Voltar à lista
set("titulo", e.target.value)} placeholder="Ex.: 5 segredos do sushi perfeito" />
Conteúdo
{draft.topicos.map((t, i) => (
Bloco {i + 1}
{draft.topicos.length > 1 && }
setTopico(i, "h2", e.target.value)} placeholder="Ex.: O arroz certo" />
))}
+ Adicionar bloco
Dica do chef (opcional)
set("dicas", { ...draft.dicas, h3: e.target.value })} placeholder="Ex.: Dica para repetir em casa" />
save(true)} disabled={!canSave}>Salvar e ver no blog
save(false)} disabled={!canSave}>Salvar e continuar
setMode("list")}>Cancelar
);
}
/* ---------- LISTA ---------- */
return (
{toast &&
{toast}
}
Painel do blog · privado
Meu Blog
Seus artigos ({userPosts.length})
{userPosts.length === 0 && (
Você ainda não publicou nenhum artigo. Clique em “Novo artigo” para escrever o primeiro — ele aparece no blog automaticamente.
)}
{userPosts.map((p) => (
{!p.img && ◆}
{p.categoria} · {p.dataLabel || p.data}
{p.titulo}
{p.resumo}
ver
))}
Artigos de exemplo ({staticPosts.length})
Estes vêm com o site. Você pode usá-los como modelo: clique em “duplicar” para criar uma cópia editável.
{staticPosts.map((p) => (
{p.categoria} · {p.dataLabel}
{p.titulo}
{p.resumo}
ver
))}
);
}
window.AdminPage = AdminPage;