@questpie/admin 3.0.0 → 3.0.2
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.
- package/README.md +76 -43
- package/dist/client/blocks/block-renderer.d.mts +2 -2
- package/dist/client/preview/block-scope-context.d.mts +2 -2
- package/dist/client/preview/preview-banner.d.mts +2 -2
- package/dist/client/preview/preview-field.d.mts +4 -4
- package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
- package/dist/client/views/pages/invite-page.d.mts +2 -2
- package/dist/components/rich-text/rich-text-renderer.d.mts +2 -2
- package/dist/server/modules/admin/block/prefetch.d.mts +4 -0
- package/dist/server/modules/admin/collections/account.d.mts +23 -23
- package/dist/server/modules/admin/collections/admin-locks.d.mts +54 -54
- package/dist/server/modules/admin/collections/admin-preferences.d.mts +35 -35
- package/dist/server/modules/admin/collections/admin-saved-views.d.mts +47 -47
- package/dist/server/modules/admin/collections/apikey.d.mts +68 -68
- package/dist/server/modules/admin/collections/assets.d.mts +20 -20
- package/dist/server/modules/admin/collections/session.d.mts +42 -42
- package/dist/server/modules/admin/collections/user.d.mts +28 -28
- package/dist/server/modules/admin/collections/verification.d.mts +36 -36
- package/dist/server/modules/admin/routes/admin-config.d.mts +2 -2
- package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
- package/dist/server/modules/admin/routes/locales.d.mts +2 -2
- package/dist/server/modules/admin/routes/preview.d.mts +11 -11
- package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
- package/dist/server/modules/admin/routes/setup.d.mts +7 -7
- package/dist/server/modules/admin/routes/translations.d.mts +4 -4
- package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
- package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +23 -23
- package/dist/server/modules/audit/.generated/module.d.mts +6 -6
- package/dist/server/modules/audit/collections/audit-log.d.mts +37 -37
- package/dist/server/modules/audit/jobs/audit-cleanup.d.mts +2 -2
- package/package.json +4 -3
- package/skills/questpie-admin/SKILL.md +0 -397
- package/skills/questpie-admin/blocks/SKILL.md +0 -305
- package/skills/questpie-admin/custom-ui/SKILL.md +0 -307
- package/skills/questpie-admin/views/SKILL.md +0 -442
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as questpie_shared0 from "questpie/shared";
|
|
2
|
-
import * as
|
|
2
|
+
import * as questpie21 from "questpie";
|
|
3
3
|
import * as drizzle_orm_pg_core0 from "drizzle-orm/pg-core";
|
|
4
4
|
import * as drizzle_orm0 from "drizzle-orm";
|
|
5
5
|
import * as questpie_src_server_modules_core_fields_email_js0 from "questpie/src/server/modules/core/fields/email.js";
|
|
@@ -19,22 +19,22 @@ import * as questpie_src_server_fields_operators_builtin_js0 from "questpie/src/
|
|
|
19
19
|
* - update: disallowed
|
|
20
20
|
* - read: allowed (for admin UI display)
|
|
21
21
|
*/
|
|
22
|
-
declare const auditLogCollection:
|
|
23
|
-
readonly text: typeof
|
|
24
|
-
readonly textarea: typeof
|
|
22
|
+
declare const auditLogCollection: questpie21.CollectionBuilder<questpie_shared0.Override<questpie_shared0.Override<questpie_shared0.Override<questpie_shared0.Override<questpie_shared0.Override<questpie21.EmptyCollectionState<"admin_audit_log", undefined, {
|
|
23
|
+
readonly text: typeof questpie21.text;
|
|
24
|
+
readonly textarea: typeof questpie21.textarea;
|
|
25
25
|
readonly email: typeof questpie_src_server_modules_core_fields_email_js0.email;
|
|
26
|
-
readonly url: typeof
|
|
27
|
-
readonly number: typeof
|
|
28
|
-
readonly boolean: typeof
|
|
29
|
-
readonly date: typeof
|
|
30
|
-
readonly datetime: typeof
|
|
31
|
-
readonly time: typeof
|
|
32
|
-
readonly select: typeof
|
|
33
|
-
readonly upload: typeof
|
|
34
|
-
readonly relation: typeof
|
|
35
|
-
readonly object: typeof
|
|
26
|
+
readonly url: typeof questpie21.url;
|
|
27
|
+
readonly number: typeof questpie21.number;
|
|
28
|
+
readonly boolean: typeof questpie21.boolean;
|
|
29
|
+
readonly date: typeof questpie21.date;
|
|
30
|
+
readonly datetime: typeof questpie21.datetime;
|
|
31
|
+
readonly time: typeof questpie21.time;
|
|
32
|
+
readonly select: typeof questpie21.select;
|
|
33
|
+
readonly upload: typeof questpie21.upload;
|
|
34
|
+
readonly relation: typeof questpie21.relation;
|
|
35
|
+
readonly object: typeof questpie21.object;
|
|
36
36
|
readonly json: typeof questpie_src_server_modules_core_fields_json_js0.json;
|
|
37
|
-
readonly from: typeof
|
|
37
|
+
readonly from: typeof questpie21.from;
|
|
38
38
|
}>, {
|
|
39
39
|
fields: {
|
|
40
40
|
readonly action: drizzle_orm0.NotNull<drizzle_orm_pg_core0.PgVarcharBuilder<[string, ...string[]]>>;
|
|
@@ -52,98 +52,98 @@ 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:
|
|
55
|
+
readonly action: questpie21.FieldWithMethods<Omit<questpie21.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
|
+
}, questpie21.TextFieldMethods>;
|
|
61
61
|
/** Resource type: collection, global, auth, system, custom */
|
|
62
|
-
readonly resourceType:
|
|
62
|
+
readonly resourceType: questpie21.FieldWithMethods<Omit<questpie21.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
|
+
}, questpie21.TextFieldMethods>;
|
|
68
68
|
/** Resource slug (collection/global name) */
|
|
69
|
-
readonly resource:
|
|
69
|
+
readonly resource: questpie21.FieldWithMethods<Omit<questpie21.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
|
+
}, questpie21.TextFieldMethods>;
|
|
75
75
|
/** ID of the specific record (null for globals) */
|
|
76
|
-
readonly resourceId:
|
|
76
|
+
readonly resourceId: questpie21.FieldWithMethods<questpie21.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
|
+
}, questpie21.TextFieldMethods>;
|
|
84
84
|
/** Denormalized display label for the affected record */
|
|
85
|
-
readonly resourceLabel:
|
|
85
|
+
readonly resourceLabel: questpie21.FieldWithMethods<questpie21.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
|
+
}, questpie21.TextFieldMethods>;
|
|
93
93
|
/** User who performed the action */
|
|
94
|
-
readonly userId:
|
|
94
|
+
readonly userId: questpie21.FieldWithMethods<questpie21.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
|
+
}, questpie21.TextFieldMethods>;
|
|
102
102
|
/** Denormalized user display name */
|
|
103
|
-
readonly userName:
|
|
103
|
+
readonly userName: questpie21.FieldWithMethods<questpie21.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
|
+
}, questpie21.TextFieldMethods>;
|
|
111
111
|
/** Locale context of the operation */
|
|
112
|
-
readonly locale:
|
|
112
|
+
readonly locale: questpie21.FieldWithMethods<questpie21.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
|
+
}, questpie21.TextFieldMethods>;
|
|
120
120
|
/** Field-level diffs: { field: { from, to } } or null */
|
|
121
|
-
readonly changes:
|
|
121
|
+
readonly changes: questpie21.Field<questpie21.DefaultFieldState & {
|
|
122
122
|
type: "json";
|
|
123
|
-
data:
|
|
123
|
+
data: questpie21.JsonValue;
|
|
124
124
|
column: drizzle_orm_pg_core0.PgJsonbBuilder;
|
|
125
125
|
operators: typeof questpie_src_server_fields_operators_builtin_js0.basicOps;
|
|
126
126
|
} & {
|
|
127
127
|
label: questpie_shared0.I18nText;
|
|
128
128
|
}>;
|
|
129
129
|
/** Extra context metadata */
|
|
130
|
-
readonly metadata:
|
|
130
|
+
readonly metadata: questpie21.Field<questpie21.DefaultFieldState & {
|
|
131
131
|
type: "json";
|
|
132
|
-
data:
|
|
132
|
+
data: questpie21.JsonValue;
|
|
133
133
|
column: drizzle_orm_pg_core0.PgJsonbBuilder;
|
|
134
134
|
operators: typeof questpie_src_server_fields_operators_builtin_js0.basicOps;
|
|
135
135
|
} & {
|
|
136
136
|
label: questpie_shared0.I18nText;
|
|
137
137
|
}>;
|
|
138
138
|
/** Human-readable title: "User Action ResourceType 'ResourceLabel'" */
|
|
139
|
-
readonly title:
|
|
139
|
+
readonly title: questpie21.FieldWithMethods<questpie21.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
|
+
}, questpie21.TextFieldMethods>;
|
|
147
147
|
};
|
|
148
148
|
}>, {
|
|
149
149
|
options: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as questpie151 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:
|
|
9
|
+
declare const auditCleanupJob: questpie151.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.
|
|
3
|
+
"version": "3.0.2",
|
|
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.
|
|
63
|
+
"@questpie/tanstack-query": "^3.0.2",
|
|
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.
|
|
89
|
+
"questpie": "^3.0.2",
|
|
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`.
|