@kyro-cms/admin 0.1.5 → 0.1.7
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 +52 -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 +136 -27
- 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 +1417 -661
- package/src/components/BrandingHub.tsx +267 -0
- package/src/components/BulkActionsBar.tsx +3 -3
- package/src/components/CreateView.tsx +3 -3
- package/src/components/Dashboard.tsx +393 -0
- package/src/components/DetailView.tsx +199 -57
- package/src/components/DeveloperCenter.tsx +403 -0
- package/src/components/EnhancedListView.tsx +786 -0
- package/src/components/GraphQLExplorer.tsx +675 -0
- package/src/components/GraphQLPlayground.tsx +627 -0
- package/src/components/ListView.tsx +191 -53
- package/src/components/MediaGallery.tsx +1569 -0
- package/src/components/Modal.tsx +149 -0
- package/src/components/RestPlayground.tsx +951 -0
- package/src/components/Sidebar.astro +237 -0
- 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 +97 -0
- package/src/components/blocks/ArrayBlock.tsx +75 -0
- package/src/components/blocks/BlockEditModal.MARKER +12 -0
- package/src/components/blocks/BlockEditModal.tsx +774 -0
- package/src/components/blocks/ButtonBlock.tsx +165 -0
- package/src/components/blocks/ChildBlocksTree.tsx +551 -0
- package/src/components/blocks/CodeBlock.tsx +66 -0
- package/src/components/blocks/ColumnsBlock.tsx +151 -0
- package/src/components/blocks/DividerBlock.tsx +43 -0
- package/src/components/blocks/FileBlock.tsx +64 -0
- package/src/components/blocks/HeadingBlock.tsx +81 -0
- package/src/components/blocks/HeroBlock.tsx +157 -0
- package/src/components/blocks/ImageBlock.tsx +83 -0
- package/src/components/blocks/LinkBlock.tsx +71 -0
- package/src/components/blocks/ListBlock.tsx +39 -0
- package/src/components/blocks/ParagraphBlock.tsx +61 -0
- package/src/components/blocks/RelationshipBlock.tsx +279 -0
- package/src/components/blocks/VStackBlock.tsx +75 -0
- package/src/components/blocks/VideoBlock.tsx +45 -0
- package/src/components/blocks/index.ts +10 -0
- package/src/components/fields/BlocksField.tsx +323 -0
- package/src/components/fields/CheckboxField.tsx +15 -9
- package/src/components/fields/CodeField.tsx +234 -0
- package/src/components/fields/DateField.tsx +38 -11
- package/src/components/fields/EditorClient.tsx +271 -0
- package/src/components/fields/FileField.tsx +390 -0
- package/src/components/fields/HybridContentField.tsx +109 -0
- package/src/components/fields/ImageField.tsx +429 -0
- package/src/components/fields/JSONField.tsx +361 -0
- package/src/components/fields/MarkdownField.tsx +282 -0
- package/src/components/fields/NumberField.tsx +42 -12
- package/src/components/fields/PortableTextField.tsx +143 -0
- package/src/components/fields/PortableTextRenderer.tsx +68 -0
- package/src/components/fields/RelationshipField.tsx +231 -59
- package/src/components/fields/SelectField.tsx +25 -15
- package/src/components/fields/TextField.tsx +45 -14
- package/src/components/fields/extensions/blockComponents.tsx +237 -0
- package/src/components/fields/extensions/blocksStore.ts +273 -0
- package/src/components/fields/index.ts +13 -0
- package/src/components/index.ts +1 -2
- package/src/components/layout/Header.tsx +2 -2
- package/src/components/layout/Layout.tsx +2 -2
- 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 +50 -0
- package/src/lib/MediaService.ts +541 -0
- package/src/lib/auth/sqlite-adapter.ts +319 -0
- package/src/lib/config.ts +22 -6
- package/src/lib/dataStore.ts +132 -74
- 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/graphql/index.ts +1 -0
- package/src/lib/graphql/schema.ts +443 -0
- package/src/lib/rate-limit.ts +267 -0
- package/src/lib/storage.ts +374 -0
- package/src/lib/store.ts +85 -0
- package/src/middleware.ts +116 -28
- 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 +286 -0
- 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 +44 -0
- package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
- package/src/pages/api/[collection]/[id]/versions.ts +36 -0
- package/src/pages/api/[collection]/[id].ts +102 -159
- package/src/pages/api/[collection]/index.ts +151 -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 +50 -20
- 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 +82 -0
- package/src/pages/media.astro +10 -0
- package/src/pages/preview/[collection]/[id].astro +178 -0
- package/src/pages/register.astro +102 -0
- 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 +553 -128
- package/src/components/layout/Sidebar.tsx +0 -497
- package/src/pages/index.astro +0 -225
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { ChevronRight, X, Plus, Minus, Columns3 } from "lucide-react";
|
|
7
|
+
import { ChildBlocksTree } from "./ChildBlocksTree";
|
|
8
|
+
|
|
9
|
+
interface ColumnsBlockProps {
|
|
10
|
+
block: any;
|
|
11
|
+
index: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ColumnsBlock: React.FC<ColumnsBlockProps> = ({ block, index }) => {
|
|
15
|
+
const blockData = useBlockById(block.id);
|
|
16
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
17
|
+
|
|
18
|
+
const data = blockData?.data ?? block.data ?? {};
|
|
19
|
+
const columns = data.columns || 2;
|
|
20
|
+
const columnData = data.columnData || [];
|
|
21
|
+
|
|
22
|
+
const ensureColumnData = () => {
|
|
23
|
+
if (columnData.length !== columns) {
|
|
24
|
+
return Array.from({ length: columns }, (_, i) => ({
|
|
25
|
+
id: i,
|
|
26
|
+
children: columnData[i]?.children || [],
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
return columnData;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const currentColumnData = ensureColumnData();
|
|
33
|
+
|
|
34
|
+
const handleColumnsChange = (newColumns: number) => {
|
|
35
|
+
if (newColumns < 1 || newColumns > 6) return;
|
|
36
|
+
const newColumnData = Array.from({ length: newColumns }, (_, i) => ({
|
|
37
|
+
id: i,
|
|
38
|
+
children: currentColumnData[i]?.children || [],
|
|
39
|
+
}));
|
|
40
|
+
updateBlock(block.id, {
|
|
41
|
+
data: { ...data, columns: newColumns, columnData: newColumnData },
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleUpdateColumnChildren = (
|
|
46
|
+
columnIndex: number,
|
|
47
|
+
newChildren: any[],
|
|
48
|
+
) => {
|
|
49
|
+
const newColumnData = [...currentColumnData];
|
|
50
|
+
newColumnData[columnIndex] = {
|
|
51
|
+
...newColumnData[columnIndex],
|
|
52
|
+
children: newChildren,
|
|
53
|
+
};
|
|
54
|
+
updateBlock(block.id, {
|
|
55
|
+
data: { ...data, columnData: newColumnData },
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="block-columns border border-[var(--kyro-border)] rounded-lg mb-4 overflow-hidden">
|
|
61
|
+
<div className="flex items-center justify-between px-4 py-2 bg-[var(--kyro-surface)] border-b border-[var(--kyro-border)]">
|
|
62
|
+
<div className="flex items-center gap-2">
|
|
63
|
+
<Columns3 className="w-4 h-4 text-[var(--kyro-primary)]" />
|
|
64
|
+
<span className="text-sm font-medium text-[var(--kyro-text-primary)]">
|
|
65
|
+
Columns
|
|
66
|
+
</span>
|
|
67
|
+
<span className="text-xs text-[var(--kyro-text-muted)]">
|
|
68
|
+
({columns})
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="flex items-center gap-1 bg-[var(--kyro-surface-accent)] rounded">
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
onClick={() => handleColumnsChange(columns - 1)}
|
|
75
|
+
disabled={columns <= 1}
|
|
76
|
+
className="p-1.5 hover:bg-[var(--kyro-surface)] rounded disabled:opacity-30"
|
|
77
|
+
title="Remove column"
|
|
78
|
+
>
|
|
79
|
+
<Minus className="w-3 h-3" />
|
|
80
|
+
</button>
|
|
81
|
+
<span className="px-2 text-xs font-medium">{columns}</span>
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
onClick={() => handleColumnsChange(columns + 1)}
|
|
85
|
+
disabled={columns >= 6}
|
|
86
|
+
className="p-1.5 hover:bg-[var(--kyro-surface)] rounded disabled:opacity-30"
|
|
87
|
+
title="Add column"
|
|
88
|
+
>
|
|
89
|
+
<Plus className="w-3 h-3" />
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
<div className="flex items-center gap-1">
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
96
|
+
className="p-1.5 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
97
|
+
title="Move up"
|
|
98
|
+
>
|
|
99
|
+
<ChevronRight className="w-3.5 h-3.5 rotate-[-90deg]" />
|
|
100
|
+
</button>
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
104
|
+
className="p-1.5 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
105
|
+
title="Move down"
|
|
106
|
+
>
|
|
107
|
+
<ChevronRight className="w-3.5 h-3.5 rotate-90" />
|
|
108
|
+
</button>
|
|
109
|
+
<button
|
|
110
|
+
type="button"
|
|
111
|
+
onClick={() => removeBlock(block.id)}
|
|
112
|
+
className="p-1.5 hover:bg-red-50 rounded"
|
|
113
|
+
title="Remove"
|
|
114
|
+
>
|
|
115
|
+
<X className="w-3.5 h-3.5 text-red-500" />
|
|
116
|
+
</button>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div
|
|
121
|
+
className="grid gap-4 p-4"
|
|
122
|
+
style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }}
|
|
123
|
+
>
|
|
124
|
+
{currentColumnData.map((col: any, colIndex: number) => (
|
|
125
|
+
<div
|
|
126
|
+
key={colIndex}
|
|
127
|
+
className="border-2 border-dashed border-[var(--kyro-border)] rounded-lg overflow-hidden"
|
|
128
|
+
>
|
|
129
|
+
<div className="px-3 py-2 bg-[var(--kyro-surface-accent)]/30 border-b border-[var(--kyro-border)] flex items-center justify-between">
|
|
130
|
+
<span className="text-xs font-medium text-[var(--kyro-text-muted)]">
|
|
131
|
+
Column {colIndex + 1}
|
|
132
|
+
</span>
|
|
133
|
+
<span className="text-xs text-[var(--kyro-text-muted)]">
|
|
134
|
+
({col.children?.length || 0})
|
|
135
|
+
</span>
|
|
136
|
+
</div>
|
|
137
|
+
<div className="p-3 min-h-[80px]">
|
|
138
|
+
<ChildBlocksTree
|
|
139
|
+
blockId={`${block.id}-col-${colIndex}`}
|
|
140
|
+
children={col.children || []}
|
|
141
|
+
onUpdateChildren={(newChildren) =>
|
|
142
|
+
handleUpdateColumnChildren(colIndex, newChildren)
|
|
143
|
+
}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
@@ -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,64 @@
|
|
|
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 FileBlock: 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
|
+
|
|
16
|
+
const handleChange = (field: string, value: any) => {
|
|
17
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="block-file border border-[var(--kyro-border)] rounded-md p-4 mb-2 relative group flex items-center gap-3">
|
|
22
|
+
<div className="flex-1">
|
|
23
|
+
<div className="flex items-center justify-between mb-1">
|
|
24
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
25
|
+
File
|
|
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
|
+
<input
|
|
45
|
+
type="text"
|
|
46
|
+
value={block.data.filename || ""}
|
|
47
|
+
onChange={(e) => handleChange("filename", e.target.value)}
|
|
48
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
49
|
+
placeholder="File name..."
|
|
50
|
+
/>
|
|
51
|
+
<input
|
|
52
|
+
type="url"
|
|
53
|
+
value={block.data.url || ""}
|
|
54
|
+
onChange={(e) => handleChange("url", e.target.value)}
|
|
55
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm mt-2"
|
|
56
|
+
placeholder="Download URL..."
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
<div className="w-6 h-6 flex items-center justify-center text-[var(--kyro-text-muted)] flex-shrink-0">
|
|
60
|
+
📄
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
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 HeadingBlock: 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-heading border border-[var(--kyro-border)] rounded-md p-3 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
|
+
Heading
|
|
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 className="space-y-2">
|
|
52
|
+
<div>
|
|
53
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
54
|
+
Level
|
|
55
|
+
</label>
|
|
56
|
+
<select
|
|
57
|
+
value={data.level || 1}
|
|
58
|
+
onChange={(e) => handleChange("level", parseInt(e.target.value))}
|
|
59
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
60
|
+
>
|
|
61
|
+
<option value={1}>H1</option>
|
|
62
|
+
<option value={2}>H2</option>
|
|
63
|
+
<option value={3}>H3</option>
|
|
64
|
+
</select>
|
|
65
|
+
</div>
|
|
66
|
+
<div>
|
|
67
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
68
|
+
Text
|
|
69
|
+
</label>
|
|
70
|
+
<input
|
|
71
|
+
type="text"
|
|
72
|
+
value={data.text || ""}
|
|
73
|
+
onChange={(e) => handleChange("text", e.target.value)}
|
|
74
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
75
|
+
placeholder="Enter heading text..."
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { X, ChevronRight } from "lucide-react";
|
|
7
|
+
import { ChildBlocksTree } from "./ChildBlocksTree";
|
|
8
|
+
|
|
9
|
+
interface HeroBlockProps {
|
|
10
|
+
block: any;
|
|
11
|
+
index: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const HeroBlock: React.FC<HeroBlockProps> = ({ block, index }) => {
|
|
15
|
+
const blockData = useBlockById(block.id);
|
|
16
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
17
|
+
const data = blockData?.data ?? block.data ?? {};
|
|
18
|
+
const children = blockData?.children ?? block.children ?? [];
|
|
19
|
+
|
|
20
|
+
const handleChange = (field: string, value: any) => {
|
|
21
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const handleUpdateChildren = (newChildren: any[]) => {
|
|
25
|
+
updateBlock(block.id, { children: newChildren });
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="border border-[var(--kyro-border)] rounded-lg p-4 mb-4 relative group bg-[var(--kyro-surface)]">
|
|
30
|
+
<div className="flex items-center justify-between mb-4">
|
|
31
|
+
<div className="flex items-center gap-2">
|
|
32
|
+
<span className="text-sm font-medium text-[var(--kyro-text-primary)]">
|
|
33
|
+
Hero Section
|
|
34
|
+
</span>
|
|
35
|
+
</div>
|
|
36
|
+
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
40
|
+
className="p-1.5 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
41
|
+
title="Move up"
|
|
42
|
+
>
|
|
43
|
+
<ChevronRight className="w-3.5 h-3.5 rotate-[-90deg] text-[var(--kyro-text-muted)]" />
|
|
44
|
+
</button>
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
48
|
+
className="p-1.5 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
49
|
+
title="Move down"
|
|
50
|
+
>
|
|
51
|
+
<ChevronRight className="w-3.5 h-3.5 rotate-90 text-[var(--kyro-text-muted)]" />
|
|
52
|
+
</button>
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
onClick={() => removeBlock(block.id)}
|
|
56
|
+
className="p-1.5 hover:bg-red-50 rounded"
|
|
57
|
+
title="Remove"
|
|
58
|
+
>
|
|
59
|
+
<X className="w-3.5 h-3.5 text-red-500" />
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div className="space-y-4">
|
|
65
|
+
<div>
|
|
66
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
67
|
+
Heading
|
|
68
|
+
</label>
|
|
69
|
+
<input
|
|
70
|
+
type="text"
|
|
71
|
+
value={data.heading || ""}
|
|
72
|
+
onChange={(e) => handleChange("heading", e.target.value)}
|
|
73
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-primary)] text-lg font-bold"
|
|
74
|
+
placeholder="Hero heading..."
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div>
|
|
79
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
80
|
+
Subheading
|
|
81
|
+
</label>
|
|
82
|
+
<textarea
|
|
83
|
+
value={data.subheading || ""}
|
|
84
|
+
onChange={(e) => handleChange("subheading", e.target.value)}
|
|
85
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-primary)] text-sm min-h-[60px] resize-none"
|
|
86
|
+
placeholder="Hero subheading..."
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div className="grid grid-cols-2 gap-3">
|
|
91
|
+
<div>
|
|
92
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
93
|
+
Background Image URL
|
|
94
|
+
</label>
|
|
95
|
+
<input
|
|
96
|
+
type="url"
|
|
97
|
+
value={data.bgImage || ""}
|
|
98
|
+
onChange={(e) => handleChange("bgImage", e.target.value)}
|
|
99
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-primary)] text-sm"
|
|
100
|
+
placeholder="https://..."
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
<div>
|
|
104
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
105
|
+
Video URL (YouTube/Vimeo)
|
|
106
|
+
</label>
|
|
107
|
+
<input
|
|
108
|
+
type="url"
|
|
109
|
+
value={data.videoUrl || ""}
|
|
110
|
+
onChange={(e) => handleChange("videoUrl", e.target.value)}
|
|
111
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-primary)] text-sm"
|
|
112
|
+
placeholder="https://youtube.com/..."
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div className="grid grid-cols-2 gap-3">
|
|
118
|
+
<div>
|
|
119
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
120
|
+
CTA Text
|
|
121
|
+
</label>
|
|
122
|
+
<input
|
|
123
|
+
type="text"
|
|
124
|
+
value={data.ctaText || ""}
|
|
125
|
+
onChange={(e) => handleChange("ctaText", e.target.value)}
|
|
126
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-primary)] text-sm"
|
|
127
|
+
placeholder="Button text..."
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
<div>
|
|
131
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
132
|
+
CTA URL
|
|
133
|
+
</label>
|
|
134
|
+
<input
|
|
135
|
+
type="url"
|
|
136
|
+
value={data.ctaUrl || ""}
|
|
137
|
+
onChange={(e) => handleChange("ctaUrl", e.target.value)}
|
|
138
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-primary)] text-sm"
|
|
139
|
+
placeholder="https://..."
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div className="pt-4 border-t border-[var(--kyro-border)]">
|
|
145
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-2 block">
|
|
146
|
+
Children ({children.length})
|
|
147
|
+
</label>
|
|
148
|
+
<ChildBlocksTree
|
|
149
|
+
blockId={block.id}
|
|
150
|
+
children={children}
|
|
151
|
+
onUpdateChildren={handleUpdateChildren}
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
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 ImageBlock: 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
|
+
|
|
16
|
+
const handleChange = (field: string, value: any) => {
|
|
17
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="block-image border border-[var(--kyro-border)] rounded-lg p-4 mb-4 relative group">
|
|
22
|
+
<div className="flex items-center justify-between mb-2">
|
|
23
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
24
|
+
Image
|
|
25
|
+
</span>
|
|
26
|
+
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
27
|
+
<button type="button"
|
|
28
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
29
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
30
|
+
title="Move up"
|
|
31
|
+
>
|
|
32
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
33
|
+
</button>
|
|
34
|
+
<button type="button"
|
|
35
|
+
onClick={() => removeBlock(block.id)}
|
|
36
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
37
|
+
title="Remove"
|
|
38
|
+
>
|
|
39
|
+
<X className="w-3 h-3" />
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<div className="space-y-3">
|
|
44
|
+
<div>
|
|
45
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
46
|
+
Image URL
|
|
47
|
+
</label>
|
|
48
|
+
<input
|
|
49
|
+
type="url"
|
|
50
|
+
value={data.src || ""}
|
|
51
|
+
onChange={(e) => handleChange("src", e.target.value)}
|
|
52
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
53
|
+
placeholder="https://..."
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
<div>
|
|
57
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
58
|
+
Alt Text
|
|
59
|
+
</label>
|
|
60
|
+
<input
|
|
61
|
+
type="text"
|
|
62
|
+
value={data.alt || ""}
|
|
63
|
+
onChange={(e) => handleChange("alt", e.target.value)}
|
|
64
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
65
|
+
placeholder="Alternative text..."
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
<div>
|
|
69
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
70
|
+
Caption
|
|
71
|
+
</label>
|
|
72
|
+
<input
|
|
73
|
+
type="text"
|
|
74
|
+
value={data.caption || ""}
|
|
75
|
+
onChange={(e) => handleChange("caption", e.target.value)}
|
|
76
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
77
|
+
placeholder="Image caption..."
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
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 LinkBlock: 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
|
+
|
|
16
|
+
const handleChange = (field: string, value: any) => {
|
|
17
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="block-link border border-dashed border-[var(--kyro-border)] rounded-md p-4 mb-2 relative group">
|
|
22
|
+
<div className="flex items-center justify-between mb-1">
|
|
23
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
24
|
+
Link
|
|
25
|
+
</span>
|
|
26
|
+
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
27
|
+
<button type="button"
|
|
28
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
29
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
30
|
+
title="Move up"
|
|
31
|
+
>
|
|
32
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
33
|
+
</button>
|
|
34
|
+
<button type="button"
|
|
35
|
+
onClick={() => removeBlock(block.id)}
|
|
36
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
37
|
+
title="Remove"
|
|
38
|
+
>
|
|
39
|
+
<X className="w-3 h-3" />
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<div className="space-y-3">
|
|
44
|
+
<div>
|
|
45
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
46
|
+
URL
|
|
47
|
+
</label>
|
|
48
|
+
<input
|
|
49
|
+
type="url"
|
|
50
|
+
value={block.data.url || ""}
|
|
51
|
+
onChange={(e) => handleChange("url", e.target.value)}
|
|
52
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
53
|
+
placeholder="https://..."
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
<div>
|
|
57
|
+
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
58
|
+
Text
|
|
59
|
+
</label>
|
|
60
|
+
<input
|
|
61
|
+
type="text"
|
|
62
|
+
value={block.data.text || ""}
|
|
63
|
+
onChange={(e) => handleChange("text", e.target.value)}
|
|
64
|
+
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
65
|
+
placeholder="Link text..."
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
};
|