@kyro-cms/admin 0.1.6 → 0.1.8
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 +149 -51
- package/package.json +54 -5
- package/src/collections/auth/index.ts +2 -2
- package/src/collections/portfolio/index.ts +343 -0
- package/src/components/ActionBar.tsx +153 -16
- package/src/components/Admin.tsx +137 -28
- package/src/components/ApiExplorer.tsx +325 -0
- package/src/components/ApiKeysManager.tsx +563 -0
- package/src/components/AuditLogsPage.tsx +664 -0
- package/src/components/AutoForm.tsx +2155 -770
- package/src/components/BrandingHub.tsx +267 -0
- package/src/components/BulkActionsBar.tsx +3 -3
- package/src/components/CreateView.tsx +4 -4
- package/src/components/Dashboard.tsx +393 -0
- package/src/components/DetailView.tsx +200 -58
- package/src/components/DeveloperCenter.tsx +403 -0
- package/src/components/EnhancedListView.tsx +890 -0
- package/src/components/GraphQLExplorer.tsx +675 -0
- package/src/components/GraphQLPlayground.tsx +627 -0
- package/src/components/ListView.tsx +192 -54
- package/src/components/MediaGallery.tsx +1569 -0
- package/src/components/Modal.tsx +206 -0
- package/src/components/RestPlayground.tsx +951 -0
- package/src/components/Sidebar.astro +237 -0
- package/src/components/ThemeProvider.tsx +8 -2
- package/src/components/UserManagement.tsx +204 -0
- package/src/components/VersionHistoryPanel.tsx +3 -3
- package/src/components/WebhookManager.tsx +608 -0
- package/src/components/blocks/AccordionBlock.tsx +65 -0
- package/src/components/blocks/ArrayBlock.tsx +84 -0
- package/src/components/blocks/BlockEditModal.tsx +363 -0
- package/src/components/blocks/ButtonBlock.tsx +64 -0
- package/src/components/blocks/ChildBlocksTree.tsx +551 -0
- package/src/components/blocks/CodeBlock.tsx +114 -0
- package/src/components/blocks/ColumnsBlock.tsx +93 -0
- package/src/components/blocks/DividerBlock.tsx +43 -0
- package/src/components/blocks/FileBlock.tsx +63 -0
- package/src/components/blocks/HeadingBlock.tsx +59 -0
- package/src/components/blocks/HeroBlock.tsx +99 -0
- package/src/components/blocks/ImageBlock.tsx +82 -0
- package/src/components/blocks/LinkBlock.tsx +65 -0
- package/src/components/blocks/ListBlock.tsx +60 -0
- package/src/components/blocks/ParagraphBlock.tsx +61 -0
- package/src/components/blocks/RelationshipBlock.tsx +72 -0
- package/src/components/blocks/RichTextBlock.tsx +66 -0
- package/src/components/blocks/VStackBlock.tsx +61 -0
- package/src/components/blocks/VideoBlock.tsx +65 -0
- package/src/components/blocks/index.ts +10 -0
- package/src/components/fields/AccordionField.tsx +213 -0
- package/src/components/fields/ArrayField.tsx +241 -0
- package/src/components/fields/BlocksField.tsx +323 -0
- package/src/components/fields/ButtonField.tsx +53 -0
- package/src/components/fields/CheckboxField.tsx +18 -8
- package/src/components/fields/ChildrenField.tsx +48 -0
- package/src/components/fields/CodeField.tsx +294 -0
- package/src/components/fields/ColumnsField.tsx +137 -0
- package/src/components/fields/DateField.tsx +24 -12
- package/src/components/fields/EditorClient.tsx +537 -0
- package/src/components/fields/HeadingField.tsx +31 -0
- package/src/components/fields/HeroField.tsx +101 -0
- package/src/components/fields/JSONField.tsx +341 -0
- package/src/components/fields/LinkField.tsx +81 -0
- package/src/components/fields/ListField.tsx +74 -0
- package/src/components/fields/MarkdownField.tsx +260 -0
- package/src/components/fields/NumberField.tsx +25 -13
- package/src/components/fields/PortableTextField.tsx +155 -0
- package/src/components/fields/PortableTextRenderer.tsx +68 -0
- package/src/components/fields/RelationshipBlockField.tsx +233 -0
- package/src/components/fields/RelationshipField.tsx +278 -60
- package/src/components/fields/SelectField.tsx +28 -16
- package/src/components/fields/TextField.tsx +31 -15
- package/src/components/fields/UploadField.tsx +613 -0
- package/src/components/fields/VideoField.tsx +73 -0
- package/src/components/fields/extensions/blockComponents.tsx +247 -0
- package/src/components/fields/extensions/blocksStore.ts +273 -0
- package/src/components/fields/index.ts +24 -0
- package/src/components/index.ts +1 -2
- package/src/components/layout/Header.tsx +2 -2
- package/src/components/layout/Layout.tsx +3 -3
- package/src/components/ui/Badge.tsx +9 -4
- package/src/components/ui/BlockDrawer.tsx +79 -0
- package/src/components/ui/Button.tsx +1 -1
- package/src/components/ui/CommandPalette.tsx +362 -0
- package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
- package/src/components/ui/Dropdown.tsx +1 -1
- package/src/components/ui/Modal.tsx +37 -12
- package/src/components/ui/PromptModal.tsx +94 -0
- package/src/components/ui/SlidePanel.tsx +43 -16
- package/src/components/ui/Toast.tsx +80 -14
- package/src/env.d.ts +16 -0
- package/src/env.ts +20 -0
- package/src/index.ts +0 -1
- package/src/layouts/AdminLayout.astro +164 -170
- package/src/layouts/AuthLayout.astro +23 -6
- package/src/lib/MediaService.ts +541 -0
- package/src/lib/api.ts +163 -0
- package/src/lib/auth/sqlite-adapter.ts +319 -0
- package/src/lib/config.ts +23 -7
- package/src/lib/dataStore.ts +188 -73
- package/src/lib/date-utils.ts +69 -0
- package/src/lib/db/adapter.ts +54 -0
- package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
- package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
- package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
- package/src/lib/db/index.ts +449 -0
- package/src/lib/db/mongodb-adapter.ts +207 -0
- package/src/lib/db/mongodb-auth-adapter.ts +305 -0
- package/src/lib/db/schema/mysql-auth.ts +113 -0
- package/src/lib/db/schema/mysql-content.ts +20 -0
- package/src/lib/db/schema/postgres-auth.ts +116 -0
- package/src/lib/db/schema/postgres-content.ts +35 -0
- package/src/lib/db/schema/postgres-media.ts +52 -0
- package/src/lib/db/schema/postgres-settings.ts +11 -0
- package/src/lib/db/schema/sqlite-auth.ts +112 -0
- package/src/lib/db/schema/sqlite-content.ts +20 -0
- package/src/lib/db/version-adapter.ts +248 -0
- package/src/lib/graphql/index.ts +1 -0
- package/src/lib/graphql/schema.ts +443 -0
- package/src/lib/i18n.tsx +353 -0
- package/src/lib/rate-limit.ts +267 -0
- package/src/lib/slugify.ts +15 -0
- package/src/lib/storage.ts +374 -0
- package/src/lib/store.ts +85 -0
- package/src/lib/validation.ts +250 -0
- package/src/middleware.ts +70 -11
- package/src/pages/[collection]/[id].astro +178 -122
- package/src/pages/[collection]/index.astro +24 -156
- package/src/pages/admin/api-explorer.astro +98 -0
- package/src/pages/admin/graphql-explorer.astro +40 -0
- package/src/pages/admin/graphql.astro +97 -0
- package/src/pages/admin/index.astro +200 -139
- package/src/pages/admin/keys.astro +8 -0
- package/src/pages/admin/rest-playground.astro +44 -0
- package/src/pages/admin/webhooks.astro +8 -0
- package/src/pages/api/[collection]/[id]/publish.ts +52 -0
- package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
- package/src/pages/api/[collection]/[id]/versions.ts +66 -0
- package/src/pages/api/[collection]/[id].ts +114 -159
- package/src/pages/api/[collection]/index.ts +150 -230
- package/src/pages/api/auth/[id].ts +48 -69
- package/src/pages/api/auth/audit-logs.ts +20 -43
- package/src/pages/api/auth/login.ts +159 -45
- package/src/pages/api/auth/logout.ts +42 -24
- package/src/pages/api/auth/refresh.ts +119 -0
- package/src/pages/api/auth/register.ts +110 -40
- package/src/pages/api/auth/users.ts +22 -97
- package/src/pages/api/collections.ts +59 -0
- package/src/pages/api/globals/[slug]/test.ts +172 -0
- package/src/pages/api/globals/[slug].ts +42 -0
- package/src/pages/api/graphql.ts +90 -0
- package/src/pages/api/health.ts +417 -40
- package/src/pages/api/keys/[id].ts +26 -0
- package/src/pages/api/keys/index.ts +75 -0
- package/src/pages/api/media/[id].ts +309 -0
- package/src/pages/api/media/folders.ts +609 -0
- package/src/pages/api/media/index.ts +146 -0
- package/src/pages/api/media/resize.ts +267 -0
- package/src/pages/api/search.ts +82 -0
- package/src/pages/api/slug-availability.ts +70 -0
- package/src/pages/api/storage-config.ts +20 -0
- package/src/pages/api/storage-status.ts +206 -0
- package/src/pages/api/upload.ts +334 -0
- package/src/pages/api/webhooks/index.ts +71 -0
- package/src/pages/audit/index.astro +2 -104
- package/src/pages/login.astro +11 -11
- package/src/pages/media.astro +10 -0
- package/src/pages/preview/[collection]/[id].astro +178 -0
- package/src/pages/register.astro +13 -13
- package/src/pages/roles/index.astro +21 -21
- package/src/pages/settings/[slug].astro +162 -0
- package/src/pages/settings/index.astro +9 -0
- package/src/pages/users/[id].astro +29 -21
- package/src/pages/users/index.astro +22 -17
- package/src/pages/users/new.astro +18 -17
- package/src/styles/main.css +563 -128
- package/src/components/layout/Sidebar.tsx +0 -497
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { ChevronRight, X, Columns3 } from "lucide-react";
|
|
7
|
+
import { ColumnsField } from "../fields/ColumnsField";
|
|
8
|
+
|
|
9
|
+
export const ColumnsBlock: React.FC<{ block: any; index: number }> = ({
|
|
10
|
+
block,
|
|
11
|
+
index,
|
|
12
|
+
}) => {
|
|
13
|
+
const blockData = useBlockById(block.id);
|
|
14
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
|
+
|
|
16
|
+
const data = blockData?.data ?? block.data ?? {};
|
|
17
|
+
const columns = data.columns || 2;
|
|
18
|
+
const columnData = data.columnData || [];
|
|
19
|
+
|
|
20
|
+
const handleColumnsChange = (newColumns: number) => {
|
|
21
|
+
if (newColumns < 1 || newColumns > 6) return;
|
|
22
|
+
const newColumnData = Array.from({ length: newColumns }, (_, i) => ({
|
|
23
|
+
id: i,
|
|
24
|
+
children: columnData[i]?.children || [],
|
|
25
|
+
}));
|
|
26
|
+
updateBlock(block.id, {
|
|
27
|
+
data: { ...data, columns: newColumns, columnData: newColumnData },
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleUpdateColumnChildren = (
|
|
32
|
+
columnIndex: number,
|
|
33
|
+
newChildren: any[],
|
|
34
|
+
) => {
|
|
35
|
+
const newColumnData = [...columnData];
|
|
36
|
+
newColumnData[columnIndex] = {
|
|
37
|
+
...newColumnData[columnIndex],
|
|
38
|
+
children: newChildren,
|
|
39
|
+
};
|
|
40
|
+
updateBlock(block.id, {
|
|
41
|
+
data: { ...data, columnData: newColumnData },
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="block-columns border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
|
|
47
|
+
<div className="flex items-center justify-between mb-2">
|
|
48
|
+
<div className="flex items-center gap-2">
|
|
49
|
+
<Columns3 className="w-3.5 h-3.5 text-[var(--kyro-primary)]" />
|
|
50
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
51
|
+
Columns
|
|
52
|
+
</span>
|
|
53
|
+
<span className="text-[10px] text-[var(--kyro-text-muted)]">
|
|
54
|
+
({columns})
|
|
55
|
+
</span>
|
|
56
|
+
</div>
|
|
57
|
+
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
61
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
62
|
+
title="Move up"
|
|
63
|
+
>
|
|
64
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
65
|
+
</button>
|
|
66
|
+
<button
|
|
67
|
+
type="button"
|
|
68
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
69
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
70
|
+
title="Move down"
|
|
71
|
+
>
|
|
72
|
+
<ChevronRight className="w-3 h-3" />
|
|
73
|
+
</button>
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
onClick={() => removeBlock(block.id)}
|
|
77
|
+
className="p-1 hover:bg-red-50 rounded text-red-500"
|
|
78
|
+
title="Remove"
|
|
79
|
+
>
|
|
80
|
+
<X className="w-3 h-3" />
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<ColumnsField
|
|
86
|
+
columns={columns}
|
|
87
|
+
columnData={columnData}
|
|
88
|
+
onColumnsChange={handleColumnsChange}
|
|
89
|
+
onUpdateColumnChildren={handleUpdateColumnChildren}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
|
|
8
|
+
export const DividerBlock: React.FC<{ block: any; index: number }> = ({
|
|
9
|
+
block,
|
|
10
|
+
index,
|
|
11
|
+
}) => {
|
|
12
|
+
const blockData = useBlockById(block.id);
|
|
13
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
14
|
+
const data = blockData?.data ?? block.data ?? {};
|
|
15
|
+
const handleChange = (field: string, value: any) => {
|
|
16
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="my-4">
|
|
21
|
+
<div className="border-t border-[var(--kyro-border)]" />
|
|
22
|
+
<div className="flex justify-between text-[var(--kyro-text-muted)] text-xs mt-1">
|
|
23
|
+
<span>Divider</span>
|
|
24
|
+
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
25
|
+
<button type="button"
|
|
26
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
27
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
28
|
+
title="Move up"
|
|
29
|
+
>
|
|
30
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
31
|
+
</button>
|
|
32
|
+
<button type="button"
|
|
33
|
+
onClick={() => removeBlock(block.id)}
|
|
34
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
35
|
+
title="Remove"
|
|
36
|
+
>
|
|
37
|
+
<X className="w-3 h-3" />
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { UploadField } from "../fields/UploadField";
|
|
8
|
+
|
|
9
|
+
export const FileBlock: React.FC<{ block: any; index: number }> = ({
|
|
10
|
+
block,
|
|
11
|
+
index,
|
|
12
|
+
}) => {
|
|
13
|
+
const blockData = useBlockById(block.id);
|
|
14
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
|
+
|
|
16
|
+
const data = blockData?.data ?? block.data ?? {};
|
|
17
|
+
|
|
18
|
+
const handleChange = (field: string, value: any) => {
|
|
19
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="block-file border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
|
|
24
|
+
<div className="flex items-center justify-between mb-1">
|
|
25
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
26
|
+
File
|
|
27
|
+
</span>
|
|
28
|
+
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
32
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
33
|
+
title="Move up"
|
|
34
|
+
>
|
|
35
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
36
|
+
</button>
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
40
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
41
|
+
title="Move down"
|
|
42
|
+
>
|
|
43
|
+
<ChevronRight className="w-3 h-3" />
|
|
44
|
+
</button>
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
onClick={() => removeBlock(block.id)}
|
|
48
|
+
className="p-1 hover:bg-red-50 rounded text-red-500"
|
|
49
|
+
title="Remove"
|
|
50
|
+
>
|
|
51
|
+
<X className="w-3 h-3" />
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<UploadField
|
|
57
|
+
field={{ label: "File", name: "file", maxCount: 1 }}
|
|
58
|
+
value={data.file}
|
|
59
|
+
onChange={(v) => handleChange("file", v)}
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { HeadingField } from "../fields/HeadingField";
|
|
8
|
+
|
|
9
|
+
export const HeadingBlock: React.FC<{ block: any; index: number }> = ({
|
|
10
|
+
block,
|
|
11
|
+
index,
|
|
12
|
+
}) => {
|
|
13
|
+
const blockData = useBlockById(block.id);
|
|
14
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
|
+
|
|
16
|
+
const data = blockData?.data || block.data || {};
|
|
17
|
+
|
|
18
|
+
const handleChange = (field: string, value: any) => {
|
|
19
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="block-heading border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
|
|
24
|
+
<div className="flex items-center justify-between mb-1">
|
|
25
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
26
|
+
Heading
|
|
27
|
+
</span>
|
|
28
|
+
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
32
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
33
|
+
title="Move up"
|
|
34
|
+
>
|
|
35
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
36
|
+
</button>
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
40
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
41
|
+
title="Move down"
|
|
42
|
+
>
|
|
43
|
+
<ChevronRight className="w-3 h-3" />
|
|
44
|
+
</button>
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
onClick={() => removeBlock(block.id)}
|
|
48
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
49
|
+
title="Remove"
|
|
50
|
+
>
|
|
51
|
+
<X className="w-3 h-3" />
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<HeadingField text={data.text || ""} onChange={handleChange} compact />
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { HeroField } from "../fields/HeroField";
|
|
8
|
+
import { UploadField } from "../fields/UploadField";
|
|
9
|
+
import { ChildBlocksTree } from "./ChildBlocksTree";
|
|
10
|
+
|
|
11
|
+
export const HeroBlock: React.FC<{ block: any; index: number }> = ({
|
|
12
|
+
block,
|
|
13
|
+
index,
|
|
14
|
+
}) => {
|
|
15
|
+
const blockData = useBlockById(block.id);
|
|
16
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
17
|
+
|
|
18
|
+
const data = blockData?.data ?? block.data ?? {};
|
|
19
|
+
const children = blockData?.children ?? block.children ?? [];
|
|
20
|
+
|
|
21
|
+
const handleChange = (field: string, value: any) => {
|
|
22
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="block-hero border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
|
|
27
|
+
<div className="flex items-center justify-between mb-2">
|
|
28
|
+
<div className="flex items-center gap-2">
|
|
29
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
30
|
+
Hero Section
|
|
31
|
+
</span>
|
|
32
|
+
</div>
|
|
33
|
+
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
37
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
38
|
+
title="Move up"
|
|
39
|
+
>
|
|
40
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
41
|
+
</button>
|
|
42
|
+
<button
|
|
43
|
+
type="button"
|
|
44
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
45
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
46
|
+
title="Move down"
|
|
47
|
+
>
|
|
48
|
+
<ChevronRight className="w-3 h-3" />
|
|
49
|
+
</button>
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
onClick={() => removeBlock(block.id)}
|
|
53
|
+
className="p-1 hover:bg-red-50 rounded text-red-500"
|
|
54
|
+
title="Remove"
|
|
55
|
+
>
|
|
56
|
+
<X className="w-3 h-3" />
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div className="space-y-3">
|
|
62
|
+
<HeroField
|
|
63
|
+
heading={data.heading || ""}
|
|
64
|
+
subheading={data.subheading || ""}
|
|
65
|
+
ctaText={data.ctaText || ""}
|
|
66
|
+
ctaUrl={data.ctaUrl || ""}
|
|
67
|
+
onChange={handleChange}
|
|
68
|
+
compact
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<div className="grid grid-cols-2 gap-2">
|
|
72
|
+
<UploadField
|
|
73
|
+
field={{ label: "Background", name: "bgImage", maxCount: 1 }}
|
|
74
|
+
value={data.bgImage}
|
|
75
|
+
onChange={(v) => handleChange("bgImage", v)}
|
|
76
|
+
/>
|
|
77
|
+
<input
|
|
78
|
+
type="url"
|
|
79
|
+
value={data.videoUrl || ""}
|
|
80
|
+
onChange={(e) => handleChange("videoUrl", e.target.value)}
|
|
81
|
+
className="w-full px-2.5 py-1.5 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-sm text-[var(--kyro-text-primary)] focus:outline-none focus:ring-1 focus:ring-[var(--kyro-sidebar-active)] focus:border-transparent font-mono text-xs"
|
|
82
|
+
placeholder="Video URL..."
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div className="pt-3 border-t border-[var(--kyro-border)]">
|
|
87
|
+
<label className="text-[10px] font-medium text-[var(--kyro-text-muted)] mb-1.5 block">
|
|
88
|
+
Children ({children.length})
|
|
89
|
+
</label>
|
|
90
|
+
<ChildBlocksTree
|
|
91
|
+
blockId={block.id}
|
|
92
|
+
children={children}
|
|
93
|
+
onUpdateChildren={(c) => updateBlock(block.id, { children: c })}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { UploadField } from "../fields/UploadField";
|
|
8
|
+
|
|
9
|
+
export const ImageBlock: React.FC<{ block: any; index: number }> = ({
|
|
10
|
+
block,
|
|
11
|
+
index,
|
|
12
|
+
}) => {
|
|
13
|
+
const blockData = useBlockById(block.id);
|
|
14
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
|
+
const data = blockData?.data || block.data || {};
|
|
16
|
+
|
|
17
|
+
const handleChange = (field: string, value: any) => {
|
|
18
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="block-image border border-[var(--kyro-border)] rounded-lg p-4 mb-4 relative group">
|
|
23
|
+
<div className="flex items-center justify-between mb-2">
|
|
24
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
25
|
+
Image
|
|
26
|
+
</span>
|
|
27
|
+
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
28
|
+
<button type="button"
|
|
29
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
30
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
31
|
+
title="Move up"
|
|
32
|
+
>
|
|
33
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
34
|
+
</button>
|
|
35
|
+
<button type="button"
|
|
36
|
+
onClick={() => removeBlock(block.id)}
|
|
37
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
38
|
+
title="Remove"
|
|
39
|
+
>
|
|
40
|
+
<X className="w-3 h-3" />
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<div className="space-y-3">
|
|
45
|
+
<div>
|
|
46
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
47
|
+
Image Asset
|
|
48
|
+
</label>
|
|
49
|
+
<UploadField
|
|
50
|
+
field={{ label: "Image Asset", name: "src", maxCount: 1 }}
|
|
51
|
+
value={data.src}
|
|
52
|
+
onChange={(value) => handleChange("src", value)}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
<div>
|
|
56
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
57
|
+
Alt Text
|
|
58
|
+
</label>
|
|
59
|
+
<input
|
|
60
|
+
type="text"
|
|
61
|
+
value={data.alt || ""}
|
|
62
|
+
onChange={(e) => handleChange("alt", e.target.value)}
|
|
63
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
64
|
+
placeholder="Alternative text..."
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
<div>
|
|
68
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
69
|
+
Caption
|
|
70
|
+
</label>
|
|
71
|
+
<input
|
|
72
|
+
type="text"
|
|
73
|
+
value={data.caption || ""}
|
|
74
|
+
onChange={(e) => handleChange("caption", e.target.value)}
|
|
75
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
76
|
+
placeholder="Image caption..."
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { LinkField } from "../fields/LinkField";
|
|
8
|
+
|
|
9
|
+
export const LinkBlock: React.FC<{ block: any; index: number }> = ({
|
|
10
|
+
block,
|
|
11
|
+
index,
|
|
12
|
+
}) => {
|
|
13
|
+
const blockData = useBlockById(block.id);
|
|
14
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
|
+
|
|
16
|
+
const data = blockData?.data ?? block.data ?? {};
|
|
17
|
+
|
|
18
|
+
const handleChange = (field: string, value: any) => {
|
|
19
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="block-link border border-[var(--kyro-border)] rounded-md p-2.5 mb-2 relative group">
|
|
24
|
+
<div className="flex items-center gap-2">
|
|
25
|
+
<span className="text-[10px] font-bold text-[var(--kyro-text-muted)] uppercase shrink-0 w-8">
|
|
26
|
+
Link
|
|
27
|
+
</span>
|
|
28
|
+
<div className="flex-1 min-w-0">
|
|
29
|
+
<LinkField
|
|
30
|
+
text={data.text || ""}
|
|
31
|
+
url={data.url || ""}
|
|
32
|
+
onChange={handleChange}
|
|
33
|
+
compact
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
<div className="flex gap-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
40
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
41
|
+
title="Move up"
|
|
42
|
+
>
|
|
43
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
44
|
+
</button>
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
48
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
49
|
+
title="Move down"
|
|
50
|
+
>
|
|
51
|
+
<ChevronRight className="w-3 h-3" />
|
|
52
|
+
</button>
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
onClick={() => removeBlock(block.id)}
|
|
56
|
+
className="p-1 hover:bg-red-50 rounded text-red-500"
|
|
57
|
+
title="Remove"
|
|
58
|
+
>
|
|
59
|
+
<X className="w-3 h-3" />
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { ListField } from "../fields/ListField";
|
|
8
|
+
|
|
9
|
+
export const ListBlock: React.FC<{ block: any; index: number }> = ({
|
|
10
|
+
block,
|
|
11
|
+
index,
|
|
12
|
+
}) => {
|
|
13
|
+
const blockData = useBlockById(block.id);
|
|
14
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
|
+
|
|
16
|
+
const data = blockData?.data || block.data || {};
|
|
17
|
+
const listItems = Array.isArray(data.items) ? data.items : [];
|
|
18
|
+
|
|
19
|
+
const handleChange = (items: string[]) => {
|
|
20
|
+
updateBlock(block.id, { data: { ...data, items } });
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="block-list border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
|
|
25
|
+
<div className="flex items-center justify-between mb-1">
|
|
26
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
27
|
+
List
|
|
28
|
+
</span>
|
|
29
|
+
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
33
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
34
|
+
title="Move up"
|
|
35
|
+
>
|
|
36
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
37
|
+
</button>
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
41
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
42
|
+
title="Move down"
|
|
43
|
+
>
|
|
44
|
+
<ChevronRight className="w-3 h-3" />
|
|
45
|
+
</button>
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
onClick={() => removeBlock(block.id)}
|
|
49
|
+
className="p-1 hover:bg-red-50 rounded text-red-500"
|
|
50
|
+
title="Remove"
|
|
51
|
+
>
|
|
52
|
+
<X className="w-3 h-3" />
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<ListField items={listItems} onChange={handleChange} compact />
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
|
|
8
|
+
export const ParagraphBlock: React.FC<{ block: any; index: number }> = ({
|
|
9
|
+
block,
|
|
10
|
+
index,
|
|
11
|
+
}) => {
|
|
12
|
+
const blockData = useBlockById(block.id);
|
|
13
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
14
|
+
|
|
15
|
+
const data = blockData?.data || block.data || {};
|
|
16
|
+
|
|
17
|
+
const handleChange = (field: string, value: any) => {
|
|
18
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="block-paragraph border-l-4 border-[var(--kyro-border)] pl-4 py-2 mb-2 relative group">
|
|
23
|
+
<div className="flex items-center justify-between mb-1">
|
|
24
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
25
|
+
Paragraph
|
|
26
|
+
</span>
|
|
27
|
+
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
28
|
+
<button type="button"
|
|
29
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
30
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
31
|
+
title="Move up"
|
|
32
|
+
>
|
|
33
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
34
|
+
</button>
|
|
35
|
+
<button type="button"
|
|
36
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
37
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
38
|
+
title="Move down"
|
|
39
|
+
>
|
|
40
|
+
<ChevronRight className="w-3 h-3" />
|
|
41
|
+
</button>
|
|
42
|
+
<button type="button"
|
|
43
|
+
onClick={() => removeBlock(block.id)}
|
|
44
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
45
|
+
title="Remove"
|
|
46
|
+
>
|
|
47
|
+
<X className="w-3 h-3" />
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div>
|
|
52
|
+
<textarea
|
|
53
|
+
value={data.text || ""}
|
|
54
|
+
onChange={(e) => handleChange("text", e.target.value)}
|
|
55
|
+
className="w-full px-3 py-3 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm min-h-[100px] resize-none"
|
|
56
|
+
placeholder="Enter paragraph text..."
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
};
|