@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.
@@ -1,4 +1,5 @@
1
1
  import React, { useState, useEffect } from "react";
2
+ import { apiGet, apiPost, apiDelete } from "@kyro-cms/utils/lib/api";
2
3
  import {
3
4
  Terminal,
4
5
  Key,
@@ -41,11 +42,8 @@ export function DeveloperCenter({ collections }: { collections: any }) {
41
42
 
42
43
  const loadKeys = async () => {
43
44
  try {
44
- const res = await fetch("/api/keys");
45
- if (res.ok) {
46
- const data = await res.json();
47
- setKeys(data);
48
- }
45
+ const data = await apiGet("/api/keys");
46
+ setKeys(data);
49
47
  } catch (e) {
50
48
  console.error(e);
51
49
  }
@@ -63,14 +61,8 @@ export function DeveloperCenter({ collections }: { collections: any }) {
63
61
  const confirmGenerateKey = async () => {
64
62
  if (!newKeyName.trim()) return;
65
63
  try {
66
- const res = await fetch("/api/keys", {
67
- method: "POST",
68
- headers: { "Content-Type": "application/json" },
69
- body: JSON.stringify({ name: newKeyName }),
70
- });
71
- if (res.ok) {
72
- loadKeys();
73
- }
64
+ await apiPost("/api/keys", { name: newKeyName });
65
+ loadKeys();
74
66
  } catch (e) {
75
67
  console.error(e);
76
68
  }
@@ -86,10 +78,8 @@ export function DeveloperCenter({ collections }: { collections: any }) {
86
78
  const confirmRevokeKey = async () => {
87
79
  if (!deleteKeyId) return;
88
80
  try {
89
- const res = await fetch(`/api/keys/${deleteKeyId}`, { method: "DELETE" });
90
- if (res.ok) {
91
- loadKeys();
92
- }
81
+ await apiDelete(`/api/keys/${deleteKeyId}`);
82
+ loadKeys();
93
83
  } catch (e) {
94
84
  console.error(e);
95
85
  }
@@ -126,7 +116,8 @@ export function DeveloperCenter({ collections }: { collections: any }) {
126
116
  </p>
127
117
  </div>
128
118
  <div className="flex items-center gap-3">
129
- <button type="button"
119
+ <button
120
+ type="button"
130
121
  onClick={handleGenerateKey}
131
122
  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:shadow-[var(--kyro-primary)] active:scale-95 transition-all"
132
123
  >
@@ -167,7 +158,8 @@ export function DeveloperCenter({ collections }: { collections: any }) {
167
158
  : "••••••••••••••••••••••••••••••••"}
168
159
  </code>
169
160
  <div className="flex items-center gap-1 shrink-0">
170
- <button type="button"
161
+ <button
162
+ type="button"
171
163
  onClick={() =>
172
164
  setShowKey(showKey === key.id ? null : key.id)
173
165
  }
@@ -179,7 +171,10 @@ export function DeveloperCenter({ collections }: { collections: any }) {
179
171
  <Eye className="w-3.5 h-3.5" />
180
172
  )}
181
173
  </button>
182
- <button type="button" className="p-1.5 hover:bg-[var(--kyro-surface)] rounded-md transition-all text-[var(--kyro-text-secondary)]">
174
+ <button
175
+ type="button"
176
+ className="p-1.5 hover:bg-[var(--kyro-surface)] rounded-md transition-all text-[var(--kyro-text-secondary)]"
177
+ >
183
178
  <Copy className="w-3.5 h-3.5" />
184
179
  </button>
185
180
  </div>
@@ -198,7 +193,8 @@ export function DeveloperCenter({ collections }: { collections: any }) {
198
193
  </div>
199
194
  </div>
200
195
  <div className="flex items-center gap-3">
201
- <button type="button"
196
+ <button
197
+ type="button"
202
198
  onClick={() => handleRevokeKey(key.id)}
203
199
  className="p-3 bg-red-500/5 text-red-500 rounded-xl hover:bg-red-500/10 transition-all border border-transparent hover:border-red-500/20"
204
200
  title="Revoke Key"
@@ -224,7 +220,10 @@ export function DeveloperCenter({ collections }: { collections: any }) {
224
220
  All content is delivered via our optimized REST API. Use your
225
221
  keys to authorize requests.
226
222
  </p>
227
- <button type="button" className="w-full py-3 bg-white text-[var(--kyro-primary)] rounded-xl font-black text-xs uppercase tracking-widest hover:shadow-2xl transition-all flex items-center justify-center gap-2">
223
+ <button
224
+ type="button"
225
+ className="w-full py-3 bg-white text-[var(--kyro-primary)] rounded-xl font-black text-xs uppercase tracking-widest hover:shadow-2xl transition-all flex items-center justify-center gap-2"
226
+ >
228
227
  Review API Docs
229
228
  <ExternalLink className="w-3.5 h-3.5" />
230
229
  </button>
@@ -290,7 +289,8 @@ export function DeveloperCenter({ collections }: { collections: any }) {
290
289
  className="w-full pl-20 pr-4 py-3 bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-2xl focus:outline-none focus:ring-2 focus:ring-[var(--kyro-primary)] transition-all font-mono text-xs font-bold"
291
290
  />
292
291
  </div>
293
- <button type="button"
292
+ <button
293
+ type="button"
294
294
  onClick={handleRunTest}
295
295
  disabled={exploring || !testEndpoint}
296
296
  className="px-6 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-2xl font-black text-xs uppercase tracking-widest shadow-xl disabled:opacity-50 disabled:cursor-not-allowed hover:shadow-[var(--kyro-primary)] transition-all flex items-center gap-2 shrink-0"
@@ -355,13 +355,15 @@ export function DeveloperCenter({ collections }: { collections: any }) {
355
355
  />
356
356
  </ModalContent>
357
357
  <ModalActions>
358
- <button type="button"
358
+ <button
359
+ type="button"
359
360
  onClick={() => setShowCreateModal(false)}
360
361
  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)] hover:text-[var(--kyro-text-primary)] transition-colors"
361
362
  >
362
363
  Cancel
363
364
  </button>
364
- <button type="button"
365
+ <button
366
+ type="button"
365
367
  onClick={confirmGenerateKey}
366
368
  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 transition-colors"
367
369
  >
@@ -384,13 +386,15 @@ export function DeveloperCenter({ collections }: { collections: any }) {
384
386
  </p>
385
387
  </ModalContent>
386
388
  <ModalActions>
387
- <button type="button"
389
+ <button
390
+ type="button"
388
391
  onClick={() => setShowDeleteModal(false)}
389
392
  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)] hover:text-[var(--kyro-text-primary)] transition-colors"
390
393
  >
391
394
  Cancel
392
395
  </button>
393
- <button type="button"
396
+ <button
397
+ type="button"
394
398
  onClick={confirmRevokeKey}
395
399
  className="px-4 py-2 rounded-lg font-medium text-sm bg-red-500 text-white hover:bg-red-600 transition-colors"
396
400
  >
@@ -1,6 +1,7 @@
1
1
  import { useState, useEffect, useMemo, useCallback } from "react";
2
2
  import { Spinner } from "./ui/Spinner";
3
3
  import { ConfirmModal } from "./ui/Modal";
4
+ import { apiGet, apiDelete, withCacheBust } from "@kyro-cms/utils/lib/api";
4
5
 
5
6
  export interface FieldConfig {
6
7
  name: string;
@@ -217,11 +218,9 @@ export function EnhancedListView({
217
218
  params.append("filters", JSON.stringify(filters));
218
219
  }
219
220
 
220
- const response = await fetch(
221
- `/api/${collectionSlug}?${params}&t=${Date.now()}`,
221
+ const result = await apiGet(
222
+ withCacheBust(`/api/${collectionSlug}?${params}`),
222
223
  );
223
- if (!response.ok) throw new Error("Failed to load");
224
- const result = await response.json();
225
224
  setDocs(result.docs || []);
226
225
  setTotalDocs(result.totalDocs || 0);
227
226
  } catch (error) {
@@ -261,7 +260,7 @@ export function EnhancedListView({
261
260
  try {
262
261
  await Promise.all(
263
262
  Array.from(selectedIds).map((id) =>
264
- fetch(`/api/${collectionSlug}/${id}`, { method: "DELETE" }),
263
+ apiDelete(`/api/${collectionSlug}/${id}`),
265
264
  ),
266
265
  );
267
266
  setSelectedIds(new Set());
@@ -852,7 +851,7 @@ export function EnhancedListView({
852
851
  const confirmDelete = async () => {
853
852
  try {
854
853
  for (const id of deleteConfirm.ids || []) {
855
- await fetch(`/api/${collectionSlug}/${id}`, { method: "DELETE" });
854
+ await apiDelete(`/api/${collectionSlug}/${id}`);
856
855
  }
857
856
  fetchDocs();
858
857
  } catch (error) {
@@ -1,4 +1,5 @@
1
1
  import { useState, useEffect } from "react";
2
+ import { apiGet, apiDelete } from "@kyro-cms/utils/lib/api";
2
3
  import type { CollectionConfig, KyroConfig } from "@kyro-cms/core/client";
3
4
  import { Spinner } from "./ui/Spinner";
4
5
  import { ConfirmModal } from "./ui/Modal";
@@ -35,11 +36,9 @@ export function ListView({
35
36
  const loadDocs = async () => {
36
37
  try {
37
38
  setLoading(true);
38
- const response = await fetch(
39
+ const result = await apiGet(
39
40
  `/api/${collection.slug}?page=${page}&limit=${limit}`,
40
41
  );
41
- if (!response.ok) throw new Error("Failed to load");
42
- const result = await response.json();
43
42
  setDocs(result.docs || []);
44
43
  setTotalPages(result.totalPages || 1);
45
44
  } catch (error) {
@@ -62,12 +61,8 @@ export function ListView({
62
61
  const confirmDelete = async () => {
63
62
  if (!deleteId) return;
64
63
  try {
65
- const response = await fetch(`/api/${collection.slug}/${deleteId}`, {
66
- method: "DELETE",
67
- });
68
- if (response.ok) {
69
- setDocs((prev) => prev.filter((d) => d.id !== deleteId));
70
- }
64
+ await apiDelete(`/api/${collection.slug}/${deleteId}`);
65
+ setDocs((prev) => prev.filter((d) => d.id !== deleteId));
71
66
  } catch (error) {
72
67
  console.error("Failed to delete:", error);
73
68
  }
@@ -101,7 +96,8 @@ export function ListView({
101
96
  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
97
  />
103
98
  </div>
104
- <button type="button"
99
+ <button
100
+ type="button"
105
101
  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
102
  onClick={onCreate}
107
103
  >
@@ -134,7 +130,8 @@ export function ListView({
134
130
  <p className="kyro-empty-text">
135
131
  Get started by creating your first one.
136
132
  </p>
137
- <button type="button"
133
+ <button
134
+ type="button"
138
135
  className="kyro-btn kyro-btn-primary kyro-btn-md"
139
136
  style={{ marginTop: 16 }}
140
137
  onClick={onCreate}
@@ -228,14 +225,16 @@ export function ListView({
228
225
  onClick={(e) => e.stopPropagation()}
229
226
  >
230
227
  <div className="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
231
- <button type="button"
228
+ <button
229
+ type="button"
232
230
  className="p-2 text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-bg-secondary)] rounded-lg transition-all"
233
231
  onClick={() => onEdit(doc.id)}
234
232
  title="Edit"
235
233
  >
236
234
  <Settings className="w-4 h-4" />
237
235
  </button>
238
- <button type="button"
236
+ <button
237
+ type="button"
239
238
  className="p-2 text-red-500 hover:bg-red-500/10 rounded-lg transition-all"
240
239
  onClick={() => handleDelete(doc.id)}
241
240
  title="Delete"
@@ -270,7 +269,8 @@ export function ListView({
270
269
  <p className="text-xs font-black uppercase tracking-widest">
271
270
  Docs Selected
272
271
  </p>
273
- <button type="button"
272
+ <button
273
+ type="button"
274
274
  onClick={() => setSelectedIds(new Set())}
275
275
  className="text-[10px] font-bold text-[var(--kyro-primary)] hover:underline"
276
276
  >
@@ -279,10 +279,14 @@ export function ListView({
279
279
  </div>
280
280
  </div>
281
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">
282
+ <button
283
+ type="button"
284
+ 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"
285
+ >
283
286
  Publish
284
287
  </button>
285
- <button type="button"
288
+ <button
289
+ type="button"
286
290
  onClick={async () => {
287
291
  if (
288
292
  window.confirm(
@@ -290,9 +294,7 @@ export function ListView({
290
294
  )
291
295
  ) {
292
296
  for (const id of Array.from(selectedIds)) {
293
- await fetch(`/api/${collection.slug}/${id}`, {
294
- method: "DELETE",
295
- });
297
+ await apiDelete(`/api/${collection.slug}/${id}`);
296
298
  }
297
299
  loadDocs();
298
300
  setSelectedIds(new Set());
@@ -1,4 +1,5 @@
1
1
  import { useState, useEffect } from "react";
2
+ import { apiGet, apiPost } from "@kyro-cms/utils/lib/api";
2
3
  import { ThemeProvider, type ThemeMode } from "./ThemeProvider";
3
4
  import { Toast, ToastProvider } from "./ui/Toast";
4
5
 
@@ -30,11 +31,7 @@ export function LoginPage({ onAuth, theme = "light" }: LoginPageProps) {
30
31
 
31
32
  const checkIfFirstUser = async () => {
32
33
  try {
33
- const res = await fetch("/api/auth/users");
34
- if (res.status === 401 || res.status === 404) {
35
- setIsFirstUser(true);
36
- setMode("register");
37
- }
34
+ await apiGet("/api/auth/users");
38
35
  } catch {
39
36
  setIsFirstUser(true);
40
37
  setMode("register");
@@ -61,18 +58,7 @@ export function LoginPage({ onAuth, theme = "light" }: LoginPageProps) {
61
58
  body.confirmPassword = confirmPassword;
62
59
  }
63
60
 
64
- const res = await fetch(endpoint, {
65
- method: "POST",
66
- headers: { "Content-Type": "application/json" },
67
- body: JSON.stringify(body),
68
- });
69
-
70
- const data = await res.json();
71
-
72
- if (!res.ok) {
73
- addToast("error", data.error || "Something went wrong");
74
- return;
75
- }
61
+ const data = await apiPost(endpoint, body);
76
62
 
77
63
  if (data.isFirstUser) {
78
64
  setIsFirstUser(true);