@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,6 +1,6 @@
1
1
  import { auditLogCollection } from "../collections/audit-log.mjs";
2
2
  import { auditCleanupJob } from "../jobs/audit-cleanup.mjs";
3
- import * as questpie55 from "questpie";
3
+ import * as questpie64 from "questpie";
4
4
 
5
5
  //#region src/server/modules/audit/.generated/module.d.ts
6
6
  interface AuditCollections {
@@ -43,13 +43,13 @@ declare const _module: {
43
43
  app: {
44
44
  hooks: {
45
45
  collections: {
46
- afterChange: (ctx: questpie55.GlobalCollectionHookContext) => Promise<void>;
47
- afterDelete: (ctx: questpie55.GlobalCollectionHookContext) => Promise<void>;
48
- afterTransition: (ctx: questpie55.GlobalCollectionTransitionHookContext) => Promise<void>;
46
+ afterChange: (ctx: questpie64.GlobalCollectionHookContext) => Promise<void>;
47
+ afterDelete: (ctx: questpie64.GlobalCollectionHookContext) => Promise<void>;
48
+ afterTransition: (ctx: questpie64.GlobalCollectionTransitionHookContext) => Promise<void>;
49
49
  };
50
50
  globals: {
51
- afterChange: (ctx: questpie55.GlobalGlobalHookContext) => Promise<void>;
52
- afterTransition: (ctx: questpie55.GlobalGlobalTransitionHookContext) => Promise<void>;
51
+ afterChange: (ctx: questpie64.GlobalGlobalHookContext) => Promise<void>;
52
+ afterTransition: (ctx: questpie64.GlobalGlobalTransitionHookContext) => Promise<void>;
53
53
  };
54
54
  };
55
55
  };
@@ -52,71 +52,71 @@ declare const auditLogCollection: questpie16.CollectionBuilder<questpie_shared0.
52
52
  localized: readonly string[];
53
53
  fieldDefinitions: {
54
54
  /** Action performed: create, update, delete, transition, custom */
55
- readonly action: questpie16.Field<Omit<questpie16.TextFieldState, "notNull" | "column"> & {
55
+ readonly action: questpie16.FieldWithMethods<Omit<questpie16.TextFieldState, "notNull" | "column"> & {
56
56
  notNull: true;
57
57
  column: drizzle_orm0.NotNull<drizzle_orm_pg_core0.PgVarcharBuilder<[string, ...string[]]>>;
58
58
  } & {
59
59
  label: questpie_shared0.I18nText;
60
- }>;
60
+ }, questpie16.TextFieldMethods>;
61
61
  /** Resource type: collection, global, auth, system, custom */
62
- readonly resourceType: questpie16.Field<Omit<questpie16.TextFieldState, "notNull" | "column"> & {
62
+ readonly resourceType: questpie16.FieldWithMethods<Omit<questpie16.TextFieldState, "notNull" | "column"> & {
63
63
  notNull: true;
64
64
  column: drizzle_orm0.NotNull<drizzle_orm_pg_core0.PgVarcharBuilder<[string, ...string[]]>>;
65
65
  } & {
66
66
  label: questpie_shared0.I18nText;
67
- }>;
67
+ }, questpie16.TextFieldMethods>;
68
68
  /** Resource slug (collection/global name) */
69
- readonly resource: questpie16.Field<Omit<questpie16.TextFieldState, "notNull" | "column"> & {
69
+ readonly resource: questpie16.FieldWithMethods<Omit<questpie16.TextFieldState, "notNull" | "column"> & {
70
70
  notNull: true;
71
71
  column: drizzle_orm0.NotNull<drizzle_orm_pg_core0.PgVarcharBuilder<[string, ...string[]]>>;
72
72
  } & {
73
73
  label: questpie_shared0.I18nText;
74
- }>;
74
+ }, questpie16.TextFieldMethods>;
75
75
  /** ID of the specific record (null for globals) */
76
- readonly resourceId: questpie16.Field<questpie16.DefaultFieldState & {
76
+ readonly resourceId: questpie16.FieldWithMethods<questpie16.DefaultFieldState & {
77
77
  type: "text";
78
78
  data: string;
79
79
  column: drizzle_orm_pg_core0.PgVarcharBuilder<[string, ...string[]]>;
80
80
  operators: typeof questpie_src_server_fields_operators_builtin_js0.stringOps;
81
81
  } & {
82
82
  label: questpie_shared0.I18nText;
83
- }>;
83
+ }, questpie16.TextFieldMethods>;
84
84
  /** Denormalized display label for the affected record */
85
- readonly resourceLabel: questpie16.Field<questpie16.DefaultFieldState & {
85
+ readonly resourceLabel: questpie16.FieldWithMethods<questpie16.DefaultFieldState & {
86
86
  type: "text";
87
87
  data: string;
88
88
  column: drizzle_orm_pg_core0.PgVarcharBuilder<[string, ...string[]]>;
89
89
  operators: typeof questpie_src_server_fields_operators_builtin_js0.stringOps;
90
90
  } & {
91
91
  label: questpie_shared0.I18nText;
92
- }>;
92
+ }, questpie16.TextFieldMethods>;
93
93
  /** User who performed the action */
94
- readonly userId: questpie16.Field<questpie16.DefaultFieldState & {
94
+ readonly userId: questpie16.FieldWithMethods<questpie16.DefaultFieldState & {
95
95
  type: "text";
96
96
  data: string;
97
97
  column: drizzle_orm_pg_core0.PgVarcharBuilder<[string, ...string[]]>;
98
98
  operators: typeof questpie_src_server_fields_operators_builtin_js0.stringOps;
99
99
  } & {
100
100
  label: questpie_shared0.I18nText;
101
- }>;
101
+ }, questpie16.TextFieldMethods>;
102
102
  /** Denormalized user display name */
103
- readonly userName: questpie16.Field<questpie16.DefaultFieldState & {
103
+ readonly userName: questpie16.FieldWithMethods<questpie16.DefaultFieldState & {
104
104
  type: "text";
105
105
  data: string;
106
106
  column: drizzle_orm_pg_core0.PgVarcharBuilder<[string, ...string[]]>;
107
107
  operators: typeof questpie_src_server_fields_operators_builtin_js0.stringOps;
108
108
  } & {
109
109
  label: questpie_shared0.I18nText;
110
- }>;
110
+ }, questpie16.TextFieldMethods>;
111
111
  /** Locale context of the operation */
112
- readonly locale: questpie16.Field<questpie16.DefaultFieldState & {
112
+ readonly locale: questpie16.FieldWithMethods<questpie16.DefaultFieldState & {
113
113
  type: "text";
114
114
  data: string;
115
115
  column: drizzle_orm_pg_core0.PgVarcharBuilder<[string, ...string[]]>;
116
116
  operators: typeof questpie_src_server_fields_operators_builtin_js0.stringOps;
117
117
  } & {
118
118
  label: questpie_shared0.I18nText;
119
- }>;
119
+ }, questpie16.TextFieldMethods>;
120
120
  /** Field-level diffs: { field: { from, to } } or null */
121
121
  readonly changes: questpie16.Field<questpie16.DefaultFieldState & {
122
122
  type: "json";
@@ -136,14 +136,14 @@ declare const auditLogCollection: questpie16.CollectionBuilder<questpie_shared0.
136
136
  label: questpie_shared0.I18nText;
137
137
  }>;
138
138
  /** Human-readable title: "User Action ResourceType 'ResourceLabel'" */
139
- readonly title: questpie16.Field<questpie16.DefaultFieldState & {
139
+ readonly title: questpie16.FieldWithMethods<questpie16.DefaultFieldState & {
140
140
  type: "text";
141
141
  data: string;
142
142
  column: drizzle_orm_pg_core0.PgVarcharBuilder<[string, ...string[]]>;
143
143
  operators: typeof questpie_src_server_fields_operators_builtin_js0.stringOps;
144
144
  } & {
145
145
  label: questpie_shared0.I18nText;
146
- }>;
146
+ }, questpie16.TextFieldMethods>;
147
147
  };
148
148
  }>, {
149
149
  options: {
@@ -1,4 +1,4 @@
1
- import * as questpie120 from "questpie";
1
+ import * as questpie132 from "questpie";
2
2
 
3
3
  //#region src/server/modules/audit/jobs/audit-cleanup.d.ts
4
4
 
@@ -6,7 +6,7 @@ import * as questpie120 from "questpie";
6
6
  * Audit log cleanup job.
7
7
  * Runs daily (via cron) to delete entries older than the configured retention period.
8
8
  */
9
- declare const auditCleanupJob: questpie120.JobDefinition<{
9
+ declare const auditCleanupJob: questpie132.JobDefinition<{
10
10
  retentionDays: number;
11
11
  }, void, "audit-cleanup">;
12
12
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@questpie/admin",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/questpie/questpie.git",
@@ -60,7 +60,7 @@
60
60
  "@fontsource-variable/jetbrains-mono": "^5.2.8",
61
61
  "@hookform/resolvers": "^5.1.0",
62
62
  "@iconify/react": "^6.0.2",
63
- "@questpie/tanstack-query": "^3.0.0",
63
+ "@questpie/tanstack-query": "^3.0.1",
64
64
  "@tailwindcss/vite": "^4.0.6",
65
65
  "@tiptap/core": "^2.x",
66
66
  "@tiptap/extension-character-count": "^2.x",
@@ -86,7 +86,7 @@
86
86
  "date-fns": "^4.1.0",
87
87
  "lowlight": "^3.x",
88
88
  "next-themes": "^0.4.6",
89
- "questpie": "^3.0.0",
89
+ "questpie": "^3.0.1",
90
90
  "react-day-picker": "^9.12.0",
91
91
  "react-hook-form": "^7.54.0",
92
92
  "react-resizable-panels": "^4.4.2",
@@ -101,6 +101,7 @@
101
101
  },
102
102
  "devDependencies": {
103
103
  "@rollup/plugin-babel": "^6.1.0",
104
+ "bun-types": "latest",
104
105
  "@tanstack/db": "^0.1.1",
105
106
  "@types/react": "^19.2.0",
106
107
  "@types/react-dom": "^19.2.0",
@@ -1,397 +0,0 @@
1
- ---
2
- name: questpie-admin
3
- description: QUESTPIE admin panel setup branding theming sidebar dashboard media localization live-preview auth dark-mode CSS variables configuration
4
- type: skill
5
- ---
6
-
7
- # QUESTPIE Admin Panel
8
-
9
- The QUESTPIE admin panel is a **projection of your server schema** — not the framework itself. It reads collections, globals, and config via introspection and generates a full admin interface. Your backend works without it.
10
-
11
- ## Sub-Skills
12
-
13
- | Skill | Covers |
14
- | ------------------------------------------------ | -------------------------------------------------------------------------------------- |
15
- | [questpie-admin/views](./views/SKILL.md) | List views, form views, dashboard, sidebar, filters, bulk actions, visibility, history |
16
- | [questpie-admin/blocks](./blocks/SKILL.md) | Block definitions, fields, prefetch, renderers, block picker |
17
- | [questpie-admin/custom-ui](./custom-ui/SKILL.md) | Custom fields, custom views, registries, reactive fields, widgets |
18
-
19
- ## Tech Stack
20
-
21
- - **React** + **Tailwind CSS v4** + **shadcn** components
22
- - **@base-ui/react** primitives (NOT @radix-ui)
23
- - **@iconify/react** with Phosphor icon set (`ph:icon-name`)
24
- - **sonner** for toasts — `toast.error()`, `toast.success()`
25
- - Brutalist flat design: `--radius: 0px`, no shadows
26
-
27
- ## Setup
28
-
29
- ### 1. Install
30
-
31
- ```bash
32
- bun add @questpie/admin
33
- ```
34
-
35
- ### 2. Server Plugin
36
-
37
- ```ts title="questpie.config.ts"
38
- import { adminPlugin } from "@questpie/admin/plugin";
39
- import { runtimeConfig } from "questpie";
40
-
41
- export default runtimeConfig({
42
- plugins: [adminPlugin()],
43
- app: { url: process.env.APP_URL || "http://localhost:3000" },
44
- db: { url: process.env.DATABASE_URL },
45
- secret: process.env.APP_SECRET,
46
- });
47
- ```
48
-
49
- The plugin tells codegen to discover admin-specific file conventions: `sidebar.ts`, `dashboard.ts`, `branding.ts`, `blocks/`, `admin-locale.ts`.
50
-
51
- ### 3. Modules
52
-
53
- ```ts title="modules.ts"
54
- import { adminModule, auditModule } from "@questpie/admin/server";
55
-
56
- export default [adminModule, auditModule] as const;
57
- ```
58
-
59
- | Module | Provides |
60
- | ------------- | ------------------------------------- |
61
- | `adminModule` | User collection, auth pages, admin UI |
62
- | `auditModule` | Audit log collection, timeline widget |
63
-
64
- ### 4. Convention Files
65
-
66
- ```ts title="sidebar.ts"
67
- import { sidebar } from "#questpie";
68
-
69
- export default sidebar({
70
- sections: [
71
- { id: "overview", title: { en: "Overview" } },
72
- { id: "content", title: { en: "Content" } },
73
- ],
74
- items: [
75
- {
76
- sectionId: "overview",
77
- type: "link",
78
- label: { en: "Dashboard" },
79
- href: "/admin",
80
- icon: { type: "icon", props: { name: "ph:house" } },
81
- },
82
- {
83
- sectionId: "content",
84
- type: "collection",
85
- collection: "posts",
86
- },
87
- ],
88
- });
89
- ```
90
-
91
- ```ts title="branding.ts"
92
- import { branding } from "#questpie";
93
-
94
- export default branding({
95
- name: { en: "My App Admin" },
96
- });
97
- ```
98
-
99
- ### 5. Codegen
100
-
101
- ```bash
102
- bunx questpie generate
103
- ```
104
-
105
- ### 6. Mount the Admin
106
-
107
- ```ts title="routes/admin/$.tsx"
108
- import { AdminRouter } from "@questpie/admin/client";
109
- import { admin } from "@/questpie/admin/admin";
110
-
111
- export default function AdminPage() {
112
- return <AdminRouter admin={admin} />;
113
- }
114
- ```
115
-
116
- The admin client config is auto-generated by codegen at `admin/.generated/client.ts`. No manual builder setup needed.
117
-
118
- ## Branding
119
-
120
- ```ts title="branding.ts"
121
- import { branding } from "#questpie";
122
-
123
- export default branding({
124
- name: { en: "Barbershop Control", sk: "Riadenie barbershopu" },
125
- });
126
- ```
127
-
128
- | Option | Type | Description |
129
- | ------ | ---------------- | -------------------------------- |
130
- | `name` | `string \| i18n` | App name shown in sidebar header |
131
- | `logo` | `string` | Logo URL or path |
132
-
133
- ## Theming (CSS Variables)
134
-
135
- The admin uses CSS variables for all theming. Override them in your own CSS file.
136
-
137
- ### Light Theme (`:root`)
138
-
139
- | Variable | Default | Purpose |
140
- | ---------------------- | --------- | -------------------------------- |
141
- | `--background` | `#FFFFFF` | Page background |
142
- | `--foreground` | `#0A0A0A` | Primary text |
143
- | `--card` | `#F8F8F8` | Cards, panels, sidebar |
144
- | `--popover` | `#FFFFFF` | Dropdowns, tooltips, dialogs |
145
- | `--muted` | `#F0F0F0` | Hover states, table headers |
146
- | `--muted-foreground` | `#666666` | Secondary text, placeholders |
147
- | `--primary` | `#B700FF` | Brand accent (CTAs, focus rings) |
148
- | `--primary-foreground` | `#FFFFFF` | Text on primary backgrounds |
149
- | `--destructive` | `#FF3D57` | Errors, delete actions |
150
- | `--success` | `#00E676` | Positive states |
151
- | `--warning` | `#FFB300` | Caution states |
152
- | `--info` | `#40C4FF` | Informational emphasis |
153
- | `--border` | `#E0E0E0` | All structural borders |
154
- | `--ring` | `#B700FF` | Focus ring color |
155
- | `--radius` | `0px` | Border radius (0 = brutalist) |
156
-
157
- ### Dark Theme (`.dark` class)
158
-
159
- Dark mode uses the `.dark` class on the root element. Key overrides:
160
-
161
- | Variable | Dark Value |
162
- | -------------- | ---------- |
163
- | `--background` | `#0A0A0A` |
164
- | `--foreground` | `#FFFFFF` |
165
- | `--card` | `#111111` |
166
- | `--border` | `#333333` |
167
- | `--muted` | `#1A1A1A` |
168
-
169
- ### Typography
170
-
171
- | Variable | Value |
172
- | ------------- | ------------------------------------------------------------------- |
173
- | `--font-sans` | `"Geist Variable"` — body text, descriptions |
174
- | `--font-mono` | `"JetBrains Mono Variable"` — UI chrome: nav, buttons, tabs, badges |
175
-
176
- ### Sidebar Variables
177
-
178
- Separate tokens for independent sidebar theming: `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-accent`, `--sidebar-border`, `--sidebar-ring`.
179
-
180
- ### Custom Theme
181
-
182
- 1. Copy the admin CSS file
183
- 2. Change variable values
184
- 3. Import your copy instead
185
- 4. Zero component changes needed
186
-
187
- ## Content Localization
188
-
189
- When collections have `.localized()` fields, the admin shows a locale switcher:
190
-
191
- ```ts title="questpie.config.ts"
192
- export default runtimeConfig({
193
- localization: {
194
- defaultLocale: "en",
195
- locales: [
196
- { code: "en", label: { en: "English" }, flagCountryCode: "gb" },
197
- { code: "sk", label: { en: "Slovak" } },
198
- ],
199
- },
200
- });
201
- ```
202
-
203
- The admin tracks content locale separately from UI locale. Only localized field values change when switching.
204
-
205
- ## Media & Uploads
206
-
207
- ```ts
208
- avatar: f.upload({
209
- to: "assets",
210
- mimeTypes: ["image/*"],
211
- maxSize: 5_000_000,
212
- }),
213
- ```
214
-
215
- The admin renders drag-and-drop upload, image preview, file info, and remove button.
216
-
217
- ## Live Preview (V2)
218
-
219
- Live Preview V2 uses a direct `postMessage` patch bus for instant same-tab feedback. Save/autosave is persistence only — NOT the live transport. Realtime (SSE/WebSocket) is reserved for detached or shared preview sessions.
220
-
221
- For full architecture details, see: `/docs/workspace/live-preview/architecture`
222
-
223
- ### Server Config
224
-
225
- Add `.preview()` to a collection to enable split-screen editing:
226
-
227
- ```ts title="collections/pages.ts"
228
- export const pages = collection("pages")
229
- .fields(({ f }) => ({
230
- title: f.text({ required: true, localized: true }),
231
- slug: f.text({ required: true }),
232
- content: f.blocks({ localized: true }),
233
- }))
234
- .preview({
235
- url: ({ record }) => {
236
- const slug = record.slug as string;
237
- return slug === "home" ? "/?preview=true" : `/${slug}?preview=true`;
238
- },
239
- watch: ["title", "slug", "content"],
240
- strategy: "hybrid", // "instant" | "server" | "hybrid"
241
- });
242
- ```
243
-
244
- | Strategy | Behavior |
245
- | ----------- | ------------------------------------------------------------------- |
246
- | `"instant"` | Patches applied locally only — fastest, no server round-trip |
247
- | `"server"` | Every change round-trips through server before preview updates |
248
- | `"hybrid"` | Local patches for instant response + server reconcile derived data |
249
-
250
- ### Frontend Integration
251
-
252
- Use `useQuestpiePreview` (replaces the old `useCollectionPreview`) with `<PreviewRoot>`, `<PreviewField>`, and `<PreviewBlock>` components:
253
-
254
- ```tsx
255
- import { useQuestpiePreview, PreviewRoot, PreviewField } from "@questpie/admin/client";
256
-
257
- function PagePreview({ initialData }) {
258
- const router = useRouter();
259
- const { data } = useQuestpiePreview({
260
- initialData,
261
- reconcile: () => router.invalidate(),
262
- });
263
-
264
- return (
265
- <PreviewRoot>
266
- <h1><PreviewField path="title">{data.title}</PreviewField></h1>
267
- </PreviewRoot>
268
- );
269
- }
270
- ```
271
-
272
- ### Key Principles
273
-
274
- - Same-tab preview = direct `postMessage` patch bus (NOT save-driven reload)
275
- - Save/autosave = persistence only
276
- - Realtime = extension for detached/shared preview only
277
- - Each message carries `sessionId`, `seq`, `timestamp`, `protocolVersion`
278
- - Preview wrappers must prevent accidental navigation in the iframe
279
-
280
- ## History & Versions
281
-
282
- Enable `auditModule` for activity timeline. Enable `.versioning()` on collections for snapshot restore:
283
-
284
- ```ts
285
- export const pages = collection("pages")
286
- .versioning({ drafts: true, maxVersions: 20 })
287
- .fields(({ f }) => ({ ... }));
288
- ```
289
-
290
- ## Scope (Multi-Tenancy)
291
-
292
- The admin provides scope primitives for multi-tenant applications. Import from `@questpie/admin/client`.
293
-
294
- ### ScopeProvider
295
-
296
- Wraps the admin to enable scope selection. Manages scope ID in React state and persists to localStorage.
297
-
298
- ```tsx
299
- import { ScopeProvider } from "@questpie/admin/client";
300
-
301
- <ScopeProvider
302
- headerName="x-selected-workspace" // HTTP header for scope ID
303
- storageKey="admin-workspace" // localStorage key
304
- defaultScope={null} // default value
305
- >
306
- <AdminLayout>...</AdminLayout>
307
- </ScopeProvider>;
308
- ```
309
-
310
- ### ScopePicker
311
-
312
- Dropdown for selecting the current scope. Place in sidebar via `slots.afterBrand`:
313
-
314
- ```tsx
315
- import { ScopePicker } from "@questpie/admin/client";
316
-
317
- <AdminLayout
318
- admin={admin}
319
- basePath="/admin"
320
- slots={{
321
- afterBrand: (
322
- <div className="px-3 py-2 border-b">
323
- <ScopePicker
324
- collection="workspaces"
325
- labelField="name"
326
- allowClear
327
- compact
328
- />
329
- </div>
330
- ),
331
- }}
332
- />;
333
- ```
334
-
335
- Three data sources: `collection` (queries a collection), `options` (static array), `loadOptions` (async function).
336
-
337
- ### useScopedFetch / createScopedFetch
338
-
339
- Inject scope header into all API calls:
340
-
341
- ```tsx
342
- import { useScopedFetch, createScopedFetch } from "@questpie/admin/client";
343
-
344
- // React hook
345
- const scopedFetch = useScopedFetch();
346
- const client = useMemo(
347
- () => createClient({ baseURL: "/api", fetch: scopedFetch }),
348
- [scopedFetch],
349
- );
350
-
351
- // Non-React
352
- const scopedFetch = createScopedFetch("x-selected-workspace", () =>
353
- getScopeId(),
354
- );
355
- ```
356
-
357
- ### useScope / useScopeSafe
358
-
359
- Access current scope state in any component:
360
-
361
- ```tsx
362
- import { useScope, useScopeSafe } from "@questpie/admin/client";
363
-
364
- const { scopeId, setScope, clearScope, headerName } = useScope(); // throws outside ScopeProvider
365
- const scope = useScopeSafe(); // returns null outside ScopeProvider
366
- ```
367
-
368
- For the full server-side setup (context resolver, type augmentation, access rules), see the `questpie-core/multi-tenancy` skill.
369
-
370
- ## Common Mistakes
371
-
372
- 1. **CRITICAL: Using `asChild` prop** — QUESTPIE admin uses `@base-ui/react`, which uses the `render` prop. `asChild` is a Radix pattern and does NOT work here.
373
-
374
- ```tsx
375
- // WRONG
376
- <DialogTrigger asChild><Button>Open</Button></DialogTrigger>
377
- // CORRECT
378
- <DialogTrigger render={<Button>Open</Button>} />
379
- ```
380
-
381
- 2. **CRITICAL: Importing from `@radix-ui/*`** — use `@base-ui/react` instead.
382
-
383
- 3. **HIGH: Using `@phosphor-icons/react`** — use `@iconify/react` with `ph:` prefix.
384
-
385
- ```tsx
386
- // WRONG
387
- import { CaretDown } from "@phosphor-icons/react";
388
- // CORRECT
389
- import { Icon } from "@iconify/react";
390
- <Icon icon="ph:caret-down" width={16} height={16} />;
391
- ```
392
-
393
- 4. **HIGH: Using lucide-react icons** — use `@iconify/react` with Phosphor icon set.
394
-
395
- 5. **MEDIUM: Custom `<button>` or `<div>` instead of shadcn components** — use `<Button>`, `<Card>`, etc.
396
-
397
- 6. **MEDIUM: `console.error` for user errors** — use `toast.error()` from `sonner`.