@kyro-cms/admin 0.3.2 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/dist/EditorClient-XEUOVAAC.js +466 -0
  2. package/dist/EditorClient-XEUOVAAC.js.map +1 -0
  3. package/dist/EditorClient-YLCGVDXY.cjs +468 -0
  4. package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
  5. package/dist/chunk-7KPIUCGT.js +384 -0
  6. package/dist/chunk-7KPIUCGT.js.map +1 -0
  7. package/dist/chunk-GOACG6R7.cjs +473 -0
  8. package/dist/chunk-GOACG6R7.cjs.map +1 -0
  9. package/dist/index.cjs +14861 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +1661 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.ts +563 -0
  14. package/dist/index.js +14784 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +19 -19
  17. package/src/components/ActionBar.tsx +7 -43
  18. package/src/components/Admin.tsx +138 -277
  19. package/src/components/ApiKeysManager.tsx +428 -419
  20. package/src/components/AuditLogsPage.tsx +35 -39
  21. package/src/components/AuthBridge.tsx +51 -0
  22. package/src/components/AutoForm.tsx +495 -1230
  23. package/src/components/BrandingHub.tsx +18 -19
  24. package/src/components/BulkActionsBar.tsx +1 -1
  25. package/src/components/CreateView.tsx +22 -36
  26. package/src/components/Dashboard.tsx +60 -84
  27. package/src/components/DetailView.tsx +113 -91
  28. package/src/components/DeveloperCenter.tsx +200 -198
  29. package/src/components/FieldRenderer.tsx +206 -0
  30. package/src/components/GraphQLPlayground.tsx +340 -480
  31. package/src/components/ListView.tsx +828 -254
  32. package/src/components/LoginPage.tsx +3 -4
  33. package/src/components/MarketplaceManager.tsx +254 -0
  34. package/src/components/MediaGallery.tsx +856 -1192
  35. package/src/components/PluginsManager.tsx +277 -0
  36. package/src/components/RestPlayground.tsx +398 -560
  37. package/src/components/SessionsManager.tsx +211 -0
  38. package/src/components/Sidebar.astro +179 -151
  39. package/src/components/ThemeProvider.tsx +7 -161
  40. package/src/components/UserManagement.tsx +162 -146
  41. package/src/components/UserMenu.tsx +110 -0
  42. package/src/components/WebhookManager.tsx +305 -367
  43. package/src/components/blocks/AccordionBlock.tsx +4 -4
  44. package/src/components/blocks/ArrayBlock.tsx +3 -3
  45. package/src/components/blocks/BlockEditModal.tsx +8 -8
  46. package/src/components/blocks/BlockWrapper.tsx +61 -0
  47. package/src/components/blocks/ButtonBlock.tsx +4 -4
  48. package/src/components/blocks/ChildBlocksTree.tsx +23 -25
  49. package/src/components/blocks/CodeBlock.tsx +15 -15
  50. package/src/components/blocks/ColumnsBlock.tsx +6 -44
  51. package/src/components/blocks/DividerBlock.tsx +3 -3
  52. package/src/components/blocks/FileBlock.tsx +4 -4
  53. package/src/components/blocks/HeadingBlock.tsx +6 -38
  54. package/src/components/blocks/HeroBlock.tsx +4 -4
  55. package/src/components/blocks/ImageBlock.tsx +4 -4
  56. package/src/components/blocks/LinkBlock.tsx +4 -4
  57. package/src/components/blocks/ListBlock.tsx +3 -3
  58. package/src/components/blocks/ParagraphBlock.tsx +12 -42
  59. package/src/components/blocks/RelationshipBlock.tsx +4 -4
  60. package/src/components/blocks/RichTextBlock.tsx +4 -4
  61. package/src/components/blocks/VStackBlock.tsx +5 -37
  62. package/src/components/blocks/VideoBlock.tsx +4 -4
  63. package/src/components/blocks/types.ts +11 -0
  64. package/src/components/fields/AccordionField.tsx +1 -1
  65. package/src/components/fields/ArrayField.tsx +2 -2
  66. package/src/components/fields/ArrayLayout.tsx +93 -0
  67. package/src/components/fields/BlocksField.tsx +122 -111
  68. package/src/components/fields/ButtonField.tsx +1 -1
  69. package/src/components/fields/CheckboxField.tsx +14 -15
  70. package/src/components/fields/ChildrenField.tsx +2 -2
  71. package/src/components/fields/CodeField.tsx +3 -3
  72. package/src/components/fields/ColumnsField.tsx +2 -2
  73. package/src/components/fields/DateField.tsx +13 -26
  74. package/src/components/fields/EditorClient.tsx +26 -28
  75. package/src/components/fields/FieldLayout.tsx +52 -0
  76. package/src/components/fields/GroupLayout.tsx +35 -0
  77. package/src/components/fields/JSONField.tsx +7 -7
  78. package/src/components/fields/LinkField.tsx +1 -1
  79. package/src/components/fields/MarkdownField.tsx +1 -1
  80. package/src/components/fields/NumberField.tsx +13 -26
  81. package/src/components/fields/PortableTextField.tsx +4 -4
  82. package/src/components/fields/PortableTextRenderer.tsx +1 -1
  83. package/src/components/fields/RelationshipBlockField.tsx +31 -23
  84. package/src/components/fields/RelationshipField.tsx +14 -14
  85. package/src/components/fields/SelectField.tsx +17 -26
  86. package/src/components/fields/TabsLayout.tsx +69 -0
  87. package/src/components/fields/TextField.tsx +85 -38
  88. package/src/components/fields/UploadField.tsx +71 -41
  89. package/src/components/fields/VideoField.tsx +1 -1
  90. package/src/components/fields/extensions/blockComponents.tsx +2 -2
  91. package/src/components/fields/extensions/blocksStore.ts +207 -193
  92. package/src/components/fields/types.ts +22 -0
  93. package/src/components/layout/Layout.tsx +1 -1
  94. package/src/components/ui/ActionMenu.tsx +63 -0
  95. package/src/components/ui/Badge.tsx +59 -5
  96. package/src/components/ui/BlockDrawer.tsx +4 -5
  97. package/src/components/ui/CommandPalette.tsx +58 -36
  98. package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
  99. package/src/components/ui/Dropdown.tsx +18 -16
  100. package/src/components/ui/EmptyState.tsx +25 -0
  101. package/src/components/ui/GlobalModal.tsx +49 -0
  102. package/src/components/ui/IconButton.tsx +44 -0
  103. package/src/components/ui/Modal.tsx +19 -20
  104. package/src/components/ui/PageHeader.tsx +158 -0
  105. package/src/components/ui/Pagination.tsx +61 -0
  106. package/src/components/ui/PromptModal.tsx +1 -1
  107. package/src/components/ui/SearchInput.tsx +57 -0
  108. package/src/components/ui/SeoPreview.tsx +31 -0
  109. package/src/components/ui/SessionModal.tsx +0 -0
  110. package/src/components/ui/SlidePanel.tsx +2 -0
  111. package/src/components/ui/Toast.tsx +65 -122
  112. package/src/components/ui/Toaster.tsx +18 -0
  113. package/src/components/ui/icons.tsx +112 -0
  114. package/src/components/users/UserDetail.tsx +290 -0
  115. package/src/components/users/UserForm.tsx +242 -0
  116. package/src/components/users/UsersList.tsx +338 -0
  117. package/src/env.d.ts +13 -13
  118. package/src/fields/index.ts +2 -1
  119. package/src/global.d.ts +7 -0
  120. package/src/hooks/data.ts +2 -9
  121. package/src/hooks/useAsyncData.ts +36 -0
  122. package/src/hooks/useAutoFormState.ts +527 -0
  123. package/src/hooks/useSelection.ts +49 -0
  124. package/src/hooks/useSession.ts +0 -0
  125. package/src/index.ts +11 -1
  126. package/src/integration.ts +86 -11
  127. package/src/kyro-cms.d.ts +209 -0
  128. package/src/layouts/AdminLayout.astro +128 -11
  129. package/src/layouts/AuthLayout.astro +21 -5
  130. package/src/lib/api.ts +175 -55
  131. package/src/lib/autoform-store.ts +435 -0
  132. package/src/lib/config.ts +82 -34
  133. package/src/lib/createRegistry.ts +29 -0
  134. package/src/lib/default-kyro-config.ts +4 -0
  135. package/src/lib/globals.ts +50 -0
  136. package/src/lib/media-utils.ts +18 -0
  137. package/src/lib/object-utils.ts +77 -0
  138. package/src/lib/paths.ts +61 -0
  139. package/src/lib/stores/index.ts +370 -0
  140. package/src/lib/types.ts +43 -0
  141. package/src/lib/useResourceManager.ts +105 -0
  142. package/src/pages/403.astro +67 -0
  143. package/src/pages/[collection]/[id].astro +14 -180
  144. package/src/pages/[collection]/index.astro +11 -6
  145. package/src/pages/api-explorer.astro +173 -0
  146. package/src/pages/audit/index.astro +2 -0
  147. package/src/pages/auth/login.astro +122 -0
  148. package/src/pages/auth/register.astro +167 -0
  149. package/src/pages/graphql-explorer.astro +59 -0
  150. package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
  151. package/src/pages/index.astro +577 -0
  152. package/src/pages/index_ALT.astro +3 -0
  153. package/src/pages/keys.astro +11 -0
  154. package/src/pages/marketplace.astro +11 -0
  155. package/src/pages/media.astro +3 -0
  156. package/src/pages/plugins.astro +8 -0
  157. package/src/pages/preview/[collection]/[id].astro +188 -123
  158. package/src/pages/rest-playground.astro +62 -0
  159. package/src/pages/roles/index.astro +183 -76
  160. package/src/pages/sessions.astro +8 -0
  161. package/src/pages/settings/[slug].astro +92 -114
  162. package/src/pages/settings/index.astro +5 -3
  163. package/src/pages/users/[id].astro +25 -154
  164. package/src/pages/users/index.astro +19 -130
  165. package/src/pages/users/new.astro +9 -86
  166. package/src/pages/webhooks.astro +11 -0
  167. package/src/routes.ts +80 -0
  168. package/src/styles/main.css +119 -79
  169. package/src/theme/tokens.ts +1 -0
  170. package/src/vite-env.d.ts +14 -0
  171. package/src/collections/auth/index.ts +0 -155
  172. package/src/collections/portfolio/index.ts +0 -343
  173. package/src/components/ApiExplorer.tsx +0 -325
  174. package/src/components/EnhancedListView.tsx +0 -889
  175. package/src/components/GraphQLExplorer.tsx +0 -675
  176. package/src/components/Icons.tsx +0 -23
  177. package/src/components/StatusBadge.tsx +0 -76
  178. package/src/lib/MediaService.ts +0 -541
  179. package/src/lib/auth/sqlite-adapter.ts +0 -319
  180. package/src/lib/dataStore.ts +0 -226
  181. package/src/lib/db/adapter.ts +0 -54
  182. package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
  183. package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
  184. package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
  185. package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
  186. package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
  187. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
  188. package/src/lib/db/index.ts +0 -449
  189. package/src/lib/db/mongodb-adapter.ts +0 -207
  190. package/src/lib/db/mongodb-auth-adapter.ts +0 -305
  191. package/src/lib/db/schema/mysql-auth.ts +0 -113
  192. package/src/lib/db/schema/mysql-content.ts +0 -20
  193. package/src/lib/db/schema/postgres-auth.ts +0 -116
  194. package/src/lib/db/schema/postgres-content.ts +0 -35
  195. package/src/lib/db/schema/postgres-media.ts +0 -52
  196. package/src/lib/db/schema/postgres-settings.ts +0 -11
  197. package/src/lib/db/schema/sqlite-auth.ts +0 -112
  198. package/src/lib/db/schema/sqlite-content.ts +0 -20
  199. package/src/lib/db/version-adapter.ts +0 -248
  200. package/src/lib/graphql/index.ts +0 -1
  201. package/src/lib/graphql/schema.ts +0 -443
  202. package/src/lib/rate-limit.ts +0 -267
  203. package/src/lib/storage.ts +0 -374
  204. package/src/lib/store.ts +0 -85
  205. package/src/middleware.ts +0 -177
  206. package/src/pages/admin/api-explorer.astro +0 -98
  207. package/src/pages/admin/graphql-explorer.astro +0 -40
  208. package/src/pages/admin/index.astro +0 -286
  209. package/src/pages/admin/keys.astro +0 -8
  210. package/src/pages/admin/rest-playground.astro +0 -44
  211. package/src/pages/admin/webhooks.astro +0 -8
  212. package/src/pages/api/[collection]/[id]/publish.ts +0 -52
  213. package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
  214. package/src/pages/api/[collection]/[id]/versions.ts +0 -66
  215. package/src/pages/api/[collection]/[id].ts +0 -213
  216. package/src/pages/api/[collection]/index.ts +0 -209
  217. package/src/pages/api/auth/[id].ts +0 -121
  218. package/src/pages/api/auth/audit-logs.ts +0 -57
  219. package/src/pages/api/auth/login.ts +0 -211
  220. package/src/pages/api/auth/logout.ts +0 -66
  221. package/src/pages/api/auth/me.ts +0 -36
  222. package/src/pages/api/auth/refresh.ts +0 -119
  223. package/src/pages/api/auth/register.ts +0 -188
  224. package/src/pages/api/auth/users.ts +0 -97
  225. package/src/pages/api/collections.ts +0 -59
  226. package/src/pages/api/globals/[slug].ts +0 -42
  227. package/src/pages/api/graphql.ts +0 -90
  228. package/src/pages/api/health.ts +0 -426
  229. package/src/pages/api/keys/[id].ts +0 -26
  230. package/src/pages/api/keys/index.ts +0 -75
  231. package/src/pages/api/media/[id].ts +0 -309
  232. package/src/pages/api/media/folders.ts +0 -609
  233. package/src/pages/api/media/index.ts +0 -146
  234. package/src/pages/api/media/resize.ts +0 -267
  235. package/src/pages/api/search.ts +0 -82
  236. package/src/pages/api/slug-availability.ts +0 -70
  237. package/src/pages/api/storage-config.ts +0 -20
  238. package/src/pages/api/storage-status.ts +0 -206
  239. package/src/pages/api/upload.ts +0 -334
  240. package/src/pages/api/webhooks/index.ts +0 -71
  241. package/src/pages/login.astro +0 -82
  242. package/src/pages/register.astro +0 -102
@@ -1,6 +1,7 @@
1
1
  import React, { useState, useEffect } from "react";
2
2
  import { apiGet, apiPost, apiPatch, apiDelete } from "../lib/api";
3
- import { formatDate } from "../lib/date-utils";
3
+ import { useResourceManager } from "../lib/useResourceManager";
4
+
4
5
  import {
5
6
  Webhook,
6
7
  Plus,
@@ -16,8 +17,12 @@ import {
16
17
  ExternalLink,
17
18
  Zap,
18
19
  Shield,
19
- } from "lucide-react";
20
- import { ConfirmModal, Modal, ModalContent, ModalActions } from "./ui/Modal";
20
+ } from "./ui/icons";
21
+ import { useUIStore, toast } from "../lib/stores";
22
+ import { Modal, ModalContent, ModalActions } from "./ui/Modal";
23
+ import { Badge } from "./ui/Badge";
24
+ import { PageHeader } from "./ui/PageHeader";
25
+
21
26
 
22
27
  interface WebhookItem {
23
28
  id: string;
@@ -31,17 +36,25 @@ interface WebhookItem {
31
36
  }
32
37
 
33
38
  export function WebhookManager() {
34
- const [webhooks, setWebhooks] = useState<WebhookItem[]>([]);
35
- const [loading, setLoading] = useState(false);
36
- const [showCreateModal, setShowCreateModal] = useState(false);
37
- const [showDeleteModal, setShowDeleteModal] = useState(false);
39
+ const {
40
+ items: webhooks,
41
+ loading,
42
+ create,
43
+ remove,
44
+ update,
45
+ isCreateModalOpen: showCreateModal,
46
+ setIsCreateModalOpen: setShowCreateModal,
47
+ } = useResourceManager<WebhookItem>({
48
+ endpoint: "/api/webhooks",
49
+ });
50
+
51
+ const { alert } = useUIStore();
38
52
  const [showTestModal, setShowTestModal] = useState(false);
39
53
  const [showHelpModal, setShowHelpModal] = useState(false);
40
54
  const [testResult, setTestResult] = useState<{
41
55
  success: boolean;
42
56
  message: string;
43
57
  } | null>(null);
44
- const [deleteId, setDeleteId] = useState<string | null>(null);
45
58
  const [testId, setTestId] = useState<string | null>(null);
46
59
  const [formData, setFormData] = useState({
47
60
  name: "",
@@ -51,22 +64,6 @@ export function WebhookManager() {
51
64
  });
52
65
  const [createError, setCreateError] = useState("");
53
66
 
54
- const loadWebhooks = async () => {
55
- setLoading(true);
56
- try {
57
- const data = await apiGet("/api/webhooks");
58
- setWebhooks(data);
59
- } catch (e) {
60
- console.error(e);
61
- } finally {
62
- setLoading(false);
63
- }
64
- };
65
-
66
- useEffect(() => {
67
- loadWebhooks();
68
- }, []);
69
-
70
67
  const handleCreate = async () => {
71
68
  if (!formData.name.trim() || !formData.url.trim()) {
72
69
  setCreateError("Name and URL are required");
@@ -74,45 +71,26 @@ export function WebhookManager() {
74
71
  }
75
72
 
76
73
  try {
77
- await apiPost("/api/webhooks", formData);
78
- setShowCreateModal(false);
74
+ await create(formData);
79
75
  setFormData({ name: "", url: "", events: [], secret: "" });
80
- loadWebhooks();
76
+ toast.success(`Webhook established: ${formData.name}`);
81
77
  } catch (e) {
82
- console.error(e);
83
78
  setCreateError("Failed to create webhook");
84
79
  }
85
80
  };
86
81
 
87
- const handleDelete = async (id: string) => {
88
- setDeleteId(id);
89
- setShowDeleteModal(true);
90
- };
91
-
92
- const confirmDelete = async () => {
93
- if (!deleteId) return;
94
-
95
- try {
96
- await apiDelete(`/api/webhooks/${deleteId}`);
97
- loadWebhooks();
98
- } catch (e) {
99
- console.error(e);
100
- }
101
- setShowDeleteModal(false);
102
- setDeleteId(null);
103
- };
104
82
 
105
83
  const handleTest = async (id: string) => {
106
84
  setTestId(id);
107
85
  setTestResult(null);
108
86
  setShowTestModal(true);
109
-
110
87
  try {
111
- const data = await apiPost(`/api/webhooks/${id}/test`);
88
+ const data = await apiPost<any>(`/api/webhooks/${id}/test`);
112
89
  setTestResult({
113
90
  success: true,
114
91
  message: data.message || "Webhook triggered successfully",
115
92
  });
93
+ toast.success("Dispatch verified: Remote endpoint responded");
116
94
  } catch (e) {
117
95
  setTestResult({ success: false, message: "Failed to trigger webhook" });
118
96
  }
@@ -120,10 +98,11 @@ export function WebhookManager() {
120
98
 
121
99
  const toggleStatus = async (id: string, currentStatus: string) => {
122
100
  try {
123
- await apiPatch(`/api/webhooks/${id}`, {
124
- status: currentStatus === "active" ? "paused" : "active",
101
+ const newStatus = currentStatus === "active" ? "paused" : "active";
102
+ await update(id, {
103
+ status: newStatus,
125
104
  });
126
- loadWebhooks();
105
+ toast.success(newStatus === "active" ? "Signals resumed" : "Dispatcher paused");
127
106
  } catch (e) {
128
107
  console.error(e);
129
108
  }
@@ -149,61 +128,59 @@ export function WebhookManager() {
149
128
  ];
150
129
 
151
130
  return (
152
- <div className="w-full space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700 px-8 pb-32">
153
- {/* Header */}
154
- <div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 pt-4">
155
- <div>
156
- <div className="flex items-center gap-3">
157
- <h1 className="text-4xl font-black tracking-tighter text-[var(--kyro-text-primary)]">
158
- Web<span className="text-[var(--kyro-primary)]">hooks</span>
159
- </h1>
160
- <button
161
- type="button"
162
- onClick={() => setShowHelpModal(true)}
163
- className="p-2 rounded-lg text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-primary)] hover:bg-[var(--kyro-surface-accent)] transition-all"
164
- title="Learn how webhooks work"
165
- >
166
- <Info className="w-5 h-5" />
167
- </button>
168
- </div>
169
- <p className="text-[var(--kyro-text-secondary)] mt-1 font-medium opacity-70">
170
- Receive real-time notifications when events occur in your CMS.
171
- </p>
172
- </div>
173
- <button
174
- type="button"
175
- onClick={() => {
176
- setFormData({ name: "", url: "", events: [], secret: "" });
177
- setCreateError("");
178
- setShowCreateModal(true);
179
- }}
180
- className="flex items-center gap-2 px-6 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-full font-black text-sm shadow-xl hover:opacity-90 active:scale-95 transition-all"
181
- >
182
- <Plus className="w-4 h-4" />
183
- Add Webhook
184
- </button>
185
- </div>
131
+ <div className="w-full space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700 pb-32">
132
+ <PageHeader
133
+ title="Webhooks"
134
+ description="Receive real-time notifications when your content changes."
135
+ icon={Webhook}
136
+ actions={[
137
+ {
138
+ label: "Create Webhook",
139
+ onClick: () => {
140
+ setFormData({
141
+ name: "",
142
+ url: "",
143
+ events: ["collection.create", "collection.update", "collection.delete"],
144
+ secret: "",
145
+ });
146
+ setCreateError("");
147
+ setShowCreateModal(true);
148
+ },
149
+ icon: Plus,
150
+ },
151
+ {
152
+ label: "Guide",
153
+ onClick: () => setShowHelpModal(true),
154
+ icon: Info,
155
+ variant: "outline"
156
+ }
157
+ ]}
158
+ />
186
159
 
187
- {/* Info Banner */}
188
- <div className="surface-tile p-6 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)]">
189
- <div className="flex items-start gap-4">
190
- <div className="p-3 bg-[var(--kyro-primary)]/10 rounded-xl">
191
- <Zap className="w-6 h-6 text-[var(--kyro-primary)]" />
160
+ {/* Info Banner - Modernized */}
161
+ <div className="relative overflow-hidden rounded-[2rem] border border-[var(--kyro-border)] bg-gradient-to-br from-[var(--kyro-surface)] to-[var(--kyro-surface-accent)] p-8">
162
+ <div className="absolute top-0 right-0 p-8 opacity-[0.03] pointer-events-none">
163
+ <Zap className="w-64 h-64 rotate-12" />
164
+ </div>
165
+ <div className="flex flex-col md:flex-row items-center gap-8 relative z-10">
166
+ <div className="p-5 bg-gradient-to-br from-[var(--kyro-primary)] to-[var(--kyro-primary)]/50 rounded-[1.5rem] shadow-xl shadow-[var(--kyro-primary)]/20">
167
+ <Zap className="w-8 h-8 text-white" />
192
168
  </div>
193
- <div className="flex-1">
194
- <h3 className="text-lg font-black mb-1">What are webhooks?</h3>
195
- <p className="text-sm text-[var(--kyro-text-secondary)] opacity-70 mb-3">
196
- Webhooks allow your application to receive real-time HTTP
197
- notifications when events happen in your CMS. Instead of polling
198
- the API, your endpoint gets notified immediately.
169
+ <div className="flex-1 text-center md:text-left">
170
+ <h3 className="text-xl font-bold mb-2">Real-time Event Synchronization</h3>
171
+ <p className="text-sm text-[var(--kyro-text-secondary)] opacity-70 max-w-2xl leading-relaxed">
172
+ Webhooks allow your application to receive instant HTTP notifications when events happen in your CMS.
173
+ Eliminate polling and build responsive, event-driven architectures with ease.
199
174
  </p>
200
- <div className="grid grid-cols-2 gap-3 text-xs">
201
- {eventOptions.map((opt) => (
202
- <div key={opt.value} className="flex items-center gap-2">
203
- <span className="w-2 h-2 rounded-full bg-[var(--kyro-primary)]" />
204
- <span className="font-medium">{opt.label}</span>
205
- </div>
206
- ))}
175
+ </div>
176
+ <div className="flex gap-4">
177
+ <div className="flex flex-col items-center px-4 py-2 bg-[var(--kyro-surface)]/50 border border-[var(--kyro-border)] rounded-2xl">
178
+ <span className="text-[10px] font-bold opacity-40 uppercase tracking-tighter">Collections</span>
179
+ <span className="text-sm font-bold text-[var(--kyro-primary)]">Triggered</span>
180
+ </div>
181
+ <div className="flex flex-col items-center px-4 py-2 bg-[var(--kyro-surface)]/50 border border-[var(--kyro-border)] rounded-2xl">
182
+ <span className="text-[10px] font-bold opacity-40 uppercase tracking-tighter">Latency</span>
183
+ <span className="text-sm font-bold text-[var(--kyro-primary)]">&lt;200ms</span>
207
184
  </div>
208
185
  </div>
209
186
  </div>
@@ -211,31 +188,36 @@ export function WebhookManager() {
211
188
 
212
189
  {/* Webhooks List */}
213
190
  <section className="space-y-6">
214
- <div className="flex items-center gap-2 px-2">
215
- <Webhook className="w-4 h-4 text-[var(--kyro-primary)] opacity-40" />
216
- <span className="text-[10px] font-black uppercase tracking-widest opacity-40">
217
- Configured Webhooks
218
- </span>
191
+ <div className="flex items-center justify-between px-2">
192
+ <div className="flex items-center gap-2">
193
+ <div className="w-1 h-4 bg-[var(--kyro-primary)] rounded-full" />
194
+ <h2 className="text-sm font-medium tracking-[0.2em] opacity-40">ACTIVE ENDPOINTS</h2>
195
+ </div>
196
+ <div className="text-[10px] font-bold opacity-40 uppercase">
197
+ {webhooks.length} Hook{webhooks.length !== 1 && "s"}
198
+ </div>
219
199
  </div>
220
200
 
221
201
  {loading ? (
222
- <div className="surface-tile p-8 text-center">Loading...</div>
202
+ <div className="flex items-center justify-center p-20 surface-tile rounded-3xl opacity-50 italic">
203
+ Connecting to dispatcher...
204
+ </div>
223
205
  ) : webhooks.length === 0 ? (
224
- <div className="surface-tile p-12 text-center border-2 border-dashed border-[var(--kyro-border)]">
225
- <div className="w-16 h-16 mx-auto mb-6 bg-[var(--kyro-surface-accent)] rounded-2xl flex items-center justify-center">
226
- <Webhook className="w-8 h-8 text-[var(--kyro-text-secondary)] opacity-20" />
206
+ <div className="p-16 text-center rounded-[3rem] border-2 border-dashed border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/30">
207
+ <div className="w-20 h-20 mx-auto mb-6 bg-gradient-to-tr from-[var(--kyro-surface)] to-[var(--kyro-surface-accent)] rounded-3xl flex items-center justify-center shadow-xl border border-[var(--kyro-border)]">
208
+ <Webhook className="w-10 h-10 text-[var(--kyro-primary)]" />
227
209
  </div>
228
- <h3 className="text-lg font-black mb-2">No Webhooks Configured</h3>
229
- <p className="text-sm text-[var(--kyro-text-secondary)] opacity-60 mb-6">
230
- Add your first webhook to start receiving event notifications.
210
+ <h3 className="text-2xl font-bold mb-3">No Webhooks Found</h3>
211
+ <p className="text-sm text-[var(--kyro-text-secondary)] opacity-60 mb-8 max-w-sm mx-auto">
212
+ Your CMS is currently silent. Create a webhook to start broadcasting events to your external services.
231
213
  </p>
232
214
  <button
233
215
  type="button"
234
216
  onClick={() => setShowCreateModal(true)}
235
- className="px-6 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-full font-black text-sm hover:opacity-90 transition-all"
217
+ className="inline-flex items-center gap-3 px-8 py-4 bg-[var(--kyro-primary)] text-white rounded-2xl font-bold hover:scale-[1.05] transition-all shadow-xl shadow-[var(--kyro-primary)]/10"
236
218
  >
237
- <Plus className="w-4 h-4 inline mr-2" />
238
- Create Your First Webhook
219
+ <Plus className="w-5 h-5" />
220
+ Configure Webhook
239
221
  </button>
240
222
  </div>
241
223
  ) : (
@@ -243,98 +225,86 @@ export function WebhookManager() {
243
225
  {webhooks.map((webhook) => (
244
226
  <div
245
227
  key={webhook.id}
246
- className="surface-tile p-6 group hover:border-[var(--kyro-primary)] transition-all"
228
+ className="group relative overflow-hidden bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-3xl p-6 hover:border-[var(--kyro-primary)]/50 transition-all duration-300"
247
229
  >
248
- <div className="flex items-start justify-between gap-6">
230
+ <div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 relative z-10">
249
231
  <div className="flex-1">
250
- <div className="flex items-center gap-3 mb-3">
251
- <h3 className="text-lg font-black">{webhook.name}</h3>
252
- <span
253
- className={`px-2 py-0.5 rounded-full text-[8px] font-black uppercase ${
254
- webhook.status === "active"
255
- ? "bg-green-500/10 text-green-500"
256
- : "bg-amber-500/10 text-amber-500"
257
- }`}
258
- >
259
- {webhook.status}
260
- </span>
261
- </div>
262
- <p className="text-sm text-[var(--kyro-text-secondary)] opacity-50 font-mono mb-4 break-all">
263
- {webhook.url}
264
- </p>
265
- <div className="flex items-center gap-2 flex-wrap mb-4">
266
- {webhook.events?.map((event) => (
267
- <span
268
- key={event}
269
- className="px-3 py-1 bg-[var(--kyro-surface-accent)] rounded-lg text-[10px] font-black uppercase opacity-60"
270
- >
271
- {event}
272
- </span>
273
- ))}
232
+ <div className="flex items-center gap-3 mb-4">
233
+ <div className="p-2.5 bg-[var(--kyro-surface-accent)] rounded-xl group-hover:bg-[var(--kyro-primary)]/10 transition-colors">
234
+ <Webhook className="w-5 h-5 text-[var(--kyro-text-secondary)] group-hover:text-[var(--kyro-primary)] transition-colors" />
235
+ </div>
236
+ <div>
237
+ <h3 className="text-lg font-bold group-hover:text-[var(--kyro-primary)] transition-colors">{webhook.name}</h3>
238
+ <div className="flex items-center gap-2 mt-0.5">
239
+ <span className={`w-2 h-2 rounded-full ${webhook.status === "active" ? "bg-green-500 animate-pulse" : "bg-amber-500"}`} />
240
+ <span className={`text-[10px] font-bold uppercase tracking-wider ${webhook.status === "active" ? "text-green-500" : "text-amber-500"}`}>
241
+ {webhook.status}
242
+ </span>
243
+ </div>
244
+ </div>
274
245
  </div>
275
- <div className="flex items-center gap-6 text-[10px] font-black uppercase opacity-40">
276
- <div className="flex items-center gap-2">
277
- <RefreshCw className="w-3 h-3" />
278
- {webhook.lastTriggered
279
- ? `Last triggered: ${new Date(webhook.lastTriggered).toLocaleDateString()}`
280
- : "Never triggered"}
246
+
247
+ <div className="grid sm:grid-cols-3 gap-6 pt-2">
248
+ <div className="space-y-1">
249
+ <span className="text-[9px] font-bold uppercase tracking-widest opacity-30">Destination</span>
250
+ <div className="font-mono text-xs opacity-60 truncate max-w-[200px]" title={webhook.url}>
251
+ {webhook.url}
252
+ </div>
281
253
  </div>
282
- <div className="flex items-center gap-2">
283
- <Clock className="w-3 h-3" />
284
- Created:{" "}
285
- {new Date(webhook.createdAt).toLocaleDateString()}
254
+ <div className="space-y-1 border-l border-[var(--kyro-border)] pl-6">
255
+ <span className="text-[9px] font-bold uppercase tracking-widest opacity-30">Events</span>
256
+ <div className="flex flex-wrap gap-1">
257
+ {webhook.events.slice(0, 2).map((event) => (
258
+ <Badge key={event} variant="outline" className="text-[8px] font-bold px-1.5 opacity-60">
259
+ {event}
260
+ </Badge>
261
+ ))}
262
+ {webhook.events.length > 2 && (
263
+ <span className="text-[8px] font-bold opacity-30">+{webhook.events.length - 2} more</span>
264
+ )}
265
+ </div>
266
+ </div>
267
+ <div className="space-y-1 border-l border-[var(--kyro-border)] pl-6">
268
+ <span className="text-[9px] font-bold uppercase tracking-widest opacity-30">Activity</span>
269
+ <div className="text-[10px] font-bold opacity-60 flex items-center gap-1.5">
270
+ <Clock className="w-3 h-3" />
271
+ {webhook.lastTriggered
272
+ ? `Last triggered: ${new Date(webhook.lastTriggered).toLocaleDateString()}`
273
+ : "Never triggered"}
274
+ </div>
286
275
  </div>
287
276
  </div>
288
277
  </div>
289
- <div className="flex items-center gap-2 flex-shrink-0">
278
+
279
+ <div className="flex items-center gap-2 lg:bg-[var(--kyro-surface-accent)]/50 lg:p-2 lg:rounded-2xl lg:opacity-0 group-hover:opacity-100 transition-all duration-300 lg:translate-x-4 group-hover:translate-x-0">
290
280
  <button
291
281
  type="button"
292
282
  onClick={() => handleTest(webhook.id)}
293
- className="p-3 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-xl hover:bg-[var(--kyro-surface)] flex items-center gap-2"
283
+ className="flex-1 lg:flex-none p-3 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-xl hover:border-[var(--kyro-primary)] transition-all flex items-center justify-center gap-2"
294
284
  title="Send test request"
295
285
  >
296
286
  <Send className="w-4 h-4" />
297
- <span className="text-xs font-medium hidden lg:inline">
298
- Test
299
- </span>
287
+ <span className="text-xs font-bold">Test</span>
300
288
  </button>
289
+
290
+ <div className="w-px h-6 bg-[var(--kyro-border)] mx-1 hidden lg:block" />
291
+
301
292
  <button
302
293
  type="button"
303
294
  onClick={() => toggleStatus(webhook.id, webhook.status)}
304
- className={`p-3 rounded-xl flex items-center gap-2 ${
305
- webhook.status === "active"
306
- ? "text-amber-500 bg-amber-500/10 hover:bg-amber-500/20"
307
- : "text-green-500 bg-green-500/10 hover:bg-green-500/20"
308
- }`}
309
- title={
310
- webhook.status === "active"
311
- ? "Pause webhook"
312
- : "Activate webhook"
313
- }
295
+ className={`p-3 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-xl hover:border-[var(--kyro-primary)] transition-all group/toggle ${webhook.status === "active" ? "text-amber-500/50 hover:text-amber-500" : "text-green-500/50 hover:text-green-500"}`}
296
+ title={webhook.status === "active" ? "Pause webhook" : "Activate webhook"}
314
297
  >
315
- {webhook.status === "active" ? (
316
- <>
317
- <Pause className="w-4 h-4" />
318
- <span className="text-xs font-medium hidden lg:inline">
319
- Pause
320
- </span>
321
- </>
322
- ) : (
323
- <>
324
- <Play className="w-4 h-4" />
325
- <span className="text-xs font-medium hidden lg:inline">
326
- Activate
327
- </span>
328
- </>
329
- )}
298
+ {webhook.status === "active" ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
330
299
  </button>
300
+
331
301
  <button
332
302
  type="button"
333
- onClick={() => handleDelete(webhook.id)}
334
- className="p-3 text-red-500 bg-red-500/10 rounded-xl hover:bg-red-500/20"
303
+ onClick={() => remove(webhook.id, "Webhook")}
304
+ className="p-3 bg-red-500/5 border border-red-500/10 rounded-xl hover:bg-red-500/10 hover:border-red-500/30 transition-all group/delete"
335
305
  title="Delete webhook"
336
306
  >
337
- <Trash2 className="w-4 h-4" />
307
+ <Trash2 className="w-4 h-4 text-red-500/50 group-hover/delete:text-red-500" />
338
308
  </button>
339
309
  </div>
340
310
  </div>
@@ -348,89 +318,87 @@ export function WebhookManager() {
348
318
  <Modal
349
319
  open={showCreateModal}
350
320
  onClose={() => setShowCreateModal(false)}
351
- title="Add New Webhook"
321
+ title="Register Webhook"
322
+ size="lg"
352
323
  >
353
324
  <ModalContent>
354
- <div className="space-y-5">
355
- <div>
356
- <label className="block text-sm font-medium mb-2">
357
- Name <span className="text-red-500">*</span>
358
- </label>
359
- <input
360
- type="text"
361
- value={formData.name}
362
- onChange={(e) =>
363
- setFormData({ ...formData, name: e.target.value })
364
- }
365
- placeholder="e.g., Slack Notifications, Email Parser"
366
- className="w-full px-4 py-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl focus:outline-none focus:border-[var(--kyro-primary)]"
367
- />
368
- </div>
369
- <div>
370
- <label className="block text-sm font-medium mb-2">
371
- Endpoint URL <span className="text-red-500">*</span>
372
- </label>
373
- <input
374
- type="url"
375
- value={formData.url}
376
- onChange={(e) =>
377
- setFormData({ ...formData, url: e.target.value })
378
- }
379
- placeholder="https://your-server.com/webhook"
380
- className="w-full px-4 py-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl focus:outline-none focus:border-[var(--kyro-primary)]"
381
- />
382
- <p className="text-xs text-[var(--kyro-text-secondary)] mt-1 opacity-60">
383
- The URL that will receive the POST request when events occur.
384
- </p>
385
- </div>
386
- <div>
387
- <label className="block text-sm font-medium mb-2">
388
- Events to trigger on
389
- </label>
390
- <div className="grid grid-cols-2 gap-3">
391
- {eventOptions.map((opt) => (
392
- <button
393
- type="button"
394
- key={opt.value}
395
- onClick={() => {
396
- const events = formData.events.includes(opt.value)
397
- ? formData.events.filter((e) => e !== opt.value)
398
- : [...formData.events, opt.value];
399
- setFormData({ ...formData, events });
400
- }}
401
- className={`p-3 rounded-xl text-left transition-all ${
402
- formData.events.includes(opt.value)
403
- ? "bg-[var(--kyro-primary)]/10 border border-[var(--kyro-primary)] text-[var(--kyro-primary)]"
404
- : "bg-[var(--kyro-surface-accent)] border border-transparent"
405
- }`}
406
- >
407
- <div className="font-medium text-sm">{opt.label}</div>
408
- <div className="text-[10px] opacity-60">
409
- {opt.description}
410
- </div>
411
- </button>
412
- ))}
325
+ <div className="space-y-6">
326
+ <div className="flex flex-col gap-6">
327
+ <div className="space-y-4">
328
+ <div>
329
+ <label className="block text-xs font-bold mb-1.5 text-[var(--kyro-text-secondary)] uppercase tracking-wider">Webhook Name</label>
330
+ <input
331
+ type="text"
332
+ value={formData.name}
333
+ onChange={(e) =>
334
+ setFormData({ ...formData, name: e.target.value })
335
+ }
336
+ placeholder="e.g., Slack Notifications"
337
+ className="w-full px-4 py-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-primary)]"
338
+ />
339
+ </div>
340
+ <div>
341
+ <label className="block text-xs font-bold mb-1.5 text-[var(--kyro-text-secondary)] uppercase tracking-wider">Payload URL</label>
342
+ <input
343
+ type="url"
344
+ value={formData.url}
345
+ onChange={(e) =>
346
+ setFormData({ ...formData, url: e.target.value })
347
+ }
348
+ placeholder="https://your-server.com/webhook"
349
+ className="w-full px-4 py-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-primary)]"
350
+ />
351
+ </div>
352
+ <div>
353
+ <label className="block text-xs font-bold mb-1.5 text-[var(--kyro-text-secondary)] uppercase tracking-wider">Signing Secret</label>
354
+ <input
355
+ type="text"
356
+ value={formData.secret}
357
+ onChange={(e) =>
358
+ setFormData({ ...formData, secret: e.target.value })
359
+ }
360
+ placeholder="Optional secret for verification"
361
+ className="w-full px-4 py-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-primary)]"
362
+ />
363
+ </div>
364
+ </div>
365
+
366
+ <div className="space-y-4">
367
+ <label className="block text-xs font-bold mb-1.5 text-[var(--kyro-text-secondary)] uppercase tracking-wider">Subscribed Events</label>
368
+ <div className="grid grid-cols-2 gap-4">
369
+ {eventOptions.map((opt) => (
370
+ <button
371
+ type="button"
372
+ key={opt.value}
373
+ onClick={() => {
374
+ const events = formData.events.includes(opt.value)
375
+ ? formData.events.filter((e) => e !== opt.value)
376
+ : [...formData.events, opt.value];
377
+ setFormData({ ...formData, events });
378
+ }}
379
+ className={`flex items-center gap-3 p-3 rounded-lg border transition-all text-left ${formData.events.includes(opt.value)
380
+ ? "bg-[var(--kyro-primary)]/5 border-[var(--kyro-primary)]/30 text-[var(--kyro-primary)]"
381
+ : "bg-[var(--kyro-surface-accent)]/50 border-[var(--kyro-border)] hover:border-[var(--kyro-primary)]/30"
382
+ }`}
383
+ >
384
+ <div className={`w-4 h-4 rounded flex items-center justify-center border transition-all ${formData.events.includes(opt.value) ? "bg-[var(--kyro-primary)] border-[var(--kyro-primary)]" : "border-[var(--kyro-border)]"}`}>
385
+ {formData.events.includes(opt.value) && <CheckCircle2 className="w-3 h-3 text-white" />}
386
+ </div>
387
+ <div>
388
+ <div className="text-xs font-bold">{opt.label}</div>
389
+ <div className="text-[10px] opacity-50">{opt.description}</div>
390
+ </div>
391
+ </button>
392
+ ))}
393
+ </div>
413
394
  </div>
414
395
  </div>
415
- <div>
416
- <label className="block text-sm font-medium mb-2">
417
- Signing Secret (optional)
418
- </label>
419
- <input
420
- type="text"
421
- value={formData.secret}
422
- onChange={(e) =>
423
- setFormData({ ...formData, secret: e.target.value })
424
- }
425
- placeholder="Your webhook signing secret"
426
- className="w-full px-4 py-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl focus:outline-none focus:border-[var(--kyro-primary)]"
427
- />
428
- <p className="text-xs text-[var(--kyro-text-secondary)] mt-1 opacity-60">
429
- Used to verify that requests came from Kyro CMS.
430
- </p>
431
- </div>
396
+
432
397
  {createError && (
433
- <p className="text-sm text-red-500">{createError}</p>
398
+ <div className="p-3 bg-red-500/10 border border-red-500/20 rounded-xl flex items-center gap-2 text-red-500 text-xs font-bold">
399
+ <AlertTriangle className="w-4 h-4" />
400
+ {createError}
401
+ </div>
434
402
  )}
435
403
  </div>
436
404
  </ModalContent>
@@ -438,95 +406,58 @@ export function WebhookManager() {
438
406
  <button
439
407
  type="button"
440
408
  onClick={() => setShowCreateModal(false)}
441
- className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"
409
+ className="px-6 py-2.5 rounded-xl font-bold text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors"
442
410
  >
443
411
  Cancel
444
412
  </button>
445
413
  <button
446
414
  type="button"
447
415
  onClick={handleCreate}
448
- className="px-4 py-2 rounded-lg font-medium text-sm bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90"
416
+ className="px-6 py-2.5 rounded-xl font-bold text-sm bg-[var(--kyro-primary)] text-white hover:opacity-90 transition-all shadow-lg shadow-[var(--kyro-primary)]/10"
449
417
  >
450
418
  Create Webhook
451
419
  </button>
452
420
  </ModalActions>
453
421
  </Modal>
454
422
 
455
- {/* Delete Modal */}
456
- <Modal
457
- open={showDeleteModal}
458
- onClose={() => setShowDeleteModal(false)}
459
- title="Delete Webhook"
460
- variant="danger"
461
- >
462
- <ModalContent>
463
- <p className="text-sm text-[var(--kyro-text-secondary)] mb-4">
464
- Are you sure you want to delete this webhook? This action cannot be
465
- undone.
466
- </p>
467
- <div className="p-4 bg-red-500/10 border border-red-500/20 rounded-xl">
468
- <p className="text-sm font-medium text-red-500">
469
- This endpoint will stop receiving notifications immediately.
470
- </p>
471
- </div>
472
- </ModalContent>
473
- <ModalActions>
474
- <button
475
- type="button"
476
- onClick={() => setShowDeleteModal(false)}
477
- className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"
478
- >
479
- Keep Webhook
480
- </button>
481
- <button
482
- type="button"
483
- onClick={confirmDelete}
484
- className="px-4 py-2 rounded-lg font-medium text-sm bg-red-500 text-white hover:bg-red-600"
485
- >
486
- Delete Permanently
487
- </button>
488
- </ModalActions>
489
- </Modal>
490
-
491
423
  {/* Test Webhook Modal */}
492
424
  <Modal
493
425
  open={showTestModal}
494
426
  onClose={() => setShowTestModal(false)}
495
- title="Test Webhook"
427
+ title="Webhook Test Dispatcher"
496
428
  >
497
429
  <ModalContent>
498
- {testResult ? (
499
- <div
500
- className={`flex items-center gap-3 ${testResult.success ? "text-green-500" : "text-red-500"}`}
501
- >
502
- {testResult.success ? (
503
- <CheckCircle2 className="w-6 h-6" />
504
- ) : (
505
- <AlertTriangle className="w-6 h-6" />
506
- )}
507
- <div>
508
- <p className="font-bold">
509
- {testResult.success ? "Success!" : "Failed"}
510
- </p>
511
- <p className="text-sm text-[var(--kyro-text-secondary)]">
512
- {testResult.message}
513
- </p>
430
+ <div className="p-8 rounded-[2rem] bg-[var(--kyro-surface-accent)]/30 border border-[var(--kyro-border)] text-center">
431
+ {testResult ? (
432
+ <div className="space-y-6">
433
+ <div className={`w-16 h-16 mx-auto rounded-2xl flex items-center justify-center shadow-xl ${testResult.success ? "bg-green-500/10 text-green-500" : "bg-red-500/10 text-red-500"}`}>
434
+ {testResult.success ? <CheckCircle2 className="w-8 h-8" /> : <AlertTriangle className="w-8 h-8" />}
435
+ </div>
436
+ <div>
437
+ <h4 className="text-xl font-bold mb-2">{testResult.success ? "Dispatch Successful" : "Dispatch Failed"}</h4>
438
+ <p className="text-sm text-[var(--kyro-text-secondary)] opacity-70">
439
+ {testResult.message}
440
+ </p>
441
+ </div>
514
442
  </div>
515
- </div>
516
- ) : (
517
- <div className="flex items-center gap-3">
518
- <RefreshCw className="w-5 h-5 animate-spin" />
519
- <p>Sending test request...</p>
520
- </div>
521
- )}
443
+ ) : (
444
+ <div className="space-y-6 py-4">
445
+ <RefreshCw className="w-12 h-12 text-[var(--kyro-primary)] animate-spin mx-auto opacity-50" />
446
+ <div>
447
+ <h4 className="text-xl font-bold mb-1">Synthesizing Payload</h4>
448
+ <p className="text-sm text-[var(--kyro-text-secondary)] opacity-50 italic">Dispatching to remote endpoint...</p>
449
+ </div>
450
+ </div>
451
+ )}
452
+ </div>
522
453
  </ModalContent>
523
454
  <ModalActions>
524
455
  <button
525
456
  type="button"
526
457
  onClick={() => setShowTestModal(false)}
527
- className="px-4 py-2 rounded-lg font-medium text-sm bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90"
458
+ className="w-full py-3 rounded-xl font-bold text-sm bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] hover:bg-[var(--kyro-surface)] transition-all"
528
459
  >
529
- Close
460
+ Acknowledge
530
461
  </button>
531
462
  </ModalActions>
532
463
  </Modal>
@@ -535,58 +466,65 @@ export function WebhookManager() {
535
466
  <Modal
536
467
  open={showHelpModal}
537
468
  onClose={() => setShowHelpModal(false)}
538
- title="How Webhooks Work"
469
+ title="Webhook Integration Guide"
470
+ size="lg"
539
471
  >
540
472
  <ModalContent>
541
- <div className="space-y-6">
542
- <div>
543
- <h4 className="font-bold mb-2">What is a webhook?</h4>
544
- <p className="text-sm text-[var(--kyro-text-secondary)]">
545
- A webhook is an HTTP POST request sent to your endpoint when an
546
- event occurs in your CMS. Unlike API polling (continuously
547
- checking for changes), webhooks give you instant notifications.
548
- </p>
549
- </div>
550
- <div>
551
- <h4 className="font-bold mb-2">Request format</h4>
552
- <p className="text-sm text-[var(--kyro-text-secondary)] mb-3">
553
- When an event triggers, Kyro sends a POST request with:
554
- </p>
555
- <div className="bg-[var(--kyro-bg)] rounded-lg p-4 font-mono text-sm space-y-2">
556
- <div className="text-[var(--kyro-text-secondary)]">{"{"}</div>
557
- <div className="pl-4">"event": "create",</div>
558
- <div className="pl-4">"collection": "posts",</div>
559
- <div className="pl-4">"documentId": "xxx",</div>
560
- <div className="pl-4">"timestamp": "2024-..."</div>
561
- <div className="text-[var(--kyro-text-secondary)]">{"}"}</div>
473
+ <div className="space-y-8">
474
+ <div className="grid grid-cols-1 gap-6">
475
+ <div className="p-6 rounded-3xl bg-[var(--kyro-surface-accent)]/50 border border-[var(--kyro-border)]">
476
+ <h4 className="font-bold mb-3 flex items-center gap-2">
477
+ <ExternalLink className="w-4 h-4 text-[var(--kyro-primary)]" />
478
+ Request Context
479
+ </h4>
480
+ <p className="text-sm text-[var(--kyro-text-secondary)] leading-relaxed">
481
+ When an event triggers, Kyro sends a standard <span className="font-bold text-[var(--kyro-text-primary)]">POST</span> request
482
+ to your endpoint with a JSON payload containing document metadata and operation details.
483
+ </p>
562
484
  </div>
563
- </div>
564
- <div>
565
- <h4 className="font-bold mb-2">Verifying requests</h4>
566
- <p className="text-sm text-[var(--kyro-text-secondary)]">
567
- If you provide a signing secret, the request will include an
568
- X-Kyro-Signature header that you can use to verify the request
569
- came from Kyro CMS.
570
- </p>
571
- </div>
572
- <div className="bg-amber-500/10 border border-amber-500/20 rounded-xl p-4">
573
- <div className="flex items-start gap-2">
574
- <Shield className="w-5 h-5 text-amber-500 flex-shrink-0" />
575
- <p className="text-sm text-[var(--kyro-text-secondary)]">
576
- Always verify webhook signatures in production to ensure
577
- requests are authentic.
485
+ <div className="p-6 rounded-3xl bg-[var(--kyro-surface-accent)]/50 border border-[var(--kyro-border)]">
486
+ <h4 className="font-bold mb-3 flex items-center gap-2">
487
+ <Shield className="w-4 h-4 text-[var(--kyro-primary)]" />
488
+ Security Verification
489
+ </h4>
490
+ <p className="text-sm text-[var(--kyro-text-secondary)] leading-relaxed">
491
+ If a secret is provided, each request includes an <span className="font-mono text-[var(--kyro-primary)]">X-Kyro-Signature</span> header.
492
+ Always verify this signature in production environments.
578
493
  </p>
579
494
  </div>
580
495
  </div>
496
+
497
+ <div className="space-y-3">
498
+ <h4 className="text-xs font-bold uppercase tracking-widest opacity-40 px-1">Payload Architecture</h4>
499
+ <div className="relative group">
500
+ <div className="absolute -inset-1 bg-gradient-to-r from-[var(--kyro-primary)]/20 to-transparent rounded-2xl blur opacity-25 group-hover:opacity-50 transition-all"></div>
501
+ <div className="relative bg-[var(--kyro-bg)] rounded-2xl border border-[var(--kyro-border)] p-6 font-mono text-sm overflow-hidden">
502
+ <div className="flex items-center gap-2 mb-4 border-b border-[var(--kyro-border)] pb-4 opacity-30">
503
+ <div className="w-2 h-2 rounded-full bg-red-500/50" />
504
+ <div className="w-2 h-2 rounded-full bg-amber-500/50" />
505
+ <div className="w-2 h-2 rounded-full bg-green-500/50" />
506
+ <span className="text-[10px] ml-2 tracking-tighter">payload.json</span>
507
+ </div>
508
+ <div className="space-y-1">
509
+ <div className="text-[var(--kyro-text-secondary)]">{"{"}</div>
510
+ <div className="pl-4"><span className="text-[var(--kyro-primary)]">"event"</span>: <span className="text-green-500">"collection.create"</span>,</div>
511
+ <div className="pl-4"><span className="text-[var(--kyro-primary)]">"collection"</span>: <span className="text-green-500">"products"</span>,</div>
512
+ <div className="pl-4"><span className="text-[var(--kyro-primary)]">"id"</span>: <span className="text-green-500">"prod_8273"</span>,</div>
513
+ <div className="pl-4"><span className="text-[var(--kyro-primary)]">"timestamp"</span>: <span className="text-green-500">"2026-05-14T02:53:22Z"</span></div>
514
+ <div className="text-[var(--kyro-text-secondary)]">{"}"}</div>
515
+ </div>
516
+ </div>
517
+ </div>
518
+ </div>
581
519
  </div>
582
520
  </ModalContent>
583
521
  <ModalActions>
584
522
  <button
585
523
  type="button"
586
524
  onClick={() => setShowHelpModal(false)}
587
- className="px-4 py-2 rounded-lg font-medium text-sm bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90"
525
+ className="w-full py-3 rounded-xl font-bold text-sm bg-[var(--kyro-primary)] text-white hover:opacity-90 transition-all shadow-lg shadow-[var(--kyro-primary)]/20"
588
526
  >
589
- Got it
527
+ I Understand
590
528
  </button>
591
529
  </ModalActions>
592
530
  </Modal>