@kyro-cms/admin 0.12.3 → 0.12.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 (110) hide show
  1. package/dist/index.cjs +64 -26
  2. package/dist/index.d.cts +2 -1
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.js +64 -26
  5. package/package.json +8 -2
  6. package/src/components/ActionBar.tsx +16 -16
  7. package/src/components/ApiHealth.tsx +522 -0
  8. package/src/components/ApiKeysManager.tsx +6 -4
  9. package/src/components/AuditLogsPage.tsx +4 -2
  10. package/src/components/AutoForm.tsx +10 -7
  11. package/src/components/BrandingHub.tsx +3 -1
  12. package/src/components/Dashboard.tsx +44 -37
  13. package/src/components/DashboardMetrics.tsx +168 -58
  14. package/src/components/DetailView.tsx +125 -50
  15. package/src/components/DeveloperCenter.tsx +5 -3
  16. package/src/components/GraphQLPlayground.tsx +10 -8
  17. package/src/components/ListView.tsx +66 -45
  18. package/src/components/LoginPage.tsx +21 -19
  19. package/src/components/MarketplaceManager.tsx +3 -1
  20. package/src/components/MediaGallery.tsx +12 -9
  21. package/src/components/PluginsManager.tsx +6 -4
  22. package/src/components/RestPlayground.tsx +8 -6
  23. package/src/components/SessionsManager.tsx +3 -1
  24. package/src/components/Sidebar.astro +180 -92
  25. package/src/components/UserManagement.tsx +8 -6
  26. package/src/components/UserMenu.tsx +19 -17
  27. package/src/components/VersionHistoryPanel.tsx +6 -4
  28. package/src/components/WebhookManager.tsx +143 -15
  29. package/src/components/autoform/AutoFormApiView.tsx +3 -1
  30. package/src/components/autoform/AutoFormEditView.tsx +5 -3
  31. package/src/components/autoform/AutoFormHeader.tsx +6 -6
  32. package/src/components/blocks/AccordionBlock.tsx +5 -3
  33. package/src/components/blocks/ArrayBlock.tsx +5 -3
  34. package/src/components/blocks/BlockWrapper.tsx +5 -3
  35. package/src/components/blocks/CodeBlock.tsx +4 -2
  36. package/src/components/blocks/FileBlock.tsx +5 -3
  37. package/src/components/blocks/HeroBlock.tsx +5 -3
  38. package/src/components/blocks/ImageBlock.tsx +6 -4
  39. package/src/components/blocks/ListBlock.tsx +5 -3
  40. package/src/components/blocks/ParagraphBlock.tsx +3 -1
  41. package/src/components/blocks/RelationshipBlock.tsx +5 -3
  42. package/src/components/blocks/RichTextBlock.tsx +5 -3
  43. package/src/components/blocks/VideoBlock.tsx +5 -3
  44. package/src/components/fields/AccordionField.tsx +10 -51
  45. package/src/components/fields/ArrayField.tsx +9 -7
  46. package/src/components/fields/ArrayLayout.tsx +3 -1
  47. package/src/components/fields/BlocksField.tsx +24 -75
  48. package/src/components/fields/ButtonField.tsx +3 -1
  49. package/src/components/fields/CardField.tsx +6 -4
  50. package/src/components/fields/GroupLayout.tsx +2 -49
  51. package/src/components/fields/HeadingField.tsx +3 -1
  52. package/src/components/fields/HeadingSubheadingField.tsx +4 -2
  53. package/src/components/fields/HeroField.tsx +8 -6
  54. package/src/components/fields/JSONField.tsx +9 -10
  55. package/src/components/fields/LinkField.tsx +4 -2
  56. package/src/components/fields/ListField.tsx +3 -1
  57. package/src/components/fields/MarkdownField.tsx +3 -17
  58. package/src/components/fields/RelationshipBlockField.tsx +7 -5
  59. package/src/components/fields/RelationshipField.tsx +3 -1
  60. package/src/components/fields/RichTextField.tsx +19 -16
  61. package/src/components/fields/SecretField.tsx +9 -69
  62. package/src/components/fields/SelectField.tsx +11 -6
  63. package/src/components/fields/TabsLayout.tsx +2 -48
  64. package/src/components/fields/TextField.tsx +9 -7
  65. package/src/components/fields/UploadField.tsx +3 -1
  66. package/src/components/fields/VideoField.tsx +6 -4
  67. package/src/components/ui/BlockDrawer.tsx +3 -1
  68. package/src/components/ui/CommandPalette.tsx +9 -7
  69. package/src/components/ui/CommandPaletteWrapper.tsx +3 -1
  70. package/src/components/ui/IconPickerModal.tsx +4 -2
  71. package/src/components/ui/PageHeader.tsx +48 -20
  72. package/src/components/users/UserDetail.tsx +3 -1
  73. package/src/components/users/UserForm.tsx +5 -3
  74. package/src/fields/examples/sample-field-2.tsx +3 -1
  75. package/src/fields/examples/sample-field.tsx +3 -1
  76. package/src/hooks/useAutoFormState.ts +19 -11
  77. package/src/integration.ts +21 -0
  78. package/src/layouts/AdminLayout.astro +252 -189
  79. package/src/layouts/AuthLayout.astro +52 -31
  80. package/src/lib/api.ts +7 -7
  81. package/src/lib/autoform-store.ts +4 -0
  82. package/src/lib/deep-equal.ts +3 -3
  83. package/src/lib/i18n.ts +44 -0
  84. package/src/lib/vite-shim-plugin.ts +3 -0
  85. package/src/locales/de.json +163 -0
  86. package/src/locales/en.json +163 -0
  87. package/src/locales/es.json +163 -0
  88. package/src/locales/fr.json +163 -0
  89. package/src/locales/pt.json +163 -0
  90. package/src/pages/403.astro +2 -1
  91. package/src/pages/[collection]/[id].astro +23 -10
  92. package/src/pages/[collection]/index.astro +48 -34
  93. package/src/pages/audit/index.astro +2 -1
  94. package/src/pages/graphql-explorer.astro +2 -1
  95. package/src/pages/graphql.astro +2 -1
  96. package/src/pages/health.astro +9 -0
  97. package/src/pages/index.astro +6 -5
  98. package/src/pages/keys.astro +2 -1
  99. package/src/pages/marketplace.astro +2 -1
  100. package/src/pages/media.astro +2 -1
  101. package/src/pages/plugins.astro +2 -1
  102. package/src/pages/rest-playground.astro +2 -1
  103. package/src/pages/roles/index.astro +16 -52
  104. package/src/pages/sessions.astro +2 -1
  105. package/src/pages/settings/[slug].astro +45 -30
  106. package/src/pages/users/[id].astro +1 -0
  107. package/src/pages/users/index.astro +2 -1
  108. package/src/pages/users/new.astro +2 -1
  109. package/src/pages/webhooks.astro +2 -1
  110. package/src/pages/index_ALT.astro +0 -3
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect, useState } from "react";
2
2
  import { apiGet } from "../lib/api";
3
+ import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts";
3
4
 
4
5
  interface MetricsData {
5
6
  totalDocuments: number;
@@ -70,15 +71,24 @@ function MetricCard({
70
71
 
71
72
 
72
73
 
73
- export function DashboardMetrics() {
74
+ export interface DashboardMetricsProps {
75
+ isEcommerce?: boolean;
76
+ }
77
+
78
+ export const DashboardMetrics: React.FC<DashboardMetricsProps> = ({ isEcommerce }) => {
74
79
  const [data, setData] = useState<MetricsData | null>(null);
80
+ const [analyticsData, setAnalyticsData] = useState<any>(null);
75
81
  const [loading, setLoading] = useState(true);
76
82
  const [error, setError] = useState<string | null>(null);
77
83
 
78
84
  useEffect(() => {
79
- apiGet<MetricsData>("/api/metrics", { autoToast: false })
80
- .then((res) => {
81
- setData(res);
85
+ Promise.all([
86
+ apiGet<MetricsData>("/api/metrics", { autoToast: false }),
87
+ isEcommerce ? apiGet<any>("/api/analytics", { autoToast: false }).catch(() => null) : Promise.resolve(null)
88
+ ])
89
+ .then(([metricsRes, analyticsRes]) => {
90
+ setData(metricsRes);
91
+ if (analyticsRes) setAnalyticsData(analyticsRes);
82
92
  setError(null);
83
93
  })
84
94
  .catch((err) => {
@@ -86,14 +96,14 @@ export function DashboardMetrics() {
86
96
  setError("Unable to load metrics");
87
97
  })
88
98
  .finally(() => setLoading(false));
89
- }, []);
99
+ }, [isEcommerce]);
90
100
 
91
101
  if (error) {
92
102
  return (
93
- <div className="surface-tile overflow-hidden">
103
+ <div className="surface-tile overflow-hidden mb-6">
94
104
  <div className="p-6 border-b border-[var(--kyro-border)]">
95
105
  <h2 className="text-xl font-bold tracking-tight text-[var(--kyro-text-primary)]">
96
- CMS Metrics
106
+ Metrics
97
107
  </h2>
98
108
  </div>
99
109
  <div className="p-6">
@@ -108,21 +118,8 @@ export function DashboardMetrics() {
108
118
  <div className="p-6 border-b border-[var(--kyro-border)]">
109
119
  <div className="flex items-center justify-between">
110
120
  <div>
111
- <h2 className="text-xl font-bold tracking-tight text-[var(--kyro-text-primary)] flex items-center gap-2">
112
- <svg
113
- className="w-5 h-5 text-[var(--kyro-primary)]"
114
- fill="none"
115
- stroke="currentColor"
116
- viewBox="0 0 24 24"
117
- >
118
- <path
119
- strokeLinecap="round"
120
- strokeLinejoin="round"
121
- strokeWidth="2"
122
- d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
123
- />
124
- </svg>
125
- CMS Metrics
121
+ <h2 className="text-xl font-bold tracking-tight text-[var(--kyro-text-primary)]">
122
+ Metrics
126
123
  </h2>
127
124
  <p className="text-xs text-[var(--kyro-text-secondary)] font-medium mt-1">
128
125
  Real-time content, performance, and system health overview
@@ -147,31 +144,86 @@ export function DashboardMetrics() {
147
144
  <SkeletonCard />
148
145
  </>
149
146
  ) : data ? (
150
- <>
151
- <MetricCard
152
- icon={
153
- <svg
154
- className="w-5 h-5"
155
- fill="none"
156
- stroke="currentColor"
157
- viewBox="0 0 24 24"
158
- >
159
- <path
160
- strokeLinecap="round"
161
- strokeLinejoin="round"
162
- strokeWidth="2"
163
- d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
164
- />
165
- </svg>
166
- }
167
- iconBg="rgba(99, 102, 241, 0.1)"
168
- iconColor="#6366f1"
169
- value={data.totalDocuments}
170
- label="Documents"
171
- subtext={`Across ${data.collections} collection${data.collections !== 1 ? "s" : ""}`}
172
- />
147
+ isEcommerce ? (
148
+ <>
149
+ <MetricCard
150
+ icon={
151
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
152
+ }
153
+ iconBg="rgba(34, 197, 94, 0.1)"
154
+ iconColor="#22c55e"
155
+ value={analyticsData?.totalRevenue
156
+ ? new Intl.NumberFormat('en-US', { style: 'currency', currency: analyticsData.currencyCode || 'USD' }).format(analyticsData.totalRevenue)
157
+ : "$0.00"}
158
+ label="Sales"
159
+ subtext="Total revenue generated"
160
+ />
161
+ <MetricCard
162
+ icon={
163
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" /></svg>
164
+ }
165
+ iconBg="rgba(236, 72, 153, 0.1)"
166
+ iconColor="#ec4899"
167
+ value={data.collectionCounts?.['products'] || 0}
168
+ label="Products"
169
+ subtext="Active inventory items"
170
+ />
171
+ <MetricCard
172
+ icon={
173
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" /></svg>
174
+ }
175
+ iconBg="rgba(16, 185, 129, 0.1)"
176
+ iconColor="#10b981"
177
+ value={data.collectionCounts?.['orders'] || 0}
178
+ label="Orders"
179
+ subtext="Total orders placed"
180
+ />
181
+ <MetricCard
182
+ icon={
183
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
184
+ }
185
+ iconBg="rgba(59, 130, 246, 0.1)"
186
+ iconColor="#3b82f6"
187
+ value={data.collectionCounts?.['customers'] || 0}
188
+ label="Customers"
189
+ subtext="Registered shoppers"
190
+ />
191
+ <MetricCard
192
+ icon={
193
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" /></svg>
194
+ }
195
+ iconBg="rgba(245, 158, 11, 0.1)"
196
+ iconColor="#f59e0b"
197
+ value={data.collectionCounts?.['reviews'] || 0}
198
+ label="Reviews"
199
+ subtext="Customer feedback"
200
+ />
201
+ </>
202
+ ) : (
203
+ <>
204
+ <MetricCard
205
+ icon={
206
+ <svg
207
+ className="w-5 h-5"
208
+ fill="none"
209
+ stroke="currentColor"
210
+ viewBox="0 0 24 24"
211
+ >
212
+ <path
213
+ strokeLinecap="round"
214
+ strokeLinejoin="round"
215
+ strokeWidth="2"
216
+ d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
217
+ />
218
+ </svg>
219
+ }
220
+ iconBg="rgba(99, 102, 241, 0.1)"
221
+ iconColor="#6366f1"
222
+ value={data.totalDocuments || 0}
223
+ label="Documents"
224
+ subtext={`Across ${data.collections || 0} collection${data.collections !== 1 ? "s" : ""}`}
225
+ />
173
226
 
174
- {data.totalMedia !== undefined && (
175
227
  <MetricCard
176
228
  icon={
177
229
  <svg
@@ -190,13 +242,11 @@ export function DashboardMetrics() {
190
242
  }
191
243
  iconBg="rgba(168, 85, 247, 0.1)"
192
244
  iconColor="#a855f7"
193
- value={data.totalMedia}
245
+ value={data.totalMedia || 0}
194
246
  label="Media Files"
195
247
  subtext="Images, videos & docs"
196
248
  />
197
- )}
198
249
 
199
- {data.totalUsers !== undefined && (
200
250
  <MetricCard
201
251
  icon={
202
252
  <svg
@@ -215,13 +265,11 @@ export function DashboardMetrics() {
215
265
  }
216
266
  iconBg="rgba(14, 165, 233, 0.1)"
217
267
  iconColor="#0ea5e9"
218
- value={data.totalUsers}
268
+ value={data.totalUsers || 0}
219
269
  label="Team Members"
220
270
  subtext="Active user accounts"
221
271
  />
222
- )}
223
272
 
224
- {data.totalWebhooks !== undefined && (
225
273
  <MetricCard
226
274
  icon={
227
275
  <svg
@@ -240,13 +288,11 @@ export function DashboardMetrics() {
240
288
  }
241
289
  iconBg="rgba(34, 197, 94, 0.1)"
242
290
  iconColor="#22c55e"
243
- value={data.totalWebhooks}
291
+ value={data.totalWebhooks || 0}
244
292
  label="Webhooks"
245
293
  subtext="Active integrations"
246
294
  />
247
- )}
248
295
 
249
- {data.totalApiKeys !== undefined && (
250
296
  <MetricCard
251
297
  icon={
252
298
  <svg
@@ -265,12 +311,12 @@ export function DashboardMetrics() {
265
311
  }
266
312
  iconBg="rgba(245, 158, 11, 0.1)"
267
313
  iconColor="#f59e0b"
268
- value={data.totalApiKeys}
314
+ value={data.totalApiKeys || 0}
269
315
  label="API Keys"
270
316
  subtext="Developer access tokens"
271
317
  />
272
- )}
273
- </>
318
+ </>
319
+ )
274
320
  ) : null}
275
321
  </div>
276
322
 
@@ -278,3 +324,67 @@ export function DashboardMetrics() {
278
324
  </div>
279
325
  );
280
326
  }
327
+
328
+ export const RevenueChart: React.FC = () => {
329
+ const [data, setData] = useState<any>(null);
330
+ const [loading, setLoading] = useState(true);
331
+
332
+ useEffect(() => {
333
+ apiGet<any>("/api/analytics", { autoToast: false })
334
+ .then(setData)
335
+ .catch(console.error)
336
+ .finally(() => setLoading(false));
337
+ }, []);
338
+
339
+ if (loading) return (
340
+ <div className="surface-tile mt-6 p-6 overflow-hidden">
341
+ <div className="h-64 animate-pulse bg-[var(--kyro-bg-secondary)] rounded-lg"></div>
342
+ </div>
343
+ );
344
+ const hasChartData = data && data.chartData && data.chartData.length > 0;
345
+
346
+ return (
347
+ <div className="surface-tile mt-6 p-6 overflow-hidden">
348
+ <div className="mb-6 flex justify-between items-end">
349
+ <div>
350
+ <h2 className="text-xl font-bold tracking-tight text-[var(--kyro-text-primary)]">Revenue Overview</h2>
351
+ <p className="text-xs text-[var(--kyro-text-secondary)] font-medium mt-1">Daily revenue and order volume</p>
352
+ </div>
353
+ <div className="text-right">
354
+ <h3 className="text-2xl font-bold text-[var(--kyro-primary)]">
355
+ {new Intl.NumberFormat('en-US', { style: 'currency', currency: data?.currencyCode || 'USD' }).format(data?.totalRevenue || 0)}
356
+ </h3>
357
+ <p className="text-xs text-[var(--kyro-text-muted)]">{data?.totalOrders || 0} total orders</p>
358
+ </div>
359
+ </div>
360
+ <div className="h-72 w-full">
361
+ {!hasChartData ? (
362
+ <div className="flex flex-col items-center justify-center w-full h-full text-[var(--kyro-text-secondary)] bg-[var(--kyro-bg-secondary)] rounded-xl border border-dashed border-[var(--kyro-border)]">
363
+ <svg className="w-10 h-10 mb-2 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /></svg>
364
+ <p className="text-sm font-medium">No chart data to display</p>
365
+ </div>
366
+ ) : (
367
+ <ResponsiveContainer width="100%" height="100%">
368
+ <AreaChart data={data.chartData} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
369
+ <defs>
370
+ <linearGradient id="colorRevenue" x1="0" y1="0" x2="0" y2="1">
371
+ <stop offset="5%" stopColor="var(--kyro-primary)" stopOpacity={0.3}/>
372
+ <stop offset="95%" stopColor="var(--kyro-primary)" stopOpacity={0}/>
373
+ </linearGradient>
374
+ </defs>
375
+ <XAxis dataKey="date" stroke="var(--kyro-text-muted)" fontSize={12} tickLine={false} axisLine={false} />
376
+ <YAxis stroke="var(--kyro-text-muted)" fontSize={12} tickLine={false} axisLine={false} tickFormatter={(value) => new Intl.NumberFormat('en-US', { style: 'currency', currency: data?.currencyCode || 'USD', maximumFractionDigits: 0 }).format(value)} />
377
+ <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="var(--kyro-border)" />
378
+ <Tooltip
379
+ contentStyle={{ backgroundColor: 'var(--kyro-bg-primary)', border: '1px solid var(--kyro-border)', borderRadius: '8px', color: 'var(--kyro-text-primary)' }}
380
+ itemStyle={{ color: 'var(--kyro-text-primary)' }}
381
+ formatter={(value: any) => [new Intl.NumberFormat('en-US', { style: 'currency', currency: data?.currencyCode || 'USD' }).format(value), 'Revenue']}
382
+ />
383
+ <Area type="monotone" dataKey="revenue" stroke="var(--kyro-primary)" fillOpacity={1} fill="url(#colorRevenue)" strokeWidth={2} />
384
+ </AreaChart>
385
+ </ResponsiveContainer>
386
+ )}
387
+ </div>
388
+ </div>
389
+ );
390
+ }