@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.
- package/README.md +28 -0
- package/admin/components/AdminCard.astro +25 -0
- package/admin/components/AiGenerateButton.tsx +102 -0
- package/admin/components/AssetsGrid.tsx +711 -0
- package/admin/components/BlockEditor.tsx +996 -0
- package/admin/components/CheckboxField.tsx +31 -0
- package/admin/components/DocumentActions.tsx +317 -0
- package/admin/components/DocumentLock.tsx +54 -0
- package/admin/components/DocumentsDataTable.tsx +804 -0
- package/admin/components/FieldControl.astro +397 -0
- package/admin/components/FocalPointSelector.tsx +100 -0
- package/admin/components/ImageBrowseDialog.tsx +176 -0
- package/admin/components/ImagePicker.tsx +149 -0
- package/admin/components/InternalLinkPicker.tsx +80 -0
- package/admin/components/LiveHeading.tsx +17 -0
- package/admin/components/MobileSidebar.tsx +29 -0
- package/admin/components/RelationField.tsx +204 -0
- package/admin/components/RichTextEditor.tsx +685 -0
- package/admin/components/SelectField.tsx +65 -0
- package/admin/components/SidebarUserMenu.tsx +99 -0
- package/admin/components/SlugField.tsx +77 -0
- package/admin/components/TaxonomySelect.tsx +52 -0
- package/admin/components/Toast.astro +40 -0
- package/admin/components/TreeItemsEditor.tsx +790 -0
- package/admin/components/TreeSelect.tsx +166 -0
- package/admin/components/UnsavedGuard.tsx +181 -0
- package/admin/components/tree-utils.ts +86 -0
- package/admin/components/ui/alert-dialog.tsx +92 -0
- package/admin/components/ui/badge.tsx +83 -0
- package/admin/components/ui/button.tsx +53 -0
- package/admin/components/ui/card.tsx +70 -0
- package/admin/components/ui/checkbox.tsx +28 -0
- package/admin/components/ui/collapsible.tsx +26 -0
- package/admin/components/ui/command.tsx +88 -0
- package/admin/components/ui/dialog.tsx +92 -0
- package/admin/components/ui/dropdown-menu.tsx +259 -0
- package/admin/components/ui/input.tsx +20 -0
- package/admin/components/ui/label.tsx +20 -0
- package/admin/components/ui/popover.tsx +42 -0
- package/admin/components/ui/select.tsx +165 -0
- package/admin/components/ui/separator.tsx +21 -0
- package/admin/components/ui/sheet.tsx +104 -0
- package/admin/components/ui/skeleton.tsx +7 -0
- package/admin/components/ui/table.tsx +74 -0
- package/admin/components/ui/textarea.tsx +18 -0
- package/admin/components/ui/tooltip.tsx +52 -0
- package/admin/layouts/AdminLayout.astro +340 -0
- package/admin/lib/utils.ts +19 -0
- package/dist/admin.js +92 -0
- package/dist/ai.js +67 -0
- package/dist/api.js +827 -0
- package/dist/assets.js +163 -0
- package/dist/auth.js +132 -0
- package/dist/blocks.js +110 -0
- package/dist/content.js +29 -0
- package/dist/create-admin.js +23 -0
- package/dist/define.js +36 -0
- package/dist/generator.js +370 -0
- package/dist/image.js +69 -0
- package/dist/index.js +16 -0
- package/dist/integration.js +256 -0
- package/dist/locks.js +37 -0
- package/dist/richtext.js +1 -0
- package/dist/runtime.js +26 -0
- package/dist/schema.js +13 -0
- package/dist/seed.js +84 -0
- package/dist/values.js +102 -0
- package/middleware/auth.ts +100 -0
- package/package.json +102 -0
- package/routes/api/cms/[collection]/[...path].ts +366 -0
- package/routes/api/cms/ai/alt-text.ts +25 -0
- package/routes/api/cms/ai/seo.ts +25 -0
- package/routes/api/cms/ai/translate.ts +31 -0
- package/routes/api/cms/assets/[id].ts +82 -0
- package/routes/api/cms/assets/folders.ts +81 -0
- package/routes/api/cms/assets/index.ts +23 -0
- package/routes/api/cms/assets/upload.ts +112 -0
- package/routes/api/cms/auth/invite.ts +166 -0
- package/routes/api/cms/auth/login.ts +124 -0
- package/routes/api/cms/auth/logout.ts +33 -0
- package/routes/api/cms/auth/setup.ts +77 -0
- package/routes/api/cms/cron/publish.ts +33 -0
- package/routes/api/cms/img/[...path].ts +24 -0
- package/routes/api/cms/locks/[...path].ts +37 -0
- package/routes/api/cms/preview/render.ts +36 -0
- package/routes/api/cms/references/[collection]/[id].ts +60 -0
- package/routes/pages/admin/[...path].astro +1104 -0
- package/routes/pages/admin/assets/[id].astro +183 -0
- package/routes/pages/admin/assets/index.astro +58 -0
- package/routes/pages/admin/invite.astro +116 -0
- package/routes/pages/admin/login.astro +57 -0
- package/routes/pages/admin/setup.astro +91 -0
- 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} × {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
|
+
}
|