@questpie/admin 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +76 -43
  2. package/dist/client/blocks/block-renderer.d.mts +2 -2
  3. package/dist/client/preview/block-scope-context.d.mts +2 -2
  4. package/dist/client/preview/preview-banner.d.mts +2 -2
  5. package/dist/client/preview/preview-field.d.mts +4 -4
  6. package/dist/client/views/auth/auth-layout.d.mts +2 -2
  7. package/dist/client/views/auth/forgot-password-form.d.mts +2 -2
  8. package/dist/client/views/auth/login-form.d.mts +2 -2
  9. package/dist/client/views/auth/setup-form.d.mts +2 -2
  10. package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
  11. package/dist/client/views/pages/dashboard-page.d.mts +2 -2
  12. package/dist/components/rich-text/rich-text-renderer.d.mts +2 -2
  13. package/dist/server/modules/admin/block/prefetch.d.mts +4 -0
  14. package/dist/server/modules/admin/collections/account.d.mts +50 -50
  15. package/dist/server/modules/admin/collections/admin-locks.d.mts +49 -49
  16. package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
  17. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +42 -42
  18. package/dist/server/modules/admin/collections/apikey.d.mts +38 -38
  19. package/dist/server/modules/admin/collections/assets.d.mts +20 -20
  20. package/dist/server/modules/admin/collections/session.d.mts +42 -42
  21. package/dist/server/modules/admin/collections/user.d.mts +28 -28
  22. package/dist/server/modules/admin/collections/verification.d.mts +36 -36
  23. package/dist/server/modules/admin/routes/admin-config.d.mts +2 -2
  24. package/dist/server/modules/admin/routes/execute-action.d.mts +5 -5
  25. package/dist/server/modules/admin/routes/locales.d.mts +2 -2
  26. package/dist/server/modules/admin/routes/preview.d.mts +6 -6
  27. package/dist/server/modules/admin/routes/reactive.d.mts +5 -5
  28. package/dist/server/modules/admin/routes/setup.d.mts +5 -5
  29. package/dist/server/modules/admin/routes/translations.d.mts +3 -3
  30. package/dist/server/modules/admin/routes/widget-data.d.mts +3 -3
  31. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +23 -23
  32. package/dist/server/modules/audit/.generated/module.d.mts +6 -6
  33. package/dist/server/modules/audit/collections/audit-log.d.mts +18 -18
  34. package/dist/server/modules/audit/jobs/audit-cleanup.d.mts +2 -2
  35. package/package.json +4 -3
  36. package/skills/questpie-admin/SKILL.md +0 -397
  37. package/skills/questpie-admin/blocks/SKILL.md +0 -305
  38. package/skills/questpie-admin/custom-ui/SKILL.md +0 -307
  39. package/skills/questpie-admin/views/SKILL.md +0 -442
@@ -1,305 +0,0 @@
1
- ---
2
- name: questpie-admin/blocks
3
- description: QUESTPIE blocks content-blocks page-builder block-definition fields prefetch renderers block-picker categories nested-blocks upload relation
4
- type: skill
5
- ---
6
-
7
- # QUESTPIE Blocks
8
-
9
- This skill builds on questpie-admin.
10
-
11
- Blocks are reusable content components for page builders. Define them server-side with fields and admin metadata, then render them client-side with React components.
12
-
13
- ```text
14
- Server: block("hero") Client: HeroRenderer
15
- .fields({ title, image }) -> Receives { values, data }
16
- .admin({ label, icon }) Returns JSX
17
- .prefetch({ with: {...} })
18
- ```
19
-
20
- ## Defining Blocks
21
-
22
- Blocks are defined in `blocks/` using the `block()` factory:
23
-
24
- ```ts title="blocks/hero.ts"
25
- import { block } from "#questpie";
26
-
27
- export const heroBlock = block("hero")
28
- .admin(({ c }) => ({
29
- label: { en: "Hero Section", sk: "Hero sekcia" },
30
- icon: c.icon("ph:image"),
31
- category: "sections",
32
- }))
33
- .fields(({ f }) => ({
34
- title: f.text({ localized: true, required: true }),
35
- subtitle: f.textarea({ localized: true }),
36
- backgroundImage: f.upload({ to: "assets" }),
37
- overlayOpacity: f.number({ defaultValue: 60 }),
38
- alignment: f.select({
39
- options: ["left", "center", "right"],
40
- defaultValue: "center",
41
- }),
42
- ctaText: f.text({ localized: true }),
43
- ctaLink: f.text(),
44
- }))
45
- .prefetch({ with: { backgroundImage: true } });
46
- ```
47
-
48
- ### Admin Metadata
49
-
50
- ```ts
51
- .admin(({ c }) => ({
52
- label: { en: "Hero Section" }, // Display name in block picker
53
- icon: c.icon("ph:image"), // Icon in block picker (Phosphor set)
54
- category: "sections", // Group in block picker
55
- }))
56
- ```
57
-
58
- ### Multiple Blocks Per File
59
-
60
- Export multiple named blocks from one file:
61
-
62
- ```ts title="blocks/layout.ts"
63
- import { block } from "#questpie";
64
-
65
- export const twoColumnBlock = block("twoColumn")
66
- .admin(({ c }) => ({
67
- label: { en: "Two Columns" },
68
- icon: c.icon("ph:columns"),
69
- category: "layout",
70
- }))
71
- .fields(({ f }) => ({
72
- left: f.blocks(),
73
- right: f.blocks(),
74
- }));
75
-
76
- export const spacerBlock = block("spacer")
77
- .admin(({ c }) => ({
78
- label: { en: "Spacer" },
79
- icon: c.icon("ph:arrows-out-line-vertical"),
80
- category: "layout",
81
- }))
82
- .fields(({ f }) => ({
83
- height: f.select({
84
- options: ["sm", "md", "lg", "xl"],
85
- defaultValue: "md",
86
- }),
87
- }));
88
- ```
89
-
90
- ## Using Blocks in Collections
91
-
92
- Add a `blocks` field to any collection:
93
-
94
- ```ts title="collections/pages.ts"
95
- import { collection } from "#questpie";
96
-
97
- export const pages = collection("pages").fields(({ f }) => ({
98
- title: f.text({ required: true, localized: true }),
99
- slug: f.text({ required: true }),
100
- content: f.blocks({ localized: true }),
101
- }));
102
- ```
103
-
104
- The admin renders a visual block editor for this field.
105
-
106
- ## Prefetch
107
-
108
- Blocks often reference related data (images, linked records). Use `.prefetch()` to load them alongside block values.
109
-
110
- ### Declarative Prefetch
111
-
112
- ```ts
113
- .prefetch({
114
- with: {
115
- backgroundImage: true, // Load the full image record
116
- },
117
- })
118
- ```
119
-
120
- ### Nested Prefetch
121
-
122
- ```ts
123
- .prefetch({
124
- with: {
125
- featuredBarber: {
126
- with: {
127
- avatar: true,
128
- services: true,
129
- },
130
- },
131
- },
132
- })
133
- ```
134
-
135
- ### Functional Prefetch
136
-
137
- For complex queries, use a function. The `ctx` parameter provides fully typed `collections` and `globals` via `AppContext` augmentation — no imports needed:
138
-
139
- ```ts title="blocks/featured.ts"
140
- import { block } from "#questpie";
141
-
142
- export const featuredBlock = block("featured")
143
- .fields(({ f }) => ({
144
- heading: f.text({ required: true }),
145
- }))
146
- .prefetch(async ({ values, ctx }) => {
147
- return {
148
- posts: (await ctx.collections.posts.find({ limit: 5 })).docs,
149
- };
150
- });
151
- ```
152
-
153
- ### Using Prefetched Data in Renderers
154
-
155
- ```tsx
156
- function HeroRenderer({ values, data }: BlockProps<"hero">) {
157
- // values.backgroundImage = "asset-id-123" (just the ID)
158
- // data.backgroundImage = { url: "/api/assets/...", filename: "hero.jpg", ... }
159
-
160
- return (
161
- <section>
162
- {data?.backgroundImage?.url && (
163
- <img src={data.backgroundImage.url} alt="" />
164
- )}
165
- <h1>{values.title}</h1>
166
- </section>
167
- );
168
- }
169
- ```
170
-
171
- ## Block Renderers
172
-
173
- React components that receive block data and return JSX.
174
-
175
- ### Defining a Renderer
176
-
177
- ```tsx title="admin/blocks/hero.tsx"
178
- import type { BlockProps } from "@questpie/admin/client";
179
-
180
- export function HeroRenderer({ values, data }: BlockProps<"hero">) {
181
- return (
182
- <section
183
- className="relative flex items-center justify-center"
184
- style={{ minHeight: "60vh" }}
185
- >
186
- {data?.backgroundImage?.url && (
187
- <img
188
- src={data.backgroundImage.url}
189
- alt=""
190
- className="absolute inset-0 w-full h-full object-cover"
191
- />
192
- )}
193
- <div className="relative text-center">
194
- <h1 className="text-5xl font-bold">{values.title}</h1>
195
- {values.subtitle && <p className="text-xl mt-4">{values.subtitle}</p>}
196
- {values.ctaText && (
197
- <a href={values.ctaLink} className="mt-6 inline-block btn">
198
- {values.ctaText}
199
- </a>
200
- )}
201
- </div>
202
- </section>
203
- );
204
- }
205
- ```
206
-
207
- ### BlockProps
208
-
209
- | Property | Type | Description |
210
- | ---------- | ----------- | -------------------------------------------------- |
211
- | `values` | `object` | Block field values (title, subtitle, etc.) |
212
- | `data` | `object` | Prefetched relation data (images, related records) |
213
- | `children` | `ReactNode` | Nested block content |
214
-
215
- ### Registering Renderers
216
-
217
- ```tsx title="admin/blocks/index.tsx"
218
- import { HeroRenderer } from "./hero";
219
- import { GalleryRenderer } from "./gallery";
220
- import { CTARenderer } from "./cta";
221
-
222
- export const renderers = {
223
- hero: HeroRenderer,
224
- gallery: GalleryRenderer,
225
- cta: CTARenderer,
226
- };
227
- ```
228
-
229
- ### Frontend Rendering
230
-
231
- Use block renderers on the public frontend:
232
-
233
- ```tsx title="components/page-renderer.tsx"
234
- import { renderers } from "@/questpie/admin/blocks";
235
-
236
- function PageRenderer({ page }) {
237
- return (
238
- <div>
239
- {page.content?.map((block, i) => {
240
- const Renderer = renderers[block.type];
241
- if (!Renderer) return null;
242
- return <Renderer key={i} values={block.values} data={block.data} />;
243
- })}
244
- </div>
245
- );
246
- }
247
- ```
248
-
249
- ## Common Mistakes
250
-
251
- 1. **HIGH: Not using `ctx.collections.*` in functional prefetch** — use the context-injected collections directly. Do NOT import `app` from `#questpie` inside block files (causes circular dependencies).
252
-
253
- ```ts
254
- // WRONG — importing app creates circular dependency
255
- import { app } from "#questpie";
256
- .prefetch(async ({ values, ctx }) => {
257
- const posts = await app.collections.posts.find({});
258
- })
259
-
260
- // CORRECT — use ctx.collections directly
261
- .prefetch(async ({ values, ctx }) => {
262
- const posts = await ctx.collections.posts.find({});
263
- })
264
- ```
265
-
266
- 2. **HIGH: Importing from `.generated/` inside block files** — block files are imported BY `.generated/index.ts`, so importing from it back creates circular dependencies. Use the `ctx` parameter instead.
267
-
268
- 3. **MEDIUM: Block renderer not exported as default or named export** — codegen discovers named exports from block renderer files. Ensure the component is exported.
269
-
270
- 4. **MEDIUM: Using `{ with: { field: true } }` prefetch for complex queries** — declarative prefetch only loads related records by ID. For filtered/sorted/limited queries, use functional prefetch instead.
271
-
272
- 5. **MEDIUM: Forgetting `.prefetch()` for upload fields** — without prefetch, `values.backgroundImage` is just an ID string. Add `{ with: { backgroundImage: true } }` to get the full asset record with `url`.
273
-
274
- 6. **LOW: Missing `category` in `.admin()`** — blocks without a category won't be grouped in the block picker, making it harder to find them.
275
-
276
- ## Blocks in Live Preview
277
-
278
- When a collection has `.preview()` configured, blocks participate in the live preview patch bus.
279
-
280
- ### PreviewBlock Wrapper
281
-
282
- Use `<PreviewBlock>` in your frontend to mark block regions for live preview focus and patch targeting:
283
-
284
- ```tsx
285
- import { PreviewBlock } from "@questpie/admin/client";
286
-
287
- function PageRenderer({ blocks, previewData }) {
288
- return blocks.map((block) => {
289
- const Renderer = renderers[block.type];
290
- return (
291
- <PreviewBlock key={block.id} id={block.id}>
292
- <Renderer values={block.values} data={block.data} />
293
- </PreviewBlock>
294
- );
295
- });
296
- }
297
- ```
298
-
299
- When the editor focuses a block, `<PreviewBlock>` highlights it in the preview. Patches target individual blocks by ID for granular updates.
300
-
301
- ### Block Prefetch and Server Reconcile
302
-
303
- In `"hybrid"` preview strategy, blocks with functional `.prefetch()` trigger a server reconcile when their field values change. For example, a "featured posts" block with a `limit` field will re-run its prefetch handler on the server to fetch updated data, while simple field changes (title, subtitle) apply instantly via the local patch bus.
304
-
305
- Blocks with declarative prefetch (`{ with: { image: true } }`) resolve relations during reconcile — the preview shows the image URL immediately after the server round-trip completes, not just the asset ID.
@@ -1,307 +0,0 @@
1
- ---
2
- name: questpie-admin/custom-ui
3
- description: QUESTPIE custom-fields custom-views registries field-registry view-registry component-registry reactive-fields dynamic-options widgets field-renderer cell-renderer
4
- type: skill
5
- ---
6
-
7
- # QUESTPIE Custom UI
8
-
9
- This skill builds on questpie-admin.
10
-
11
- Extend the QUESTPIE admin with custom field types, custom view types, custom components, and reactive field behaviors.
12
-
13
- ## Registries
14
-
15
- Registries connect server-side schema to client-side rendering. When the admin encounters a field type, it looks up the renderer in the field registry.
16
-
17
- ```text
18
- Server: f.text({ ... })
19
- |
20
- Generated: { type: "text", options: {...} }
21
- |
22
- Admin Client: fieldRegistry.get("text")
23
- |
24
- React: <TextFieldRenderer value={...} onChange={...} />
25
- ```
26
-
27
- ### Built-in Field Registry
28
-
29
- ```
30
- text -> TextInput
31
- textarea -> TextareaInput
32
- richText -> RichTextEditor (TipTap)
33
- number -> NumberInput
34
- boolean -> Checkbox / Switch
35
- date -> DatePicker
36
- datetime -> DateTimePicker
37
- select -> SelectDropdown
38
- relation -> RelationPicker
39
- upload -> FileUpload
40
- object -> NestedForm
41
- array -> RepeatableItems
42
- blocks -> BlockEditor
43
- json -> JSONEditor
44
- ```
45
-
46
- ### Extending Registries
47
-
48
- Place files in the admin directory. Codegen discovers them automatically:
49
-
50
- ```
51
- questpie/admin/
52
- fields/
53
- color.tsx # Custom color field renderer
54
- currency.tsx # Custom currency field renderer
55
- views/
56
- kanban.tsx # Custom kanban list view
57
- ```
58
-
59
- These are merged with built-in defaults during codegen and exported in `.generated/client.ts`.
60
-
61
- ## Custom Fields
62
-
63
- ### Server-Side Registration
64
-
65
- Register custom fields through modules:
66
-
67
- ```ts
68
- const myModule = module({
69
- name: "custom-fields",
70
- fields: {
71
- color: colorField,
72
- currency: currencyField,
73
- phone: phoneField,
74
- },
75
- });
76
- ```
77
-
78
- Once registered and codegen runs, the field becomes available on the `f` builder:
79
-
80
- ```ts
81
- .fields(({ f }) => ({
82
- brandColor: f.color({ default: "#000000" }),
83
- price: f.currency({ currency: "USD" }),
84
- }))
85
- ```
86
-
87
- ### Admin Field Renderer
88
-
89
- Create a React component for the field's edit form:
90
-
91
- ```tsx title="admin/fields/color.tsx"
92
- import { Icon } from "@iconify/react";
93
-
94
- function ColorFieldRenderer({ value, onChange }) {
95
- return (
96
- <div className="flex items-center gap-2">
97
- <input
98
- type="color"
99
- value={value || "#000000"}
100
- onChange={(e) => onChange(e.target.value)}
101
- className="w-10 h-10 border border-border cursor-pointer"
102
- />
103
- <span className="font-mono text-sm text-muted-foreground">
104
- {value || "#000000"}
105
- </span>
106
- </div>
107
- );
108
- }
109
- ```
110
-
111
- ### Cell Renderer
112
-
113
- For custom table column rendering, provide a `cell` component alongside the field renderer:
114
-
115
- ```tsx title="admin/fields/color.tsx"
116
- // Cell component for list view table
117
- export function ColorCell({ value }) {
118
- return (
119
- <div className="flex items-center gap-2">
120
- <div
121
- className="w-4 h-4 border border-border"
122
- style={{ backgroundColor: value || "transparent" }}
123
- />
124
- <span className="text-xs font-mono">{value}</span>
125
- </div>
126
- );
127
- }
128
- ```
129
-
130
- ## Custom Views
131
-
132
- Create view types beyond built-in table and form — kanban boards, calendars, galleries.
133
-
134
- ### Server-Side Declaration
135
-
136
- ```ts
137
- const myModule = module({
138
- name: "custom-views",
139
- views: {
140
- kanban: kanbanViewDefinition,
141
- calendar: calendarViewDefinition,
142
- },
143
- });
144
- ```
145
-
146
- ### Usage in Collections
147
-
148
- ```ts
149
- .list(({ v }) => v.kanban({
150
- columns: "status",
151
- cardTitle: "title",
152
- }))
153
- ```
154
-
155
- ### Client Rendering
156
-
157
- ```tsx title="admin/views/kanban.tsx"
158
- function KanbanView({ data, columns, onDrop }) {
159
- return (
160
- <div className="flex gap-4">
161
- {columns.map((col) => (
162
- <div key={col.id} className="flex-1">
163
- <h3 className="font-mono text-sm font-semibold mb-2">{col.label}</h3>
164
- {data
165
- .filter((item) => item.status === col.id)
166
- .map((item) => (
167
- <div
168
- key={item.id}
169
- className="border border-border bg-card p-3 mb-2"
170
- >
171
- {item.title}
172
- </div>
173
- ))}
174
- </div>
175
- ))}
176
- </div>
177
- );
178
- }
179
- ```
180
-
181
- ## Reactive Field System
182
-
183
- Fields support reactive behaviors configured in the collection's `.form()` view or on the field definition itself.
184
-
185
- ### Conditional Visibility
186
-
187
- ```ts
188
- {
189
- field: f.cancellationReason,
190
- hidden: ({ data }) => data.status !== "cancelled",
191
- }
192
- ```
193
-
194
- ### Read-Only
195
-
196
- ```ts
197
- {
198
- field: f.customerName,
199
- readOnly: ({ data }) => !!data.customer,
200
- }
201
- ```
202
-
203
- ### Computed Values
204
-
205
- ```ts
206
- {
207
- field: f.slug,
208
- compute: {
209
- handler: ({ data }) => {
210
- if (data.name && !data.slug?.trim()) {
211
- return slugify(data.name);
212
- }
213
- return undefined;
214
- },
215
- deps: ({ data }) => [data.name, data.slug],
216
- debounce: 300,
217
- },
218
- }
219
- ```
220
-
221
- ### Dynamic Options (Server-Side)
222
-
223
- For select/relation fields with options that depend on other field values:
224
-
225
- ```ts
226
- city: f.relation({
227
- to: "cities",
228
- options: {
229
- handler: async ({ data, search, ctx }) => {
230
- const cities = await ctx.db.query.cities.findMany({
231
- where: { countryId: data.country },
232
- });
233
- return {
234
- options: cities.map((c) => ({ value: c.id, label: c.name })),
235
- };
236
- },
237
- deps: ({ data }) => [data.country],
238
- },
239
- }),
240
- ```
241
-
242
- The `handler` runs **server-side** with full access to `ctx.db`, `ctx.user`, `ctx.req`. It re-executes when any value in `deps` changes.
243
-
244
- ## UI Component Reference
245
-
246
- When building custom admin UI, use these patterns:
247
-
248
- ### Icons
249
-
250
- ```tsx
251
- import { Icon } from "@iconify/react";
252
-
253
- // Phosphor icon set with ph: prefix
254
- <Icon icon="ph:house" width={20} height={20} />
255
- <Icon icon="ph:caret-down-bold" width={16} height={16} /> // bold weight
256
- <Icon icon="ph:heart-fill" width={16} height={16} /> // fill weight
257
- ```
258
-
259
- ### Toasts
260
-
261
- ```tsx
262
- import { toast } from "sonner";
263
-
264
- toast.success("Record saved");
265
- toast.error("Failed to save");
266
- ```
267
-
268
- ### Primitives (base-ui)
269
-
270
- ```tsx
271
- // CORRECT — render prop
272
- <DialogTrigger render={<Button>Open</Button>} />
273
-
274
- // WRONG — asChild is Radix, not base-ui
275
- <DialogTrigger asChild><Button>Open</Button></DialogTrigger>
276
- ```
277
-
278
- ### Responsive Components
279
-
280
- - `ResponsivePopover` — Popover on desktop, Drawer on mobile
281
- - `ResponsiveDialog` — Dialog on desktop, fullscreen Drawer on mobile
282
- - Hooks: `useIsMobile()`, `useIsDesktop()`, `useMediaQuery()`
283
-
284
- ## Common Mistakes
285
-
286
- 1. **HIGH: Not registering custom field in the field registry** — if codegen doesn't discover the field renderer file, the admin will render nothing for that field type. Place it in `questpie/admin/fields/<name>.tsx`.
287
-
288
- 2. **HIGH: Missing `cell` component for custom fields** — without a cell component, the list view table shows raw values for your custom field instead of a formatted display.
289
-
290
- 3. **MEDIUM: Reactive field handlers running client-side** — `options.handler`, `compute.handler`, and other reactive handlers run **SERVER-SIDE** with access to `ctx.db`, `ctx.user`. Do not import client-side modules or use browser APIs in them.
291
-
292
- 4. **MEDIUM: Using `onChange` wrong in field components** — the field renderer receives `onChange` that expects the **value directly**, not a DOM event.
293
-
294
- ```tsx
295
- // WRONG
296
- onChange={(e) => onChange(e)}
297
- // CORRECT
298
- onChange={(e) => onChange(e.target.value)}
299
- // Or for non-DOM values:
300
- onChange={newValue}
301
- ```
302
-
303
- 5. **MEDIUM: Importing from `@radix-ui/*`** — QUESTPIE admin uses `@base-ui/react`. Never import Radix primitives.
304
-
305
- 6. **MEDIUM: Using `@phosphor-icons/react` or `lucide-react`** — use `@iconify/react` with `ph:` prefix for all icons.
306
-
307
- 7. **LOW: Not using shadcn components** — prefer `<Button>`, `<Card>`, `<Input>` from the shadcn component library instead of raw HTML elements. The admin has a consistent brutalist design system.