create-kide-app 0.0.7 → 0.0.9

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 (42) hide show
  1. package/index.js +143 -51
  2. package/package.json +1 -1
  3. package/templates/base/.env.example +2 -0
  4. package/templates/base/package.json +39 -0
  5. package/templates/base/src/cms/adapters/db.ts +49 -0
  6. package/templates/base/src/cms/adapters/email.ts +34 -0
  7. package/templates/base/src/cms/adapters/storage.ts +29 -0
  8. package/templates/base/src/cms/cms.config.ts +7 -0
  9. package/templates/base/src/cms/collections/users.ts +20 -0
  10. package/templates/base/src/cms/internals/create-admin.ts +40 -0
  11. package/templates/base/src/cms/internals/generator.ts +10 -0
  12. package/templates/base/src/cms/internals/runtime.ts +76 -0
  13. package/templates/base/src/env.d.ts +12 -0
  14. package/templates/base/src/pages/index.astro +11 -0
  15. package/templates/base/src/styles/admin.css +253 -0
  16. package/templates/base/src/styles/public.css +2 -0
  17. package/templates/base/tsconfig.json +11 -0
  18. package/templates/cloudflare/astro.config.mjs +1 -1
  19. package/templates/cloudflare/uploads-route.ts +1 -1
  20. package/templates/cloudflare/wrangler.toml +1 -0
  21. package/templates/demo/src/cms/cms.config.ts +17 -0
  22. package/templates/demo/src/cms/collections/authors.ts +20 -0
  23. package/templates/demo/src/cms/collections/front-page.ts +37 -0
  24. package/templates/demo/src/cms/collections/menus.ts +18 -0
  25. package/templates/demo/src/cms/collections/pages.ts +59 -0
  26. package/templates/demo/src/cms/collections/posts.ts +55 -0
  27. package/templates/demo/src/cms/collections/taxonomies.ts +18 -0
  28. package/templates/demo/src/cms/collections/users.ts +20 -0
  29. package/templates/demo/src/cms/internals/seed.data.ts +506 -0
  30. package/templates/demo/src/cms/internals/seed.ts +7 -0
  31. package/templates/demo/src/components/BlockRenderer.astro +32 -0
  32. package/templates/demo/src/components/RichTextContent.astro +18 -0
  33. package/templates/demo/src/components/blocks/Faq.astro +22 -0
  34. package/templates/demo/src/components/blocks/Hero.astro +16 -0
  35. package/templates/demo/src/components/blocks/Image.astro +25 -0
  36. package/templates/demo/src/components/blocks/Text.astro +16 -0
  37. package/templates/demo/src/layouts/PublicLayout.astro +54 -0
  38. package/templates/demo/src/pages/[...slug].astro +31 -0
  39. package/templates/demo/src/pages/blog/[slug].astro +41 -0
  40. package/templates/demo/src/pages/index.astro +51 -0
  41. package/templates/demo/src/scripts/preview.ts +33 -0
  42. package/templates/local/astro.config.mjs +1 -1
@@ -0,0 +1,253 @@
1
+ @import "tailwindcss";
2
+ @custom-variant dark (&:is(.dark *));
3
+
4
+ /* ── Light: cool blue-gray, neutral surfaces ─────────────────────── */
5
+ :root {
6
+ --background: oklch(0.985 0.002 247);
7
+ --foreground: oklch(0.145 0.015 285);
8
+ --card: oklch(1 0 0);
9
+ --card-foreground: oklch(0.145 0.015 285);
10
+ --popover: oklch(1 0 0);
11
+ --popover-foreground: oklch(0.145 0.015 285);
12
+ --primary: oklch(0.145 0.015 285);
13
+ --primary-foreground: oklch(0.985 0.002 247);
14
+ --secondary: oklch(0.968 0.003 264);
15
+ --secondary-foreground: oklch(0.373 0.013 285);
16
+ --muted: oklch(0.968 0.003 264);
17
+ --muted-foreground: oklch(0.556 0.01 264);
18
+ --accent: oklch(0.968 0.003 264);
19
+ --accent-foreground: oklch(0.145 0.015 285);
20
+ --destructive: oklch(0.577 0.245 27.325);
21
+ --border: oklch(0.929 0.006 264);
22
+ --input: oklch(0.872 0.009 264);
23
+ --ring: oklch(0.556 0.01 264);
24
+ --chart-1: oklch(0.623 0.214 259.815);
25
+ --chart-2: oklch(0.546 0.245 262.881);
26
+ --chart-3: oklch(0.488 0.243 264.376);
27
+ --chart-4: oklch(0.424 0.199 265.638);
28
+ --chart-5: oklch(0.809 0.105 251.813);
29
+ --sidebar: oklch(0.985 0.002 247);
30
+ --sidebar-foreground: oklch(0.145 0.015 285);
31
+ --sidebar-primary: oklch(0.145 0.015 285);
32
+ --sidebar-primary-foreground: oklch(0.985 0.002 247);
33
+ --sidebar-accent: oklch(0.968 0.003 264);
34
+ --sidebar-accent-foreground: oklch(0.373 0.013 285);
35
+ --sidebar-border: oklch(0.929 0.006 264);
36
+ --sidebar-ring: oklch(0.556 0.01 264);
37
+ --radius: 0.375rem;
38
+
39
+ /* Extended palette */
40
+ --color-surface: oklch(1 0 0);
41
+ --color-muted-strong: oklch(0.929 0.006 264);
42
+ --color-foreground-secondary: oklch(0.373 0.013 285);
43
+ --color-foreground-tertiary: oklch(0.446 0.011 264);
44
+ --color-placeholder: oklch(0.708 0.01 264);
45
+ --color-input-border: oklch(0.872 0.009 264);
46
+ --color-hover: oklch(0.968 0.003 264);
47
+ --color-destructive-subtle: oklch(0.971 0.013 17.38);
48
+ --color-accent-subtle: oklch(0.932 0.032 255);
49
+ }
50
+
51
+ /* ── Dark: deep blue-gray, elevated surfaces ─────────────────────── */
52
+ /* .dark class is set by inline script before paint (handles system/dark/light) */
53
+ .dark {
54
+ --background: oklch(0.175 0.014 265);
55
+ --foreground: oklch(0.96 0.004 265);
56
+ --card: oklch(0.215 0.014 265);
57
+ --card-foreground: oklch(0.96 0.004 265);
58
+ --popover: oklch(0.215 0.014 265);
59
+ --popover-foreground: oklch(0.96 0.004 265);
60
+ --primary: oklch(0.96 0.004 265);
61
+ --primary-foreground: oklch(0.175 0.014 265);
62
+ --secondary: oklch(0.255 0.014 265);
63
+ --secondary-foreground: oklch(0.78 0.006 265);
64
+ --muted: oklch(0.255 0.014 265);
65
+ --muted-foreground: oklch(0.6 0.006 265);
66
+ --accent: oklch(0.255 0.014 265);
67
+ --accent-foreground: oklch(0.96 0.004 265);
68
+ --destructive: oklch(0.704 0.191 22.216);
69
+ --border: oklch(0.3 0.012 265);
70
+ --input: oklch(0.36 0.012 265);
71
+ --ring: oklch(0.6 0.006 265);
72
+ --sidebar: oklch(0.175 0.014 265);
73
+ --sidebar-foreground: oklch(0.96 0.004 265);
74
+ --sidebar-primary: oklch(0.623 0.214 259.815);
75
+ --sidebar-primary-foreground: oklch(0.96 0.004 265);
76
+ --sidebar-accent: oklch(0.255 0.014 265);
77
+ --sidebar-accent-foreground: oklch(0.78 0.006 265);
78
+ --sidebar-border: oklch(0.3 0.012 265);
79
+ --sidebar-ring: oklch(0.6 0.006 265);
80
+
81
+ --color-surface: oklch(0.215 0.014 265);
82
+ --color-muted-strong: oklch(0.31 0.014 265);
83
+ --color-foreground-secondary: oklch(0.78 0.006 265);
84
+ --color-foreground-tertiary: oklch(0.72 0.006 265);
85
+ --color-placeholder: oklch(0.48 0.006 265);
86
+ --color-input-border: oklch(0.36 0.012 265);
87
+ --color-hover: oklch(0.245 0.014 265);
88
+ --color-destructive-subtle: oklch(0.24 0.05 25);
89
+ --color-accent-subtle: oklch(0.24 0.03 265);
90
+ }
91
+
92
+ @theme inline {
93
+ --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
94
+ --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", monospace;
95
+
96
+ --color-background: var(--background);
97
+ --color-foreground: var(--foreground);
98
+ --color-card: var(--card);
99
+ --color-card-foreground: var(--card-foreground);
100
+ --color-popover: var(--popover);
101
+ --color-popover-foreground: var(--popover-foreground);
102
+ --color-primary: var(--primary);
103
+ --color-primary-foreground: var(--primary-foreground);
104
+ --color-secondary: var(--secondary);
105
+ --color-secondary-foreground: var(--secondary-foreground);
106
+ --color-muted: var(--muted);
107
+ --color-muted-foreground: var(--muted-foreground);
108
+ --color-accent: var(--accent);
109
+ --color-accent-foreground: var(--accent-foreground);
110
+ --color-destructive: var(--destructive);
111
+ --color-border: var(--border);
112
+ --color-input: var(--input);
113
+ --color-ring: var(--ring);
114
+ --color-chart-1: var(--chart-1);
115
+ --color-chart-2: var(--chart-2);
116
+ --color-chart-3: var(--chart-3);
117
+ --color-chart-4: var(--chart-4);
118
+ --color-chart-5: var(--chart-5);
119
+ --color-sidebar: var(--sidebar);
120
+ --color-sidebar-foreground: var(--sidebar-foreground);
121
+ --color-sidebar-primary: var(--sidebar-primary);
122
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
123
+ --color-sidebar-accent: var(--sidebar-accent);
124
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
125
+ --color-sidebar-border: var(--sidebar-border);
126
+ --color-sidebar-ring: var(--sidebar-ring);
127
+
128
+ --radius-sm: calc(var(--radius) - 2px);
129
+ --radius-md: var(--radius);
130
+ --radius-lg: calc(var(--radius) + 2px);
131
+ --radius-xl: calc(var(--radius) + 6px);
132
+ }
133
+
134
+ @layer base {
135
+ * {
136
+ @apply border-border outline-ring/50;
137
+ }
138
+ body {
139
+ @apply bg-background text-foreground;
140
+ }
141
+ html {
142
+ @apply font-sans;
143
+ }
144
+ }
145
+
146
+ *:focus-visible {
147
+ outline: none;
148
+ }
149
+
150
+ /* ── Dark mode: native input controls, select arrow, focus ring ──── */
151
+ .dark input::-webkit-calendar-picker-indicator {
152
+ filter: invert(1);
153
+ }
154
+
155
+ .dark select {
156
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%239ca3af' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
157
+ }
158
+
159
+ .dark *:focus-visible {
160
+ --tw-ring-offset-color: var(--color-surface);
161
+ }
162
+
163
+ /* ── Strip focus styles from command input ────────────────────────── */
164
+ [data-slot="command-input"] {
165
+ border: none;
166
+ background: transparent;
167
+ padding: 0;
168
+ box-shadow: none;
169
+ outline: none;
170
+ }
171
+
172
+ [data-slot="command-input"]:focus,
173
+ [data-slot="command-input"]:focus-visible {
174
+ border: none;
175
+ box-shadow: none;
176
+ outline: none;
177
+ --tw-ring-shadow: none;
178
+ --tw-ring-offset-shadow: none;
179
+ }
180
+
181
+ /* ── Tiptap editor styles ────────────────────────────────────────── */
182
+ .tiptap {
183
+ outline: none;
184
+ }
185
+
186
+ .tiptap p {
187
+ margin: 0.5em 0;
188
+ color: inherit;
189
+ font-size: inherit;
190
+ line-height: 1.6;
191
+ }
192
+
193
+ .tiptap h2 {
194
+ font-size: 1.25rem;
195
+ font-weight: 600;
196
+ margin: 1em 0 0.5em;
197
+ }
198
+
199
+ .tiptap h3 {
200
+ font-size: 1.1rem;
201
+ font-weight: 600;
202
+ margin: 0.75em 0 0.4em;
203
+ }
204
+
205
+ .tiptap ul {
206
+ padding-left: 1.5em;
207
+ margin: 0.5em 0;
208
+ list-style-type: disc;
209
+ }
210
+
211
+ .tiptap ol {
212
+ padding-left: 1.5em;
213
+ margin: 0.5em 0;
214
+ list-style-type: decimal;
215
+ }
216
+
217
+ .tiptap li {
218
+ margin: 0.2em 0;
219
+ }
220
+
221
+ .tiptap blockquote {
222
+ border-left: 3px solid var(--border);
223
+ padding-left: 1em;
224
+ margin: 0.5em 0;
225
+ color: var(--muted-foreground);
226
+ }
227
+
228
+ /* ── Toast animations ────────────────────────────────────────────── */
229
+ @keyframes toast-in {
230
+ from {
231
+ opacity: 0;
232
+ transform: translateY(-12px) scale(0.96);
233
+ }
234
+ to {
235
+ opacity: 1;
236
+ transform: translateY(0) scale(1);
237
+ }
238
+ }
239
+ @keyframes toast-out {
240
+ from {
241
+ opacity: 1;
242
+ transform: translateY(0) scale(1);
243
+ }
244
+ to {
245
+ opacity: 0;
246
+ transform: translateY(-8px) scale(0.96);
247
+ }
248
+ }
249
+
250
+ /* ── Dark mode: Tiptap placeholder ───────────────────────────────── */
251
+ .dark .tiptap p.is-editor-empty:first-child::before {
252
+ color: var(--color-placeholder) !important;
253
+ }
@@ -0,0 +1,2 @@
1
+ @import "tailwindcss";
2
+ @plugin "@tailwindcss/typography";
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "astro/tsconfigs/strict",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "@/*": ["./src/*"]
7
+ }
8
+ },
9
+ "include": [".astro/types.d.ts", "**/*"],
10
+ "exclude": ["dist"]
11
+ }
@@ -3,7 +3,7 @@ import cloudflare from "@astrojs/cloudflare";
3
3
  import react from "@astrojs/react";
4
4
  import tailwindcss from "@tailwindcss/vite";
5
5
  import { defineConfig } from "astro/config";
6
- import cmsIntegration from "./src/cms/integration";
6
+ import cmsIntegration from "@kidecms/core/integration";
7
7
 
8
8
  // https://astro.build/config
9
9
  export default defineConfig({
@@ -1,5 +1,5 @@
1
1
  import type { APIRoute } from "astro";
2
- import { getFile } from "@/cms/core/storage";
2
+ import { getFile } from "@/cms/adapters/storage";
3
3
 
4
4
  export const prerender = false;
5
5
 
@@ -6,6 +6,7 @@ compatibility_flags = ["nodejs_compat"]
6
6
  binding = "CMS_DB"
7
7
  database_name = "{{PROJECT_NAME}}-db"
8
8
  database_id = "" # Run: npx wrangler d1 create {{PROJECT_NAME}}-db
9
+ migrations_dir = "src/cms/migrations"
9
10
 
10
11
  [triggers]
11
12
  crons = ["* * * * *"] # scheduled publishing — every minute
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from "@kidecms/core";
2
+ import users from "./collections/users";
3
+ import authors from "./collections/authors";
4
+ import posts from "./collections/posts";
5
+ import taxonomies from "./collections/taxonomies";
6
+ import menus from "./collections/menus";
7
+ import frontPage from "./collections/front-page";
8
+ import pages from "./collections/pages";
9
+
10
+ export default defineConfig({
11
+ database: { dialect: "sqlite" },
12
+ locales: {
13
+ default: "en",
14
+ supported: ["en", "de"],
15
+ },
16
+ collections: [users, authors, posts, taxonomies, menus, frontPage, pages],
17
+ });
@@ -0,0 +1,20 @@
1
+ import { defineCollection, fields } from "@kidecms/core";
2
+
3
+ export default defineCollection({
4
+ slug: "authors",
5
+ labels: { singular: "Author", plural: "Authors" },
6
+ labelField: "name",
7
+ timestamps: true,
8
+ views: {
9
+ list: { columns: ["name", "title", "_updatedAt"] },
10
+ },
11
+ fields: {
12
+ name: fields.text({ required: true }),
13
+ description: fields.text({ translatable: true }),
14
+ slug: fields.slug({ from: "name", unique: true, admin: { position: "sidebar" } }),
15
+ title: fields.text({ required: true }),
16
+ avatar: fields.image({
17
+ admin: { placeholder: "https://images.example.com/avatar.jpg" },
18
+ }),
19
+ },
20
+ });
@@ -0,0 +1,37 @@
1
+ import { defineCollection, fields, hasRole } from "@kidecms/core";
2
+
3
+ export default defineCollection({
4
+ slug: "front-page",
5
+ labels: { singular: "Front Page", plural: "Front Page" },
6
+ singleton: true,
7
+ preview: "/",
8
+ timestamps: true,
9
+ drafts: true,
10
+ access: {
11
+ publish: hasRole("admin"),
12
+ },
13
+ fields: {
14
+ blocks: fields.blocks({
15
+ translatable: true,
16
+ types: {
17
+ hero: {
18
+ eyebrow: fields.text(),
19
+ heading: fields.text({ required: true }),
20
+ body: fields.text(),
21
+ ctaLabel: fields.text(),
22
+ ctaHref: fields.relation({ collection: "pages" }),
23
+ },
24
+ text: {
25
+ heading: fields.text(),
26
+ content: fields.richText(),
27
+ },
28
+ faq: {
29
+ heading: fields.text(),
30
+ items: fields.json({
31
+ admin: { component: "repeater", help: "Add question and answer pairs" },
32
+ }),
33
+ },
34
+ },
35
+ }),
36
+ },
37
+ });
@@ -0,0 +1,18 @@
1
+ import { defineCollection, fields } from "@kidecms/core";
2
+
3
+ export default defineCollection({
4
+ slug: "menus",
5
+ labels: { singular: "Menu", plural: "Menus" },
6
+ timestamps: true,
7
+ views: {
8
+ list: { columns: ["name", "slug", "_updatedAt"] },
9
+ },
10
+ fields: {
11
+ name: fields.text({ required: true }),
12
+ slug: fields.slug({ from: "name", unique: true, admin: { position: "sidebar" } }),
13
+ items: fields.json({
14
+ translatable: true,
15
+ admin: { component: "menu-items" },
16
+ }),
17
+ },
18
+ });
@@ -0,0 +1,59 @@
1
+ import { defineCollection, fields, hasRole } from "@kidecms/core";
2
+
3
+ export default defineCollection({
4
+ slug: "pages",
5
+ labels: { singular: "Page", plural: "Pages" },
6
+ preview: true,
7
+ timestamps: true,
8
+ drafts: true,
9
+ versions: { max: 20 },
10
+ views: {
11
+ list: { columns: ["title", "_status", "_updatedAt"] },
12
+ },
13
+ fields: {
14
+ title: fields.text({ required: true, translatable: true }),
15
+ slug: fields.slug({ from: "title", unique: true, translatable: true, admin: { position: "sidebar" } }),
16
+ summary: fields.text({
17
+ translatable: true,
18
+ admin: { rows: 3 },
19
+ access: { read: hasRole("admin") },
20
+ }),
21
+ image: fields.image(),
22
+ relatedPosts: fields.relation({ collection: "posts", hasMany: true, admin: { position: "sidebar" } }),
23
+ seoDescription: fields.text({
24
+ maxLength: 160,
25
+ translatable: true,
26
+ admin: { rows: 3, help: "Meta description for search engines. Max 160 characters.", position: "sidebar" },
27
+ access: { update: hasRole("admin") },
28
+ }),
29
+ blocks: fields.blocks({
30
+ translatable: true,
31
+ types: {
32
+ text: {
33
+ heading: fields.text(),
34
+ content: fields.richText(),
35
+ },
36
+ image: {
37
+ images: fields.array({ of: fields.image(), defaultValue: [] }),
38
+ },
39
+ faq: {
40
+ heading: fields.text(),
41
+ items: fields.json({
42
+ admin: { component: "repeater", help: "Add question and answer pairs" },
43
+ }),
44
+ },
45
+ },
46
+ }),
47
+ },
48
+ hooks: {
49
+ afterPublish(doc, context) {
50
+ context.cache?.invalidate({ tags: ["pages", "home", `page:${doc._id}`] });
51
+ },
52
+ afterUpdate(doc, context) {
53
+ context.cache?.invalidate({ tags: ["pages", `page:${doc._id}`] });
54
+ },
55
+ afterDelete(doc, context) {
56
+ context.cache?.invalidate({ tags: ["pages", "home", `page:${doc._id}`] });
57
+ },
58
+ },
59
+ });
@@ -0,0 +1,55 @@
1
+ import { defineCollection, fields, richTextToPlainText } from "@kidecms/core";
2
+
3
+ export default defineCollection({
4
+ slug: "posts",
5
+ labels: { singular: "Post", plural: "Posts" },
6
+ pathPrefix: "blog",
7
+ timestamps: true,
8
+ drafts: true,
9
+ versions: { max: 20 },
10
+ views: {
11
+ list: { columns: ["title", "category", "_status", "_updatedAt"] },
12
+ },
13
+ fields: {
14
+ title: fields.text({
15
+ required: true,
16
+ indexed: true,
17
+ translatable: true,
18
+ }),
19
+ slug: fields.slug({ from: "title", unique: true, translatable: true, admin: { position: "sidebar" } }),
20
+ excerpt: fields.text({
21
+ maxLength: 300,
22
+ translatable: true,
23
+ admin: { rows: 3 },
24
+ }),
25
+ image: fields.image(),
26
+ body: fields.richText({ translatable: true, admin: { rows: 14 } }),
27
+ category: fields.text({
28
+ admin: { component: "taxonomy-select", placeholder: "categories", position: "sidebar" },
29
+ }),
30
+ author: fields.relation({ collection: "authors", admin: { position: "sidebar" } }),
31
+ seoDescription: fields.text({
32
+ maxLength: 160,
33
+ translatable: true,
34
+ admin: { rows: 3, help: "Meta description for search engines. Max 160 characters.", position: "sidebar" },
35
+ }),
36
+ },
37
+ hooks: {
38
+ beforeCreate(data) {
39
+ if (!data.excerpt && typeof data.body === "object" && data.body) {
40
+ const text = richTextToPlainText(data.body as never);
41
+ if (text) data.excerpt = text.slice(0, 180);
42
+ }
43
+ return data;
44
+ },
45
+ afterPublish(doc, context) {
46
+ context.cache?.invalidate({ tags: ["posts", "home", `post:${doc._id}`] });
47
+ },
48
+ afterUpdate(doc, context) {
49
+ context.cache?.invalidate({ tags: ["posts", `post:${doc._id}`] });
50
+ },
51
+ afterDelete(doc, context) {
52
+ context.cache?.invalidate({ tags: ["posts", "home", `post:${doc._id}`] });
53
+ },
54
+ },
55
+ });
@@ -0,0 +1,18 @@
1
+ import { defineCollection, fields } from "@kidecms/core";
2
+
3
+ export default defineCollection({
4
+ slug: "taxonomies",
5
+ labels: { singular: "Taxonomy", plural: "Taxonomies" },
6
+ timestamps: true,
7
+ views: {
8
+ list: { columns: ["name", "slug", "_updatedAt"] },
9
+ },
10
+ fields: {
11
+ name: fields.text({ required: true }),
12
+ slug: fields.slug({ from: "name", unique: true, admin: { position: "sidebar" } }),
13
+ terms: fields.json({
14
+ translatable: true,
15
+ admin: { component: "taxonomy-terms" },
16
+ }),
17
+ },
18
+ });
@@ -0,0 +1,20 @@
1
+ import { defineCollection, fields } from "@kidecms/core";
2
+
3
+ export default defineCollection({
4
+ slug: "users",
5
+ labels: { singular: "User", plural: "Users" },
6
+ auth: true,
7
+ timestamps: true,
8
+ views: {
9
+ list: { columns: ["name", "email", "role", "_updatedAt"] },
10
+ },
11
+ fields: {
12
+ name: fields.text({ required: true }),
13
+ email: fields.email({ required: true, unique: true }),
14
+ role: fields.select({
15
+ options: ["admin", "editor", "viewer"],
16
+ defaultValue: "editor",
17
+ }),
18
+ password: fields.text({ admin: { hidden: true } }),
19
+ },
20
+ });