@kyro-cms/admin 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -51
- package/package.json +52 -5
- package/src/collections/auth/index.ts +2 -2
- package/src/collections/portfolio/index.ts +343 -0
- package/src/components/ActionBar.tsx +153 -16
- package/src/components/Admin.tsx +136 -27
- package/src/components/ApiExplorer.tsx +325 -0
- package/src/components/ApiKeysManager.tsx +563 -0
- package/src/components/AuditLogsPage.tsx +664 -0
- package/src/components/AutoForm.tsx +1417 -661
- package/src/components/BrandingHub.tsx +267 -0
- package/src/components/BulkActionsBar.tsx +3 -3
- package/src/components/CreateView.tsx +3 -3
- package/src/components/Dashboard.tsx +393 -0
- package/src/components/DetailView.tsx +199 -57
- package/src/components/DeveloperCenter.tsx +403 -0
- package/src/components/EnhancedListView.tsx +786 -0
- package/src/components/GraphQLExplorer.tsx +675 -0
- package/src/components/GraphQLPlayground.tsx +627 -0
- package/src/components/ListView.tsx +191 -53
- package/src/components/MediaGallery.tsx +1569 -0
- package/src/components/Modal.tsx +149 -0
- package/src/components/RestPlayground.tsx +951 -0
- package/src/components/Sidebar.astro +237 -0
- package/src/components/UserManagement.tsx +204 -0
- package/src/components/VersionHistoryPanel.tsx +3 -3
- package/src/components/WebhookManager.tsx +608 -0
- package/src/components/blocks/AccordionBlock.tsx +97 -0
- package/src/components/blocks/ArrayBlock.tsx +75 -0
- package/src/components/blocks/BlockEditModal.MARKER +12 -0
- package/src/components/blocks/BlockEditModal.tsx +774 -0
- package/src/components/blocks/ButtonBlock.tsx +165 -0
- package/src/components/blocks/ChildBlocksTree.tsx +551 -0
- package/src/components/blocks/CodeBlock.tsx +66 -0
- package/src/components/blocks/ColumnsBlock.tsx +151 -0
- package/src/components/blocks/DividerBlock.tsx +43 -0
- package/src/components/blocks/FileBlock.tsx +64 -0
- package/src/components/blocks/HeadingBlock.tsx +81 -0
- package/src/components/blocks/HeroBlock.tsx +157 -0
- package/src/components/blocks/ImageBlock.tsx +83 -0
- package/src/components/blocks/LinkBlock.tsx +71 -0
- package/src/components/blocks/ListBlock.tsx +39 -0
- package/src/components/blocks/ParagraphBlock.tsx +61 -0
- package/src/components/blocks/RelationshipBlock.tsx +279 -0
- package/src/components/blocks/VStackBlock.tsx +75 -0
- package/src/components/blocks/VideoBlock.tsx +45 -0
- package/src/components/blocks/index.ts +10 -0
- package/src/components/fields/BlocksField.tsx +323 -0
- package/src/components/fields/CheckboxField.tsx +15 -9
- package/src/components/fields/CodeField.tsx +234 -0
- package/src/components/fields/DateField.tsx +38 -11
- package/src/components/fields/EditorClient.tsx +271 -0
- package/src/components/fields/FileField.tsx +390 -0
- package/src/components/fields/HybridContentField.tsx +109 -0
- package/src/components/fields/ImageField.tsx +429 -0
- package/src/components/fields/JSONField.tsx +361 -0
- package/src/components/fields/MarkdownField.tsx +282 -0
- package/src/components/fields/NumberField.tsx +42 -12
- package/src/components/fields/PortableTextField.tsx +143 -0
- package/src/components/fields/PortableTextRenderer.tsx +68 -0
- package/src/components/fields/RelationshipField.tsx +231 -59
- package/src/components/fields/SelectField.tsx +25 -15
- package/src/components/fields/TextField.tsx +45 -14
- package/src/components/fields/extensions/blockComponents.tsx +237 -0
- package/src/components/fields/extensions/blocksStore.ts +273 -0
- package/src/components/fields/index.ts +13 -0
- package/src/components/index.ts +1 -2
- package/src/components/layout/Header.tsx +2 -2
- package/src/components/layout/Layout.tsx +2 -2
- package/src/components/ui/Badge.tsx +9 -4
- package/src/components/ui/BlockDrawer.tsx +79 -0
- package/src/components/ui/Button.tsx +1 -1
- package/src/components/ui/CommandPalette.tsx +362 -0
- package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
- package/src/components/ui/Dropdown.tsx +1 -1
- package/src/components/ui/Modal.tsx +37 -12
- package/src/components/ui/PromptModal.tsx +94 -0
- package/src/components/ui/SlidePanel.tsx +43 -16
- package/src/components/ui/Toast.tsx +80 -14
- package/src/env.d.ts +16 -0
- package/src/env.ts +20 -0
- package/src/index.ts +0 -1
- package/src/layouts/AdminLayout.astro +164 -170
- package/src/layouts/AuthLayout.astro +50 -0
- package/src/lib/MediaService.ts +541 -0
- package/src/lib/auth/sqlite-adapter.ts +319 -0
- package/src/lib/config.ts +22 -6
- package/src/lib/dataStore.ts +132 -74
- package/src/lib/db/adapter.ts +54 -0
- package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
- package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
- package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
- package/src/lib/db/index.ts +449 -0
- package/src/lib/db/mongodb-adapter.ts +207 -0
- package/src/lib/db/mongodb-auth-adapter.ts +305 -0
- package/src/lib/db/schema/mysql-auth.ts +113 -0
- package/src/lib/db/schema/mysql-content.ts +20 -0
- package/src/lib/db/schema/postgres-auth.ts +116 -0
- package/src/lib/db/schema/postgres-content.ts +35 -0
- package/src/lib/db/schema/postgres-media.ts +52 -0
- package/src/lib/db/schema/postgres-settings.ts +11 -0
- package/src/lib/db/schema/sqlite-auth.ts +112 -0
- package/src/lib/db/schema/sqlite-content.ts +20 -0
- package/src/lib/graphql/index.ts +1 -0
- package/src/lib/graphql/schema.ts +443 -0
- package/src/lib/rate-limit.ts +267 -0
- package/src/lib/storage.ts +374 -0
- package/src/lib/store.ts +85 -0
- package/src/middleware.ts +116 -28
- package/src/pages/[collection]/[id].astro +178 -122
- package/src/pages/[collection]/index.astro +24 -156
- package/src/pages/admin/api-explorer.astro +98 -0
- package/src/pages/admin/graphql-explorer.astro +40 -0
- package/src/pages/admin/graphql.astro +97 -0
- package/src/pages/admin/index.astro +286 -0
- package/src/pages/admin/keys.astro +8 -0
- package/src/pages/admin/rest-playground.astro +44 -0
- package/src/pages/admin/webhooks.astro +8 -0
- package/src/pages/api/[collection]/[id]/publish.ts +44 -0
- package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
- package/src/pages/api/[collection]/[id]/versions.ts +36 -0
- package/src/pages/api/[collection]/[id].ts +102 -159
- package/src/pages/api/[collection]/index.ts +151 -230
- package/src/pages/api/auth/[id].ts +48 -69
- package/src/pages/api/auth/audit-logs.ts +20 -43
- package/src/pages/api/auth/login.ts +159 -45
- package/src/pages/api/auth/logout.ts +50 -20
- package/src/pages/api/auth/refresh.ts +119 -0
- package/src/pages/api/auth/register.ts +110 -40
- package/src/pages/api/auth/users.ts +22 -97
- package/src/pages/api/collections.ts +59 -0
- package/src/pages/api/globals/[slug]/test.ts +172 -0
- package/src/pages/api/globals/[slug].ts +42 -0
- package/src/pages/api/graphql.ts +90 -0
- package/src/pages/api/health.ts +417 -40
- package/src/pages/api/keys/[id].ts +26 -0
- package/src/pages/api/keys/index.ts +75 -0
- package/src/pages/api/media/[id].ts +309 -0
- package/src/pages/api/media/folders.ts +609 -0
- package/src/pages/api/media/index.ts +146 -0
- package/src/pages/api/media/resize.ts +267 -0
- package/src/pages/api/search.ts +82 -0
- package/src/pages/api/slug-availability.ts +70 -0
- package/src/pages/api/storage-config.ts +20 -0
- package/src/pages/api/storage-status.ts +206 -0
- package/src/pages/api/upload.ts +334 -0
- package/src/pages/api/webhooks/index.ts +71 -0
- package/src/pages/audit/index.astro +2 -104
- package/src/pages/login.astro +82 -0
- package/src/pages/media.astro +10 -0
- package/src/pages/preview/[collection]/[id].astro +178 -0
- package/src/pages/register.astro +102 -0
- package/src/pages/roles/index.astro +21 -21
- package/src/pages/settings/[slug].astro +162 -0
- package/src/pages/settings/index.astro +9 -0
- package/src/pages/users/[id].astro +29 -21
- package/src/pages/users/index.astro +22 -17
- package/src/pages/users/new.astro +18 -17
- package/src/styles/main.css +553 -128
- package/src/components/layout/Sidebar.tsx +0 -497
- package/src/pages/index.astro +0 -225
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
2
|
import type { CollectionConfig, KyroConfig } from "@kyro-cms/core";
|
|
3
3
|
import { Spinner } from "./ui/Spinner";
|
|
4
|
+
import { ConfirmModal } from "./ui/Modal";
|
|
5
|
+
import { Search, Plus, Settings } from "lucide-react";
|
|
4
6
|
|
|
5
7
|
interface ListViewProps {
|
|
6
8
|
config: KyroConfig;
|
|
@@ -20,6 +22,9 @@ export function ListView({
|
|
|
20
22
|
const [page, setPage] = useState(1);
|
|
21
23
|
const [totalPages, setTotalPages] = useState(1);
|
|
22
24
|
const [limit] = useState(25);
|
|
25
|
+
const [deleteId, setDeleteId] = useState<string | null>(null);
|
|
26
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
27
|
+
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
23
28
|
|
|
24
29
|
const label = collection.label || collection.slug;
|
|
25
30
|
|
|
@@ -44,18 +49,29 @@ export function ListView({
|
|
|
44
49
|
}
|
|
45
50
|
};
|
|
46
51
|
|
|
52
|
+
const filteredDocs = docs.filter((d) =>
|
|
53
|
+
Object.values(d).some((v) =>
|
|
54
|
+
String(v).toLowerCase().includes(searchQuery.toLowerCase()),
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
|
|
47
58
|
const handleDelete = async (id: string) => {
|
|
48
|
-
|
|
59
|
+
setDeleteId(id);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const confirmDelete = async () => {
|
|
63
|
+
if (!deleteId) return;
|
|
49
64
|
try {
|
|
50
|
-
const response = await fetch(`/api/${collection.slug}/${
|
|
65
|
+
const response = await fetch(`/api/${collection.slug}/${deleteId}`, {
|
|
51
66
|
method: "DELETE",
|
|
52
67
|
});
|
|
53
68
|
if (response.ok) {
|
|
54
|
-
setDocs((prev) => prev.filter((d) => d.id !==
|
|
69
|
+
setDocs((prev) => prev.filter((d) => d.id !== deleteId));
|
|
55
70
|
}
|
|
56
71
|
} catch (error) {
|
|
57
72
|
console.error("Failed to delete:", error);
|
|
58
73
|
}
|
|
74
|
+
setDeleteId(null);
|
|
59
75
|
};
|
|
60
76
|
|
|
61
77
|
const columns =
|
|
@@ -63,25 +79,36 @@ export function ListView({
|
|
|
63
79
|
Object.keys(collection.fields || {}).slice(0, 4);
|
|
64
80
|
|
|
65
81
|
return (
|
|
66
|
-
|
|
67
|
-
<div className="
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
<>
|
|
83
|
+
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 mb-8 pt-4">
|
|
84
|
+
<div>
|
|
85
|
+
<h2 className="text-3xl font-black tracking-tighter text-[var(--kyro-text-primary)]">
|
|
86
|
+
{label}
|
|
87
|
+
</h2>
|
|
88
|
+
<p className="text-[11px] font-bold uppercase tracking-widest opacity-40 mt-1">
|
|
89
|
+
Manage and explore your {label.toLowerCase()} entries
|
|
90
|
+
</p>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className="flex items-center gap-4 flex-1 max-w-2xl lg:justify-end">
|
|
94
|
+
<div className="relative flex-1 max-w-md group">
|
|
95
|
+
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--kyro-text-secondary)] opacity-40 group-focus-within:opacity-100 transition-opacity" />
|
|
96
|
+
<input
|
|
97
|
+
type="text"
|
|
98
|
+
placeholder={`Search ${label}...`}
|
|
99
|
+
value={searchQuery}
|
|
100
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
101
|
+
className="w-full pl-11 pr-4 py-2.5 bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-xl focus:outline-none focus:ring-2 focus:ring-[var(--kyro-primary)] focus:border-[var(--kyro-primary)] transition-all text-sm font-medium"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
<button type="button"
|
|
105
|
+
className="flex items-center gap-2 px-6 py-2.5 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl font-black text-xs shadow-lg active:scale-95 transition-all"
|
|
106
|
+
onClick={onCreate}
|
|
80
107
|
>
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
</
|
|
108
|
+
<Plus className="w-4 h-4" />
|
|
109
|
+
Create New
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
85
112
|
</div>
|
|
86
113
|
|
|
87
114
|
{loading ? (
|
|
@@ -107,7 +134,7 @@ export function ListView({
|
|
|
107
134
|
<p className="kyro-empty-text">
|
|
108
135
|
Get started by creating your first one.
|
|
109
136
|
</p>
|
|
110
|
-
<button
|
|
137
|
+
<button type="button"
|
|
111
138
|
className="kyro-btn kyro-btn-primary kyro-btn-md"
|
|
112
139
|
style={{ marginTop: 16 }}
|
|
113
140
|
onClick={onCreate}
|
|
@@ -117,43 +144,99 @@ export function ListView({
|
|
|
117
144
|
</div>
|
|
118
145
|
</div>
|
|
119
146
|
) : (
|
|
120
|
-
<div className="
|
|
121
|
-
<table className="
|
|
147
|
+
<div className="surface-tile overflow-hidden animate-in fade-in slide-in-from-bottom-4 duration-500">
|
|
148
|
+
<table className="w-full border-collapse">
|
|
122
149
|
<thead>
|
|
123
|
-
<tr>
|
|
124
|
-
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
150
|
+
<tr className="bg-[var(--kyro-bg-secondary)] text-[10px] font-black uppercase tracking-[0.2em] opacity-40 text-left">
|
|
151
|
+
<th className="px-6 py-4 w-12">
|
|
152
|
+
<input
|
|
153
|
+
type="checkbox"
|
|
154
|
+
className="accent-[var(--kyro-primary)]"
|
|
155
|
+
onChange={(e) => {
|
|
156
|
+
if (e.target.checked)
|
|
157
|
+
setSelectedIds(new Set(docs.map((d) => d.id)));
|
|
158
|
+
else setSelectedIds(new Set());
|
|
159
|
+
}}
|
|
160
|
+
/>
|
|
161
|
+
</th>
|
|
162
|
+
<th className="px-6 py-4">Document</th>
|
|
163
|
+
{columns
|
|
164
|
+
.filter((c) => c !== "title" && c !== "name")
|
|
165
|
+
.map((col) => (
|
|
166
|
+
<th key={col} className="px-6 py-4">
|
|
167
|
+
{col}
|
|
168
|
+
</th>
|
|
169
|
+
))}
|
|
170
|
+
<th key="status" className="px-6 py-4">
|
|
171
|
+
Status
|
|
172
|
+
</th>
|
|
173
|
+
<th className="px-6 py-4 text-right">Actions</th>
|
|
128
174
|
</tr>
|
|
129
175
|
</thead>
|
|
130
|
-
<tbody>
|
|
131
|
-
{
|
|
132
|
-
<tr
|
|
133
|
-
{
|
|
134
|
-
|
|
135
|
-
))}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
176
|
+
<tbody className="divide-y divide-[var(--kyro-border)]">
|
|
177
|
+
{filteredDocs.map((doc) => (
|
|
178
|
+
<tr
|
|
179
|
+
key={doc.id}
|
|
180
|
+
className={`group hover:bg-[var(--kyro-primary)] transition-all cursor-pointer ${selectedIds.has(doc.id) ? "bg-[var(--kyro-primary)]" : ""}`}
|
|
181
|
+
onClick={() => onEdit(doc.id)}
|
|
182
|
+
>
|
|
183
|
+
<td
|
|
184
|
+
className="px-6 py-5"
|
|
185
|
+
onClick={(e) => e.stopPropagation()}
|
|
186
|
+
>
|
|
187
|
+
<input
|
|
188
|
+
type="checkbox"
|
|
189
|
+
checked={selectedIds.has(doc.id)}
|
|
190
|
+
onChange={() => {
|
|
191
|
+
const next = new Set(selectedIds);
|
|
192
|
+
if (next.has(doc.id)) next.delete(doc.id);
|
|
193
|
+
else next.add(doc.id);
|
|
194
|
+
setSelectedIds(next);
|
|
195
|
+
}}
|
|
196
|
+
className="accent-[var(--kyro-primary)]"
|
|
197
|
+
/>
|
|
198
|
+
</td>
|
|
199
|
+
<td className="px-6 py-5">
|
|
200
|
+
<div className="flex flex-col">
|
|
201
|
+
<span className="font-black text-sm group-hover:text-[var(--kyro-primary)] transition-colors">
|
|
202
|
+
{doc.title || doc.name || doc.id}
|
|
203
|
+
</span>
|
|
204
|
+
<span className="text-[10px] font-bold opacity-30 uppercase tracking-widest mt-1">
|
|
205
|
+
ID: {doc.id.slice(-8)}
|
|
206
|
+
</span>
|
|
207
|
+
</div>
|
|
208
|
+
</td>
|
|
209
|
+
{columns
|
|
210
|
+
.filter((c) => c !== "title" && c !== "name")
|
|
211
|
+
.map((col) => (
|
|
212
|
+
<td
|
|
213
|
+
key={col}
|
|
214
|
+
className="px-6 py-5 text-sm font-medium text-[var(--kyro-text-secondary)]"
|
|
215
|
+
>
|
|
216
|
+
{formatValue(doc[col])}
|
|
217
|
+
</td>
|
|
218
|
+
))}
|
|
219
|
+
<td className="px-6 py-5">
|
|
220
|
+
<span
|
|
221
|
+
className={`inline-flex items-center px-3 py-1 rounded-full text-[9px] font-black uppercase tracking-widest ${doc.status === "published" ? "bg-green-500/10 text-green-500" : "bg-amber-500/10 text-amber-500"}`}
|
|
222
|
+
>
|
|
223
|
+
{doc.status || "draft"}
|
|
224
|
+
</span>
|
|
225
|
+
</td>
|
|
226
|
+
<td
|
|
227
|
+
className="px-6 py-5 text-right"
|
|
228
|
+
onClick={(e) => e.stopPropagation()}
|
|
229
|
+
>
|
|
230
|
+
<div className="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
231
|
+
<button type="button"
|
|
232
|
+
className="p-2 text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-bg-secondary)] rounded-lg transition-all"
|
|
140
233
|
onClick={() => onEdit(doc.id)}
|
|
141
234
|
title="Edit"
|
|
142
235
|
>
|
|
143
|
-
<
|
|
144
|
-
width="16"
|
|
145
|
-
height="16"
|
|
146
|
-
viewBox="0 0 24 24"
|
|
147
|
-
fill="none"
|
|
148
|
-
stroke="currentColor"
|
|
149
|
-
strokeWidth="2"
|
|
150
|
-
>
|
|
151
|
-
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
152
|
-
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
153
|
-
</svg>
|
|
236
|
+
<Settings className="w-4 h-4" />
|
|
154
237
|
</button>
|
|
155
|
-
<button
|
|
156
|
-
className="
|
|
238
|
+
<button type="button"
|
|
239
|
+
className="p-2 text-red-500 hover:bg-red-500/10 rounded-lg transition-all"
|
|
157
240
|
onClick={() => handleDelete(doc.id)}
|
|
158
241
|
title="Delete"
|
|
159
242
|
>
|
|
@@ -176,7 +259,62 @@ export function ListView({
|
|
|
176
259
|
</table>
|
|
177
260
|
</div>
|
|
178
261
|
)}
|
|
179
|
-
|
|
262
|
+
|
|
263
|
+
{selectedIds.size > 0 && (
|
|
264
|
+
<div className="fixed bottom-12 left-1/2 -translate-x-1/2 z-[60] bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-3xl shadow-2xl p-4 flex items-center gap-8 animate-in slide-in-from-bottom-12 duration-500 ring-1 ring-white/10">
|
|
265
|
+
<div className="flex items-center gap-4 border-r border-[var(--kyro-border)] pr-8 ml-2">
|
|
266
|
+
<div className="w-10 h-10 rounded-2xl bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] flex items-center justify-center font-black">
|
|
267
|
+
{selectedIds.size}
|
|
268
|
+
</div>
|
|
269
|
+
<div>
|
|
270
|
+
<p className="text-xs font-black uppercase tracking-widest">
|
|
271
|
+
Docs Selected
|
|
272
|
+
</p>
|
|
273
|
+
<button type="button"
|
|
274
|
+
onClick={() => setSelectedIds(new Set())}
|
|
275
|
+
className="text-[10px] font-bold text-[var(--kyro-primary)] hover:underline"
|
|
276
|
+
>
|
|
277
|
+
Clear Selection
|
|
278
|
+
</button>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
<div className="flex items-center gap-3 pr-2">
|
|
282
|
+
<button type="button" className="flex items-center gap-2 px-6 py-2.5 bg-green-500 text-white rounded-xl font-black text-[10px] uppercase tracking-widest shadow-lg hover:shadow-green-500/20 active:scale-95 transition-all">
|
|
283
|
+
Publish
|
|
284
|
+
</button>
|
|
285
|
+
<button type="button"
|
|
286
|
+
onClick={async () => {
|
|
287
|
+
if (
|
|
288
|
+
window.confirm(
|
|
289
|
+
`Are you sure you want to delete ${selectedIds.size} documents?`,
|
|
290
|
+
)
|
|
291
|
+
) {
|
|
292
|
+
for (const id of Array.from(selectedIds)) {
|
|
293
|
+
await fetch(`/api/${collection.slug}/${id}`, {
|
|
294
|
+
method: "DELETE",
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
loadDocs();
|
|
298
|
+
setSelectedIds(new Set());
|
|
299
|
+
}
|
|
300
|
+
}}
|
|
301
|
+
className="flex items-center gap-2 px-6 py-2.5 bg-red-500 text-white rounded-xl font-black text-[10px] uppercase tracking-widest shadow-lg hover:shadow-red-500/20 active:scale-95 transition-all"
|
|
302
|
+
>
|
|
303
|
+
Delete
|
|
304
|
+
</button>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
)}
|
|
308
|
+
<ConfirmModal
|
|
309
|
+
open={!!deleteId}
|
|
310
|
+
onClose={() => setDeleteId(null)}
|
|
311
|
+
onConfirm={confirmDelete}
|
|
312
|
+
title="Delete Document"
|
|
313
|
+
message="Are you sure you want to delete this document? This cannot be undone."
|
|
314
|
+
confirmLabel="Delete"
|
|
315
|
+
variant="danger"
|
|
316
|
+
/>
|
|
317
|
+
</>
|
|
180
318
|
);
|
|
181
319
|
}
|
|
182
320
|
|