@kyro-cms/admin 0.12.19 → 0.12.21

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 (60) hide show
  1. package/dist/index.cjs +24 -24
  2. package/dist/index.d.cts +2 -1
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.js +19 -19
  5. package/package.json +6 -6
  6. package/src/components/ApiKeysManager.tsx +177 -227
  7. package/src/components/AuditLogsPage.tsx +7 -7
  8. package/src/components/AuthorDashboard.tsx +235 -0
  9. package/src/components/AutoForm.tsx +15 -4
  10. package/src/components/CustomerDashboard.tsx +213 -0
  11. package/src/components/Dashboard.tsx +1 -1
  12. package/src/components/DashboardMetrics.tsx +74 -9
  13. package/src/components/DashboardQuickSections.tsx +237 -0
  14. package/src/components/ListView.tsx +3 -3
  15. package/src/components/MediaGallery.tsx +2 -2
  16. package/src/components/PluginsManager.tsx +1 -1
  17. package/src/components/SessionsManager.tsx +15 -15
  18. package/src/components/Sidebar.astro +564 -116
  19. package/src/components/UserManagement.tsx +43 -9
  20. package/src/components/UserMenu.tsx +123 -61
  21. package/src/components/autoform/AutoFormEditView.tsx +4 -4
  22. package/src/components/autoform/AutoFormHeader.tsx +2 -2
  23. package/src/components/fields/ArrayLayout.tsx +8 -8
  24. package/src/components/fields/CodeField.tsx +0 -1
  25. package/src/components/fields/TabsLayout.tsx +5 -2
  26. package/src/components/ui/CommandPalette.tsx +1 -1
  27. package/src/components/ui/Dropdown.tsx +71 -16
  28. package/src/components/ui/PageHeader.tsx +1 -1
  29. package/src/components/ui/Pagination.tsx +1 -1
  30. package/src/components/ui/SeoPreview.tsx +25 -21
  31. package/src/components/ui/Toast.tsx +3 -2
  32. package/src/components/ui/Toaster.tsx +1 -1
  33. package/src/components/users/UserDetail.tsx +74 -29
  34. package/src/components/users/UserForm.tsx +1 -1
  35. package/src/components/users/UsersList.tsx +2 -2
  36. package/src/integration.ts +4 -0
  37. package/src/layouts/AdminLayout.astro +26 -34
  38. package/src/layouts/AuthLayout.astro +18 -9
  39. package/src/lib/auth-server.ts +99 -0
  40. package/src/pages/403.astro +54 -35
  41. package/src/pages/[collection]/[id].astro +8 -0
  42. package/src/pages/[collection]/index.astro +7 -0
  43. package/src/pages/audit/index.astro +4 -1
  44. package/src/pages/auth/check-email.astro +146 -0
  45. package/src/pages/auth/forgot-password.astro +95 -0
  46. package/src/pages/auth/login.astro +29 -12
  47. package/src/pages/auth/register.astro +38 -12
  48. package/src/pages/auth/reset-password.astro +125 -0
  49. package/src/pages/auth/verify-email.astro +87 -0
  50. package/src/pages/index.astro +220 -121
  51. package/src/pages/keys.astro +3 -1
  52. package/src/pages/media.astro +3 -1
  53. package/src/pages/preview/[collection]/[id].astro +23 -73
  54. package/src/pages/roles/index.astro +134 -101
  55. package/src/pages/settings/[slug].astro +43 -3
  56. package/src/pages/users/[id].astro +5 -1
  57. package/src/pages/users/index.astro +4 -0
  58. package/src/pages/users/new.astro +4 -0
  59. package/src/pages/webhooks.astro +3 -1
  60. package/src/routes.ts +16 -0
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  import AdminLayout from "../../layouts/AdminLayout.astro";
3
3
  import i18next from "../../lib/i18n";
4
+ import { requireAuthServer } from "../../lib/auth-server";
5
+
6
+ const redirect = await requireAuthServer(Astro.request, { requireSuperAdmin: true });
7
+ if (redirect) return redirect;
4
8
 
5
9
  const defaultRoles = [
6
10
  {
@@ -58,52 +62,83 @@ const permissions: Record<string, string[]> = {
58
62
  ---
59
63
 
60
64
  <AdminLayout title={i18next.t("pages." + "Roles".replace(/ /g, ""), { defaultValue: "Roles" })}>
61
- <div class="flex-1 overflow-y-auto space-y-8">
65
+ <div class="flex-1 overflow-y-auto space-y-6">
62
66
  <!-- Header -->
63
- <div class="surface-tile p-6">
64
- <h1 class="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]">Roles & Permissions</h1>
65
- <p class="text-sm text-[var(--kyro-text-secondary)] mt-1 font-regular">
66
- Role-based access control with hierarchical permissions
67
- </p>
67
+ <div
68
+ class="surface-tile p-6 rounded-lg border border-[var(--kyro-border)] flex flex-col md:flex-row md:items-center justify-between gap-4"
69
+ >
70
+ <div>
71
+ <h1 class="text-lg font-semibold text-[var(--kyro-text-primary)]">Roles & Access Control</h1>
72
+ <p class="text-xs text-[var(--kyro-text-secondary)] mt-0.5">
73
+ Role hierarchy, inheritance rules, and system-wide permission assignments.
74
+ </p>
75
+ </div>
76
+ <div
77
+ class="flex items-center gap-2 text-xs font-medium text-[var(--kyro-text-secondary)] bg-[var(--kyro-surface-accent)] px-3 py-1.5 rounded-lg border border-[var(--kyro-border)]"
78
+ >
79
+ <svg class="w-4 h-4 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
80
+ <path
81
+ stroke-linecap="round"
82
+ stroke-linejoin="round"
83
+ stroke-width="2"
84
+ d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
85
+ ></path>
86
+ </svg>
87
+ <span>6 System Roles Defined</span>
88
+ </div>
68
89
  </div>
69
90
 
70
- <!-- Role Hierarchy -->
91
+ <!-- Role Cards Grid -->
71
92
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
72
93
  {
73
94
  defaultRoles.map((role) => (
74
- <div class={`surface-tile p-6 border-2 ${role.color.split(" ").slice(2).join(" ")} rounded-2xl`}>
75
- <div class="flex items-center justify-between mb-4">
76
- <h3 class="text-xl font-bold text-[var(--kyro-text-primary)] tracking-tighter">{role.name}</h3>
77
- <span class={`px-3 py-1 rounded-lg text-xs font-bold ${role.color.split(" ").slice(0, 2).join(" ")}`}>
78
- Level {role.level}
79
- </span>
95
+ <div class="surface-tile p-5 rounded-lg border border-[var(--kyro-border)] flex flex-col justify-between transition-all hover:border-[var(--kyro-text-secondary)]/30">
96
+ <div>
97
+ <div class="flex items-center justify-between gap-2 mb-2">
98
+ <div class="flex items-center gap-2">
99
+ <h3 class="text-sm font-semibold text-[var(--kyro-text-primary)] capitalize">
100
+ {role.name.replace("_", " ")}
101
+ </h3>
102
+ {role.name === "super_admin" && (
103
+ <span class="px-2 py-0.5 text-[10px] font-semibold tracking-wide uppercase bg-amber-500/10 text-amber-600 dark:text-amber-400 rounded-md border border-amber-500/20">
104
+ Root
105
+ </span>
106
+ )}
107
+ </div>
108
+ <span class="px-2.5 py-0.5 rounded-full text-xs font-medium bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] border border-[var(--kyro-border)]">
109
+ Lvl {role.level}
110
+ </span>
111
+ </div>
112
+ <p class="text-xs text-[var(--kyro-text-secondary)] leading-relaxed mb-4">{role.description}</p>
80
113
  </div>
81
- <p class="text-sm text-[var(--kyro-text-secondary)] mb-4">{role.description}</p>
82
- {role.inherits.length > 0 && (
83
- <div class="mb-4">
84
- <span class="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-[0.1em] opacity-50">
85
- Inherits:
114
+
115
+ <div class="space-y-3 pt-3 border-t border-[var(--kyro-border)]">
116
+ {role.inherits.length > 0 && (
117
+ <div>
118
+ <span class="text-[11px] font-medium text-[var(--kyro-text-muted)] uppercase tracking-wider">
119
+ Inherits From
120
+ </span>
121
+ <div class="flex flex-wrap gap-1.5 mt-1">
122
+ {role.inherits.map((parent) => (
123
+ <span class="px-2 py-0.5 bg-[var(--kyro-surface-accent)] rounded text-xs font-mono font-medium text-[var(--kyro-text-secondary)] border border-[var(--kyro-border)]">
124
+ {parent}
125
+ </span>
126
+ ))}
127
+ </div>
128
+ </div>
129
+ )}
130
+ <div>
131
+ <span class="text-[11px] font-medium text-[var(--kyro-text-muted)] uppercase tracking-wider">
132
+ Granted Scope
86
133
  </span>
87
- <div class="flex gap-2 mt-2">
88
- {role.inherits.map((parent) => (
89
- <span class="px-2 py-1 bg-[var(--kyro-surface-accent)] rounded text-xs font-bold text-[var(--kyro-text-secondary)]">
90
- {parent}
134
+ <div class="flex flex-wrap gap-1 mt-1">
135
+ {(permissions[role.name] || []).map((perm) => (
136
+ <span class="px-2 py-0.5 bg-[var(--kyro-surface-accent)] rounded text-[11px] font-mono text-[var(--kyro-text-primary)] border border-[var(--kyro-border)]">
137
+ {perm}
91
138
  </span>
92
139
  ))}
93
140
  </div>
94
141
  </div>
95
- )}
96
- <div>
97
- <span class="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-[0.1em] opacity-50">
98
- Permissions:
99
- </span>
100
- <div class="flex flex-wrap gap-1.5 mt-2">
101
- {(permissions[role.name] || []).map((perm) => (
102
- <span class="px-2 py-0.5 bg-[var(--kyro-surface-accent)] rounded text-[10px] font-medium text-[var(--kyro-text-primary)]">
103
- {perm}
104
- </span>
105
- ))}
106
- </div>
107
142
  </div>
108
143
  </div>
109
144
  ))
@@ -111,75 +146,73 @@ const permissions: Record<string, string[]> = {
111
146
  </div>
112
147
 
113
148
  <!-- Permission Matrix -->
114
- <div class="surface-tile overflow-hidden">
115
- <div class="p-6 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]">
116
- <h2 class="text-xl font-bold text-[var(--kyro-text-primary)] tracking-tighter">Permission Matrix</h2>
149
+ <div class="surface-tile rounded-lg border border-[var(--kyro-border)] overflow-hidden">
150
+ <div class="p-5 border-b border-[var(--kyro-border)]">
151
+ <h2 class="text-sm font-semibold text-[var(--kyro-text-primary)]">Permission Matrix</h2>
152
+ <p class="text-xs text-[var(--kyro-text-secondary)] mt-0.5">
153
+ Resource capability distribution across system roles
154
+ </p>
117
155
  </div>
118
- <table class="w-full text-left">
119
- <thead>
120
- <tr
121
- class="text-[var(--kyro-text-secondary)] font-bold text-[10px] tracking-[0.3em] border-b border-[var(--kyro-border)]"
122
- >
123
- <th class="px-6 py-4">Resource</th>
124
- {defaultRoles.map((role) => <th class="px-4 py-4 text-center">{role.name}</th>)}
125
- </tr>
126
- </thead>
127
- <tbody class="divide-y divide-[var(--kyro-border)]">
128
- {
129
- [
156
+ <div class="overflow-x-auto">
157
+ <table class="w-full text-left border-collapse">
158
+ <thead>
159
+ <tr
160
+ class="bg-[var(--kyro-surface-accent)] border-b border-[var(--kyro-border)] text-xs font-medium text-[var(--kyro-text-secondary)]"
161
+ >
162
+ <th class="px-5 py-3">Resource</th>
130
163
  {
131
- resource: "Users",
132
- actions: ["create", "read", "update", "delete"],
133
- },
134
- {
135
- resource: "Roles",
136
- actions: ["create", "read", "update", "delete"],
137
- },
138
- {
139
- resource: "Content",
140
- actions: ["create", "read", "update", "delete", "publish"],
141
- },
142
- {
143
- resource: "Media",
144
- actions: ["create", "read", "update", "delete"],
145
- },
146
- { resource: "Settings", actions: ["read", "update"] },
147
- { resource: "Audit Logs", actions: ["read"] },
148
- ].map((row) => (
149
- <tr class="hover:bg-[var(--kyro-surface-accent)] transition-colors">
150
- <td class="px-6 py-4 font-medium text-[var(--kyro-text-primary)]">{row.resource}</td>
151
- {defaultRoles.map((role) => {
152
- const hasAll = permissions[role.name]?.includes("*");
153
- const hasResource = permissions[role.name]?.some(
154
- (p) => p.startsWith(row.resource.toLowerCase() + ":") || p === "*",
155
- );
156
- return (
157
- <td class="px-4 py-4 text-center">
158
- {hasAll || hasResource ? (
159
- <svg class="w-5 h-5 text-green-500 mx-auto" fill="currentColor" viewBox="0 0 20 20">
160
- <path
161
- fill-rule="evenodd"
162
- 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"
163
- clip-rule="evenodd"
164
- />
165
- </svg>
166
- ) : (
167
- <svg class="w-5 h-5 text-[var(--kyro-border)] mx-auto" fill="currentColor" viewBox="0 0 20 20">
168
- <path
169
- fill-rule="evenodd"
170
- 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"
171
- clip-rule="evenodd"
172
- />
173
- </svg>
174
- )}
175
- </td>
176
- );
177
- })}
178
- </tr>
179
- ))
180
- }
181
- </tbody>
182
- </table>
164
+ defaultRoles.map((role) => (
165
+ <th class="px-4 py-3 text-center capitalize font-semibold">{role.name.replace("_", " ")}</th>
166
+ ))
167
+ }
168
+ </tr>
169
+ </thead>
170
+ <tbody class="divide-y divide-[var(--kyro-border)] text-xs">
171
+ {
172
+ [
173
+ { resource: "Users", key: "users" },
174
+ { resource: "Roles & Access", key: "roles" },
175
+ { resource: "Content Collections", key: "content" },
176
+ { resource: "Media Library", key: "media" },
177
+ { resource: "Global Settings", key: "settings" },
178
+ { resource: "Audit Logs", key: "audit" },
179
+ ].map((row) => (
180
+ <tr class="hover:bg-[var(--kyro-surface-accent)]/50 transition-colors">
181
+ <td class="px-5 py-3 font-medium text-[var(--kyro-text-primary)]">{row.resource}</td>
182
+ {defaultRoles.map((role) => {
183
+ const hasAll = permissions[role.name]?.includes("*");
184
+ const hasResource = permissions[role.name]?.some(
185
+ (p) => p.startsWith(row.key + ":") || p === "*" || p.startsWith(row.resource.toLowerCase() + ":"),
186
+ );
187
+ return (
188
+ <td class="px-4 py-3 text-center">
189
+ {hasAll || hasResource ? (
190
+ <span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
191
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
192
+ <path
193
+ stroke-linecap="round"
194
+ stroke-linejoin="round"
195
+ stroke-width="2.5"
196
+ d="M5 13l4 4L19 7"
197
+ />
198
+ </svg>
199
+ </span>
200
+ ) : (
201
+ <span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-muted)] opacity-50">
202
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
203
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4" />
204
+ </svg>
205
+ </span>
206
+ )}
207
+ </td>
208
+ );
209
+ })}
210
+ </tr>
211
+ ))
212
+ }
213
+ </tbody>
214
+ </table>
215
+ </div>
183
216
  </div>
184
217
  </div>
185
218
  </AdminLayout>
@@ -6,6 +6,10 @@ import { SettingsPage } from "../../components/SettingsPage";
6
6
  import { getGlobal } from "../../lib/globals";
7
7
 
8
8
  import { adminPath } from "../../lib/paths";
9
+ import { requireAuthServer } from "../../lib/auth-server";
10
+
11
+ const redirect = await requireAuthServer(Astro.request, { requireAdmin: true });
12
+ if (redirect) return redirect;
9
13
 
10
14
  const slug = Astro.props.slug || Astro.params.slug || Astro.params.id;
11
15
 
@@ -30,7 +34,7 @@ const description =
30
34
  <AdminLayout title={`Settings - ${title}`}>
31
35
  <div class="flex-1 overflow-y-auto">
32
36
  { !hasVersioning && (
33
- <div class="surface-tile p-8 pb-0 mb-6">
37
+ <div class="surface-tile p-8 pb-0 mb-6 rounded-lg">
34
38
  <div class="flex items-center justify-between mb-8">
35
39
  <div>
36
40
  <h1
@@ -61,6 +65,7 @@ const description =
61
65
  allGlobals.map((g) => (
62
66
  <a
63
67
  href={`${adminPath}/settings/${g.slug}`}
68
+ data-global-tab={g.slug}
64
69
  class={`pb-4 text-sm font-bold transition-all border-b-2 whitespace-nowrap ${
65
70
  activeSlug === g.slug
66
71
  ? "text-[var(--kyro-text-primary)] border-[var(--kyro-text-primary)]"
@@ -76,7 +81,7 @@ const description =
76
81
  )}
77
82
 
78
83
  <!-- Form Section -->
79
- <div class={hasVersioning ? "" : "surface-tile p-10 pt-4 mb-6 rounded-4xl"}>
84
+ <div class={hasVersioning ? "" : "surface-tile p-10 pt-4 mb-6 rounded-lg"}>
80
85
  { !hasVersioning && (
81
86
  <div class="mb-8">
82
87
  <h2
@@ -120,7 +125,42 @@ const description =
120
125
  });
121
126
  };
122
127
 
123
- document.addEventListener("astro:page-load", initSettingsPage);
128
+ const pruneGlobalTabs = (user?: any, permissions?: any) => {
129
+ if (!user) {
130
+ // @ts-ignore
131
+ user = window.__kyroAuth?.user;
132
+ // @ts-ignore
133
+ permissions = window.__kyroAuth?.permissions;
134
+ }
135
+ if (!user) return;
136
+ const userRole = user.role || "";
137
+ const isAdmin = userRole === "admin" || userRole === "super_admin";
138
+
139
+ const tabs = document.querySelectorAll("[data-global-tab]");
140
+ tabs.forEach((tab) => {
141
+ const globalSlug = tab.getAttribute("data-global-tab");
142
+ if (!globalSlug) return;
143
+ const hasRead = isAdmin || permissions?.globals?.[globalSlug]?.read === true;
144
+ if (!hasRead) {
145
+ (tab as HTMLElement).style.display = "none";
146
+ } else {
147
+ (tab as HTMLElement).style.display = "";
148
+ }
149
+ });
150
+ };
151
+
152
+ const runInitAndPrune = () => {
153
+ initSettingsPage();
154
+ pruneGlobalTabs();
155
+ };
156
+
157
+ document.addEventListener("astro:page-load", runInitAndPrune);
158
+ runInitAndPrune();
159
+
160
+ window.addEventListener("kyro:auth-ready", (event: any) => {
161
+ const { user, permissions } = event.detail || {};
162
+ pruneGlobalTabs(user, permissions);
163
+ });
124
164
 
125
165
  // Global listeners should only be attached once
126
166
  // @ts-ignore
@@ -4,6 +4,10 @@ import i18next from '../../lib/i18n';
4
4
  import { UserDetail } from "../../components/users/UserDetail";
5
5
 
6
6
  import { adminPath, apiPath } from "../../lib/paths";
7
+ import { requireAuthServer } from "../../lib/auth-server";
8
+
9
+ const redirect = await requireAuthServer(Astro.request, { collectionRead: 'users' });
10
+ if (redirect) return redirect;
7
11
 
8
12
  const { id } = Astro.params;
9
13
  let user: any = null;
@@ -39,7 +43,7 @@ if (id) {
39
43
  </div>
40
44
  ) : user ? (
41
45
  <UserDetail
42
- client:load
46
+ client:only="react"
43
47
  user={user}
44
48
  apiPath={apiPath}
45
49
  adminPath={adminPath}
@@ -4,6 +4,10 @@ import i18next from '../../lib/i18n';
4
4
  import { UsersList } from "../../components/users/UsersList";
5
5
 
6
6
  import { adminPath, apiPath } from "../../lib/paths";
7
+ import { requireAuthServer } from "../../lib/auth-server";
8
+
9
+ const redirect = await requireAuthServer(Astro.request, { collectionRead: 'users' });
10
+ if (redirect) return redirect;
7
11
 
8
12
  let users: any[] = [];
9
13
  let totalUsers = 0;
@@ -4,6 +4,10 @@ import i18next from '../../lib/i18n';
4
4
  import { UserForm } from "../../components/users/UserForm";
5
5
 
6
6
  import { adminPath, apiPath } from "../../lib/paths";
7
+ import { requireAuthServer } from "../../lib/auth-server";
8
+
9
+ const redirect = await requireAuthServer(Astro.request, { collectionCreate: 'users' });
10
+ if (redirect) return redirect;
7
11
  ---
8
12
 
9
13
  <AdminLayout title={i18next.t("pages." + "New User".replace(/ /g, ""), { defaultValue: "New User" })}>
@@ -3,8 +3,10 @@ import AdminLayout from "../layouts/AdminLayout.astro";
3
3
  import i18next from '../lib/i18n';
4
4
  import { WebhookManager } from "../components/WebhookManager";
5
5
 
6
- import { adminPath, apiPath } from '../lib/paths';
6
+ import { requireAuthServer } from '../lib/auth-server';
7
7
 
8
+ const redirect = await requireAuthServer(Astro.request, { requireAdmin: true });
9
+ if (redirect) return redirect;
8
10
  ---
9
11
 
10
12
  <AdminLayout title={i18next.t("pages." + "Webhooks".replace(/ /g, ""), { defaultValue: "Webhooks" })}>
package/src/routes.ts CHANGED
@@ -32,6 +32,22 @@ export const ADMIN_ROUTES = [
32
32
  pattern: "/register",
33
33
  entrypoint: "@kyro-cms/admin/src/pages/auth/register.astro",
34
34
  },
35
+ {
36
+ pattern: "/auth/check-email",
37
+ entrypoint: "@kyro-cms/admin/src/pages/auth/check-email.astro",
38
+ },
39
+ {
40
+ pattern: "/auth/forgot-password",
41
+ entrypoint: "@kyro-cms/admin/src/pages/auth/forgot-password.astro",
42
+ },
43
+ {
44
+ pattern: "/auth/reset-password",
45
+ entrypoint: "@kyro-cms/admin/src/pages/auth/reset-password.astro",
46
+ },
47
+ {
48
+ pattern: "/auth/verify-email",
49
+ entrypoint: "@kyro-cms/admin/src/pages/auth/verify-email.astro",
50
+ },
35
51
  { pattern: "/media", entrypoint: "@kyro-cms/admin/src/pages/media.astro" },
36
52
  {
37
53
  pattern: "/users",