@kyro-cms/admin 0.3.2 → 0.3.4

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 (242) hide show
  1. package/dist/EditorClient-XEUOVAAC.js +466 -0
  2. package/dist/EditorClient-XEUOVAAC.js.map +1 -0
  3. package/dist/EditorClient-YLCGVDXY.cjs +468 -0
  4. package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
  5. package/dist/chunk-7KPIUCGT.js +384 -0
  6. package/dist/chunk-7KPIUCGT.js.map +1 -0
  7. package/dist/chunk-GOACG6R7.cjs +473 -0
  8. package/dist/chunk-GOACG6R7.cjs.map +1 -0
  9. package/dist/index.cjs +14861 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +1661 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.ts +563 -0
  14. package/dist/index.js +14784 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +19 -19
  17. package/src/components/ActionBar.tsx +7 -43
  18. package/src/components/Admin.tsx +138 -277
  19. package/src/components/ApiKeysManager.tsx +428 -419
  20. package/src/components/AuditLogsPage.tsx +35 -39
  21. package/src/components/AuthBridge.tsx +51 -0
  22. package/src/components/AutoForm.tsx +495 -1230
  23. package/src/components/BrandingHub.tsx +18 -19
  24. package/src/components/BulkActionsBar.tsx +1 -1
  25. package/src/components/CreateView.tsx +22 -36
  26. package/src/components/Dashboard.tsx +60 -84
  27. package/src/components/DetailView.tsx +113 -91
  28. package/src/components/DeveloperCenter.tsx +200 -198
  29. package/src/components/FieldRenderer.tsx +206 -0
  30. package/src/components/GraphQLPlayground.tsx +340 -480
  31. package/src/components/ListView.tsx +828 -254
  32. package/src/components/LoginPage.tsx +3 -4
  33. package/src/components/MarketplaceManager.tsx +254 -0
  34. package/src/components/MediaGallery.tsx +856 -1192
  35. package/src/components/PluginsManager.tsx +277 -0
  36. package/src/components/RestPlayground.tsx +398 -560
  37. package/src/components/SessionsManager.tsx +211 -0
  38. package/src/components/Sidebar.astro +179 -151
  39. package/src/components/ThemeProvider.tsx +7 -161
  40. package/src/components/UserManagement.tsx +162 -146
  41. package/src/components/UserMenu.tsx +110 -0
  42. package/src/components/WebhookManager.tsx +305 -367
  43. package/src/components/blocks/AccordionBlock.tsx +4 -4
  44. package/src/components/blocks/ArrayBlock.tsx +3 -3
  45. package/src/components/blocks/BlockEditModal.tsx +8 -8
  46. package/src/components/blocks/BlockWrapper.tsx +61 -0
  47. package/src/components/blocks/ButtonBlock.tsx +4 -4
  48. package/src/components/blocks/ChildBlocksTree.tsx +23 -25
  49. package/src/components/blocks/CodeBlock.tsx +15 -15
  50. package/src/components/blocks/ColumnsBlock.tsx +6 -44
  51. package/src/components/blocks/DividerBlock.tsx +3 -3
  52. package/src/components/blocks/FileBlock.tsx +4 -4
  53. package/src/components/blocks/HeadingBlock.tsx +6 -38
  54. package/src/components/blocks/HeroBlock.tsx +4 -4
  55. package/src/components/blocks/ImageBlock.tsx +4 -4
  56. package/src/components/blocks/LinkBlock.tsx +4 -4
  57. package/src/components/blocks/ListBlock.tsx +3 -3
  58. package/src/components/blocks/ParagraphBlock.tsx +12 -42
  59. package/src/components/blocks/RelationshipBlock.tsx +4 -4
  60. package/src/components/blocks/RichTextBlock.tsx +4 -4
  61. package/src/components/blocks/VStackBlock.tsx +5 -37
  62. package/src/components/blocks/VideoBlock.tsx +4 -4
  63. package/src/components/blocks/types.ts +11 -0
  64. package/src/components/fields/AccordionField.tsx +1 -1
  65. package/src/components/fields/ArrayField.tsx +2 -2
  66. package/src/components/fields/ArrayLayout.tsx +93 -0
  67. package/src/components/fields/BlocksField.tsx +122 -111
  68. package/src/components/fields/ButtonField.tsx +1 -1
  69. package/src/components/fields/CheckboxField.tsx +14 -15
  70. package/src/components/fields/ChildrenField.tsx +2 -2
  71. package/src/components/fields/CodeField.tsx +3 -3
  72. package/src/components/fields/ColumnsField.tsx +2 -2
  73. package/src/components/fields/DateField.tsx +13 -26
  74. package/src/components/fields/EditorClient.tsx +26 -28
  75. package/src/components/fields/FieldLayout.tsx +52 -0
  76. package/src/components/fields/GroupLayout.tsx +35 -0
  77. package/src/components/fields/JSONField.tsx +7 -7
  78. package/src/components/fields/LinkField.tsx +1 -1
  79. package/src/components/fields/MarkdownField.tsx +1 -1
  80. package/src/components/fields/NumberField.tsx +13 -26
  81. package/src/components/fields/PortableTextField.tsx +4 -4
  82. package/src/components/fields/PortableTextRenderer.tsx +1 -1
  83. package/src/components/fields/RelationshipBlockField.tsx +31 -23
  84. package/src/components/fields/RelationshipField.tsx +14 -14
  85. package/src/components/fields/SelectField.tsx +17 -26
  86. package/src/components/fields/TabsLayout.tsx +69 -0
  87. package/src/components/fields/TextField.tsx +85 -38
  88. package/src/components/fields/UploadField.tsx +71 -41
  89. package/src/components/fields/VideoField.tsx +1 -1
  90. package/src/components/fields/extensions/blockComponents.tsx +2 -2
  91. package/src/components/fields/extensions/blocksStore.ts +207 -193
  92. package/src/components/fields/types.ts +22 -0
  93. package/src/components/layout/Layout.tsx +1 -1
  94. package/src/components/ui/ActionMenu.tsx +63 -0
  95. package/src/components/ui/Badge.tsx +59 -5
  96. package/src/components/ui/BlockDrawer.tsx +4 -5
  97. package/src/components/ui/CommandPalette.tsx +58 -36
  98. package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
  99. package/src/components/ui/Dropdown.tsx +18 -16
  100. package/src/components/ui/EmptyState.tsx +25 -0
  101. package/src/components/ui/GlobalModal.tsx +49 -0
  102. package/src/components/ui/IconButton.tsx +44 -0
  103. package/src/components/ui/Modal.tsx +19 -20
  104. package/src/components/ui/PageHeader.tsx +158 -0
  105. package/src/components/ui/Pagination.tsx +61 -0
  106. package/src/components/ui/PromptModal.tsx +1 -1
  107. package/src/components/ui/SearchInput.tsx +57 -0
  108. package/src/components/ui/SeoPreview.tsx +31 -0
  109. package/src/components/ui/SessionModal.tsx +0 -0
  110. package/src/components/ui/SlidePanel.tsx +2 -0
  111. package/src/components/ui/Toast.tsx +65 -122
  112. package/src/components/ui/Toaster.tsx +18 -0
  113. package/src/components/ui/icons.tsx +112 -0
  114. package/src/components/users/UserDetail.tsx +290 -0
  115. package/src/components/users/UserForm.tsx +242 -0
  116. package/src/components/users/UsersList.tsx +338 -0
  117. package/src/env.d.ts +13 -13
  118. package/src/fields/index.ts +2 -1
  119. package/src/global.d.ts +7 -0
  120. package/src/hooks/data.ts +2 -9
  121. package/src/hooks/useAsyncData.ts +36 -0
  122. package/src/hooks/useAutoFormState.ts +527 -0
  123. package/src/hooks/useSelection.ts +49 -0
  124. package/src/hooks/useSession.ts +0 -0
  125. package/src/index.ts +11 -1
  126. package/src/integration.ts +86 -11
  127. package/src/kyro-cms.d.ts +209 -0
  128. package/src/layouts/AdminLayout.astro +128 -11
  129. package/src/layouts/AuthLayout.astro +21 -5
  130. package/src/lib/api.ts +175 -55
  131. package/src/lib/autoform-store.ts +435 -0
  132. package/src/lib/config.ts +82 -34
  133. package/src/lib/createRegistry.ts +29 -0
  134. package/src/lib/default-kyro-config.ts +4 -0
  135. package/src/lib/globals.ts +50 -0
  136. package/src/lib/media-utils.ts +18 -0
  137. package/src/lib/object-utils.ts +77 -0
  138. package/src/lib/paths.ts +61 -0
  139. package/src/lib/stores/index.ts +370 -0
  140. package/src/lib/types.ts +43 -0
  141. package/src/lib/useResourceManager.ts +105 -0
  142. package/src/pages/403.astro +67 -0
  143. package/src/pages/[collection]/[id].astro +14 -180
  144. package/src/pages/[collection]/index.astro +11 -6
  145. package/src/pages/api-explorer.astro +173 -0
  146. package/src/pages/audit/index.astro +2 -0
  147. package/src/pages/auth/login.astro +122 -0
  148. package/src/pages/auth/register.astro +167 -0
  149. package/src/pages/graphql-explorer.astro +59 -0
  150. package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
  151. package/src/pages/index.astro +577 -0
  152. package/src/pages/index_ALT.astro +3 -0
  153. package/src/pages/keys.astro +11 -0
  154. package/src/pages/marketplace.astro +11 -0
  155. package/src/pages/media.astro +3 -0
  156. package/src/pages/plugins.astro +8 -0
  157. package/src/pages/preview/[collection]/[id].astro +188 -123
  158. package/src/pages/rest-playground.astro +62 -0
  159. package/src/pages/roles/index.astro +183 -76
  160. package/src/pages/sessions.astro +8 -0
  161. package/src/pages/settings/[slug].astro +92 -114
  162. package/src/pages/settings/index.astro +5 -3
  163. package/src/pages/users/[id].astro +25 -154
  164. package/src/pages/users/index.astro +19 -130
  165. package/src/pages/users/new.astro +9 -86
  166. package/src/pages/webhooks.astro +11 -0
  167. package/src/routes.ts +80 -0
  168. package/src/styles/main.css +119 -79
  169. package/src/theme/tokens.ts +1 -0
  170. package/src/vite-env.d.ts +14 -0
  171. package/src/collections/auth/index.ts +0 -155
  172. package/src/collections/portfolio/index.ts +0 -343
  173. package/src/components/ApiExplorer.tsx +0 -325
  174. package/src/components/EnhancedListView.tsx +0 -889
  175. package/src/components/GraphQLExplorer.tsx +0 -675
  176. package/src/components/Icons.tsx +0 -23
  177. package/src/components/StatusBadge.tsx +0 -76
  178. package/src/lib/MediaService.ts +0 -541
  179. package/src/lib/auth/sqlite-adapter.ts +0 -319
  180. package/src/lib/dataStore.ts +0 -226
  181. package/src/lib/db/adapter.ts +0 -54
  182. package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
  183. package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
  184. package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
  185. package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
  186. package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
  187. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
  188. package/src/lib/db/index.ts +0 -449
  189. package/src/lib/db/mongodb-adapter.ts +0 -207
  190. package/src/lib/db/mongodb-auth-adapter.ts +0 -305
  191. package/src/lib/db/schema/mysql-auth.ts +0 -113
  192. package/src/lib/db/schema/mysql-content.ts +0 -20
  193. package/src/lib/db/schema/postgres-auth.ts +0 -116
  194. package/src/lib/db/schema/postgres-content.ts +0 -35
  195. package/src/lib/db/schema/postgres-media.ts +0 -52
  196. package/src/lib/db/schema/postgres-settings.ts +0 -11
  197. package/src/lib/db/schema/sqlite-auth.ts +0 -112
  198. package/src/lib/db/schema/sqlite-content.ts +0 -20
  199. package/src/lib/db/version-adapter.ts +0 -248
  200. package/src/lib/graphql/index.ts +0 -1
  201. package/src/lib/graphql/schema.ts +0 -443
  202. package/src/lib/rate-limit.ts +0 -267
  203. package/src/lib/storage.ts +0 -374
  204. package/src/lib/store.ts +0 -85
  205. package/src/middleware.ts +0 -177
  206. package/src/pages/admin/api-explorer.astro +0 -98
  207. package/src/pages/admin/graphql-explorer.astro +0 -40
  208. package/src/pages/admin/index.astro +0 -286
  209. package/src/pages/admin/keys.astro +0 -8
  210. package/src/pages/admin/rest-playground.astro +0 -44
  211. package/src/pages/admin/webhooks.astro +0 -8
  212. package/src/pages/api/[collection]/[id]/publish.ts +0 -52
  213. package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
  214. package/src/pages/api/[collection]/[id]/versions.ts +0 -66
  215. package/src/pages/api/[collection]/[id].ts +0 -213
  216. package/src/pages/api/[collection]/index.ts +0 -209
  217. package/src/pages/api/auth/[id].ts +0 -121
  218. package/src/pages/api/auth/audit-logs.ts +0 -57
  219. package/src/pages/api/auth/login.ts +0 -211
  220. package/src/pages/api/auth/logout.ts +0 -66
  221. package/src/pages/api/auth/me.ts +0 -36
  222. package/src/pages/api/auth/refresh.ts +0 -119
  223. package/src/pages/api/auth/register.ts +0 -188
  224. package/src/pages/api/auth/users.ts +0 -97
  225. package/src/pages/api/collections.ts +0 -59
  226. package/src/pages/api/globals/[slug].ts +0 -42
  227. package/src/pages/api/graphql.ts +0 -90
  228. package/src/pages/api/health.ts +0 -426
  229. package/src/pages/api/keys/[id].ts +0 -26
  230. package/src/pages/api/keys/index.ts +0 -75
  231. package/src/pages/api/media/[id].ts +0 -309
  232. package/src/pages/api/media/folders.ts +0 -609
  233. package/src/pages/api/media/index.ts +0 -146
  234. package/src/pages/api/media/resize.ts +0 -267
  235. package/src/pages/api/search.ts +0 -82
  236. package/src/pages/api/slug-availability.ts +0 -70
  237. package/src/pages/api/storage-config.ts +0 -20
  238. package/src/pages/api/storage-status.ts +0 -206
  239. package/src/pages/api/upload.ts +0 -334
  240. package/src/pages/api/webhooks/index.ts +0 -71
  241. package/src/pages/login.astro +0 -82
  242. package/src/pages/register.astro +0 -102
@@ -4,30 +4,105 @@ import fs from "fs";
4
4
 
5
5
  export interface KyroAdminOptions {
6
6
  basePath?: string;
7
+ apiPath?: string;
7
8
  configPath?: string;
8
- disableAuth?: boolean;
9
9
  }
10
10
 
11
11
  export function kyroAdmin(options: KyroAdminOptions = {}): AstroIntegration {
12
12
  const {
13
13
  basePath = "/admin",
14
+ apiPath = "/api",
14
15
  configPath = "kyro.config.ts",
15
- disableAuth = false,
16
16
  } = options;
17
17
 
18
18
  return {
19
19
  name: "@kyro-cms/admin",
20
20
  hooks: {
21
- "astro:config:setup": ({ config, updateConfig, logger }) => {
22
- logger.info(`Kyro Admin mounted at ${basePath}`);
21
+ "astro:config:setup": ({ config, updateConfig, injectRoute, logger }) => {
22
+ logger.info(`Kyro Admin mounted at ${basePath} (API: ${apiPath})`);
23
23
 
24
- const resolvedConfig = path.resolve(config.root.pathname, configPath);
25
- if (fs.existsSync(resolvedConfig)) {
26
- logger.info(`Loaded config from ${configPath}`);
24
+ const fallbackConfig = path.resolve(
25
+ new URL(".", import.meta.url).pathname,
26
+ "lib/default-kyro-config.ts",
27
+ );
28
+
29
+ // Try to resolve config from root first, then admin local
30
+ const rootConfig = path.resolve(config.root.pathname, "..", configPath);
31
+ const localConfig = path.resolve(config.root.pathname, configPath);
32
+
33
+ const resolvedConfig = fs.existsSync(rootConfig)
34
+ ? rootConfig
35
+ : fs.existsSync(localConfig)
36
+ ? localConfig
37
+ : fallbackConfig;
38
+
39
+ if (resolvedConfig !== fallbackConfig) {
40
+ logger.info(`Loaded config from ${resolvedConfig}`);
27
41
  } else {
28
- logger.warn(
29
- `Config file not found at ${configPath}. Using defaults.`,
42
+ logger.warn(`Config file not found. Using defaults.`);
43
+ }
44
+
45
+ // Inject API Routes
46
+ if (apiPath) {
47
+ const apiHandlerPath = path.resolve(
48
+ config.root.pathname,
49
+ "..",
50
+ "src/api-handler.ts",
30
51
  );
52
+ injectRoute({
53
+ pattern: `${apiPath}/[...path]`,
54
+ entrypoint: apiHandlerPath,
55
+ });
56
+ }
57
+
58
+ // Inject Admin UI Routes
59
+ const pages = [
60
+ { pattern: "", entrypoint: "./pages/index.astro" },
61
+ { pattern: "/login", entrypoint: "./pages/auth/login.astro" },
62
+ { pattern: "/register", entrypoint: "./pages/auth/register.astro" },
63
+ { pattern: "/media", entrypoint: "./pages/media.astro" },
64
+ { pattern: "/users", entrypoint: "./pages/users/index.astro" },
65
+ { pattern: "/users/[id]", entrypoint: "./pages/users/[id].astro" },
66
+ { pattern: "/roles", entrypoint: "./pages/roles/index.astro" },
67
+ { pattern: "/settings", entrypoint: "./pages/settings/index.astro" },
68
+ {
69
+ pattern: "/settings/[slug]",
70
+ entrypoint: "./pages/settings/[slug].astro",
71
+ },
72
+ { pattern: "/audit", entrypoint: "./pages/audit/index.astro" },
73
+ { pattern: "/sessions", entrypoint: "./pages/sessions.astro" },
74
+ { pattern: "/keys", entrypoint: "./pages/keys.astro" },
75
+ { pattern: "/webhooks", entrypoint: "./pages/webhooks.astro" },
76
+ { pattern: "/plugins", entrypoint: "./pages/plugins.astro" },
77
+ { pattern: "/marketplace", entrypoint: "./pages/marketplace.astro" },
78
+ {
79
+ pattern: "/api-explorer",
80
+ entrypoint: "./pages/api-explorer.astro",
81
+ },
82
+ { pattern: "/graphql", entrypoint: "./pages/graphql.astro" },
83
+ {
84
+ pattern: "/rest-playground",
85
+ entrypoint: "./pages/rest-playground.astro",
86
+ },
87
+ {
88
+ pattern: "/[collection]",
89
+ entrypoint: "./pages/[collection]/index.astro",
90
+ },
91
+ {
92
+ pattern: "/[collection]/[id]",
93
+ entrypoint: "./pages/[collection]/[id].astro",
94
+ },
95
+ ];
96
+
97
+ for (const page of pages) {
98
+ const pattern = `${basePath}${page.pattern}`.replace(/\/$/, "");
99
+ injectRoute({
100
+ pattern: pattern || "/",
101
+ entrypoint: path.resolve(
102
+ new URL(".", import.meta.url).pathname,
103
+ page.entrypoint,
104
+ ),
105
+ });
31
106
  }
32
107
 
33
108
  updateConfig({
@@ -38,8 +113,8 @@ export function kyroAdmin(options: KyroAdminOptions = {}): AstroIntegration {
38
113
  },
39
114
  },
40
115
  define: {
41
- __KYRO_ADMIN_BASE_PATH__: JSON.stringify(basePath),
42
- __KYRO_ADMIN_AUTH_DISABLED__: JSON.stringify(disableAuth),
116
+ __KYRO_ADMIN_PATH__: JSON.stringify(basePath),
117
+ __KYRO_API_PATH__: JSON.stringify(apiPath),
43
118
  },
44
119
  },
45
120
  });
@@ -0,0 +1,209 @@
1
+ declare module '@kyro-cms/core' {
2
+ export interface CollectionConfig {
3
+ slug: string;
4
+ label?: string;
5
+ fields: FieldConfig[];
6
+ admin?: {
7
+ layout?: 'single' | 'split';
8
+ autoGenerate?: string;
9
+ defaultColumns?: string[];
10
+ description?: string;
11
+ useAsTitle?: string;
12
+ [key: string]: unknown;
13
+ };
14
+ timestamps?: boolean;
15
+ singularLabel?: string;
16
+ [key: string]: unknown;
17
+ }
18
+
19
+ export interface GlobalConfig {
20
+ slug: string;
21
+ label?: string;
22
+ fields: FieldConfig[];
23
+ [key: string]: unknown;
24
+ }
25
+
26
+ export interface FieldConfig {
27
+ name: string;
28
+ label?: string;
29
+ type: string;
30
+ required?: boolean;
31
+ admin?: {
32
+ hidden?: boolean;
33
+ layout?: 'single' | 'split';
34
+ autoGenerate?: string;
35
+ [key: string]: unknown;
36
+ };
37
+ tabs?: Array<{ label?: string; fields?: FieldConfig[]; [key: string]: unknown }>;
38
+ fields?: FieldConfig[];
39
+ options?: Array<{ label: string; value: string | number }>;
40
+ blocks?: Block[];
41
+ relationTo?: string | string[];
42
+ hasMany?: boolean;
43
+ condition?: unknown;
44
+ rowWidth?: string;
45
+ width?: string;
46
+ [key: string]: unknown;
47
+ }
48
+
49
+ export type FieldType = string;
50
+ export const ALL_FIELD_TYPES: string[];
51
+ export type ALL_FIELD_TYPES = string[];
52
+
53
+ export interface TextField extends FieldConfig { type: 'text' }
54
+ export interface NumberField extends FieldConfig { type: 'number' }
55
+ export interface CheckboxField extends FieldConfig { type: 'checkbox' }
56
+ export interface DateField extends FieldConfig { type: 'date' }
57
+ export interface SelectField extends FieldConfig { type: 'select'; options?: { label: string; value: string | number }[] }
58
+ export interface TextareaField extends FieldConfig { type: 'textarea' }
59
+ export interface MarkdownField extends FieldConfig { type: 'markdown' }
60
+ export interface RichTextField extends FieldConfig { type: 'richText' }
61
+ export interface CodeField extends FieldConfig { type: 'code'; language?: string }
62
+ export interface JSONField extends FieldConfig { type: 'json' }
63
+ export interface ImageField extends FieldConfig { type: 'image' }
64
+ export interface UploadField extends FieldConfig { type: 'upload' }
65
+ export interface RelationshipField extends FieldConfig { type: 'relationship'; relationTo: string }
66
+ export interface BlocksField extends FieldConfig { type: 'blocks'; blocks: Block[] }
67
+ export interface ArrayField extends FieldConfig { type: 'array'; fields: FieldConfig[] }
68
+ export interface GroupField extends FieldConfig { type: 'group'; fields: FieldConfig[] }
69
+
70
+ export type Field = FieldConfig;
71
+
72
+ export interface Block {
73
+ slug: string;
74
+ label?: string;
75
+ fields: FieldConfig[];
76
+ [key: string]: unknown;
77
+ }
78
+
79
+ export interface RichTextBlock extends Block {}
80
+
81
+ export interface KyroConfig {
82
+ collections?: CollectionConfig[] | Record<string, CollectionConfig>;
83
+ globals?: GlobalConfig[] | Record<string, GlobalConfig>;
84
+ [key: string]: unknown;
85
+ }
86
+
87
+ export interface Permissions {
88
+ collections?: {
89
+ [key: string]: {
90
+ read?: boolean;
91
+ create?: boolean;
92
+ update?: boolean;
93
+ delete?: boolean;
94
+ [key: string]: unknown;
95
+ };
96
+ };
97
+ globals?: {
98
+ [key: string]: {
99
+ read?: boolean;
100
+ update?: boolean;
101
+ [key: string]: unknown;
102
+ };
103
+ };
104
+ media?: {
105
+ read?: boolean;
106
+ create?: boolean;
107
+ update?: boolean;
108
+ delete?: boolean;
109
+ };
110
+ users?: {
111
+ read?: boolean;
112
+ create?: boolean;
113
+ update?: boolean;
114
+ delete?: boolean;
115
+ };
116
+ [key: string]: unknown;
117
+ }
118
+
119
+ export interface FilterConfig {
120
+ field: string;
121
+ operator: string;
122
+ value: string;
123
+ }
124
+
125
+ export interface SortConfig {
126
+ field: string;
127
+ direction: 'asc' | 'desc';
128
+ }
129
+
130
+ export interface PaginationConfig {
131
+ page: number;
132
+ limit: number;
133
+ total: number;
134
+ }
135
+
136
+ export interface ColumnConfig {
137
+ name: string;
138
+ label: string;
139
+ sortable?: boolean;
140
+ width?: string;
141
+ }
142
+
143
+ export interface BlockData {
144
+ id: string;
145
+ type: string;
146
+ data?: Record<string, unknown>;
147
+ children?: BlockData[];
148
+ order?: number;
149
+ [key: string]: unknown;
150
+ }
151
+
152
+ export interface ApiListResponse {
153
+ docs?: Record<string, unknown>[];
154
+ totalDocs?: number;
155
+ [key: string]: unknown;
156
+ }
157
+
158
+ export interface ApiDocResponse {
159
+ data?: Record<string, unknown>;
160
+ status?: string;
161
+ createdAt?: string;
162
+ updatedAt?: string;
163
+ publishedAt?: string;
164
+ [key: string]: unknown;
165
+ }
166
+
167
+ export interface Version {
168
+ id: string;
169
+ status?: string;
170
+ changeDescription?: string;
171
+ createdAt?: string;
172
+ updatedAt?: string;
173
+ createdBy?: string;
174
+ [key: string]: unknown;
175
+ }
176
+
177
+ export interface VersionDiff {
178
+ field?: string;
179
+ oldValue?: unknown;
180
+ newValue?: unknown;
181
+ [key: string]: unknown;
182
+ }
183
+ }
184
+
185
+ declare module '@kyro-cms/core/client' {
186
+ export * from '@kyro-cms/core';
187
+ }
188
+
189
+ // Ambient module for template collections (no type declarations in the package)
190
+ declare module '@kyro-cms/core/templates' {
191
+ import type { CollectionConfig, GlobalConfig } from '@kyro-cms/core';
192
+
193
+ export const blogCollections: Record<string, CollectionConfig>;
194
+ export const ecommerceCollections: Record<string, CollectionConfig>;
195
+ export const minimalCollections: Record<string, CollectionConfig>;
196
+ export const kitchenSinkCollections: Record<string, CollectionConfig>;
197
+ export const mediaCollections: Record<string, CollectionConfig>;
198
+ export const authCollections: Record<string, CollectionConfig>;
199
+ export const allSettingsGlobals: GlobalConfig[];
200
+ export const coreSettingsGlobals: GlobalConfig[];
201
+ export const ecommerceSettingsGlobals: GlobalConfig[];
202
+ }
203
+
204
+ // Ambient module for the Astro virtual import
205
+ declare module 'kyro:config' {
206
+ import type { KyroConfig } from '@kyro-cms/core';
207
+ const config: KyroConfig;
208
+ export default config;
209
+ }
@@ -1,15 +1,22 @@
1
1
  ---
2
2
  import "../styles/main.css";
3
- import { nonAuthCollections, collections, globals } from "@/lib/config";
4
- import { CommandPaletteWrapper } from "@/components/ui/CommandPaletteWrapper";
3
+ import { nonAuthCollections, collections, globals } from "../lib/config";
4
+ import { CommandPaletteWrapper } from "../components/ui/CommandPaletteWrapper";
5
5
  import Sidebar from "../components/Sidebar.astro";
6
+ import { adminPath, apiPath } from "../lib/paths";
7
+ import { AuthBridge } from "../components/AuthBridge";
8
+ import { GlobalModal } from "../components/ui/GlobalModal";
9
+ import { Toaster } from "../components/ui/Toaster";
10
+ import { getSiteSettings } from "../lib/globals";
6
11
 
7
12
  interface Props {
8
13
  title: string;
9
14
  }
10
15
 
11
16
  const { title } = Astro.props;
12
- const user = Astro.locals.user;
17
+ const siteSettings = await getSiteSettings();
18
+ const siteName = siteSettings?.siteName || "Kyro CMS";
19
+ const siteFavicon = siteSettings?.siteFavicon;
13
20
  ---
14
21
 
15
22
  <!doctype html>
@@ -17,9 +24,100 @@ const user = Astro.locals.user;
17
24
  <head>
18
25
  <meta charset="UTF-8" />
19
26
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
20
- <title>{title} - Kyro CMS</title>
21
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
22
- <script is:inline>
27
+ <title>{title} - {siteName}</title>
28
+ <link rel="icon" type={siteFavicon?.mimeType || "image/svg+xml"} href={siteFavicon?.url || "/favicon.svg"} />
29
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
30
+ <link
31
+ rel="preconnect"
32
+ href="https://fonts.gstatic.com"
33
+ crossorigin
34
+ />
35
+ <link
36
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap"
37
+ rel="stylesheet"
38
+ />
39
+ <script is:inline define:vars={{ adminPath, apiPath }}>
40
+ // Simple in-memory auth state (alternative to Zustand for SSR)
41
+ window.__kyroAuth = window.__kyroAuth || { user: null, permissions: null, verified: false };
42
+
43
+ // Verify auth - redirect to login if not authenticated
44
+ (async () => {
45
+ try {
46
+ // Fetch user and permissions using cookies
47
+ const [meRes, accessRes] = await Promise.all([
48
+ fetch(apiPath + '/auth/me', { credentials: 'include' }),
49
+ fetch(apiPath + '/auth/access', { credentials: 'include' })
50
+ ]);
51
+
52
+ if (!meRes.ok) {
53
+ window.location.href = adminPath + "/login";
54
+ return;
55
+ }
56
+
57
+ const meData = await meRes.json();
58
+ if (!meData.user) {
59
+ console.log('[AdminLayout] No user in data, redirecting to login');
60
+ window.location.href = adminPath + "/login";
61
+ return;
62
+ }
63
+
64
+ const permissions = accessRes.ok ? await accessRes.json() : null;
65
+
66
+ // Store user in memory (not localStorage)
67
+ window.__kyroAuth.user = meData.user;
68
+ window.__kyroAuth.permissions = permissions;
69
+ window.__kyroAuth.verified = true;
70
+
71
+ // Dispatch event for components to update
72
+ window.dispatchEvent(new CustomEvent('kyro:auth-ready', {
73
+ detail: {
74
+ user: meData.user,
75
+ permissions
76
+ }
77
+ }));
78
+
79
+ // Navigation Guard - Check if user has read access to current page
80
+ if (permissions) {
81
+ const path = window.location.pathname;
82
+ const relativePath = path.replace(adminPath, '');
83
+
84
+ // Extract slug and type
85
+ let slug = '';
86
+ let type = '';
87
+
88
+ if (relativePath.startsWith('/users')) { slug = 'users'; type = 'collection'; }
89
+ else if (relativePath.startsWith('/audit')) { slug = 'audit_logs'; type = 'collection'; }
90
+ else if (relativePath.startsWith('/media')) { slug = 'media'; type = 'collection'; }
91
+ else if (relativePath.startsWith('/settings/')) { slug = relativePath.split('/')[2]; type = 'global'; }
92
+ else if (relativePath.includes('/') && !relativePath.startsWith('/login') && !relativePath.startsWith('/403')) {
93
+ // Dynamic collections: /[adminPath]/[collectionSlug]/...
94
+ slug = relativePath.split('/')[1];
95
+ type = 'collection';
96
+ }
97
+
98
+ if (slug && type) {
99
+ let hasAccess = true;
100
+ if (type === 'collection' && permissions.collections) {
101
+ const p = permissions.collections[slug];
102
+ if (p && p.read === false) hasAccess = false;
103
+ } else if (type === 'global' && permissions.globals) {
104
+ const p = permissions.globals[slug];
105
+ if (p && p.read === false) hasAccess = false;
106
+ }
107
+
108
+ if (!hasAccess) {
109
+ console.log('[AdminLayout] Access denied for', slug, 'redirecting to 403');
110
+ window.location.href = adminPath + "/403";
111
+ }
112
+ }
113
+ }
114
+ } catch (err) {
115
+ console.error('[AdminLayout] Auth check error:', err);
116
+ window.location.href = adminPath + "/login";
117
+ }
118
+ })();
119
+
120
+ // Theme init (only theme persisted)
23
121
  (() => {
24
122
  const getTheme = () => {
25
123
  const stored = localStorage.getItem("theme");
@@ -38,8 +136,9 @@ const user = Astro.locals.user;
38
136
  </script>
39
137
  </head>
40
138
  <body class="bg-[var(--kyro-bg)] antialiased text-[var(--kyro-text-primary)]">
139
+ <div id="kyro-user-data" data-user=""></div>
41
140
  <div class="flex h-screen p-6 gap-6 overflow-hidden">
42
- <Sidebar title={title} user={user} />
141
+ <Sidebar title={title} />
43
142
 
44
143
  <!-- Main Content Column -->
45
144
  <main class="flex-1 flex flex-col gap-6 overflow-hidden">
@@ -89,6 +188,9 @@ const user = Astro.locals.user;
89
188
  </div>
90
189
  </div>
91
190
 
191
+ <!-- Auth Bridge: Hydrates Zustand useAuthStore with user + permissions -->
192
+ <AuthBridge client:load />
193
+
92
194
  <!-- Command Palette (React) -->
93
195
  <CommandPaletteWrapper
94
196
  client:only="react"
@@ -96,8 +198,14 @@ const user = Astro.locals.user;
96
198
  globals={globals}
97
199
  />
98
200
 
201
+ <!-- Global Modals (React) -->
202
+ <GlobalModal client:only="react" />
203
+
204
+ <!-- Toast Notifications (React) -->
205
+ <Toaster client:only="react" />
206
+
99
207
  <!-- Theme UI Logic -->
100
- <script is:inline>
208
+ <script is:inline define:vars={{ adminPath, apiPath }}>
101
209
  const lightBtn = document.getElementById("theme-light-btn");
102
210
  const darkBtn = document.getElementById("theme-dark-btn");
103
211
 
@@ -183,9 +291,18 @@ const user = Astro.locals.user;
183
291
 
184
292
  logoutBackdrop?.addEventListener("click", closeLogoutModal);
185
293
  logoutCancel?.addEventListener("click", closeLogoutModal);
186
- logoutConfirm?.addEventListener("click", () => {
187
- window.location.href = "/login";
188
- });
294
+ logoutConfirm?.addEventListener("click", async () => {
295
+ try {
296
+ await fetch(apiPath + '/auth/logout', {
297
+ method: 'POST',
298
+ credentials: 'include'
299
+ });
300
+ } finally {
301
+ // Clear auth state
302
+ window.__kyroAuth = { user: null, verified: false };
303
+ window.location.href = adminPath + "/login";
304
+ }
305
+ });
189
306
  </script>
190
307
  </body>
191
308
  </html>
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import "../styles/main.css";
3
+ import { adminPath, apiPath } from "../lib/paths";
3
4
 
4
5
  interface Props {
5
6
  title: string;
@@ -15,12 +16,24 @@ const { title } = Astro.props;
15
16
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
16
17
  <title>{title} - Kyro CMS</title>
17
18
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
18
- <script is:inline>
19
- (() => {
19
+ <script is:inline define:vars={{ adminPath, apiPath }}>
20
+ (async () => {
21
+ try {
22
+ const res = await fetch(apiPath + "/auth/me", { credentials: "include" });
23
+ if (res.ok) {
24
+ const data = await res.json();
25
+ if (data?.user) {
26
+ window.location.href = adminPath;
27
+ return;
28
+ }
29
+ }
30
+ } catch {}
20
31
  const getTheme = () => {
21
32
  const stored = localStorage.getItem("theme");
22
33
  if (stored) return stored;
23
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
34
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
35
+ ? "dark"
36
+ : "light";
24
37
  };
25
38
  const theme = getTheme();
26
39
  if (theme === "dark") {
@@ -37,10 +50,13 @@ const { title } = Astro.props;
37
50
  <!-- Logo -->
38
51
  <div class="w-full text-center mb-8">
39
52
  <a href="/" class="inline-block">
40
- <span class="text-4xl font-black tracking-tighter text-[var(--kyro-text-primary)]">KYRO.</span>
53
+ <span
54
+ class="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]"
55
+ >KYRO.</span
56
+ >
41
57
  </a>
42
58
  </div>
43
-
59
+
44
60
  <div class="w-full flex justify-center">
45
61
  <slot />
46
62
  </div>