5.2 KiB
Web Manager — Admin (Web Studio)
Admin side of Web Manager. Source: apps/admin/src/core/WebStudio/.
Routes
/web-studio/pages/listing list, search, filters, row actions
/web-studio/pages/-/create new draft
/web-studio/pages/-/:id editor (page or draft clone)
/web-studio/settings/:section header | footer | legal
The list shows title with full path, locale, slug, group key, page type, status, draft marker and
updated time; search is debounced and filters, pagination and locale live in the query string
(useWebStudioQueryParams), so links and the back arrow keep the editor's context. Row actions are
Edit, Preview, Open in web app, Publish, Unpublish, Discard and Delete — Edit opens the draft clone
when draftId is present, so an editor always lands on the working copy.
Editor
The editor is a flat two-column layout: a sticky ~320px Page details column, and Hero / Blocks / SEO
as line tabs beside it. One React Hook Form instance (PAGE_EDITOR_FORM_ID) owns everything;
the header's Save button submits it by form id, and submitting disables the whole fieldset.
Actions sit in a split button: Save draft, then Preview / Publish / Unpublish / Discard / Delete in
the dropdown, each behind its own control component in view/controls/ and its own permission.
Publish is offered whenever the page is not already live, so it doubles as republish.
Blocks are added from a sheet filtered to the page type's allowedBlockTypes, reordered by drag
(view/components/SortableList.tsx), and duplicated or removed from their own header. Nested
collections — content columns, CTAs, FAQ items, navigation links — behave the same way one level
down.
Key Files
| Concern | File |
|---|---|
| Module registration | config/module.ts, config/routing.tsx, config/menu.ts |
| Languages | config/languages.ts — code, labels, direction, public prefix |
| Page types | config/pages.ts — allowed blocks and default path per type |
| Block catalogue | config/blocks.ts — label, description, preview image, factory |
| Settings shape | config/siteSettings.ts |
| HTTP + multipart | data/api/WebStudioAPI.ts, data/api/PagePayloadSerializer.ts |
| Domain types | entities/ — PageType, PageStatus, BlockType, HeroType, block and field shapes |
| Form ↔ DTO | pages/PageEditPage/helpers/toEditorValues.ts, view/forms/PageEditorForm/helpers/toCreatePayload.ts |
Page types decide what an editor can build:
| Type | Layout | Blocks offered | Default path |
|---|---|---|---|
landing |
Standard | all six | / |
blog |
Blog index | none — the listing is rendered from published posts | /blog |
blog-item |
Editorial | all except articles |
/ |
UI Conventions
The module is a content tool, not a dashboard of cards. Flat surfaces, few separators, one primary action per screen.
| Element | Treatment |
|---|---|
| Screen heading | text-mono text-xl font-semibold |
| Supporting copy | text-muted-foreground text-sm |
| Editor settings | bg-muted/40 space-y-5 p-5 |
| Top-level block | bg-muted/35, compact header, one border-b while open |
| Nested field group | border-l-2 pl-4 (view/components/FieldCard.tsx) |
| Inline actions | small ghost icon buttons |
| Destructive trigger | ghost button, destructive text, confirmation dialog |
| Empty collection | bg-muted/35, generous padding, short copy |
view/components/BlockCard.tsx is named for compatibility but is a flat section, not a card — no
shadow, no hover lift, no bordered container nested inside every block. The block picker stays a
sheet with a divided list, because picking is a temporary task. Shared visual principles live in the
Admin UI Style Guide; style Web Studio from Web Studio and do
not edit apps/admin/src/shared for a module-only visual change.
A visual change must not alter routes, locale / group_key query behavior, list filtering or DTO
mapping, draft-clone semantics, form values or validation, block factory output, settings payload
shape, or the toast / error / loading states. Anything that does is a feature change.