@kyro-cms/admin 0.12.4 → 0.12.6
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/dist/index.cjs +64 -26
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +64 -26
- package/package.json +10 -4
- package/src/components/ActionBar.tsx +16 -16
- package/src/components/ApiHealth.tsx +522 -0
- package/src/components/ApiKeysManager.tsx +6 -4
- package/src/components/AuditLogsPage.tsx +4 -2
- package/src/components/AutoForm.tsx +10 -7
- package/src/components/BrandingHub.tsx +3 -1
- package/src/components/Dashboard.tsx +44 -37
- package/src/components/DashboardMetrics.tsx +168 -58
- package/src/components/DetailView.tsx +41 -38
- package/src/components/DeveloperCenter.tsx +5 -3
- package/src/components/GraphQLPlayground.tsx +10 -8
- package/src/components/ListView.tsx +66 -45
- package/src/components/LoginPage.tsx +21 -19
- package/src/components/MarketplaceManager.tsx +3 -1
- package/src/components/MediaGallery.tsx +12 -9
- package/src/components/PluginsManager.tsx +6 -4
- package/src/components/RestPlayground.tsx +8 -6
- package/src/components/SessionsManager.tsx +3 -1
- package/src/components/Sidebar.astro +180 -92
- package/src/components/UserManagement.tsx +8 -6
- package/src/components/UserMenu.tsx +19 -17
- package/src/components/VersionHistoryPanel.tsx +6 -4
- package/src/components/WebhookManager.tsx +11 -10
- package/src/components/autoform/AutoFormApiView.tsx +3 -1
- package/src/components/autoform/AutoFormEditView.tsx +3 -1
- package/src/components/autoform/AutoFormHeader.tsx +6 -6
- package/src/components/blocks/AccordionBlock.tsx +5 -3
- package/src/components/blocks/ArrayBlock.tsx +5 -3
- package/src/components/blocks/BlockWrapper.tsx +5 -3
- package/src/components/blocks/CodeBlock.tsx +4 -2
- package/src/components/blocks/FileBlock.tsx +5 -3
- package/src/components/blocks/HeroBlock.tsx +5 -3
- package/src/components/blocks/ImageBlock.tsx +6 -4
- package/src/components/blocks/ListBlock.tsx +5 -3
- package/src/components/blocks/ParagraphBlock.tsx +3 -1
- package/src/components/blocks/RelationshipBlock.tsx +5 -3
- package/src/components/blocks/RichTextBlock.tsx +5 -3
- package/src/components/blocks/VideoBlock.tsx +5 -3
- package/src/components/fields/AccordionField.tsx +10 -51
- package/src/components/fields/ArrayField.tsx +9 -7
- package/src/components/fields/ArrayLayout.tsx +3 -1
- package/src/components/fields/BlocksField.tsx +24 -75
- package/src/components/fields/ButtonField.tsx +3 -1
- package/src/components/fields/CardField.tsx +6 -4
- package/src/components/fields/GroupLayout.tsx +2 -49
- package/src/components/fields/HeadingField.tsx +3 -1
- package/src/components/fields/HeadingSubheadingField.tsx +4 -2
- package/src/components/fields/HeroField.tsx +8 -6
- package/src/components/fields/JSONField.tsx +9 -10
- package/src/components/fields/LinkField.tsx +4 -2
- package/src/components/fields/ListField.tsx +3 -1
- package/src/components/fields/MarkdownField.tsx +3 -17
- package/src/components/fields/RelationshipBlockField.tsx +7 -5
- package/src/components/fields/RelationshipField.tsx +3 -1
- package/src/components/fields/RichTextField.tsx +19 -16
- package/src/components/fields/SecretField.tsx +9 -69
- package/src/components/fields/SelectField.tsx +11 -6
- package/src/components/fields/TabsLayout.tsx +2 -48
- package/src/components/fields/TextField.tsx +9 -7
- package/src/components/fields/UploadField.tsx +3 -1
- package/src/components/fields/VideoField.tsx +6 -4
- package/src/components/ui/BlockDrawer.tsx +3 -1
- package/src/components/ui/CommandPalette.tsx +9 -7
- package/src/components/ui/CommandPaletteWrapper.tsx +3 -1
- package/src/components/ui/IconPickerModal.tsx +4 -2
- package/src/components/ui/PageHeader.tsx +48 -19
- package/src/components/users/UserDetail.tsx +3 -1
- package/src/components/users/UserForm.tsx +5 -3
- package/src/fields/examples/sample-field-2.tsx +3 -1
- package/src/fields/examples/sample-field.tsx +3 -1
- package/src/hooks/useAutoFormState.ts +19 -11
- package/src/integration.ts +21 -0
- package/src/layouts/AdminLayout.astro +252 -189
- package/src/layouts/AuthLayout.astro +52 -31
- package/src/lib/api.ts +7 -7
- package/src/lib/deep-equal.ts +3 -3
- package/src/lib/i18n.ts +44 -0
- package/src/lib/vite-shim-plugin.ts +3 -0
- package/src/locales/de.json +163 -0
- package/src/locales/en.json +163 -0
- package/src/locales/es.json +163 -0
- package/src/locales/fr.json +163 -0
- package/src/locales/pt.json +163 -0
- package/src/pages/403.astro +2 -1
- package/src/pages/[collection]/[id].astro +23 -10
- package/src/pages/[collection]/index.astro +48 -34
- package/src/pages/audit/index.astro +2 -1
- package/src/pages/graphql-explorer.astro +2 -1
- package/src/pages/graphql.astro +2 -1
- package/src/pages/health.astro +9 -0
- package/src/pages/index.astro +6 -5
- package/src/pages/keys.astro +2 -1
- package/src/pages/marketplace.astro +2 -1
- package/src/pages/media.astro +2 -1
- package/src/pages/plugins.astro +2 -1
- package/src/pages/rest-playground.astro +2 -1
- package/src/pages/roles/index.astro +16 -52
- package/src/pages/sessions.astro +2 -1
- package/src/pages/settings/[slug].astro +45 -30
- package/src/pages/users/[id].astro +1 -0
- package/src/pages/users/index.astro +2 -1
- package/src/pages/users/new.astro +2 -1
- package/src/pages/webhooks.astro +2 -1
- package/src/pages/index_ALT.astro +0 -3
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
Hexagon,
|
|
21
21
|
} from "./icons";
|
|
22
22
|
import { useAuthStore } from "../../lib/stores";
|
|
23
|
+
import { useTranslation } from "react-i18next";
|
|
23
24
|
|
|
24
25
|
interface SearchResult {
|
|
25
26
|
collection: string;
|
|
@@ -44,6 +45,7 @@ export function CommandPalette({
|
|
|
44
45
|
globals,
|
|
45
46
|
onNavigate,
|
|
46
47
|
}: CommandPaletteProps) {
|
|
48
|
+
const { t } = useTranslation();
|
|
47
49
|
const { user, permissions } = useAuthStore();
|
|
48
50
|
const [query, setQuery] = useState("");
|
|
49
51
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
@@ -107,7 +109,7 @@ export function CommandPalette({
|
|
|
107
109
|
|
|
108
110
|
const collectionItems = Object.entries(collections)
|
|
109
111
|
.filter(([slug]) => permissions?.collections?.[slug]?.read !== false)
|
|
110
|
-
.map(([slug, config]: [string,
|
|
112
|
+
.map(([slug, config]: [string, any]) => ({
|
|
111
113
|
id: `col-${slug}`,
|
|
112
114
|
label: config.label || slug,
|
|
113
115
|
type: "collection",
|
|
@@ -117,7 +119,7 @@ export function CommandPalette({
|
|
|
117
119
|
|
|
118
120
|
const globalItems = Object.entries(globals)
|
|
119
121
|
.filter(([slug]) => permissions?.globals?.[slug]?.read !== false)
|
|
120
|
-
.map(([slug, config]: [string,
|
|
122
|
+
.map(([slug, config]: [string, any]) => ({
|
|
121
123
|
id: `global-${slug}`,
|
|
122
124
|
label: config.label || slug,
|
|
123
125
|
type: "global",
|
|
@@ -267,7 +269,7 @@ export function CommandPalette({
|
|
|
267
269
|
} else if (item.view === "media") {
|
|
268
270
|
onNavigate("media", "media");
|
|
269
271
|
} else {
|
|
270
|
-
onNavigate(item.view, item.view);
|
|
272
|
+
onNavigate(item.view as string, item.view as string);
|
|
271
273
|
}
|
|
272
274
|
}
|
|
273
275
|
onClose();
|
|
@@ -298,7 +300,7 @@ export function CommandPalette({
|
|
|
298
300
|
)}
|
|
299
301
|
<input
|
|
300
302
|
ref={inputRef}
|
|
301
|
-
placeholder="Search anything..."
|
|
303
|
+
placeholder={t("fields.searchAnything", { defaultValue: "Search anything..." })}
|
|
302
304
|
className="flex-1 bg-transparent border-none focus:outline-none text-lg font-medium text-[var(--kyro-text-primary)] placeholder:text-[var(--kyro-text-muted)]"
|
|
303
305
|
value={query}
|
|
304
306
|
onChange={(e) => setQuery(e.target.value)}
|
|
@@ -334,12 +336,12 @@ export function CommandPalette({
|
|
|
334
336
|
<item.icon className="w-4 h-4" />
|
|
335
337
|
</div>
|
|
336
338
|
<div className="flex flex-col">
|
|
337
|
-
<span className="font-bold text-sm">{item.label}</span>
|
|
338
|
-
{item.type === "document" && item.label2 && (
|
|
339
|
+
<span className="font-bold text-sm">{(item as any).label}</span>
|
|
340
|
+
{item.type === "document" && (item as any).label2 && (
|
|
339
341
|
<span
|
|
340
342
|
className={`text-[10px] font-bold tracking-widest ${index === selectedIndex ? "text-[var(--kyro-sidebar-text-active)]/60" : "opacity-40"}`}
|
|
341
343
|
>
|
|
342
|
-
{item.label2}
|
|
344
|
+
{(item as any).label2}
|
|
343
345
|
</span>
|
|
344
346
|
)}
|
|
345
347
|
</div>
|
|
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
|
|
|
2
2
|
import { CommandPalette } from "./CommandPalette";
|
|
3
3
|
import { ConfirmModal } from "./Modal";
|
|
4
4
|
import { adminPath } from "../../lib/paths";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
collections: Record<string, unknown>;
|
|
@@ -9,6 +10,7 @@ interface Props {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function CommandPaletteWrapper({ collections, globals }: Props) {
|
|
13
|
+
const { t } = useTranslation();
|
|
12
14
|
const [isOpen, setIsOpen] = useState(false);
|
|
13
15
|
const [showLogoutConfirm, setShowLogoutConfirm] = useState(false);
|
|
14
16
|
|
|
@@ -87,7 +89,7 @@ export function CommandPaletteWrapper({ collections, globals }: Props) {
|
|
|
87
89
|
open={showLogoutConfirm}
|
|
88
90
|
onClose={() => setShowLogoutConfirm(false)}
|
|
89
91
|
onConfirm={handleLogoutConfirm}
|
|
90
|
-
title="Sign Out"
|
|
92
|
+
title={t("tooltips.signOut", { defaultValue: "Sign Out" })}
|
|
91
93
|
message="Are you sure you want to sign out?"
|
|
92
94
|
confirmLabel="Sign Out"
|
|
93
95
|
variant="danger"
|
|
@@ -2,6 +2,7 @@ import React, { useState, useMemo } from "react";
|
|
|
2
2
|
import { Modal } from "./Modal";
|
|
3
3
|
import * as LucideIcons from "lucide-react";
|
|
4
4
|
import { Search } from "./icons";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
5
6
|
|
|
6
7
|
// Extract only valid components from lucide-react
|
|
7
8
|
const availableIcons = Object.keys(LucideIcons).filter((key) => {
|
|
@@ -15,6 +16,7 @@ interface IconPickerModalProps {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function IconPickerModal({ open, onClose, onSelect }: IconPickerModalProps) {
|
|
19
|
+
const { t } = useTranslation();
|
|
18
20
|
const [search, setSearch] = useState("");
|
|
19
21
|
|
|
20
22
|
const filteredIcons = useMemo(() => {
|
|
@@ -27,7 +29,7 @@ export function IconPickerModal({ open, onClose, onSelect }: IconPickerModalProp
|
|
|
27
29
|
<Modal
|
|
28
30
|
open={open}
|
|
29
31
|
onClose={onClose}
|
|
30
|
-
title="Select an Icon"
|
|
32
|
+
title={t("tooltips.selectAnIcon", { defaultValue: "Select an Icon" })}
|
|
31
33
|
size="xl"
|
|
32
34
|
>
|
|
33
35
|
<div className="flex flex-col h-[60vh]">
|
|
@@ -35,7 +37,7 @@ export function IconPickerModal({ open, onClose, onSelect }: IconPickerModalProp
|
|
|
35
37
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--kyro-text-muted)]" />
|
|
36
38
|
<input
|
|
37
39
|
type="text"
|
|
38
|
-
placeholder="Search icons..."
|
|
40
|
+
placeholder={t("fields.searchIcons", { defaultValue: "Search icons..." })}
|
|
39
41
|
value={search}
|
|
40
42
|
onChange={(e) => setSearch(e.target.value)}
|
|
41
43
|
className="w-full pl-10 pr-4 py-3 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-xl focus:outline-none focus:ring-2 focus:ring-[var(--kyro-primary)] transition-all text-sm font-bold"
|
|
@@ -9,7 +9,8 @@ interface Breadcrumb {
|
|
|
9
9
|
|
|
10
10
|
interface Action {
|
|
11
11
|
label: string;
|
|
12
|
-
|
|
12
|
+
href?: string;
|
|
13
|
+
onClick?: () => void;
|
|
13
14
|
icon?: ComponentType<SVGAttributes<SVGSVGElement>>;
|
|
14
15
|
variant?: "primary" | "outline" | "ghost";
|
|
15
16
|
className?: string;
|
|
@@ -90,23 +91,40 @@ function ActionsSlot({ actions }: { actions: NonNullable<PageHeaderProps["action
|
|
|
90
91
|
if (Array.isArray(actions)) {
|
|
91
92
|
return (
|
|
92
93
|
<div className="flex items-center gap-3">
|
|
93
|
-
{actions.map((act, i) =>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
94
|
+
{actions.map((act, i) => {
|
|
95
|
+
const className = `flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm transition-all ${act.variant === "outline"
|
|
96
|
+
? "border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"
|
|
97
|
+
: act.variant === "ghost"
|
|
98
|
+
? "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] shadow-none"
|
|
99
|
+
: "kyro-btn-primary hover:opacity-90"
|
|
100
|
+
} ${act.disabled ? "opacity-50 cursor-wait pointer-events-none" : ""} ${act.className || ""}`;
|
|
101
|
+
|
|
102
|
+
if (act.href && !act.disabled) {
|
|
103
|
+
return (
|
|
104
|
+
<a
|
|
105
|
+
key={i}
|
|
106
|
+
href={act.href}
|
|
107
|
+
className={className}
|
|
108
|
+
>
|
|
109
|
+
{act.icon && <act.icon className="w-4 h-4" />}
|
|
110
|
+
{act.label}
|
|
111
|
+
</a>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<button
|
|
117
|
+
key={i}
|
|
118
|
+
type="button"
|
|
119
|
+
onClick={act.onClick}
|
|
120
|
+
disabled={act.disabled}
|
|
121
|
+
className={className}
|
|
122
|
+
>
|
|
123
|
+
{act.icon && <act.icon className="w-4 h-4" />}
|
|
124
|
+
{act.label}
|
|
125
|
+
</button>
|
|
126
|
+
);
|
|
127
|
+
})}
|
|
110
128
|
</div>
|
|
111
129
|
);
|
|
112
130
|
}
|
|
@@ -114,12 +132,23 @@ function ActionsSlot({ actions }: { actions: NonNullable<PageHeaderProps["action
|
|
|
114
132
|
}
|
|
115
133
|
|
|
116
134
|
function SingleAction({ action }: { action: NonNullable<PageHeaderProps["action"]> }) {
|
|
135
|
+
const className = `kyro-btn kyro-btn-primary flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm hover:opacity-90 transition-all w-full lg:w-auto justify-center ${action.disabled ? "opacity-50 cursor-wait pointer-events-none" : ""} ${action.className || ""}`;
|
|
136
|
+
|
|
137
|
+
if (action.href && !action.disabled) {
|
|
138
|
+
return (
|
|
139
|
+
<a href={action.href} className={className}>
|
|
140
|
+
{action.icon && <action.icon className="w-4 h-4" />}
|
|
141
|
+
{action.label}
|
|
142
|
+
</a>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
117
146
|
return (
|
|
118
147
|
<button
|
|
119
148
|
type="button"
|
|
120
149
|
onClick={action.onClick}
|
|
121
150
|
disabled={action.disabled}
|
|
122
|
-
className={
|
|
151
|
+
className={className}
|
|
123
152
|
>
|
|
124
153
|
{action.icon && <action.icon className="w-4 h-4" />}
|
|
125
154
|
{action.label}
|
|
@@ -3,6 +3,7 @@ import { apiGet } from "../../lib/api";
|
|
|
3
3
|
import { useUIStore, toast } from "../../lib/stores";
|
|
4
4
|
import { X } from "../ui/icons";
|
|
5
5
|
import { UploadField } from "../fields/UploadField";
|
|
6
|
+
import { useTranslation } from "react-i18next";
|
|
6
7
|
|
|
7
8
|
interface User {
|
|
8
9
|
id: string;
|
|
@@ -35,6 +36,7 @@ const roleOptions = [
|
|
|
35
36
|
];
|
|
36
37
|
|
|
37
38
|
export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
39
|
+
const { t } = useTranslation();
|
|
38
40
|
const [name, setName] = useState(user.name || "");
|
|
39
41
|
const [role, setRole] = useState(user.role);
|
|
40
42
|
const [avatar, setAvatar] = useState<string | undefined>(user.avatar);
|
|
@@ -228,7 +230,7 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
228
230
|
value={name}
|
|
229
231
|
onChange={(e) => setName(e.target.value)}
|
|
230
232
|
className="mt-1 w-full px-3 py-2 border border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)] rounded-lg text-sm font-medium text-[var(--kyro-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)]"
|
|
231
|
-
placeholder="Enter name"
|
|
233
|
+
placeholder={t("fields.enterName", { defaultValue: "Enter name" })}
|
|
232
234
|
/>
|
|
233
235
|
</div>
|
|
234
236
|
<div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
2
3
|
|
|
3
4
|
interface UserFormProps {
|
|
4
5
|
mode: "create" | "edit";
|
|
@@ -25,6 +26,7 @@ const roleOptions = [
|
|
|
25
26
|
];
|
|
26
27
|
|
|
27
28
|
export function UserForm({ mode, apiPath, adminPath, user }: UserFormProps) {
|
|
29
|
+
const { t } = useTranslation();
|
|
28
30
|
const [email, setEmail] = useState(user?.email || "");
|
|
29
31
|
const [name, setName] = useState(user?.name || "");
|
|
30
32
|
const [password, setPassword] = useState("");
|
|
@@ -120,7 +122,7 @@ export function UserForm({ mode, apiPath, adminPath, user }: UserFormProps) {
|
|
|
120
122
|
value={name}
|
|
121
123
|
onChange={(e) => setName(e.target.value)}
|
|
122
124
|
className="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
|
|
123
|
-
placeholder="John Doe"
|
|
125
|
+
placeholder={t("fields.johnDoe", { defaultValue: "John Doe" })}
|
|
124
126
|
/>
|
|
125
127
|
</div>
|
|
126
128
|
|
|
@@ -158,7 +160,7 @@ export function UserForm({ mode, apiPath, adminPath, user }: UserFormProps) {
|
|
|
158
160
|
required
|
|
159
161
|
minLength={12}
|
|
160
162
|
className="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
|
|
161
|
-
placeholder="Minimum 12 characters"
|
|
163
|
+
placeholder={t("fields.minimum12Characters", { defaultValue: "Minimum 12 characters" })}
|
|
162
164
|
/>
|
|
163
165
|
<p className="text-xs text-[var(--kyro-text-secondary)] mt-1">
|
|
164
166
|
Must contain , lowercase, numbers, and special
|
|
@@ -201,7 +203,7 @@ export function UserForm({ mode, apiPath, adminPath, user }: UserFormProps) {
|
|
|
201
203
|
value={tenantId}
|
|
202
204
|
onChange={(e) => setTenantId(e.target.value)}
|
|
203
205
|
className="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
|
|
204
|
-
placeholder="Leave empty for global user"
|
|
206
|
+
placeholder={t("fields.leaveEmptyForGlobal", { defaultValue: "Leave empty for global user" })}
|
|
205
207
|
/>
|
|
206
208
|
</div>
|
|
207
209
|
|
|
@@ -2,18 +2,20 @@ import React from "react";
|
|
|
2
2
|
import type { FieldEditorProps } from "../types.ts";
|
|
3
3
|
import { registerField } from "../registry.tsx";
|
|
4
4
|
import type { KyroField } from "../types.ts";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
5
6
|
|
|
6
7
|
const SampleTextareaEditor: React.FC<FieldEditorProps> = ({
|
|
7
8
|
name,
|
|
8
9
|
value,
|
|
9
10
|
onChange,
|
|
10
11
|
}) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
11
13
|
return (
|
|
12
14
|
<textarea
|
|
13
15
|
name={name}
|
|
14
16
|
value={typeof value === "string" ? value : ""}
|
|
15
17
|
onChange={(e) => onChange?.(e.target.value)}
|
|
16
|
-
placeholder="Sample textarea"
|
|
18
|
+
placeholder={t("fields.sampleTextarea", { defaultValue: "Sample textarea" })}
|
|
17
19
|
/>
|
|
18
20
|
);
|
|
19
21
|
};
|
|
@@ -2,18 +2,20 @@ import React from "react";
|
|
|
2
2
|
import type { FieldEditorProps } from "../types.ts";
|
|
3
3
|
import { registerField } from "../registry.tsx";
|
|
4
4
|
import type { KyroField } from "../types.ts";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
5
6
|
|
|
6
7
|
const SampleTextEditor: React.FC<FieldEditorProps> = ({
|
|
7
8
|
name,
|
|
8
9
|
value,
|
|
9
10
|
onChange,
|
|
10
11
|
}) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
11
13
|
return (
|
|
12
14
|
<input
|
|
13
15
|
name={name}
|
|
14
16
|
value={typeof value === "string" ? value : ""}
|
|
15
17
|
onChange={(e) => onChange?.(e.target.value)}
|
|
16
|
-
placeholder="Sample text"
|
|
18
|
+
placeholder={t("fields.sampleText", { defaultValue: "Sample text" })}
|
|
17
19
|
/>
|
|
18
20
|
);
|
|
19
21
|
};
|
|
@@ -107,7 +107,7 @@ const persistBrowserDraft = useCallback(
|
|
|
107
107
|
) => {
|
|
108
108
|
setDraftCache(documentKey, {
|
|
109
109
|
data,
|
|
110
|
-
baseUpdatedAt: lastSavedData.updatedAt ?? null,
|
|
110
|
+
baseUpdatedAt: (lastSavedData.updatedAt as string) ?? null,
|
|
111
111
|
draftUpdatedAt: new Date().toISOString(),
|
|
112
112
|
lastSyncedAt: options?.lastSyncedAt ?? null,
|
|
113
113
|
});
|
|
@@ -140,7 +140,7 @@ const persistBrowserDraft = useCallback(
|
|
|
140
140
|
const latestFormData = state.formData;
|
|
141
141
|
if (autoSaveSkipRef.current || !collectionSlug || !latestFormData.id) return;
|
|
142
142
|
if (!state.hasDirtyFields()) return;
|
|
143
|
-
const documentKey = getDocumentKey(latestFormData.id);
|
|
143
|
+
const documentKey = getDocumentKey(latestFormData.id as string);
|
|
144
144
|
if (documentKey) {
|
|
145
145
|
persistBrowserDraft(documentKey, latestFormData);
|
|
146
146
|
}
|
|
@@ -155,7 +155,7 @@ const persistBrowserDraft = useCallback(
|
|
|
155
155
|
if (!globalSlug && (!collectionSlug || !latestFormData.id)) return;
|
|
156
156
|
if (!state.hasDirtyFields()) return;
|
|
157
157
|
|
|
158
|
-
const documentKey = getDocumentKey(latestFormData.id);
|
|
158
|
+
const documentKey = getDocumentKey(latestFormData.id as string);
|
|
159
159
|
if (documentKey) {
|
|
160
160
|
persistBrowserDraft(documentKey, latestFormData);
|
|
161
161
|
}
|
|
@@ -186,7 +186,7 @@ const persistBrowserDraft = useCallback(
|
|
|
186
186
|
},
|
|
187
187
|
keepalive: options?.keepalive,
|
|
188
188
|
body: JSON.stringify({
|
|
189
|
-
...normalizeUploadFields(latestFormData),
|
|
189
|
+
...(normalizeUploadFields(latestFormData) as Record<string, unknown>),
|
|
190
190
|
baseUpdatedAt: currentLastSaved.updatedAt ?? null,
|
|
191
191
|
}),
|
|
192
192
|
});
|
|
@@ -200,7 +200,7 @@ const persistBrowserDraft = useCallback(
|
|
|
200
200
|
if (documentKey) {
|
|
201
201
|
setDraftCache(documentKey, {
|
|
202
202
|
data: latestFormData,
|
|
203
|
-
baseUpdatedAt: currentLastSaved.updatedAt ?? null,
|
|
203
|
+
baseUpdatedAt: (currentLastSaved.updatedAt as string) ?? null,
|
|
204
204
|
draftUpdatedAt: new Date().toISOString(),
|
|
205
205
|
lastSyncedAt: (await response.clone().json()).data?.updatedAt || new Date().toISOString(),
|
|
206
206
|
});
|
|
@@ -321,8 +321,7 @@ const persistBrowserDraft = useCallback(
|
|
|
321
321
|
if (response.ok) {
|
|
322
322
|
const result = await response.json();
|
|
323
323
|
const savedData = result.data || payload;
|
|
324
|
-
state.
|
|
325
|
-
state.setLastSavedData({ ...payload, ...savedData });
|
|
324
|
+
state.loadDocument(savedData, savedData);
|
|
326
325
|
setAutoSaveStatus("success");
|
|
327
326
|
setTimeout(() => {
|
|
328
327
|
if (useAutoFormStore.getState().autoSaveStatus === "success") {
|
|
@@ -352,7 +351,7 @@ const persistBrowserDraft = useCallback(
|
|
|
352
351
|
const lastInitialDataRef = useRef<string>("");
|
|
353
352
|
const initialDataLoadedRef = useRef(false);
|
|
354
353
|
useEffect(() => {
|
|
355
|
-
const currentSlug = globalSlug || initialData?.id;
|
|
354
|
+
const currentSlug = globalSlug || (initialData?.id as string);
|
|
356
355
|
const serialized = JSON.stringify(initialData);
|
|
357
356
|
if (initialDataLoadedRef.current && lastLoadedSlugRef.current === currentSlug && lastInitialDataRef.current === serialized) return;
|
|
358
357
|
|
|
@@ -365,7 +364,7 @@ const persistBrowserDraft = useCallback(
|
|
|
365
364
|
useEffect(() => {
|
|
366
365
|
if (!collectionSlug || !initialData?.id) return;
|
|
367
366
|
|
|
368
|
-
const documentKey = getDocumentKey(
|
|
367
|
+
const documentKey = getDocumentKey(formData.id as string);
|
|
369
368
|
if (!documentKey) return;
|
|
370
369
|
if (restorePromptedRef.current === documentKey) return;
|
|
371
370
|
|
|
@@ -451,11 +450,20 @@ const persistBrowserDraft = useCallback(
|
|
|
451
450
|
}
|
|
452
451
|
}, [formData, config.fields, setField]);
|
|
453
452
|
|
|
453
|
+
interface FieldConfig {
|
|
454
|
+
name?: string;
|
|
455
|
+
admin?: {
|
|
456
|
+
autoGenerate?: string;
|
|
457
|
+
[key: string]: unknown;
|
|
458
|
+
};
|
|
459
|
+
[key: string]: unknown;
|
|
460
|
+
}
|
|
461
|
+
|
|
454
462
|
// Auto-generate slug
|
|
455
463
|
useEffect(() => {
|
|
456
|
-
const fields = config.fields as
|
|
464
|
+
const fields = config.fields as FieldConfig[];
|
|
457
465
|
const slugField = fields.find(
|
|
458
|
-
(f:
|
|
466
|
+
(f: FieldConfig) => f.name === "slug" && f.admin?.autoGenerate,
|
|
459
467
|
);
|
|
460
468
|
if (!slugField?.admin?.autoGenerate) return;
|
|
461
469
|
const sourceField: string = slugField.admin.autoGenerate;
|
package/src/integration.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { pathToFileURL } from "url";
|
|
|
5
5
|
import { build } from "esbuild";
|
|
6
6
|
import { config as loadDotEnv } from "dotenv";
|
|
7
7
|
import { Worker } from "worker_threads";
|
|
8
|
+
import { useSyncExternalStoreShimPlugin } from "./lib/vite-shim-plugin";
|
|
8
9
|
|
|
9
10
|
const _shimDir = path.resolve(new URL(".", import.meta.url).pathname, "lib/shim");
|
|
10
11
|
const _shimUses = path.join(_shimDir, "use-sync-external-store.js");
|
|
@@ -163,6 +164,7 @@ export function kyroAdmin(options: KyroAdminOptions = {}): AstroIntegration {
|
|
|
163
164
|
updateConfig({
|
|
164
165
|
vite: {
|
|
165
166
|
plugins: [
|
|
167
|
+
useSyncExternalStoreShimPlugin(),
|
|
166
168
|
{
|
|
167
169
|
name: "kyro-admin-tsx-loader",
|
|
168
170
|
enforce: "pre" as const,
|
|
@@ -233,6 +235,22 @@ export default debug;
|
|
|
233
235
|
optimizeDeps: {
|
|
234
236
|
include: [
|
|
235
237
|
'@kyro-cms/admin',
|
|
238
|
+
'@kyro-cms/admin > recharts',
|
|
239
|
+
'@kyro-cms/admin > recharts > recharts-scale',
|
|
240
|
+
'@kyro-cms/admin > recharts > recharts-scale > decimal.js-light',
|
|
241
|
+
'@kyro-cms/admin > recharts > react-smooth',
|
|
242
|
+
'@kyro-cms/admin > recharts > react-smooth > react-transition-group',
|
|
243
|
+
'@kyro-cms/admin > react-i18next',
|
|
244
|
+
'@kyro-cms/admin > react-i18next > html-parse-stringify',
|
|
245
|
+
'@kyro-cms/admin > react-i18next > html-parse-stringify > void-elements',
|
|
246
|
+
'@kyro-cms/admin > use-sync-external-store',
|
|
247
|
+
'@kyro-cms/admin > use-sync-external-store/with-selector.js',
|
|
248
|
+
],
|
|
249
|
+
needsInterop: [
|
|
250
|
+
'@kyro-cms/admin > recharts > recharts-scale > decimal.js-light',
|
|
251
|
+
'@kyro-cms/admin > react-i18next > html-parse-stringify > void-elements',
|
|
252
|
+
'@kyro-cms/admin > use-sync-external-store',
|
|
253
|
+
'@kyro-cms/admin > use-sync-external-store/with-selector.js',
|
|
236
254
|
],
|
|
237
255
|
exclude: ['debug', 'react/compiler-runtime'],
|
|
238
256
|
},
|
|
@@ -292,6 +310,9 @@ export default debug;
|
|
|
292
310
|
pattern: "/rest-playground",
|
|
293
311
|
entrypoint: "./pages/rest-playground.astro",
|
|
294
312
|
},
|
|
313
|
+
{ pattern: "/health", entrypoint: "./pages/health.astro" },
|
|
314
|
+
{ pattern: "/403", entrypoint: "./pages/403.astro" },
|
|
315
|
+
{ pattern: "/graphql-explorer", entrypoint: "./pages/graphql-explorer.astro" },
|
|
295
316
|
{
|
|
296
317
|
pattern: "/[collection]",
|
|
297
318
|
entrypoint: "./pages/[collection]/index.astro",
|