Add page builder admin UI + public page route (step 5 + step 6 client)
- PagesAdmin: list view of pages (title/slug/status/protected/updated) with new/edit navigation and a View link to the live page. - PageBuilder: full-page block canvas — palette (adds any registered block), per-block editor cards with show/hide, up/down + native drag reorder, and remove; Content / Settings tabs; SEO metadata + layout/nav settings panels; publish/unpublish; protect (PATCH) and password-gated unprotect (modal); draft preview (mints a token, opens /preview/:id/:token); delete (blocked while protected). Surfaces server block-validation details on save. - CmsPage: public renderer for /:slug (published; staff see drafts) and the token-gated /preview/:id/:token, rendering blocks via BlockList and reflecting the page title/meta. - Routing: /:slug catch-all after all named routes + /preview/:id/:token outside the maintenance gate; admin /admin/pages, /pages/new, /pages/:id. - api client: public page/pagePreview + admin pages CRUD/unprotect/preview. - AdminLayout: "Pages" nav entry (Content group) with icon. - theme.css: builder canvas + preview-banner + shell-wide styles. Client builds clean (216 modules). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -13,12 +13,12 @@ const logger = require('../../../utils/logger')('pages')
|
||||
// sometimes a block-error list); anything else is an unexpected 500.
|
||||
function fail(res, err) {
|
||||
if (err && err.name === 'PageError') {
|
||||
const body = { error: err.message, code: err.code }
|
||||
const body = { message: err.message, code: err.code }
|
||||
if (err.errors) body.details = err.errors
|
||||
return res.status(err.status).json(body)
|
||||
}
|
||||
logger.error('unexpected pages error', { error: err.message })
|
||||
return res.status(500).json({ error: 'Internal error' })
|
||||
return res.status(500).json({ message: 'Internal error' })
|
||||
}
|
||||
|
||||
async function listPages(req, res) {
|
||||
@@ -27,7 +27,7 @@ async function listPages(req, res) {
|
||||
|
||||
async function getPage(req, res) {
|
||||
const page = await pages.getById(Number(req.params.id))
|
||||
if (!page) return res.status(404).json({ error: 'Page not found', code: 'not_found' })
|
||||
if (!page) return res.status(404).json({ message: 'Page not found', code: 'not_found' })
|
||||
return res.json(page)
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ async function updatePage(req, res) {
|
||||
try {
|
||||
const id = Number(req.params.id)
|
||||
const before = await pages.getRawById(id)
|
||||
if (!before) return res.status(404).json({ error: 'Page not found', code: 'not_found' })
|
||||
if (!before) return res.status(404).json({ message: 'Page not found', code: 'not_found' })
|
||||
|
||||
const page = await pages.update(id, req.body)
|
||||
await activity.log({ req, action: 'page.update', detail: { id, slug: page.slug } })
|
||||
@@ -86,13 +86,13 @@ async function unprotectPage(req, res) {
|
||||
const id = Number(req.params.id)
|
||||
const password = req.body?.password
|
||||
if (typeof password !== 'string' || password === '') {
|
||||
return res.status(400).json({ error: 'Password is required', code: 'password_required' })
|
||||
return res.status(400).json({ message: 'Password is required', code: 'password_required' })
|
||||
}
|
||||
const user = await users.getRawById(req.user.id)
|
||||
const ok = await users.validatePassword(user, password)
|
||||
if (!ok) {
|
||||
logger.warn('failed page unprotect (bad password)', { pageId: id, userId: req.user.id })
|
||||
return res.status(401).json({ error: 'Password is incorrect', code: 'bad_password' })
|
||||
return res.status(401).json({ message: 'Password is incorrect', code: 'bad_password' })
|
||||
}
|
||||
const page = await pages.unprotect(id)
|
||||
await activity.log({ req, action: 'page.unprotect', detail: { id, slug: page.slug } })
|
||||
@@ -108,7 +108,7 @@ async function createPreview(req, res) {
|
||||
try {
|
||||
const id = Number(req.params.id)
|
||||
const page = await pages.getById(id)
|
||||
if (!page) return res.status(404).json({ error: 'Page not found', code: 'not_found' })
|
||||
if (!page) return res.status(404).json({ message: 'Page not found', code: 'not_found' })
|
||||
const t = token.signPagePreview(id)
|
||||
return res.json({
|
||||
token: t,
|
||||
|
||||
Reference in New Issue
Block a user