@kyro-cms/admin 0.1.9 → 0.2.0
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 +3 -0
- package/package.json +4 -4
- package/src/components/Admin.tsx +2 -1
- package/src/components/ApiKeysManager.tsx +45 -49
- package/src/components/AuditLogsPage.tsx +9 -4
- package/src/components/BrandingHub.tsx +24 -32
- package/src/components/CreateView.tsx +6 -13
- package/src/components/DetailView.tsx +8 -30
- package/src/components/DeveloperCenter.tsx +31 -27
- package/src/components/EnhancedListView.tsx +5 -6
- package/src/components/ListView.tsx +21 -19
- package/src/components/LoginPage.tsx +3 -17
- package/src/components/MediaGallery.tsx +103 -105
- package/src/components/UserManagement.tsx +28 -18
- package/src/components/WebhookManager.tsx +43 -56
- package/src/components/fields/RelationshipBlockField.tsx +4 -7
- package/src/components/fields/RelationshipField.tsx +4 -10
- package/src/components/fields/UploadField.tsx +37 -44
- package/src/components/ui/CommandPalette.tsx +1 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { apiGet, apiPost, apiPatch, apiDelete } from "@kyro-cms/utils/lib/api";
|
|
3
|
+
import { formatDate } from "@kyro-cms/utils/lib/date-utils";
|
|
2
4
|
import {
|
|
3
5
|
Webhook,
|
|
4
6
|
Plus,
|
|
@@ -52,11 +54,8 @@ export function WebhookManager() {
|
|
|
52
54
|
const loadWebhooks = async () => {
|
|
53
55
|
setLoading(true);
|
|
54
56
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
const data = await res.json();
|
|
58
|
-
setWebhooks(data);
|
|
59
|
-
}
|
|
57
|
+
const data = await apiGet("/api/webhooks");
|
|
58
|
+
setWebhooks(data);
|
|
60
59
|
} catch (e) {
|
|
61
60
|
console.error(e);
|
|
62
61
|
} finally {
|
|
@@ -75,20 +74,10 @@ export function WebhookManager() {
|
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
if (res.ok) {
|
|
85
|
-
setShowCreateModal(false);
|
|
86
|
-
setFormData({ name: "", url: "", events: [], secret: "" });
|
|
87
|
-
loadWebhooks();
|
|
88
|
-
} else {
|
|
89
|
-
const error = await res.json();
|
|
90
|
-
setCreateError(error.error || "Failed to create webhook");
|
|
91
|
-
}
|
|
77
|
+
await apiPost("/api/webhooks", formData);
|
|
78
|
+
setShowCreateModal(false);
|
|
79
|
+
setFormData({ name: "", url: "", events: [], secret: "" });
|
|
80
|
+
loadWebhooks();
|
|
92
81
|
} catch (e) {
|
|
93
82
|
console.error(e);
|
|
94
83
|
setCreateError("Failed to create webhook");
|
|
@@ -104,12 +93,8 @@ export function WebhookManager() {
|
|
|
104
93
|
if (!deleteId) return;
|
|
105
94
|
|
|
106
95
|
try {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
});
|
|
110
|
-
if (res.ok) {
|
|
111
|
-
loadWebhooks();
|
|
112
|
-
}
|
|
96
|
+
await apiDelete(`/api/webhooks/${deleteId}`);
|
|
97
|
+
loadWebhooks();
|
|
113
98
|
} catch (e) {
|
|
114
99
|
console.error(e);
|
|
115
100
|
}
|
|
@@ -123,33 +108,22 @@ export function WebhookManager() {
|
|
|
123
108
|
setShowTestModal(true);
|
|
124
109
|
|
|
125
110
|
try {
|
|
126
|
-
const
|
|
127
|
-
const data = await res.json();
|
|
111
|
+
const data = await apiPost(`/api/webhooks/${id}/test`);
|
|
128
112
|
setTestResult({
|
|
129
|
-
success:
|
|
130
|
-
message:
|
|
131
|
-
data.message ||
|
|
132
|
-
(res.ok
|
|
133
|
-
? "Webhook triggered successfully"
|
|
134
|
-
: "Failed to trigger webhook"),
|
|
113
|
+
success: true,
|
|
114
|
+
message: data.message || "Webhook triggered successfully",
|
|
135
115
|
});
|
|
136
116
|
} catch (e) {
|
|
137
|
-
setTestResult({ success: false, message: "
|
|
117
|
+
setTestResult({ success: false, message: "Failed to trigger webhook" });
|
|
138
118
|
}
|
|
139
119
|
};
|
|
140
120
|
|
|
141
121
|
const toggleStatus = async (id: string, currentStatus: string) => {
|
|
142
122
|
try {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
headers: { "Content-Type": "application/json" },
|
|
146
|
-
body: JSON.stringify({
|
|
147
|
-
status: currentStatus === "active" ? "paused" : "active",
|
|
148
|
-
}),
|
|
123
|
+
await apiPatch(`/api/webhooks/${id}`, {
|
|
124
|
+
status: currentStatus === "active" ? "paused" : "active",
|
|
149
125
|
});
|
|
150
|
-
|
|
151
|
-
loadWebhooks();
|
|
152
|
-
}
|
|
126
|
+
loadWebhooks();
|
|
153
127
|
} catch (e) {
|
|
154
128
|
console.error(e);
|
|
155
129
|
}
|
|
@@ -183,7 +157,8 @@ export function WebhookManager() {
|
|
|
183
157
|
<h1 className="text-4xl font-black tracking-tighter text-[var(--kyro-text-primary)]">
|
|
184
158
|
Web<span className="text-[var(--kyro-primary)]">hooks</span>
|
|
185
159
|
</h1>
|
|
186
|
-
<button
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
187
162
|
onClick={() => setShowHelpModal(true)}
|
|
188
163
|
className="p-2 rounded-lg text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-primary)] hover:bg-[var(--kyro-surface-accent)] transition-all"
|
|
189
164
|
title="Learn how webhooks work"
|
|
@@ -195,7 +170,8 @@ export function WebhookManager() {
|
|
|
195
170
|
Receive real-time notifications when events occur in your CMS.
|
|
196
171
|
</p>
|
|
197
172
|
</div>
|
|
198
|
-
<button
|
|
173
|
+
<button
|
|
174
|
+
type="button"
|
|
199
175
|
onClick={() => {
|
|
200
176
|
setFormData({ name: "", url: "", events: [], secret: "" });
|
|
201
177
|
setCreateError("");
|
|
@@ -253,7 +229,8 @@ export function WebhookManager() {
|
|
|
253
229
|
<p className="text-sm text-[var(--kyro-text-secondary)] opacity-60 mb-6">
|
|
254
230
|
Add your first webhook to start receiving event notifications.
|
|
255
231
|
</p>
|
|
256
|
-
<button
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
257
234
|
onClick={() => setShowCreateModal(true)}
|
|
258
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"
|
|
259
236
|
>
|
|
@@ -310,7 +287,8 @@ export function WebhookManager() {
|
|
|
310
287
|
</div>
|
|
311
288
|
</div>
|
|
312
289
|
<div className="flex items-center gap-2 flex-shrink-0">
|
|
313
|
-
<button
|
|
290
|
+
<button
|
|
291
|
+
type="button"
|
|
314
292
|
onClick={() => handleTest(webhook.id)}
|
|
315
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"
|
|
316
294
|
title="Send test request"
|
|
@@ -320,7 +298,8 @@ export function WebhookManager() {
|
|
|
320
298
|
Test
|
|
321
299
|
</span>
|
|
322
300
|
</button>
|
|
323
|
-
<button
|
|
301
|
+
<button
|
|
302
|
+
type="button"
|
|
324
303
|
onClick={() => toggleStatus(webhook.id, webhook.status)}
|
|
325
304
|
className={`p-3 rounded-xl flex items-center gap-2 ${
|
|
326
305
|
webhook.status === "active"
|
|
@@ -349,7 +328,8 @@ export function WebhookManager() {
|
|
|
349
328
|
</>
|
|
350
329
|
)}
|
|
351
330
|
</button>
|
|
352
|
-
<button
|
|
331
|
+
<button
|
|
332
|
+
type="button"
|
|
353
333
|
onClick={() => handleDelete(webhook.id)}
|
|
354
334
|
className="p-3 text-red-500 bg-red-500/10 rounded-xl hover:bg-red-500/20"
|
|
355
335
|
title="Delete webhook"
|
|
@@ -409,7 +389,8 @@ export function WebhookManager() {
|
|
|
409
389
|
</label>
|
|
410
390
|
<div className="grid grid-cols-2 gap-3">
|
|
411
391
|
{eventOptions.map((opt) => (
|
|
412
|
-
<button
|
|
392
|
+
<button
|
|
393
|
+
type="button"
|
|
413
394
|
key={opt.value}
|
|
414
395
|
onClick={() => {
|
|
415
396
|
const events = formData.events.includes(opt.value)
|
|
@@ -454,13 +435,15 @@ export function WebhookManager() {
|
|
|
454
435
|
</div>
|
|
455
436
|
</ModalContent>
|
|
456
437
|
<ModalActions>
|
|
457
|
-
<button
|
|
438
|
+
<button
|
|
439
|
+
type="button"
|
|
458
440
|
onClick={() => setShowCreateModal(false)}
|
|
459
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)]"
|
|
460
442
|
>
|
|
461
443
|
Cancel
|
|
462
444
|
</button>
|
|
463
|
-
<button
|
|
445
|
+
<button
|
|
446
|
+
type="button"
|
|
464
447
|
onClick={handleCreate}
|
|
465
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"
|
|
466
449
|
>
|
|
@@ -488,13 +471,15 @@ export function WebhookManager() {
|
|
|
488
471
|
</div>
|
|
489
472
|
</ModalContent>
|
|
490
473
|
<ModalActions>
|
|
491
|
-
<button
|
|
474
|
+
<button
|
|
475
|
+
type="button"
|
|
492
476
|
onClick={() => setShowDeleteModal(false)}
|
|
493
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)]"
|
|
494
478
|
>
|
|
495
479
|
Keep Webhook
|
|
496
480
|
</button>
|
|
497
|
-
<button
|
|
481
|
+
<button
|
|
482
|
+
type="button"
|
|
498
483
|
onClick={confirmDelete}
|
|
499
484
|
className="px-4 py-2 rounded-lg font-medium text-sm bg-red-500 text-white hover:bg-red-600"
|
|
500
485
|
>
|
|
@@ -536,7 +521,8 @@ export function WebhookManager() {
|
|
|
536
521
|
)}
|
|
537
522
|
</ModalContent>
|
|
538
523
|
<ModalActions>
|
|
539
|
-
<button
|
|
524
|
+
<button
|
|
525
|
+
type="button"
|
|
540
526
|
onClick={() => setShowTestModal(false)}
|
|
541
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"
|
|
542
528
|
>
|
|
@@ -595,7 +581,8 @@ export function WebhookManager() {
|
|
|
595
581
|
</div>
|
|
596
582
|
</ModalContent>
|
|
597
583
|
<ModalActions>
|
|
598
|
-
<button
|
|
584
|
+
<button
|
|
585
|
+
type="button"
|
|
599
586
|
onClick={() => setShowHelpModal(false)}
|
|
600
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"
|
|
601
588
|
>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Search, Loader2, X } from "lucide-react";
|
|
3
|
+
import { apiGet, buildSearchQuery } from "@kyro-cms/utils/lib/api";
|
|
3
4
|
|
|
4
5
|
interface RelationshipBlockFieldProps {
|
|
5
6
|
relationTo?: string;
|
|
@@ -28,8 +29,7 @@ export const RelationshipBlockField: React.FC<RelationshipBlockFieldProps> = ({
|
|
|
28
29
|
const [loadingCollections, setLoadingCollections] = useState(true);
|
|
29
30
|
|
|
30
31
|
useEffect(() => {
|
|
31
|
-
|
|
32
|
-
.then((res) => res.json())
|
|
32
|
+
apiGet("/api/collections")
|
|
33
33
|
.then((data) => {
|
|
34
34
|
setCollections(
|
|
35
35
|
(data.collections || []).map((c: any) => c.slug || c.name || c),
|
|
@@ -41,12 +41,9 @@ export const RelationshipBlockField: React.FC<RelationshipBlockFieldProps> = ({
|
|
|
41
41
|
|
|
42
42
|
const fetchOptions = (query: string = "") => {
|
|
43
43
|
setLoading(true);
|
|
44
|
-
const url = query
|
|
45
|
-
? `/api/${relationTo}?where[${labelField}][contains]=${encodeURIComponent(query)}&limit=20`
|
|
46
|
-
: `/api/${relationTo}?limit=20`;
|
|
44
|
+
const url = `/api/${relationTo}?${buildSearchQuery(query, [labelField], 20)}`;
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
.then((res) => res.json())
|
|
46
|
+
apiGet(url)
|
|
50
47
|
.then((data) => {
|
|
51
48
|
setOptions(data.docs || []);
|
|
52
49
|
setLoading(false);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState, useRef } from "react";
|
|
2
2
|
import { Search, X, ChevronDown, Loader2 } from "lucide-react";
|
|
3
|
+
import { apiGet, buildSearchQuery } from "@kyro-cms/utils/lib/api";
|
|
3
4
|
|
|
4
5
|
interface RelationshipFieldProps {
|
|
5
6
|
field: {
|
|
@@ -42,15 +43,9 @@ export function RelationshipField({
|
|
|
42
43
|
const fetchOptions = (query: string = "") => {
|
|
43
44
|
setLoading(true);
|
|
44
45
|
const searchFields = ["title", "name", "label", "email"];
|
|
45
|
-
const
|
|
46
|
-
.map((f) => `where[${f}][contains]=${encodeURIComponent(query)}`)
|
|
47
|
-
.join("&");
|
|
48
|
-
const url = query
|
|
49
|
-
? `/api/${targetCollection}?${searchQuery}&limit=50`
|
|
50
|
-
: `/api/${targetCollection}?limit=50`;
|
|
46
|
+
const url = `/api/${targetCollection}?${buildSearchQuery(query, searchFields)}`;
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
.then((res) => res.json())
|
|
48
|
+
apiGet(url)
|
|
54
49
|
.then((data) => {
|
|
55
50
|
setOptions((prev) => {
|
|
56
51
|
const existingIds = new Set(prev.map((o) => o.id));
|
|
@@ -77,8 +72,7 @@ export function RelationshipField({
|
|
|
77
72
|
items.forEach((itemId) => {
|
|
78
73
|
const id = typeof itemId === "object" ? itemId?.id : itemId;
|
|
79
74
|
if (id && !options.some((o) => o.id === id)) {
|
|
80
|
-
|
|
81
|
-
.then((res) => res.json())
|
|
75
|
+
apiGet(`/api/${targetCollection}/${id}`)
|
|
82
76
|
.then((doc) => {
|
|
83
77
|
setOptions((prev) => [...prev, doc]);
|
|
84
78
|
})
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef, useMemo } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import { Image, Film, FileText, Music, File, X, Loader2 } from "lucide-react";
|
|
4
|
+
import {
|
|
5
|
+
apiGet,
|
|
6
|
+
withCacheBust,
|
|
7
|
+
apiPost,
|
|
8
|
+
apiUpload,
|
|
9
|
+
} from "@kyro-cms/utils/lib/api";
|
|
4
10
|
|
|
5
11
|
interface UploadFieldProps {
|
|
6
12
|
field: any;
|
|
@@ -102,10 +108,7 @@ export function UploadField({
|
|
|
102
108
|
|
|
103
109
|
const loadFolders = async () => {
|
|
104
110
|
try {
|
|
105
|
-
const
|
|
106
|
-
credentials: "include",
|
|
107
|
-
});
|
|
108
|
-
const result = await resp.json();
|
|
111
|
+
const result = await apiGet(withCacheBust("/api/media/folders"));
|
|
109
112
|
setFolders(result.folders || []);
|
|
110
113
|
} catch {
|
|
111
114
|
setFolders([]);
|
|
@@ -115,12 +118,13 @@ export function UploadField({
|
|
|
115
118
|
const loadMedia = async () => {
|
|
116
119
|
setMediaLoading(true);
|
|
117
120
|
try {
|
|
118
|
-
let url =
|
|
121
|
+
let url = withCacheBust(
|
|
122
|
+
`/api/media?limit=60&sortBy=createdAt&sortDir=desc`,
|
|
123
|
+
);
|
|
119
124
|
if (selectedFolder) {
|
|
120
125
|
url += "&folder=" + encodeURIComponent(selectedFolder);
|
|
121
126
|
}
|
|
122
|
-
const
|
|
123
|
-
const result = await resp.json();
|
|
127
|
+
const result = await apiGet(url);
|
|
124
128
|
setMediaItems(result.docs || []);
|
|
125
129
|
} catch {
|
|
126
130
|
setMediaItems([]);
|
|
@@ -137,17 +141,11 @@ export function UploadField({
|
|
|
137
141
|
if (selectedFolder) {
|
|
138
142
|
formData.append("folder", selectedFolder);
|
|
139
143
|
}
|
|
140
|
-
const
|
|
141
|
-
method: "POST",
|
|
142
|
-
body: formData,
|
|
143
|
-
credentials: "include",
|
|
144
|
-
});
|
|
145
|
-
if (!resp.ok) throw new Error("Upload failed");
|
|
146
|
-
const result = await resp.json();
|
|
144
|
+
const result = await apiUpload("/api/upload", formData);
|
|
147
145
|
const newImage = {
|
|
148
146
|
id: result.id,
|
|
149
147
|
filename: result.filename,
|
|
150
|
-
originalName: result.originalName ?? file.name,
|
|
148
|
+
originalName: (result as any).originalName ?? file.name,
|
|
151
149
|
url: result.url,
|
|
152
150
|
mimeType: file.type,
|
|
153
151
|
};
|
|
@@ -169,17 +167,7 @@ export function UploadField({
|
|
|
169
167
|
|
|
170
168
|
setUrlError("");
|
|
171
169
|
try {
|
|
172
|
-
const
|
|
173
|
-
method: "POST",
|
|
174
|
-
headers: { "Content-Type": "application/json" },
|
|
175
|
-
body: JSON.stringify({ url }),
|
|
176
|
-
credentials: "include",
|
|
177
|
-
});
|
|
178
|
-
if (!resp.ok) {
|
|
179
|
-
const data = await resp.json();
|
|
180
|
-
throw new Error(data.error || "Failed to add URL");
|
|
181
|
-
}
|
|
182
|
-
const result = await resp.json();
|
|
170
|
+
const result = await apiPost("/api/upload", { url });
|
|
183
171
|
const originalName = (() => {
|
|
184
172
|
try {
|
|
185
173
|
return (
|
|
@@ -465,10 +453,11 @@ function MediaPickerContent({
|
|
|
465
453
|
}) {
|
|
466
454
|
return (
|
|
467
455
|
<div
|
|
468
|
-
className={`${
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
456
|
+
className={`${
|
|
457
|
+
isFullscreen
|
|
458
|
+
? "fixed inset-0 z-[9999]"
|
|
459
|
+
: "absolute z-50 w-[360px] max-h-[400px] mt-1 rounded-lg shadow-lg"
|
|
460
|
+
} overflow-hidden bg-[var(--kyro-surface)] border border-[var(--kyro-border)] flex flex-col`}
|
|
472
461
|
>
|
|
473
462
|
<div className="p-2 border-b border-[var(--kyro-border)] flex flex-col gap-2">
|
|
474
463
|
<input
|
|
@@ -483,10 +472,11 @@ function MediaPickerContent({
|
|
|
483
472
|
<button
|
|
484
473
|
type="button"
|
|
485
474
|
onClick={() => setSelectedFolder("")}
|
|
486
|
-
className={`px-2 py-1 text-xs rounded transition-colors ${
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
475
|
+
className={`px-2 py-1 text-xs rounded transition-colors ${
|
|
476
|
+
selectedFolder === ""
|
|
477
|
+
? "bg-[var(--kyro-primary)] text-white"
|
|
478
|
+
: "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-border)]"
|
|
479
|
+
}`}
|
|
490
480
|
>
|
|
491
481
|
All
|
|
492
482
|
</button>
|
|
@@ -495,10 +485,11 @@ function MediaPickerContent({
|
|
|
495
485
|
key={folder.path}
|
|
496
486
|
type="button"
|
|
497
487
|
onClick={() => setSelectedFolder(folder.path)}
|
|
498
|
-
className={`px-2 py-1 text-xs rounded transition-colors ${
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
488
|
+
className={`px-2 py-1 text-xs rounded transition-colors ${
|
|
489
|
+
selectedFolder === folder.path
|
|
490
|
+
? "bg-[var(--kyro-primary)] text-white"
|
|
491
|
+
: "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-border)]"
|
|
492
|
+
}`}
|
|
502
493
|
>
|
|
503
494
|
{folder.name}
|
|
504
495
|
</button>
|
|
@@ -519,10 +510,11 @@ function MediaPickerContent({
|
|
|
519
510
|
</div>
|
|
520
511
|
) : (
|
|
521
512
|
<div
|
|
522
|
-
className={`grid gap-1 ${
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
513
|
+
className={`grid gap-1 ${
|
|
514
|
+
isFullscreen
|
|
515
|
+
? "grid-cols-[repeat(auto-fill,minmax(140px,1fr))]"
|
|
516
|
+
: "grid-cols-3"
|
|
517
|
+
}`}
|
|
526
518
|
>
|
|
527
519
|
{filteredMedia.map((item) => (
|
|
528
520
|
<button
|
|
@@ -532,8 +524,9 @@ function MediaPickerContent({
|
|
|
532
524
|
className="border border-[var(--kyro-border)] rounded-md overflow-hidden cursor-pointer p-0 bg-[var(--kyro-surface)] hover:border-[var(--kyro-primary)] transition-all relative group"
|
|
533
525
|
>
|
|
534
526
|
<div
|
|
535
|
-
className={`w-full flex items-center justify-center bg-[var(--kyro-surface-accent)] ${
|
|
536
|
-
|
|
527
|
+
className={`w-full flex items-center justify-center bg-[var(--kyro-surface-accent)] ${
|
|
528
|
+
isFullscreen ? "h-[120px]" : "h-[80px]"
|
|
529
|
+
}`}
|
|
537
530
|
>
|
|
538
531
|
{getFileType(item.mimeType, item.filename) === "image" ? (
|
|
539
532
|
<img
|