@kidecms/core 0.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 (93) hide show
  1. package/README.md +28 -0
  2. package/admin/components/AdminCard.astro +25 -0
  3. package/admin/components/AiGenerateButton.tsx +102 -0
  4. package/admin/components/AssetsGrid.tsx +711 -0
  5. package/admin/components/BlockEditor.tsx +996 -0
  6. package/admin/components/CheckboxField.tsx +31 -0
  7. package/admin/components/DocumentActions.tsx +317 -0
  8. package/admin/components/DocumentLock.tsx +54 -0
  9. package/admin/components/DocumentsDataTable.tsx +804 -0
  10. package/admin/components/FieldControl.astro +397 -0
  11. package/admin/components/FocalPointSelector.tsx +100 -0
  12. package/admin/components/ImageBrowseDialog.tsx +176 -0
  13. package/admin/components/ImagePicker.tsx +149 -0
  14. package/admin/components/InternalLinkPicker.tsx +80 -0
  15. package/admin/components/LiveHeading.tsx +17 -0
  16. package/admin/components/MobileSidebar.tsx +29 -0
  17. package/admin/components/RelationField.tsx +204 -0
  18. package/admin/components/RichTextEditor.tsx +685 -0
  19. package/admin/components/SelectField.tsx +65 -0
  20. package/admin/components/SidebarUserMenu.tsx +99 -0
  21. package/admin/components/SlugField.tsx +77 -0
  22. package/admin/components/TaxonomySelect.tsx +52 -0
  23. package/admin/components/Toast.astro +40 -0
  24. package/admin/components/TreeItemsEditor.tsx +790 -0
  25. package/admin/components/TreeSelect.tsx +166 -0
  26. package/admin/components/UnsavedGuard.tsx +181 -0
  27. package/admin/components/tree-utils.ts +86 -0
  28. package/admin/components/ui/alert-dialog.tsx +92 -0
  29. package/admin/components/ui/badge.tsx +83 -0
  30. package/admin/components/ui/button.tsx +53 -0
  31. package/admin/components/ui/card.tsx +70 -0
  32. package/admin/components/ui/checkbox.tsx +28 -0
  33. package/admin/components/ui/collapsible.tsx +26 -0
  34. package/admin/components/ui/command.tsx +88 -0
  35. package/admin/components/ui/dialog.tsx +92 -0
  36. package/admin/components/ui/dropdown-menu.tsx +259 -0
  37. package/admin/components/ui/input.tsx +20 -0
  38. package/admin/components/ui/label.tsx +20 -0
  39. package/admin/components/ui/popover.tsx +42 -0
  40. package/admin/components/ui/select.tsx +165 -0
  41. package/admin/components/ui/separator.tsx +21 -0
  42. package/admin/components/ui/sheet.tsx +104 -0
  43. package/admin/components/ui/skeleton.tsx +7 -0
  44. package/admin/components/ui/table.tsx +74 -0
  45. package/admin/components/ui/textarea.tsx +18 -0
  46. package/admin/components/ui/tooltip.tsx +52 -0
  47. package/admin/layouts/AdminLayout.astro +340 -0
  48. package/admin/lib/utils.ts +19 -0
  49. package/dist/admin.js +92 -0
  50. package/dist/ai.js +67 -0
  51. package/dist/api.js +827 -0
  52. package/dist/assets.js +163 -0
  53. package/dist/auth.js +132 -0
  54. package/dist/blocks.js +110 -0
  55. package/dist/content.js +29 -0
  56. package/dist/create-admin.js +23 -0
  57. package/dist/define.js +36 -0
  58. package/dist/generator.js +370 -0
  59. package/dist/image.js +69 -0
  60. package/dist/index.js +16 -0
  61. package/dist/integration.js +256 -0
  62. package/dist/locks.js +37 -0
  63. package/dist/richtext.js +1 -0
  64. package/dist/runtime.js +26 -0
  65. package/dist/schema.js +13 -0
  66. package/dist/seed.js +84 -0
  67. package/dist/values.js +102 -0
  68. package/middleware/auth.ts +100 -0
  69. package/package.json +102 -0
  70. package/routes/api/cms/[collection]/[...path].ts +366 -0
  71. package/routes/api/cms/ai/alt-text.ts +25 -0
  72. package/routes/api/cms/ai/seo.ts +25 -0
  73. package/routes/api/cms/ai/translate.ts +31 -0
  74. package/routes/api/cms/assets/[id].ts +82 -0
  75. package/routes/api/cms/assets/folders.ts +81 -0
  76. package/routes/api/cms/assets/index.ts +23 -0
  77. package/routes/api/cms/assets/upload.ts +112 -0
  78. package/routes/api/cms/auth/invite.ts +166 -0
  79. package/routes/api/cms/auth/login.ts +124 -0
  80. package/routes/api/cms/auth/logout.ts +33 -0
  81. package/routes/api/cms/auth/setup.ts +77 -0
  82. package/routes/api/cms/cron/publish.ts +33 -0
  83. package/routes/api/cms/img/[...path].ts +24 -0
  84. package/routes/api/cms/locks/[...path].ts +37 -0
  85. package/routes/api/cms/preview/render.ts +36 -0
  86. package/routes/api/cms/references/[collection]/[id].ts +60 -0
  87. package/routes/pages/admin/[...path].astro +1104 -0
  88. package/routes/pages/admin/assets/[id].astro +183 -0
  89. package/routes/pages/admin/assets/index.astro +58 -0
  90. package/routes/pages/admin/invite.astro +116 -0
  91. package/routes/pages/admin/login.astro +57 -0
  92. package/routes/pages/admin/setup.astro +91 -0
  93. package/virtual.d.ts +61 -0
@@ -0,0 +1,183 @@
1
+ ---
2
+ import { ArrowLeft, Trash2 } from "lucide-react";
3
+
4
+ import AdminCard from "@kidecms/core/admin/components/AdminCard.astro";
5
+ import AiGenerateButton from "@kidecms/core/admin/components/AiGenerateButton";
6
+ import FocalPointSelector from "@kidecms/core/admin/components/FocalPointSelector";
7
+ import UnsavedGuard from "@kidecms/core/admin/components/UnsavedGuard";
8
+ import TreeSelect, { flattenByParent } from "@kidecms/core/admin/components/TreeSelect";
9
+ import { buttonVariants } from "@kidecms/core/admin/components/ui/button";
10
+ import { Label } from "@kidecms/core/admin/components/ui/label";
11
+ import { Textarea } from "@kidecms/core/admin/components/ui/textarea";
12
+ import config from "virtual:kide/config";
13
+ import { assets, folders, isAiEnabled } from "virtual:kide/runtime";
14
+ import { formatDate, initDateFormat } from "@kidecms/core";
15
+ import AdminLayout from "@kidecms/core/admin/layouts/AdminLayout.astro";
16
+
17
+ export const prerender = false;
18
+
19
+ const user = Astro.locals.user;
20
+ const accessibleCollections = config.collections.filter((c) => {
21
+ const rule = c.access?.read;
22
+ if (!rule) return true;
23
+ return rule({ user: user ?? null, doc: null, operation: "read", collection: c.slug });
24
+ });
25
+
26
+ initDateFormat(config, Astro.cookies.get("tz")?.value);
27
+ const { id } = Astro.params;
28
+ if (!id) return Astro.redirect("/admin/assets");
29
+
30
+ const asset = await assets.findById(id);
31
+ if (!asset) return Astro.redirect("/admin/assets");
32
+
33
+ const aiEnabled = isAiEnabled();
34
+ const isImage = asset.mimeType.startsWith("image/");
35
+ const currentFolder = asset.folder ? await folders.findById(asset.folder) : null;
36
+ const allFolders = await folders.findAll();
37
+
38
+ const formatSize = (bytes: number) => {
39
+ if (bytes < 1024) return `${bytes} B`;
40
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
41
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
42
+ };
43
+
44
+ const folderItems = flattenByParent(
45
+ allFolders,
46
+ (f) => f._id,
47
+ (f) => f.name,
48
+ (f) => f.parent,
49
+ );
50
+ ---
51
+
52
+ <AdminLayout
53
+ title={`${asset.filename} | Assets | Kide CMS`}
54
+ collections={accessibleCollections}
55
+ activeCollection="assets"
56
+ customNav={config.admin?.nav}
57
+ >
58
+ <section class="space-y-6">
59
+ <div class="flex items-start justify-between gap-4">
60
+ <div>
61
+ <a
62
+ href={asset.folder ? `/admin/assets?folder=${asset.folder}` : "/admin/assets"}
63
+ class="text-muted-foreground hover:text-foreground mb-2 inline-flex items-center gap-1 text-sm transition-colors"
64
+ >
65
+ <ArrowLeft className="size-3.5" />
66
+ {currentFolder ? currentFolder.name : "Assets"}
67
+ </a>
68
+ <h1 class="text-2xl font-semibold tracking-tight">{asset.filename}</h1>
69
+ </div>
70
+ </div>
71
+
72
+ <div class="flex flex-wrap items-center justify-end gap-2">
73
+ <button type="submit" form="asset-form" value="save" disabled class={buttonVariants({ variant: "outline" })}>
74
+ Save
75
+ </button>
76
+ <form method="post" action={`/api/cms/assets/${asset._id}`}>
77
+ <input type="hidden" name="_method" value="DELETE" />
78
+ <button type="submit" class={buttonVariants({ variant: "destructive" })}>
79
+ <Trash2 className="size-3.5" />
80
+ Delete
81
+ </button>
82
+ </form>
83
+ </div>
84
+
85
+ <UnsavedGuard client:load formId="asset-form" isNew={false} isDraft={false} />
86
+ <form id="asset-form" method="post" action={`/api/cms/assets/${asset._id}`}>
87
+ <input type="hidden" name="_action" value="update" />
88
+ <div class="grid gap-6 2xl:grid-cols-[minmax(0,1.55fr)_minmax(380px,460px)]">
89
+ <div class="space-y-6">
90
+ {
91
+ isImage && (
92
+ <AdminCard title="Preview">
93
+ <FocalPointSelector
94
+ client:load
95
+ src={asset.url}
96
+ alt={asset.alt ?? asset.filename}
97
+ focalX={asset.focalX}
98
+ focalY={asset.focalY}
99
+ />
100
+ </AdminCard>
101
+ )
102
+ }
103
+ </div>
104
+
105
+ <div class="space-y-6 text-sm [&_input]:text-sm [&_textarea]:text-sm">
106
+ <AdminCard title="Info">
107
+ <dl class="grid gap-3">
108
+ <div>
109
+ <dt class="text-muted-foreground text-xs">Filename</dt>
110
+ <dd class="mt-0.5">{asset.filename}</dd>
111
+ </div>
112
+ <div>
113
+ <dt class="text-muted-foreground text-xs">Type</dt>
114
+ <dd class="mt-0.5">{asset.mimeType}</dd>
115
+ </div>
116
+ <div>
117
+ <dt class="text-muted-foreground text-xs">Size</dt>
118
+ <dd class="mt-0.5">{formatSize(asset.size)}</dd>
119
+ </div>
120
+ {
121
+ asset.width && asset.height && (
122
+ <div>
123
+ <dt class="text-muted-foreground text-xs">Dimensions</dt>
124
+ <dd class="mt-0.5">
125
+ {asset.width} &times; {asset.height} px
126
+ </dd>
127
+ </div>
128
+ )
129
+ }
130
+ <div>
131
+ <dt class="text-muted-foreground text-xs">Path</dt>
132
+ <dd class="mt-0.5 font-mono text-xs break-all">{asset.storagePath}</dd>
133
+ </div>
134
+ <div>
135
+ <dt class="text-muted-foreground text-xs">Created</dt>
136
+ <dd class="mt-0.5">{formatDate(asset._createdAt)}</dd>
137
+ </div>
138
+ </dl>
139
+ </AdminCard>
140
+
141
+ <AdminCard title="Details">
142
+ <div class="grid gap-5">
143
+ <div class="grid gap-2">
144
+ <Label htmlFor="folder">Folder</Label>
145
+ <TreeSelect
146
+ client:load
147
+ name="folder"
148
+ value={asset.folder ?? ""}
149
+ placeholder="Select folder..."
150
+ searchPlaceholder="Search folders..."
151
+ emptyMessage="No folders found."
152
+ items={folderItems}
153
+ />
154
+ </div>
155
+ <div class="grid gap-2">
156
+ <Label htmlFor="alt">Alt text</Label>
157
+ <Textarea
158
+ className="w-full shadow-none"
159
+ id="alt"
160
+ name="alt"
161
+ rows={3}
162
+ defaultValue={asset.alt ?? ""}
163
+ placeholder="Describe the image for accessibility..."
164
+ />
165
+ {
166
+ aiEnabled && isImage && (
167
+ <AiGenerateButton
168
+ client:load
169
+ endpoint="/api/cms/ai/alt-text"
170
+ payload={{ imageUrl: asset.url, filename: asset.filename }}
171
+ targetField="alt"
172
+ label="Generate alt text"
173
+ />
174
+ )
175
+ }
176
+ </div>
177
+ </div>
178
+ </AdminCard>
179
+ </div>
180
+ </div>
181
+ </form>
182
+ </section>
183
+ </AdminLayout>
@@ -0,0 +1,58 @@
1
+ ---
2
+ import AssetsGrid from "@kidecms/core/admin/components/AssetsGrid";
3
+ import config from "virtual:kide/config";
4
+ import { assets, folders } from "virtual:kide/runtime";
5
+ import AdminLayout from "@kidecms/core/admin/layouts/AdminLayout.astro";
6
+
7
+ export const prerender = false;
8
+
9
+ const user = Astro.locals.user;
10
+ const accessibleCollections = config.collections.filter((c) => {
11
+ const rule = c.access?.read;
12
+ if (!rule) return true;
13
+ return rule({ user: user ?? null, doc: null, operation: "read", collection: c.slug });
14
+ });
15
+
16
+ const currentFolderId = Astro.url.searchParams.get("folder") || null;
17
+
18
+ // Get current folder details if we're inside one
19
+ const currentFolder = currentFolderId ? await folders.findById(currentFolderId) : null;
20
+ if (currentFolderId && !currentFolder) return Astro.redirect("/admin/assets");
21
+
22
+ // Get subfolders and assets for current location
23
+ const subfolders = await folders.findByParent(currentFolderId);
24
+ const folderAssets = await assets.find({ limit: 100, folder: currentFolderId });
25
+
26
+ // Build breadcrumb trail
27
+ const breadcrumbs: Array<{ label: string; href: string; id: string }> = [];
28
+ if (currentFolder) {
29
+ let folder: typeof currentFolder | null = currentFolder;
30
+ const chain: typeof breadcrumbs = [];
31
+ while (folder) {
32
+ chain.unshift({ label: folder.name, href: `/admin/assets?folder=${folder._id}`, id: folder._id });
33
+ folder = folder.parent ? await folders.findById(folder.parent) : null;
34
+ }
35
+ breadcrumbs.push(...chain);
36
+ }
37
+
38
+ // Serialize data for the React island
39
+ const folderData = subfolders.map((f) => ({ _id: f._id, name: f.name }));
40
+ const assetData = folderAssets.map((a) => ({
41
+ _id: a._id,
42
+ filename: a.filename,
43
+ mimeType: a.mimeType,
44
+ url: a.url,
45
+ alt: a.alt,
46
+ _createdAt: a._createdAt,
47
+ }));
48
+ ---
49
+
50
+ <AdminLayout title="Assets | Kide CMS" collections={accessibleCollections} activeCollection="assets" customNav={config.admin?.nav}>
51
+ <AssetsGrid
52
+ client:load
53
+ folders={folderData}
54
+ assets={assetData}
55
+ breadcrumbs={breadcrumbs}
56
+ currentFolderId={currentFolderId}
57
+ />
58
+ </AdminLayout>
@@ -0,0 +1,116 @@
1
+ ---
2
+ import "virtual:kide/admin-css";
3
+
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@kidecms/core/admin/components/ui/card";
5
+ import { Input } from "@kidecms/core/admin/components/ui/input";
6
+ import { Label } from "@kidecms/core/admin/components/ui/label";
7
+ import { buttonVariants } from "@kidecms/core/admin/components/ui/button";
8
+ import { validateInvite } from "virtual:kide/runtime";
9
+ import { getDb } from "virtual:kide/db";
10
+ import { eq } from "drizzle-orm";
11
+
12
+ export const prerender = false;
13
+
14
+ const token = Astro.url.searchParams.get("token") ?? "";
15
+ const error = Astro.url.searchParams.get("error");
16
+
17
+ let userEmail = "";
18
+ let valid = false;
19
+
20
+ if (token) {
21
+ const invite = await validateInvite(token);
22
+ if (invite) {
23
+ valid = true;
24
+ const db = await getDb();
25
+ const schema = await import("virtual:kide/schema");
26
+ const tables = schema.cmsTables as Record<string, { main: any }>;
27
+ if (tables.users) {
28
+ const rows = await db.select().from(tables.users.main).where(eq(tables.users.main._id, invite.userId)).limit(1);
29
+ if (rows.length > 0) userEmail = String((rows[0] as any).email);
30
+ }
31
+ }
32
+ }
33
+
34
+ const errorMessage =
35
+ error === "missing"
36
+ ? "Name and password are required."
37
+ : error === "password"
38
+ ? "Passwords do not match."
39
+ : error === "short"
40
+ ? "Password must be at least 8 characters."
41
+ : error === "expired" || error === "invalid"
42
+ ? "This invite link has expired or already been used."
43
+ : null;
44
+ ---
45
+
46
+ <!doctype html>
47
+ <html lang="en">
48
+ <head>
49
+ <meta charset="utf-8" />
50
+ <meta name="viewport" content="width=device-width" />
51
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
52
+ <link rel="icon" href="/favicon.ico" />
53
+ <title>Accept Invitation | Kide CMS</title>
54
+ <script is:inline>
55
+ (function () {
56
+ var t = localStorage.getItem("admin-theme") || "system";
57
+ var dark = t === "dark" || (t === "system" && matchMedia("(prefers-color-scheme: dark)").matches);
58
+ if (dark) document.documentElement.classList.add("dark");
59
+ })();
60
+ </script>
61
+ </head>
62
+ <body class="bg-background text-foreground min-h-screen antialiased">
63
+ <div class="flex min-h-screen items-center justify-center px-4">
64
+ <div class="w-full max-w-sm space-y-6">
65
+ <Card>
66
+ <CardHeader>
67
+ <CardTitle>Set up your account</CardTitle>
68
+ <CardDescription>
69
+ {valid ? `Complete your account for ${userEmail}` : "Accept your invitation to Kide CMS"}
70
+ </CardDescription>
71
+ </CardHeader>
72
+ <CardContent>
73
+ {
74
+ errorMessage && (
75
+ <div class="border-destructive/50 bg-destructive/10 text-destructive mb-4 rounded-lg border px-3 py-2 text-sm">
76
+ {errorMessage}
77
+ </div>
78
+ )
79
+ }
80
+ {
81
+ valid ? (
82
+ <form method="post" action="/api/cms/auth/invite" class="grid gap-4">
83
+ <input type="hidden" name="_action" value="accept" />
84
+ <input type="hidden" name="token" value={token} />
85
+ <div class="grid gap-2">
86
+ <Label htmlFor="name">Name</Label>
87
+ <Input id="name" name="name" type="text" placeholder="Your name" required autoFocus />
88
+ </div>
89
+ <div class="grid gap-2">
90
+ <Label htmlFor="password">Password</Label>
91
+ <Input id="password" name="password" type="password" placeholder="Min 8 characters" required />
92
+ </div>
93
+ <div class="grid gap-2">
94
+ <Label htmlFor="confirmPassword">Confirm password</Label>
95
+ <Input
96
+ id="confirmPassword"
97
+ name="confirmPassword"
98
+ type="password"
99
+ placeholder="Repeat password"
100
+ required
101
+ />
102
+ </div>
103
+ <button class={buttonVariants({ size: "lg", className: "w-full" })} type="submit">
104
+ Create account
105
+ </button>
106
+ </form>
107
+ ) : (
108
+ !errorMessage && <p class="text-muted-foreground text-sm">No valid invitation token provided.</p>
109
+ )
110
+ }
111
+ </CardContent>
112
+ </Card>
113
+ </div>
114
+ </div>
115
+ </body>
116
+ </html>
@@ -0,0 +1,57 @@
1
+ ---
2
+ import "virtual:kide/admin-css";
3
+
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@kidecms/core/admin/components/ui/card";
5
+ import { Input } from "@kidecms/core/admin/components/ui/input";
6
+ import { Label } from "@kidecms/core/admin/components/ui/label";
7
+ import { buttonVariants } from "@kidecms/core/admin/components/ui/button";
8
+
9
+ export const prerender = false;
10
+
11
+ const error = Astro.url.searchParams.get("error");
12
+ const errorMessage =
13
+ error === "invalid" ? "Invalid email or password." : error === "missing" ? "Email and password are required." : null;
14
+ ---
15
+
16
+ <!doctype html>
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="utf-8" />
20
+ <meta name="viewport" content="width=device-width" />
21
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
22
+ <link rel="icon" href="/favicon.ico" />
23
+ <title>Sign in | Kide CMS</title>
24
+ </head>
25
+ <body class="bg-background text-foreground min-h-screen antialiased">
26
+ <div class="flex min-h-screen items-center justify-center px-4">
27
+ <div class="w-full max-w-sm space-y-6">
28
+ <Card>
29
+ <CardHeader>
30
+ <CardTitle>Sign in</CardTitle>
31
+ <CardDescription>Enter your credentials to continue</CardDescription>
32
+ </CardHeader>
33
+ <CardContent>
34
+ {
35
+ errorMessage && (
36
+ <div class="border-destructive/50 bg-destructive/10 text-destructive mb-4 rounded-lg border px-3 py-2 text-sm">
37
+ {errorMessage}
38
+ </div>
39
+ )
40
+ }
41
+ <form method="post" action="/api/cms/auth/login" class="grid gap-4">
42
+ <div class="grid gap-2">
43
+ <Label htmlFor="email">Email</Label>
44
+ <Input id="email" name="email" type="email" placeholder="admin@example.com" required autoFocus />
45
+ </div>
46
+ <div class="grid gap-2">
47
+ <Label htmlFor="password">Password</Label>
48
+ <Input id="password" name="password" type="password" placeholder="Enter password" required />
49
+ </div>
50
+ <button class={buttonVariants({ size: "lg", className: "w-full" })} type="submit"> Sign in </button>
51
+ </form>
52
+ </CardContent>
53
+ </Card>
54
+ </div>
55
+ </div>
56
+ </body>
57
+ </html>
@@ -0,0 +1,91 @@
1
+ ---
2
+ import "virtual:kide/admin-css";
3
+
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@kidecms/core/admin/components/ui/card";
5
+ import { Input } from "@kidecms/core/admin/components/ui/input";
6
+ import { Label } from "@kidecms/core/admin/components/ui/label";
7
+ import { buttonVariants } from "@kidecms/core/admin/components/ui/button";
8
+ import { getDb } from "virtual:kide/db";
9
+
10
+ export const prerender = false;
11
+
12
+ // If users already exist, redirect to login
13
+ const db = await getDb();
14
+ const schema = await import("virtual:kide/schema");
15
+ const tables = schema.cmsTables as Record<string, { main: any }>;
16
+ if (tables.users) {
17
+ const rows = await db.select().from(tables.users.main).limit(1);
18
+ if (rows.length > 0) {
19
+ return Astro.redirect("/admin/login");
20
+ }
21
+ }
22
+
23
+ const error = Astro.url.searchParams.get("error");
24
+ const errorMessage =
25
+ error === "missing"
26
+ ? "All fields are required."
27
+ : error === "password"
28
+ ? "Passwords do not match."
29
+ : error === "short"
30
+ ? "Password must be at least 8 characters."
31
+ : null;
32
+ ---
33
+
34
+ <!doctype html>
35
+ <html lang="en">
36
+ <head>
37
+ <meta charset="utf-8" />
38
+ <meta name="viewport" content="width=device-width" />
39
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
40
+ <link rel="icon" href="/favicon.ico" />
41
+ <title>Setup | Kide CMS</title>
42
+ </head>
43
+ <body class="bg-background text-foreground min-h-screen antialiased">
44
+ <div class="flex min-h-screen items-center justify-center px-4">
45
+ <div class="w-full max-w-sm space-y-6">
46
+ <Card>
47
+ <CardHeader>
48
+ <CardTitle>Create admin account</CardTitle>
49
+ <CardDescription>This will be the first user with admin privileges</CardDescription>
50
+ </CardHeader>
51
+ <CardContent>
52
+ {
53
+ errorMessage && (
54
+ <div class="border-destructive/50 bg-destructive/10 text-destructive mb-4 rounded-lg border px-3 py-2 text-sm">
55
+ {errorMessage}
56
+ </div>
57
+ )
58
+ }
59
+ <form method="post" action="/api/cms/auth/setup" class="grid gap-4">
60
+ <div class="grid gap-2">
61
+ <Label htmlFor="name">Name</Label>
62
+ <Input id="name" name="name" type="text" placeholder="Your name" required autoFocus />
63
+ </div>
64
+ <div class="grid gap-2">
65
+ <Label htmlFor="email">Email</Label>
66
+ <Input id="email" name="email" type="email" placeholder="admin@example.com" required />
67
+ </div>
68
+ <div class="grid gap-2">
69
+ <Label htmlFor="password">Password</Label>
70
+ <Input id="password" name="password" type="password" placeholder="Min 8 characters" required />
71
+ </div>
72
+ <div class="grid gap-2">
73
+ <Label htmlFor="confirmPassword">Confirm password</Label>
74
+ <Input
75
+ id="confirmPassword"
76
+ name="confirmPassword"
77
+ type="password"
78
+ placeholder="Repeat password"
79
+ required
80
+ />
81
+ </div>
82
+ <button class={buttonVariants({ size: "lg", className: "w-full" })} type="submit">
83
+ Create account
84
+ </button>
85
+ </form>
86
+ </CardContent>
87
+ </Card>
88
+ </div>
89
+ </div>
90
+ </body>
91
+ </html>
package/virtual.d.ts ADDED
@@ -0,0 +1,61 @@
1
+ declare module "virtual:kide/custom-fields" {
2
+ export const customFields: Record<string, any>;
3
+ }
4
+
5
+ declare module "virtual:kide/config" {
6
+ import type { CmsConfig } from "@kidecms/core";
7
+ const config: CmsConfig;
8
+ export default config;
9
+ }
10
+
11
+ declare module "virtual:kide/api" {
12
+ const cms: Record<string, any> & { meta: any };
13
+ export { cms };
14
+ }
15
+
16
+ declare module "virtual:kide/schema" {
17
+ const cmsTables: Record<string, { main: any; translations?: any }>;
18
+ export { cmsTables };
19
+ }
20
+
21
+ declare module "virtual:kide/runtime" {
22
+ export const initCmsRuntime: () => void;
23
+ export {
24
+ getSessionUser,
25
+ destroySession,
26
+ clearSessionCookie,
27
+ verifyPassword,
28
+ hashPassword,
29
+ createSession,
30
+ setSessionCookie,
31
+ validateSession,
32
+ createInvite,
33
+ validateInvite,
34
+ consumeInvite,
35
+ SESSION_COOKIE_NAME,
36
+ acquireLock,
37
+ releaseLock,
38
+ isAiEnabled,
39
+ getAiModel,
40
+ streamAltText,
41
+ streamSeoDescription,
42
+ streamTranslation,
43
+ assets,
44
+ folders,
45
+ createCms,
46
+ } from "@kidecms/core";
47
+ }
48
+
49
+ declare module "virtual:kide/block-renderer" {
50
+ const BlockRenderer: any;
51
+ export default BlockRenderer;
52
+ }
53
+
54
+ declare module "virtual:kide/db" {
55
+ export function getDb(): Promise<any>;
56
+ }
57
+
58
+ declare module "virtual:kide/email" {
59
+ export function sendInviteEmail(options: { to: string; inviteUrl: string }): Promise<void>;
60
+ export function isEmailConfigured(): boolean;
61
+ }