@kyro-cms/admin 0.9.0 → 0.9.2
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/dist/index.cjs +11715 -11292
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +67 -65
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +564 -0
- package/dist/index.d.ts +11 -10
- package/dist/index.js +11326 -10912
- package/dist/index.js.map +1 -1
- package/package.json +16 -12
- package/src/components/ActionBar.tsx +25 -161
- package/src/components/Admin.tsx +2 -4
- package/src/components/ApiKeysManager.tsx +5 -5
- package/src/components/AuditLogsPage.tsx +2 -13
- package/src/components/AutoForm.tsx +572 -461
- package/src/components/BrandingHub.tsx +7 -4
- package/src/components/CreateView.tsx +2 -0
- package/src/components/DetailView.tsx +52 -65
- package/src/components/DeveloperCenter.tsx +8 -6
- package/src/components/FieldRenderer.tsx +94 -19
- package/src/components/ListView.tsx +57 -216
- package/src/components/MediaGallery.tsx +334 -367
- package/src/components/PluginsManager.tsx +197 -70
- package/src/components/RestPlayground.tsx +59 -52
- package/src/components/SessionsManager.tsx +1 -1
- package/src/components/SettingsPage.tsx +22 -0
- package/src/components/Sidebar.astro +13 -41
- package/src/components/UserManagement.tsx +153 -15
- package/src/components/UserMenu.tsx +30 -4
- package/src/components/VersionHistoryPanel.tsx +112 -119
- package/src/components/WebhookManager.tsx +6 -4
- package/src/components/blocks/ArrayBlock.tsx +6 -23
- package/src/components/blocks/BlockEditModal.tsx +82 -309
- package/src/components/blocks/CardBlock.tsx +35 -0
- package/src/components/blocks/ChildBlocksTree.tsx +57 -31
- package/src/components/blocks/GenericBlock.tsx +44 -0
- package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
- package/src/components/blocks/HeroBlock.tsx +5 -14
- package/src/components/blocks/RichTextBlock.tsx +5 -5
- package/src/components/blocks/index.ts +5 -3
- package/src/components/fields/AccordionField.tsx +2 -2
- package/src/components/fields/ArrayField.tsx +1 -1
- package/src/components/fields/ArrayLayout.tsx +120 -29
- package/src/components/fields/BlocksField.tsx +433 -55
- package/src/components/fields/CardField.tsx +73 -0
- package/src/components/fields/CheckboxField.tsx +7 -3
- package/src/components/fields/DateField.tsx +4 -1
- package/src/components/fields/GroupLayout.tsx +2 -2
- package/src/components/fields/HeadingSubheadingField.tsx +43 -0
- package/src/components/fields/ListField.tsx +2 -2
- package/src/components/fields/NumberField.tsx +4 -1
- package/src/components/fields/RelationshipBlockField.tsx +2 -3
- package/src/components/fields/RelationshipField.tsx +155 -90
- package/src/components/fields/RichTextField.tsx +781 -0
- package/src/components/fields/SecretField.tsx +102 -0
- package/src/components/fields/SelectField.tsx +19 -6
- package/src/components/fields/TabsLayout.tsx +19 -9
- package/src/components/fields/TextField.tsx +4 -1
- package/src/components/fields/UploadField.tsx +122 -56
- package/src/components/fields/extensions/blockComponents.tsx +103 -174
- package/src/components/fields/extensions/blocksStore.ts +8 -1
- package/src/components/fields/index.ts +4 -2
- package/src/components/fix_imports.cjs +23 -0
- package/src/components/fix_imports2.cjs +19 -0
- package/src/components/replace_svgs.cjs +63 -0
- package/src/components/ui/Dropdown.tsx +7 -2
- package/src/components/ui/Modal.tsx +24 -27
- package/src/components/ui/PageHeader.tsx +5 -5
- package/src/components/ui/PromptModal.tsx +2 -10
- package/src/components/ui/SlidePanel.tsx +10 -13
- package/src/components/ui/SplitButton.tsx +107 -0
- package/src/components/ui/Toaster.tsx +0 -1
- package/src/components/ui/icons.tsx +110 -109
- package/src/components/users/UserDetail.tsx +79 -16
- package/src/components/users/UsersList.tsx +8 -85
- package/src/hooks/useAutoFormState.ts +187 -196
- package/src/hooks/useQueue.ts +60 -0
- package/src/integration.ts +148 -46
- package/src/kyro-cms.d.ts +7 -2
- package/src/layouts/AdminLayout.astro +22 -2
- package/src/layouts/AuthLayout.astro +67 -7
- package/src/lib/autoform-store.ts +90 -53
- package/src/lib/change-source.ts +9 -0
- package/src/lib/config.ts +104 -8
- package/src/lib/globals.ts +48 -11
- package/src/lib/normalize-upload-fields.ts +41 -0
- package/src/lib/paths.ts +2 -2
- package/src/lib/resolve-field-value.ts +110 -0
- package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
- package/src/lib/shim/use-sync-external-store.js +1 -0
- package/src/lib/stores/index.ts +1 -0
- package/src/lib/useResourceManager.ts +4 -4
- package/src/lib/vite-shim-plugin.ts +100 -0
- package/src/pages/[collection]/[id].astro +1 -1
- package/src/pages/auth/register.astro +5 -2
- package/src/pages/preview/[collection]/[id].astro +4 -4
- package/src/pages/settings/[slug].astro +2 -2
- package/src/styles/main.css +60 -54
- package/README.md +0 -46
- package/dist/EditorClient-Q23UXR37.cjs +0 -468
- package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
- package/dist/EditorClient-T5PASFNR.js +0 -466
- package/dist/EditorClient-T5PASFNR.js.map +0 -1
- package/dist/chunk-3BGDYKTD.cjs +0 -348
- package/dist/chunk-3BGDYKTD.cjs.map +0 -1
- package/dist/chunk-EEFXLQVT.js +0 -3
- package/dist/chunk-EEFXLQVT.js.map +0 -1
- package/src/components/blocks/ButtonBlock.tsx +0 -64
- package/src/components/blocks/ColumnsBlock.tsx +0 -55
- package/src/components/blocks/DividerBlock.tsx +0 -43
- package/src/components/blocks/LinkBlock.tsx +0 -65
- package/src/components/blocks/VStackBlock.tsx +0 -29
- package/src/components/fields/EditorClient.tsx +0 -535
- package/src/components/fields/PortableTextField.tsx +0 -155
- package/src/components/fields/PortableTextRenderer.tsx +0 -68
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import {
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { apiGet } from "../../lib/api";
|
|
3
|
+
import { useUIStore, toast } from "../../lib/stores";
|
|
4
|
+
import { X } from "../ui/icons";
|
|
5
|
+
import { UploadField } from "../fields/UploadField";
|
|
3
6
|
|
|
4
7
|
interface User {
|
|
5
8
|
id: string;
|
|
6
9
|
name?: string;
|
|
7
10
|
email: string;
|
|
8
11
|
role: string;
|
|
12
|
+
avatar?: string;
|
|
9
13
|
tenantId?: string;
|
|
10
14
|
emailVerified?: boolean;
|
|
11
15
|
locked?: boolean;
|
|
@@ -33,12 +37,41 @@ const roleOptions = [
|
|
|
33
37
|
export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
34
38
|
const [name, setName] = useState(user.name || "");
|
|
35
39
|
const [role, setRole] = useState(user.role);
|
|
40
|
+
const [avatar, setAvatar] = useState<string | undefined>(user.avatar);
|
|
41
|
+
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
|
|
42
|
+
const [avatarMedia, setAvatarMedia] = useState<any>(user.avatar ? { id: user.avatar } : null);
|
|
36
43
|
const [saving, setSaving] = useState(false);
|
|
37
44
|
const { confirm, alert } = useUIStore();
|
|
38
45
|
const [deleting, setDeleting] = useState(false);
|
|
39
46
|
const [locking, setLocking] = useState(false);
|
|
40
47
|
const [isLocked, setIsLocked] = useState(user.locked || false);
|
|
41
48
|
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (typeof avatar === "string" && /^[0-9a-f-]+$/i.test(avatar)) {
|
|
51
|
+
apiGet<any>(`/api/media/${avatar}`)
|
|
52
|
+
.then((media) => {
|
|
53
|
+
setAvatarUrl(media?.thumbnailUrl || media?.url || null);
|
|
54
|
+
setAvatarMedia({ id: avatar, url: media.url, thumbnailUrl: media.thumbnailUrl, filename: media.filename, originalName: media.originalName, mimeType: media.mimeType });
|
|
55
|
+
})
|
|
56
|
+
.catch(() => { setAvatarUrl(null); setAvatarMedia(null); });
|
|
57
|
+
} else {
|
|
58
|
+
setAvatarUrl(null);
|
|
59
|
+
setAvatarMedia(null);
|
|
60
|
+
}
|
|
61
|
+
}, [avatar]);
|
|
62
|
+
|
|
63
|
+
const handleAvatarChange = (val: any) => {
|
|
64
|
+
if (val && typeof val === "object") {
|
|
65
|
+
setAvatar(val.id);
|
|
66
|
+
setAvatarUrl(val.url || val.thumbnailUrl || null);
|
|
67
|
+
setAvatarMedia(val);
|
|
68
|
+
} else {
|
|
69
|
+
setAvatar(undefined);
|
|
70
|
+
setAvatarUrl(null);
|
|
71
|
+
setAvatarMedia(null);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
42
75
|
const handleSave = async () => {
|
|
43
76
|
setSaving(true);
|
|
44
77
|
try {
|
|
@@ -50,6 +83,9 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
50
83
|
if (name !== user.name) {
|
|
51
84
|
body.name = name.trim() === "" ? null : name.trim();
|
|
52
85
|
}
|
|
86
|
+
if (avatar !== user.avatar) {
|
|
87
|
+
body.avatar = avatar || null;
|
|
88
|
+
}
|
|
53
89
|
|
|
54
90
|
if (Object.keys(body).length === 0) {
|
|
55
91
|
window.location.href = adminPath + "/users";
|
|
@@ -66,12 +102,13 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
66
102
|
const data = await res.json();
|
|
67
103
|
|
|
68
104
|
if (res.ok) {
|
|
105
|
+
toast.success("User updated");
|
|
69
106
|
window.location.href = adminPath + "/users";
|
|
70
107
|
} else {
|
|
71
|
-
|
|
108
|
+
toast.error(data.error || "Failed to save user");
|
|
72
109
|
}
|
|
73
110
|
} catch (e) {
|
|
74
|
-
|
|
111
|
+
toast.error("Failed to save user");
|
|
75
112
|
} finally {
|
|
76
113
|
setSaving(false);
|
|
77
114
|
}
|
|
@@ -96,9 +133,12 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
96
133
|
});
|
|
97
134
|
if (res.ok) {
|
|
98
135
|
setIsLocked(!isLocked);
|
|
136
|
+
toast.success(isLocked ? "User unlocked" : "User locked");
|
|
137
|
+
} else {
|
|
138
|
+
toast.error("Failed to update lock status");
|
|
99
139
|
}
|
|
100
140
|
} catch (e) {
|
|
101
|
-
|
|
141
|
+
toast.error("Failed to toggle lock");
|
|
102
142
|
} finally {
|
|
103
143
|
setLocking(false);
|
|
104
144
|
}
|
|
@@ -119,10 +159,13 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
119
159
|
credentials: "include",
|
|
120
160
|
});
|
|
121
161
|
if (res.ok) {
|
|
162
|
+
toast.success("User deleted");
|
|
122
163
|
window.location.href = adminPath + "/users";
|
|
164
|
+
} else {
|
|
165
|
+
toast.error("Failed to delete user");
|
|
123
166
|
}
|
|
124
167
|
} catch (e) {
|
|
125
|
-
|
|
168
|
+
toast.error("Failed to delete user");
|
|
126
169
|
} finally {
|
|
127
170
|
setDeleting(false);
|
|
128
171
|
}
|
|
@@ -139,14 +182,18 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
139
182
|
<div className="flex-1 overflow-y-auto space-y-8">
|
|
140
183
|
<div className="surface-tile p-6 flex items-center justify-between">
|
|
141
184
|
<div className="flex items-center gap-4">
|
|
142
|
-
<div className="w-14 h-14 rounded-full bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] flex items-center justify-center font-bold text-xl">
|
|
143
|
-
{
|
|
185
|
+
<div className="relative w-14 h-14 rounded-full bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] flex items-center justify-center font-bold text-xl overflow-hidden flex-shrink-0">
|
|
186
|
+
{avatarUrl ? (
|
|
187
|
+
<img src={avatarUrl} alt="" className="w-full h-full object-cover" />
|
|
188
|
+
) : (
|
|
189
|
+
(name || user.email).charAt(0).toUpperCase()
|
|
190
|
+
)}
|
|
144
191
|
</div>
|
|
145
192
|
<div>
|
|
146
193
|
<h1 className="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]">
|
|
147
194
|
{name || user.email}
|
|
148
195
|
</h1>
|
|
149
|
-
<p className="text-sm text-[var(--kyro-text-secondary)] font-medium">
|
|
196
|
+
<p className="text-sm text-[var(--kyro-text-secondary)] font-medium mb-2">
|
|
150
197
|
{name ? user.email : `User ID: ${user.id}`}
|
|
151
198
|
</p>
|
|
152
199
|
</div>
|
|
@@ -248,14 +295,20 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
248
295
|
: "Never"}
|
|
249
296
|
</p>
|
|
250
297
|
</div>
|
|
251
|
-
<div>
|
|
252
|
-
<label className="text-xs font-bold text-[var(--kyro-text-secondary)]
|
|
253
|
-
|
|
298
|
+
<div className="col-span-2">
|
|
299
|
+
<label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
|
|
300
|
+
Photo
|
|
254
301
|
</label>
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
302
|
+
<div className="mt-1 flex items-center gap-2">
|
|
303
|
+
<UploadField
|
|
304
|
+
field={{ label: "Photo", name: "avatar", maxCount: 1, allowedTypes: ["image/*"] }}
|
|
305
|
+
value={avatarMedia}
|
|
306
|
+
onChange={handleAvatarChange}
|
|
307
|
+
disabled={saving}
|
|
308
|
+
/>
|
|
309
|
+
</div>
|
|
258
310
|
</div>
|
|
311
|
+
|
|
259
312
|
<div>
|
|
260
313
|
<label className="text-xs font-bold text-[#64748b] tracking-wider">
|
|
261
314
|
Created
|
|
@@ -264,6 +317,16 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
264
317
|
{formatDate(user.createdAt)}
|
|
265
318
|
</p>
|
|
266
319
|
</div>
|
|
320
|
+
|
|
321
|
+
<div>
|
|
322
|
+
<label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
|
|
323
|
+
Failed Attempts
|
|
324
|
+
</label>
|
|
325
|
+
<p className="mt-1 text-sm font-medium text-[var(--kyro-text-primary)]">
|
|
326
|
+
{user.failedLoginAttempts || 0}
|
|
327
|
+
</p>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
267
330
|
<div>
|
|
268
331
|
<label className="text-xs font-bold text-[#64748b] tracking-wider">
|
|
269
332
|
Updated
|
|
@@ -278,7 +341,7 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
278
341
|
<button
|
|
279
342
|
onClick={handleSave}
|
|
280
343
|
disabled={saving}
|
|
281
|
-
className="px-6 py-2
|
|
344
|
+
className="kyro-btn kyro-btn-primary px-6 py-2 rounded-xl text-sm font-bold transition-colors disabled:opacity-50"
|
|
282
345
|
>
|
|
283
346
|
{saving ? "Saving…" : "Save Changes"}
|
|
284
347
|
</button>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Plus, Lock, CheckCircle2, Edit2, Trash2, XCircle, X } from "../ui/icons";
|
|
1
2
|
import React, { useState } from "react";
|
|
2
3
|
import { useUIStore } from "../../lib/stores";
|
|
3
4
|
|
|
@@ -144,19 +145,7 @@ export function UsersList({
|
|
|
144
145
|
href={`${adminPath}/users/new`}
|
|
145
146
|
className="mt-2 inline-flex items-center gap-2 px-5 py-2.5 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-lg font-bold text-sm shadow-md"
|
|
146
147
|
>
|
|
147
|
-
<
|
|
148
|
-
className="w-3.5 h-3.5"
|
|
149
|
-
fill="none"
|
|
150
|
-
stroke="currentColor"
|
|
151
|
-
viewBox="0 0 24 24"
|
|
152
|
-
>
|
|
153
|
-
<path
|
|
154
|
-
strokeLinecap="round"
|
|
155
|
-
strokeLinejoin="round"
|
|
156
|
-
strokeWidth="2.5"
|
|
157
|
-
d="M12 5v14M5 12h14"
|
|
158
|
-
/>
|
|
159
|
-
</svg>
|
|
148
|
+
<Plus className="w-4 h-4" />
|
|
160
149
|
Add User
|
|
161
150
|
</a>
|
|
162
151
|
</div>
|
|
@@ -214,32 +203,12 @@ export function UsersList({
|
|
|
214
203
|
<td className="px-6 py-5">
|
|
215
204
|
{user.locked ? (
|
|
216
205
|
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-red-50 text-red-600">
|
|
217
|
-
<
|
|
218
|
-
className="w-3 h-3"
|
|
219
|
-
fill="currentColor"
|
|
220
|
-
viewBox="0 0 20 20"
|
|
221
|
-
>
|
|
222
|
-
<path
|
|
223
|
-
fillRule="evenodd"
|
|
224
|
-
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
|
|
225
|
-
clipRule="evenodd"
|
|
226
|
-
/>
|
|
227
|
-
</svg>
|
|
206
|
+
<Lock className="w-4 h-4" />
|
|
228
207
|
Locked
|
|
229
208
|
</span>
|
|
230
209
|
) : (
|
|
231
210
|
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-green-50 text-green-600">
|
|
232
|
-
<
|
|
233
|
-
className="w-3 h-3"
|
|
234
|
-
fill="currentColor"
|
|
235
|
-
viewBox="0 0 20 20"
|
|
236
|
-
>
|
|
237
|
-
<path
|
|
238
|
-
fillRule="evenodd"
|
|
239
|
-
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
240
|
-
clipRule="evenodd"
|
|
241
|
-
/>
|
|
242
|
-
</svg>
|
|
211
|
+
<CheckCircle2 className="w-4 h-4" />
|
|
243
212
|
Active
|
|
244
213
|
</span>
|
|
245
214
|
)}
|
|
@@ -254,19 +223,7 @@ export function UsersList({
|
|
|
254
223
|
className="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-gray-100 hover:text-[#0b1222] transition-colors"
|
|
255
224
|
onClick={(e) => e.stopPropagation()}
|
|
256
225
|
>
|
|
257
|
-
<
|
|
258
|
-
className="w-4 h-4"
|
|
259
|
-
fill="none"
|
|
260
|
-
stroke="currentColor"
|
|
261
|
-
viewBox="0 0 24 24"
|
|
262
|
-
>
|
|
263
|
-
<path
|
|
264
|
-
strokeLinecap="round"
|
|
265
|
-
strokeLinejoin="round"
|
|
266
|
-
strokeWidth="2"
|
|
267
|
-
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
|
268
|
-
/>
|
|
269
|
-
</svg>
|
|
226
|
+
<Edit2 className="w-4 h-4" />
|
|
270
227
|
</a>
|
|
271
228
|
<button
|
|
272
229
|
onClick={(e) => {
|
|
@@ -275,19 +232,7 @@ export function UsersList({
|
|
|
275
232
|
}}
|
|
276
233
|
className="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-red-50 hover:text-red-600 transition-colors"
|
|
277
234
|
>
|
|
278
|
-
<
|
|
279
|
-
className="w-4 h-4"
|
|
280
|
-
fill="none"
|
|
281
|
-
stroke="currentColor"
|
|
282
|
-
viewBox="0 0 24 24"
|
|
283
|
-
>
|
|
284
|
-
<path
|
|
285
|
-
strokeLinecap="round"
|
|
286
|
-
strokeLinejoin="round"
|
|
287
|
-
strokeWidth="2"
|
|
288
|
-
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
289
|
-
/>
|
|
290
|
-
</svg>
|
|
235
|
+
<Trash2 className="w-4 h-4" />
|
|
291
236
|
</button>
|
|
292
237
|
</div>
|
|
293
238
|
</td>
|
|
@@ -300,35 +245,13 @@ export function UsersList({
|
|
|
300
245
|
|
|
301
246
|
{errorMsg && (
|
|
302
247
|
<div className="surface-tile p-4 flex items-center gap-3 border border-red-200">
|
|
303
|
-
<
|
|
304
|
-
className="w-5 h-5 text-red-500 flex-shrink-0"
|
|
305
|
-
fill="currentColor"
|
|
306
|
-
viewBox="0 0 20 20"
|
|
307
|
-
>
|
|
308
|
-
<path
|
|
309
|
-
fillRule="evenodd"
|
|
310
|
-
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
311
|
-
clipRule="evenodd"
|
|
312
|
-
/>
|
|
313
|
-
</svg>
|
|
248
|
+
<XCircle className="w-4 h-4" />
|
|
314
249
|
<p className="text-sm font-medium text-red-500">{errorMsg}</p>
|
|
315
250
|
<button
|
|
316
251
|
onClick={() => setErrorMsg(null)}
|
|
317
252
|
className="ml-auto text-red-400 hover:text-red-600"
|
|
318
253
|
>
|
|
319
|
-
<
|
|
320
|
-
className="w-4 h-4"
|
|
321
|
-
fill="none"
|
|
322
|
-
stroke="currentColor"
|
|
323
|
-
viewBox="0 0 24 24"
|
|
324
|
-
>
|
|
325
|
-
<path
|
|
326
|
-
strokeLinecap="round"
|
|
327
|
-
strokeLinejoin="round"
|
|
328
|
-
strokeWidth="2"
|
|
329
|
-
d="M6 18L18 6M6 6l12 12"
|
|
330
|
-
/>
|
|
331
|
-
</svg>
|
|
254
|
+
<X className="w-4 h-4" />
|
|
332
255
|
</button>
|
|
333
256
|
</div>
|
|
334
257
|
)}
|