Custom pages
A page is one React file under the admin that gets its own URL. It answers to no collection, so nothing about it is generated and nothing on the server declares it.
An import log. A reconciliation report. A screen that reads three collections and writes none. Those are pages.
One file
Put it in src/questpie/admin/pages/. The directory does not exist in a fresh
app, so create it.
import { AdminViewHeader, AdminViewLayout, page } from "@questpie/admin/client";
function ReportsPage() {
return (
<AdminViewLayout header={<AdminViewHeader title="Reports" />}>
<p className="text-muted-foreground text-sm">Nothing here yet.</p>
</AdminViewLayout>
);
}
export default page("reports", {
component: ReportsPage,
path: "/reports",
label: "Reports",
});Run questpie generate. The page is live at /admin/reports.
The component takes no props. Everything it needs comes from the admin hooks:
useCollectionList, useCollectionItem, useCurrentUser, useBrand. They are
the same hooks the built-in screens use.
The options
| Option | What it does |
|---|---|
component | The React component, or an async function that imports one |
path | The URL under the admin base path. Required to route |
label | The sidebar label. Falls back to the formatted page name |
icon | A React component. An icon name string is ignored here |
group | Carried on the nav entry. Nothing in the sidebar reads it |
order | Carried the same way, default 0. Nothing sorts by it |
showInNav | false keeps the page routable but out of the sidebar |
No `path`, no route
The router only matches a page that declares path. Without one the file
still registers and still generates. No URL reaches it.
Getting it into the sidebar
The server owns the sidebar, and its default lists collections and globals. A
page is not in it. Add an item in config/admin.ts.
import { adminConfig } from "#questpie/factories";
export default adminConfig({
sidebar: {
sections: [{ id: "tools", title: "Tools" }],
items: [
{
sectionId: "tools",
type: "link",
label: "Reports",
href: "/admin/reports",
icon: { type: "icon", props: { name: "ph:chart-bar" } },
},
],
},
});A link item takes the URL you give it, so write the full path including
/admin. There is also a page item keyed by pageId, the camelCased file
name. Prefer link. The page item builds its href from path alone. It never
adds the admin base path, so the link misses the route it names.
Lazy loading
component also accepts a loader, so a heavy page can stay out of the first
bundle. Write it as an async function. The router only calls a loader whose
constructor is AsyncFunction, and renders anything else as a component.
export default page("reports", {
component: async () => ({
default: (await import("./reports-screen.js")).ReportsScreen,
}),
path: "/reports",
});Next
Dashboard widgets is the smaller version: your React on a tile, arranged by the server.
Theming
Colours, fonts and radius are CSS variables, not config. Override them in one stylesheet, mirror two selectors, and the panel is yours.
Dashboard widgets
A widget is your React on one dashboard tile. The server decides where it sits and what it loads, and hands the whole item to your component.