create-questpie 2.0.4 → 2.1.0

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 (152) hide show
  1. package/dist/index.mjs +362 -119
  2. package/package.json +2 -3
  3. package/templates/elysia/AGENTS.md +56 -0
  4. package/templates/elysia/CLAUDE.md +39 -0
  5. package/templates/elysia/Dockerfile +24 -0
  6. package/templates/elysia/README.md +148 -0
  7. package/templates/elysia/docker/init-extensions.sql +11 -0
  8. package/templates/elysia/docker-compose.yml +21 -0
  9. package/templates/elysia/env.example +16 -0
  10. package/templates/elysia/gitignore +6 -0
  11. package/templates/elysia/package.json +47 -0
  12. package/templates/elysia/questpie.config.ts +12 -0
  13. package/templates/elysia/src/index.ts +21 -0
  14. package/templates/elysia/src/lib/auth-client.ts +32 -0
  15. package/templates/elysia/src/lib/client.ts +13 -0
  16. package/templates/elysia/src/lib/env.ts +24 -0
  17. package/templates/elysia/src/lib/query-client.ts +18 -0
  18. package/templates/elysia/src/lib/query.ts +18 -0
  19. package/templates/elysia/src/questpie/server/.generated/context.gen.ts +200 -0
  20. package/templates/elysia/src/questpie/server/.generated/entities.gen.ts +84 -0
  21. package/templates/elysia/src/questpie/server/.generated/factories.ts +65 -0
  22. package/templates/elysia/src/questpie/server/.generated/index.ts +131 -0
  23. package/templates/elysia/src/questpie/server/.generated/names.gen.ts +25 -0
  24. package/templates/elysia/src/questpie/server/app.ts +10 -0
  25. package/templates/elysia/src/questpie/server/collections/index.ts +1 -0
  26. package/templates/elysia/src/questpie/server/collections/posts.collection.ts +10 -0
  27. package/templates/elysia/src/questpie/server/config/auth.ts +8 -0
  28. package/templates/elysia/src/questpie/server/config/openapi.ts +10 -0
  29. package/templates/elysia/src/questpie/server/globals/index.ts +1 -0
  30. package/templates/elysia/src/questpie/server/globals/site-settings.global.ts +10 -0
  31. package/templates/elysia/src/questpie/server/modules.ts +8 -0
  32. package/templates/elysia/src/questpie/server/questpie.config.ts +21 -0
  33. package/templates/elysia/tsconfig.json +28 -0
  34. package/templates/hono/AGENTS.md +56 -0
  35. package/templates/hono/CLAUDE.md +39 -0
  36. package/templates/hono/Dockerfile +24 -0
  37. package/templates/hono/README.md +148 -0
  38. package/templates/hono/docker/init-extensions.sql +11 -0
  39. package/templates/hono/docker-compose.yml +21 -0
  40. package/templates/hono/env.example +16 -0
  41. package/templates/hono/gitignore +6 -0
  42. package/templates/hono/package.json +47 -0
  43. package/templates/hono/questpie.config.ts +12 -0
  44. package/templates/hono/src/index.ts +30 -0
  45. package/templates/hono/src/lib/auth-client.ts +32 -0
  46. package/templates/hono/src/lib/client.ts +13 -0
  47. package/templates/hono/src/lib/env.ts +24 -0
  48. package/templates/hono/src/lib/query-client.ts +18 -0
  49. package/templates/hono/src/lib/query.ts +18 -0
  50. package/templates/hono/src/questpie/server/.generated/context.gen.ts +200 -0
  51. package/templates/hono/src/questpie/server/.generated/entities.gen.ts +84 -0
  52. package/templates/hono/src/questpie/server/.generated/factories.ts +65 -0
  53. package/templates/hono/src/questpie/server/.generated/index.ts +131 -0
  54. package/templates/hono/src/questpie/server/.generated/names.gen.ts +25 -0
  55. package/templates/hono/src/questpie/server/app.ts +10 -0
  56. package/templates/hono/src/questpie/server/collections/index.ts +1 -0
  57. package/templates/hono/src/questpie/server/collections/posts.collection.ts +10 -0
  58. package/templates/hono/src/questpie/server/config/auth.ts +8 -0
  59. package/templates/hono/src/questpie/server/config/openapi.ts +10 -0
  60. package/templates/hono/src/questpie/server/globals/index.ts +1 -0
  61. package/templates/hono/src/questpie/server/globals/site-settings.global.ts +10 -0
  62. package/templates/hono/src/questpie/server/modules.ts +8 -0
  63. package/templates/hono/src/questpie/server/questpie.config.ts +21 -0
  64. package/templates/hono/tsconfig.json +28 -0
  65. package/templates/next/AGENTS.md +55 -0
  66. package/templates/next/CLAUDE.md +39 -0
  67. package/templates/next/Dockerfile +25 -0
  68. package/templates/next/README.md +148 -0
  69. package/templates/next/components.json +22 -0
  70. package/templates/next/docker/init-extensions.sql +11 -0
  71. package/templates/next/docker-compose.yml +21 -0
  72. package/templates/next/env.example +16 -0
  73. package/templates/next/gitignore +10 -0
  74. package/templates/next/next-env.d.ts +5 -0
  75. package/templates/next/next.config.ts +20 -0
  76. package/templates/next/package.json +54 -0
  77. package/templates/next/postcss.config.mjs +8 -0
  78. package/templates/next/public/.gitkeep +0 -0
  79. package/templates/next/questpie.config.ts +12 -0
  80. package/templates/next/src/app/admin/[[...all]]/page.tsx +34 -0
  81. package/templates/next/src/app/admin/admin.css +4 -0
  82. package/templates/next/src/app/admin/layout.tsx +63 -0
  83. package/templates/next/src/app/api/[...all]/route.ts +24 -0
  84. package/templates/next/src/app/layout.tsx +24 -0
  85. package/templates/next/src/app/not-found.tsx +18 -0
  86. package/templates/next/src/app/page.tsx +74 -0
  87. package/templates/next/src/app/providers.tsx +11 -0
  88. package/templates/next/src/lib/auth-client.ts +12 -0
  89. package/templates/next/src/lib/client.ts +13 -0
  90. package/templates/next/src/lib/env.ts +24 -0
  91. package/templates/next/src/lib/query-client.ts +18 -0
  92. package/templates/next/src/lib/query.ts +18 -0
  93. package/templates/next/src/questpie/admin/.generated/client.ts +13 -0
  94. package/templates/next/src/questpie/admin/admin.ts +9 -0
  95. package/templates/next/src/questpie/admin/modules.ts +3 -0
  96. package/templates/next/src/questpie/server/.generated/context.gen.ts +204 -0
  97. package/templates/next/src/questpie/server/.generated/entities.gen.ts +100 -0
  98. package/templates/next/src/questpie/server/.generated/factories.ts +204 -0
  99. package/templates/next/src/questpie/server/.generated/index.ts +139 -0
  100. package/templates/next/src/questpie/server/.generated/names.gen.ts +31 -0
  101. package/templates/next/src/questpie/server/app.ts +10 -0
  102. package/templates/next/src/questpie/server/collections/index.ts +1 -0
  103. package/templates/next/src/questpie/server/collections/posts.collection.ts +58 -0
  104. package/templates/next/src/questpie/server/config/admin.ts +80 -0
  105. package/templates/next/src/questpie/server/config/auth.ts +8 -0
  106. package/templates/next/src/questpie/server/config/openapi.ts +10 -0
  107. package/templates/next/src/questpie/server/globals/index.ts +1 -0
  108. package/templates/next/src/questpie/server/globals/site-settings.global.ts +19 -0
  109. package/templates/next/src/questpie/server/modules.ts +9 -0
  110. package/templates/next/src/questpie/server/questpie.config.ts +21 -0
  111. package/templates/next/src/styles.css +125 -0
  112. package/templates/next/tsconfig.json +37 -0
  113. package/templates/tanstack-start/AGENTS.md +35 -607
  114. package/templates/tanstack-start/CLAUDE.md +26 -134
  115. package/templates/tanstack-start/README.md +13 -1
  116. package/templates/tanstack-start/docker/init-extensions.sql +11 -0
  117. package/templates/tanstack-start/docker-compose.yml +1 -0
  118. package/templates/tanstack-start/src/lib/auth-client.ts +1 -1
  119. package/templates/tanstack-start/src/lib/client.ts +1 -1
  120. package/templates/tanstack-start/src/lib/query.ts +18 -0
  121. package/templates/tanstack-start/src/questpie/server/collections/index.ts +1 -1
  122. package/templates/tanstack-start/src/questpie/server/globals/index.ts +1 -1
  123. package/templates/tanstack-start/src/questpie/server/questpie.config.ts +1 -1
  124. package/templates/tanstack-start/src/routes/__root.tsx +31 -1
  125. package/templates/tanstack-start/src/routes/api/$.ts +1 -1
  126. package/templates/tanstack-start/src/routes/index.tsx +97 -0
  127. package/skills/questpie/AGENTS.md +0 -2871
  128. package/skills/questpie/SKILL.md +0 -293
  129. package/skills/questpie/coverage.json +0 -213
  130. package/skills/questpie/references/auth.md +0 -236
  131. package/skills/questpie/references/business-logic.md +0 -620
  132. package/skills/questpie/references/codegen-plugin-api.md +0 -382
  133. package/skills/questpie/references/crud-api.md +0 -580
  134. package/skills/questpie/references/data-modeling.md +0 -509
  135. package/skills/questpie/references/extend.md +0 -584
  136. package/skills/questpie/references/field-types.md +0 -398
  137. package/skills/questpie/references/infrastructure-adapters.md +0 -720
  138. package/skills/questpie/references/mcp.md +0 -147
  139. package/skills/questpie/references/multi-tenancy.md +0 -363
  140. package/skills/questpie/references/production.md +0 -640
  141. package/skills/questpie/references/query-operators.md +0 -125
  142. package/skills/questpie/references/quickstart.md +0 -562
  143. package/skills/questpie/references/rules.md +0 -454
  144. package/skills/questpie/references/sandbox.md +0 -110
  145. package/skills/questpie/references/tanstack-query.md +0 -543
  146. package/skills/questpie/references/type-inference.md +0 -167
  147. package/skills/questpie/references/workflows.md +0 -155
  148. package/skills/questpie-admin/AGENTS.md +0 -1515
  149. package/skills/questpie-admin/SKILL.md +0 -443
  150. package/skills/questpie-admin/references/blocks.md +0 -331
  151. package/skills/questpie-admin/references/custom-ui.md +0 -305
  152. package/skills/questpie-admin/references/views.md +0 -449
@@ -1,331 +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
- ---
5
-
6
- # QUESTPIE Blocks
7
-
8
- This skill builds on questpie-admin.
9
-
10
- 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.
11
-
12
- ```text
13
- Server: block("hero") Client: HeroRenderer
14
- .fields({ title, image }) -> Receives { values, data }
15
- .admin({ label, icon }) Returns JSX
16
- .prefetch({ with: {...} })
17
- ```
18
-
19
- ## Defining Blocks
20
-
21
- Blocks are defined in `blocks/` using the `block()` factory:
22
-
23
- ```ts title="blocks/hero.ts"
24
- import { block } from "#questpie/factories";
25
-
26
- export const heroBlock = block("hero")
27
- .admin(({ c }) => ({
28
- label: { en: "Hero Section", sk: "Hero sekcia" },
29
- icon: c.icon("ph:image"),
30
- category: "sections",
31
- }))
32
- .fields(({ f }) => ({
33
- title: f.text().localized().required(),
34
- subtitle: f.textarea().localized(),
35
- backgroundImage: f.upload({ to: "assets" }),
36
- overlayOpacity: f.number().default(60),
37
- alignment: f
38
- .select([
39
- { value: "left", label: "Left" },
40
- { value: "center", label: "Center" },
41
- { value: "right", label: "Right" },
42
- ])
43
- .default("center"),
44
- ctaText: f.text().localized(),
45
- ctaLink: f.text(),
46
- }))
47
- .prefetch({ with: { backgroundImage: true } });
48
- ```
49
-
50
- ### Admin Metadata
51
-
52
- ```ts
53
- .admin(({ c }) => ({
54
- label: { en: "Hero Section" }, // Display name in block picker
55
- icon: c.icon("ph:image"), // Icon in block picker (Phosphor set)
56
- category: "sections", // Group in block picker
57
- }))
58
- ```
59
-
60
- ### Multiple Blocks Per File
61
-
62
- Export multiple named blocks from one file:
63
-
64
- ```ts title="blocks/layout.ts"
65
- import { block } from "#questpie/factories";
66
-
67
- export const twoColumnBlock = block("twoColumn")
68
- .admin(({ c }) => ({
69
- label: { en: "Two Columns" },
70
- icon: c.icon("ph:columns"),
71
- category: "layout",
72
- }))
73
- .fields(({ f }) => ({
74
- left: f.blocks(),
75
- right: f.blocks(),
76
- }));
77
-
78
- export const spacerBlock = block("spacer")
79
- .admin(({ c }) => ({
80
- label: { en: "Spacer" },
81
- icon: c.icon("ph:arrows-out-line-vertical"),
82
- category: "layout",
83
- }))
84
- .fields(({ f }) => ({
85
- height: f
86
- .select([
87
- { value: "sm", label: "Small" },
88
- { value: "md", label: "Medium" },
89
- { value: "lg", label: "Large" },
90
- { value: "xl", label: "Extra Large" },
91
- ])
92
- .default("md"),
93
- }));
94
- ```
95
-
96
- ## Using Blocks in Collections
97
-
98
- Add a `blocks` field to any collection:
99
-
100
- ```ts title="collections/pages.ts"
101
- import { collection } from "#questpie/factories";
102
-
103
- export const pages = collection("pages").fields(({ f }) => ({
104
- title: f.text().required().localized(),
105
- slug: f.text().required(),
106
- content: f.blocks().localized(),
107
- }));
108
- ```
109
-
110
- The admin renders a visual block editor for this field.
111
-
112
- ## Prefetch
113
-
114
- Blocks often reference related data (images, linked records). Use `.prefetch()` to load them alongside block values.
115
-
116
- ### Declarative Prefetch
117
-
118
- ```ts
119
- .prefetch({
120
- with: {
121
- backgroundImage: true, // Load the full image record
122
- },
123
- })
124
- ```
125
-
126
- ### Nested Prefetch
127
-
128
- ```ts
129
- .prefetch({
130
- with: {
131
- featuredBarber: {
132
- with: {
133
- avatar: true,
134
- services: true,
135
- },
136
- },
137
- },
138
- })
139
- ```
140
-
141
- ### Functional Prefetch
142
-
143
- For complex queries, use a function. The `ctx` parameter provides fully typed `collections` and `globals` via `AppContext` augmentation — no imports needed:
144
-
145
- ```ts title="blocks/featured.ts"
146
- import { block } from "#questpie/factories";
147
-
148
- export const featuredBlock = block("featured")
149
- .fields(({ f }) => ({
150
- heading: f.text().required(),
151
- }))
152
- .prefetch(async ({ values, ctx }) => {
153
- return {
154
- posts: (await ctx.collections.posts.find({ limit: 5 })).docs,
155
- };
156
- });
157
- ```
158
-
159
- ### Using Prefetched Data in Renderers
160
-
161
- ```tsx
162
- function HeroRenderer({ values, data }: BlockProps<"hero">) {
163
- // values.backgroundImage = "asset-id-123" (just the ID)
164
- // data.backgroundImage = { url: "/api/assets/...", filename: "hero.jpg", ... }
165
-
166
- return (
167
- <section>
168
- {data?.backgroundImage?.url && (
169
- <img src={data.backgroundImage.url} alt="" />
170
- )}
171
- <h1>{values.title}</h1>
172
- </section>
173
- );
174
- }
175
- ```
176
-
177
- ## Block Renderers
178
-
179
- React components that receive block data and return JSX.
180
-
181
- ### Defining a Renderer
182
-
183
- ```tsx title="admin/blocks/hero.tsx"
184
- import type { BlockProps } from "../.generated/client";
185
-
186
- export function HeroRenderer({ values, data }: BlockProps<"hero">) {
187
- return (
188
- <section
189
- className="relative flex items-center justify-center"
190
- style={{ minHeight: "60vh" }}
191
- >
192
- {data?.backgroundImage?.url && (
193
- <img
194
- src={data.backgroundImage.url}
195
- alt=""
196
- className="absolute inset-0 w-full h-full object-cover"
197
- />
198
- )}
199
- <div className="relative text-center">
200
- <h1 className="text-5xl font-bold">{values.title}</h1>
201
- {values.subtitle && <p className="text-xl mt-4">{values.subtitle}</p>}
202
- {values.ctaText && (
203
- <a href={values.ctaLink} className="mt-6 inline-block btn">
204
- {values.ctaText}
205
- </a>
206
- )}
207
- </div>
208
- </section>
209
- );
210
- }
211
- ```
212
-
213
- ### BlockProps
214
-
215
- | Property | Type | Description |
216
- | ---------- | ----------- | -------------------------------------------------- |
217
- | `values` | `object` | Block field values (title, subtitle, etc.) |
218
- | `data` | `object` | Prefetched relation data (images, related records) |
219
- | `children` | `ReactNode` | Nested block content |
220
-
221
- ### Registering Renderers
222
-
223
- ```tsx title="admin/blocks/index.tsx"
224
- import { HeroRenderer } from "./hero";
225
- import { GalleryRenderer } from "./gallery";
226
- import { CTARenderer } from "./cta";
227
-
228
- export const renderers = {
229
- hero: HeroRenderer,
230
- gallery: GalleryRenderer,
231
- cta: CTARenderer,
232
- };
233
- ```
234
-
235
- ### Frontend Rendering
236
-
237
- Use block renderers on the public frontend:
238
-
239
- ```tsx title="components/page-renderer.tsx"
240
- import { renderers } from "@/questpie/admin/blocks";
241
-
242
- function PageRenderer({ page }) {
243
- return (
244
- <div>
245
- {page.content?.map((block, i) => {
246
- const Renderer = renderers[block.type];
247
- if (!Renderer) return null;
248
- return <Renderer key={i} values={block.values} data={block.data} />;
249
- })}
250
- </div>
251
- );
252
- }
253
- ```
254
-
255
- ## Common Mistakes
256
-
257
- 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).
258
-
259
- ```ts
260
- // WRONG — importing app creates circular dependency
261
- import { app } from "#questpie";
262
- .prefetch(async ({ values, ctx }) => {
263
- const posts = await app.collections.posts.find({});
264
- })
265
-
266
- // CORRECT — use ctx.collections directly
267
- .prefetch(async ({ values, ctx }) => {
268
- const posts = await ctx.collections.posts.find({});
269
- })
270
- ```
271
-
272
- 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.
273
-
274
- 3. **MEDIUM: Block renderer not exported as default or named export** — codegen discovers named exports from block renderer files. Ensure the component is exported.
275
-
276
- 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.
277
-
278
- 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`.
279
-
280
- 6. **LOW: Missing `category` in `.admin()`** — blocks without a category won't be grouped in the block picker, making it harder to find them.
281
-
282
- ## Blocks in Live Preview
283
-
284
- When a collection has `.preview()` configured, blocks participate in the existing Live Preview system through `BlockRenderer` and `PreviewField`. The form remains authoritative; block annotations only mirror values, focus fields, select blocks, or request inline scalar edits.
285
-
286
- ### Preferred: BlockRenderer
287
-
288
- Use `BlockRenderer` for normal frontend page rendering. It preserves `data-block-id`, routes block selection, and scopes nested `PreviewField` paths automatically.
289
-
290
- ```tsx
291
- <BlockRenderer
292
- content={preview.data.content}
293
- data={preview.data.content?._data}
294
- renderers={admin.blocks}
295
- selectedBlockId={preview.selectedBlockId}
296
- onBlockClick={preview.isPreviewMode ? preview.handleBlockClick : undefined}
297
- />
298
- ```
299
-
300
- Inside custom block renderers, annotate scalar values with `PreviewField`:
301
-
302
- ```tsx
303
- <PreviewField field="title" editable="text" as="h2">
304
- {values.title}
305
- </PreviewField>
306
- ```
307
-
308
- This resolves to `content._values.{blockId}.title` when rendered inside `BlockRenderer`.
309
-
310
- ### Manual BlockScopeProvider Wrapper
311
-
312
- Use `BlockScopeProvider` only when you manually render blocks outside `BlockRenderer`:
313
-
314
- ```tsx
315
- import { BlockScopeProvider } from "@questpie/admin/client";
316
-
317
- function PageRenderer({ blocks, previewData }) {
318
- return blocks.map((block) => {
319
- const Renderer = renderers[block.type];
320
- return (
321
- <BlockScopeProvider key={block.id} blockId={block.id}>
322
- <Renderer values={block.values} data={block.data} />
323
- </BlockScopeProvider>
324
- );
325
- });
326
- }
327
- ```
328
-
329
- `PreviewField` components inside the provider resolve paths like `content._values.{blockId}.title`.
330
-
331
- Inline block edits target `_values`, for example `content._values.<blockId>.title`. Tree edits such as add, remove, reorder, and nesting stay in the existing block editor and should trigger refresh or resync when patching is not safe.
@@ -1,305 +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
- ---
5
-
6
- # QUESTPIE Custom UI
7
-
8
- This skill builds on questpie-admin.
9
-
10
- Extend the QUESTPIE admin with custom field types, custom view types, custom components, and reactive field behaviors.
11
-
12
- ## Registries
13
-
14
- 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.
15
-
16
- ```text
17
- Server: f.text().required()
18
- |
19
- Generated: { type: "text", options: {...} }
20
- |
21
- Admin Client: fieldRegistry.get("text")
22
- |
23
- React: <TextFieldRenderer value={...} onChange={...} />
24
- ```
25
-
26
- ### Built-in Field Registry
27
-
28
- ```
29
- text -> TextInput
30
- textarea -> TextareaInput
31
- richText -> RichTextEditor (TipTap)
32
- number -> NumberInput
33
- boolean -> Checkbox / Switch
34
- date -> DatePicker
35
- datetime -> DateTimePicker
36
- select -> SelectDropdown
37
- relation -> RelationPicker
38
- upload -> FileUpload
39
- object -> NestedForm
40
- array -> RepeatableItems
41
- blocks -> BlockEditor
42
- json -> JSONEditor
43
- ```
44
-
45
- ### Extending Registries
46
-
47
- Place files in the admin directory. Codegen discovers them automatically:
48
-
49
- ```
50
- questpie/admin/
51
- fields/
52
- color.tsx # Custom color field renderer
53
- currency.tsx # Custom currency field renderer
54
- views/
55
- kanban.tsx # Custom kanban list view
56
- ```
57
-
58
- These are merged with built-in defaults during codegen and exported in `.generated/client.ts`.
59
-
60
- ## Custom Fields
61
-
62
- ### Server-Side Registration
63
-
64
- Register custom fields through modules:
65
-
66
- ```ts
67
- const myModule = module({
68
- name: "custom-fields",
69
- fields: {
70
- color: colorField,
71
- currency: currencyField,
72
- phone: phoneField,
73
- },
74
- });
75
- ```
76
-
77
- Once registered and codegen runs, the field becomes available on the `f` builder:
78
-
79
- ```ts
80
- .fields(({ f }) => ({
81
- brandColor: f.color().default("#000000"),
82
- price: f.currency({ currency: "USD" }),
83
- }))
84
- ```
85
-
86
- ### Admin Field Renderer
87
-
88
- Create a React component for the field's edit form:
89
-
90
- ```tsx title="admin/fields/color.tsx"
91
- import { Icon } from "@iconify/react";
92
-
93
- function ColorFieldRenderer({ value, onChange }) {
94
- return (
95
- <div className="flex items-center gap-2">
96
- <input
97
- type="color"
98
- value={value || "#000000"}
99
- onChange={(e) => onChange(e.target.value)}
100
- className="w-10 h-10 border border-border cursor-pointer"
101
- />
102
- <span className="font-mono text-sm text-muted-foreground">
103
- {value || "#000000"}
104
- </span>
105
- </div>
106
- );
107
- }
108
- ```
109
-
110
- ### Cell Renderer
111
-
112
- For custom table column rendering, provide a `cell` component alongside the field renderer:
113
-
114
- ```tsx title="admin/fields/color.tsx"
115
- // Cell component for list view table
116
- export function ColorCell({ value }) {
117
- return (
118
- <div className="flex items-center gap-2">
119
- <div
120
- className="w-4 h-4 border border-border"
121
- style={{ backgroundColor: value || "transparent" }}
122
- />
123
- <span className="text-xs font-mono">{value}</span>
124
- </div>
125
- );
126
- }
127
- ```
128
-
129
- ## Custom Views
130
-
131
- Create view types beyond built-in table and form — kanban boards, calendars, galleries.
132
-
133
- ### Server-Side Declaration
134
-
135
- ```ts
136
- const myModule = module({
137
- name: "custom-views",
138
- views: {
139
- kanban: kanbanViewDefinition,
140
- calendar: calendarViewDefinition,
141
- },
142
- });
143
- ```
144
-
145
- ### Usage in Collections
146
-
147
- ```ts
148
- .list(({ v }) => v.kanban({
149
- columns: "status",
150
- cardTitle: "title",
151
- }))
152
- ```
153
-
154
- ### Client Rendering
155
-
156
- ```tsx title="admin/views/kanban.tsx"
157
- function KanbanView({ data, columns, onDrop }) {
158
- return (
159
- <div className="flex gap-4">
160
- {columns.map((col) => (
161
- <div key={col.id} className="flex-1">
162
- <h3 className="font-mono text-sm font-semibold mb-2">{col.label}</h3>
163
- {data
164
- .filter((item) => item.status === col.id)
165
- .map((item) => (
166
- <div
167
- key={item.id}
168
- className="border border-border bg-card p-3 mb-2"
169
- >
170
- {item.title}
171
- </div>
172
- ))}
173
- </div>
174
- ))}
175
- </div>
176
- );
177
- }
178
- ```
179
-
180
- ## Reactive Field System
181
-
182
- Fields support reactive behaviors configured in the collection's `.form()` view or on the field definition itself.
183
-
184
- ### Conditional Visibility
185
-
186
- ```ts
187
- {
188
- field: f.cancellationReason,
189
- hidden: ({ data }) => data.status !== "cancelled",
190
- }
191
- ```
192
-
193
- ### Read-Only
194
-
195
- ```ts
196
- {
197
- field: f.customerName,
198
- readOnly: ({ data }) => !!data.customer,
199
- }
200
- ```
201
-
202
- ### Computed Values
203
-
204
- ```ts
205
- {
206
- field: f.slug,
207
- compute: {
208
- handler: ({ data }) => {
209
- if (data.name && !data.slug?.trim()) {
210
- return slugify(data.name);
211
- }
212
- return undefined;
213
- },
214
- deps: ({ data }) => [data.name, data.slug],
215
- debounce: 300,
216
- },
217
- }
218
- ```
219
-
220
- ### Dynamic Options (Server-Side)
221
-
222
- For select/relation fields with options that depend on other field values:
223
-
224
- ```ts
225
- city: f.relation("cities").admin({
226
- options: {
227
- handler: async ({ data, search, ctx }) => {
228
- const cities = await ctx.db.query.cities.findMany({
229
- where: { countryId: data.country },
230
- });
231
- return {
232
- options: cities.map((c) => ({ value: c.id, label: c.name })),
233
- };
234
- },
235
- deps: ({ data }) => [data.country],
236
- },
237
- }),
238
- ```
239
-
240
- The `handler` runs **server-side** with full access to `ctx.db`, `ctx.user`, `ctx.req`. It re-executes when any value in `deps` changes.
241
-
242
- ## UI Component Reference
243
-
244
- When building custom admin UI, use these patterns:
245
-
246
- ### Icons
247
-
248
- ```tsx
249
- import { Icon } from "@iconify/react";
250
-
251
- // Phosphor icon set with ph: prefix
252
- <Icon icon="ph:house" width={20} height={20} />
253
- <Icon icon="ph:caret-down-bold" width={16} height={16} /> // bold weight
254
- <Icon icon="ph:heart-fill" width={16} height={16} /> // fill weight
255
- ```
256
-
257
- ### Toasts
258
-
259
- ```tsx
260
- import { toast } from "sonner";
261
-
262
- toast.success("Record saved");
263
- toast.error("Failed to save");
264
- ```
265
-
266
- ### Primitives (base-ui)
267
-
268
- ```tsx
269
- // CORRECT — render prop
270
- <DialogTrigger render={<Button>Open</Button>} />
271
-
272
- // WRONG — asChild is Radix, not base-ui
273
- <DialogTrigger asChild><Button>Open</Button></DialogTrigger>
274
- ```
275
-
276
- ### Responsive Components
277
-
278
- - `ResponsivePopover` — Popover on desktop, Drawer on mobile
279
- - `ResponsiveDialog` — Dialog on desktop, fullscreen Drawer on mobile
280
- - Hooks: `useIsMobile()`, `useIsDesktop()`, `useMediaQuery()`
281
-
282
- ## Common Mistakes
283
-
284
- 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`.
285
-
286
- 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.
287
-
288
- 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.
289
-
290
- 4. **MEDIUM: Using `onChange` wrong in field components** — the field renderer receives `onChange` that expects the **value directly**, not a DOM event.
291
-
292
- ```tsx
293
- // WRONG
294
- onChange={(e) => onChange(e)}
295
- // CORRECT
296
- onChange={(e) => onChange(e.target.value)}
297
- // Or for non-DOM values:
298
- onChange={newValue}
299
- ```
300
-
301
- 5. **MEDIUM: Importing from `@radix-ui/*`** — QUESTPIE admin uses `@base-ui/react`. Never import Radix primitives.
302
-
303
- 6. **MEDIUM: Using `@phosphor-icons/react` or `lucide-react`** — use `@iconify/react` with `ph:` prefix for all icons.
304
-
305
- 7. **LOW: Not using shadcn components** — prefer `<Button>`, `<Card>`, `<Input>` from the shadcn component library instead of raw HTML elements. The admin follows QUESTPIE Neutral Design.