Files
zeropost-web/app/zero/page.js
T
Aleksei Pavlov 8700b8fc69 feat(zero): admin panel section + site /zero page + autogen card
Admin (/admin/zero):
  - new AdminZero with list, status filters, generate button (bucket + allow_dup)
  - per-note actions: approve, edit inline, regenerate (bucket pick), skip,
    'publish now' (approve + scheduled_at=now → runner picks up within 1m)
  - config panel: toggle on/off, generate/approve/publish hour MSK, site URL base
  - new pin 'Зеро' () in AdminNav

Site (zeropost.ru):
  - ZeroBlock — feed of last 3-6 Zero notes, rendered on home next to Серии
  - /zero — full Zero notes list page with character bio block (avatar + bullets)

Autogen integration:
  - ZeroAutogenCard on /admin/autogen — amber card with on/off, hour pickers,
    'generate now' and last-3 preview, link to full section

Plumbing:
  - lib/engine.js: listZeroNotes(), getZeroCharacter()
  - app/admin/api/zero/[...path]/route.js: catch-all proxy with cookie auth
2026-06-19 11:17:19 +03:00

76 lines
3.3 KiB
JavaScript
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.
import Header from '@/components/Header';
import Footer from '@/components/Footer';
import ZeroBlock from '@/components/ZeroBlock';
import { listZeroNotes, getZeroCharacter } from '@/lib/engine';
import { Coffee } from 'lucide-react';
export const dynamic = 'force-dynamic';
export const metadata = {
title: 'Заметки от Зеро',
description: 'Короткие посты от AI-персонажа Зеро — мысли программиста о работе, инструментах и забавных багах',
};
export default async function ZeroPage() {
const [notes, character] = await Promise.all([
listZeroNotes({ limit: 100 }),
getZeroCharacter(),
]);
return (
<>
<Header />
<main className="pt-10 pb-16">
<div className="container-wide mb-10">
<div
className="inline-flex items-center gap-2 text-xs accent px-3 py-1.5 rounded-full mb-4"
style={{ background: 'rgb(var(--accent) / 0.1)', border: '1px solid rgb(var(--accent) / 0.2)' }}
>
<Coffee className="w-3.5 h-3.5" /> AI-персонаж
</div>
<h1 className="text-3xl sm:text-5xl font-bold ink mb-3 leading-tight">
Заметки от Зеро
</h1>
<p className="mute text-base sm:text-lg max-w-2xl mb-8">
Короткие посты от первого лица в Telegram-канале{' '}
<a href="https://t.me/zeropostru" target="_blank" rel="noreferrer" className="accent hover:underline">@zeropostru</a>.
Программист с многолетним опытом, любит копаться под капотом, постоянно носится с кофе.
</p>
{character?.character?.bio && (
<div className="rounded-xl border border-amber-200 dark:border-amber-900 bg-amber-50/50 dark:bg-amber-950/20 p-5 sm:p-6 max-w-3xl">
<div className="flex items-start gap-4">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/uploads/zero-coffee.webp"
alt="Зеро"
className="w-16 h-16 rounded-full object-cover bg-amber-100 dark:bg-amber-950/40 ring-2 ring-amber-300 dark:ring-amber-800 shrink-0"
/>
<div className="min-w-0">
<div className="text-sm font-semibold ink mb-2">Кто такой Зеро</div>
<ul className="space-y-1 text-sm mute">
{character.character.bio.map((line, i) => (
<li key={i}> {line}</li>
))}
</ul>
</div>
</div>
</div>
)}
</div>
{notes.length > 0 ? (
<ZeroBlock notes={notes} />
) : (
<div className="container-wide">
<div className="rounded-xl border border-neutral-200 dark:border-neutral-800 p-10 text-center">
<Coffee className="w-8 h-8 text-amber-500 mx-auto mb-3" />
<p className="mute text-sm">Зеро ещё не написал ни одной заметки. Скоро появится.</p>
</div>
</div>
)}
</main>
<Footer />
</>
);
}