@kyro-cms/admin 0.3.2 → 0.3.5

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
@@ -10,7 +10,7 @@ interface LocalToast {
10
10
  }
11
11
 
12
12
  interface LoginPageProps {
13
- onAuth: (token: string, user: any) => void;
13
+ onAuth: (token: string, user: Record<string, unknown>) => void;
14
14
  theme?: ThemeMode;
15
15
  }
16
16
 
@@ -31,7 +31,7 @@ export function LoginPage({ onAuth, theme = "light" }: LoginPageProps) {
31
31
 
32
32
  const checkIfFirstUser = async () => {
33
33
  try {
34
- await apiGet("/api/auth/users");
34
+ await apiGet("/api/users");
35
35
  } catch {
36
36
  setIsFirstUser(true);
37
37
  setMode("register");
@@ -58,13 +58,12 @@ export function LoginPage({ onAuth, theme = "light" }: LoginPageProps) {
58
58
  body.confirmPassword = confirmPassword;
59
59
  }
60
60
 
61
- const data = await apiPost(endpoint, body);
61
+ const data = await apiPost<any>(endpoint, body);
62
62
 
63
63
  if (data.isFirstUser) {
64
64
  setIsFirstUser(true);
65
65
  }
66
66
 
67
- localStorage.setItem("kyro_token", data.token);
68
67
  localStorage.setItem("kyro_user", JSON.stringify(data.user));
69
68
  addToast(
70
69
  "success",
@@ -0,0 +1,254 @@
1
+ import React, { useState } from "react";
2
+ import {
3
+ Search,
4
+ DownloadCloud,
5
+ Star,
6
+ CheckCircle2,
7
+ TrendingUp,
8
+ Zap,
9
+ X,
10
+ ExternalLink,
11
+ ChevronRight
12
+ } from "./ui/icons";
13
+ import { useUIStore, toast } from "../lib/stores";
14
+ import { Badge } from "./ui/Badge";
15
+
16
+ interface Extension {
17
+ id: string;
18
+ name: string;
19
+ description: string;
20
+ developer: string;
21
+ rating: number;
22
+ downloads: string;
23
+ price: string;
24
+ tags: string[];
25
+ installed: boolean;
26
+ featured?: boolean;
27
+ }
28
+
29
+ const mockExtensions: Extension[] = [
30
+ {
31
+ id: "ext-ecommerce",
32
+ name: "Commerce Suite",
33
+ description: "Full e-commerce with cart, checkout, Stripe, and inventory management.",
34
+ developer: "Kyro Official",
35
+ rating: 4.9,
36
+ downloads: "12k+",
37
+ price: "Free",
38
+ tags: ["E-commerce", "Stripe"],
39
+ installed: false,
40
+ featured: true,
41
+ },
42
+ {
43
+ id: "ext-seo",
44
+ name: "SEO Optimizer Pro",
45
+ description: "Meta tags, sitemaps, and rich snippets for better search visibility.",
46
+ developer: "Kyro Team",
47
+ rating: 4.8,
48
+ downloads: "45k+",
49
+ price: "Free",
50
+ tags: ["SEO", "Marketing"],
51
+ installed: true,
52
+ },
53
+ {
54
+ id: "ext-algolia",
55
+ name: "Algolia Search",
56
+ description: "Fast search with Algolia for your collections and site.",
57
+ developer: "SearchBots",
58
+ rating: 4.6,
59
+ downloads: "5k+",
60
+ price: "$19/mo",
61
+ tags: ["Search", "API"],
62
+ installed: false,
63
+ },
64
+ {
65
+ id: "ext-openai",
66
+ name: "AI Content Writer",
67
+ description: "Generate content with GPT models directly in the editor.",
68
+ developer: "AI Tools",
69
+ rating: 4.7,
70
+ downloads: "8k+",
71
+ price: "$9/mo",
72
+ tags: ["AI", "Content"],
73
+ installed: false,
74
+ featured: true,
75
+ },
76
+ {
77
+ id: "ext-backup",
78
+ name: "Auto Backup",
79
+ description: "Scheduled backups to S3, Dropbox, or Google Drive.",
80
+ developer: "SafeData",
81
+ rating: 4.9,
82
+ downloads: "22k+",
83
+ price: "Free",
84
+ tags: ["Utility", "Security"],
85
+ installed: false,
86
+ },
87
+ {
88
+ id: "ext-slack",
89
+ name: "Slack Notifications",
90
+ description: "Get notified in Slack when content changes.",
91
+ developer: "Kyro Team",
92
+ rating: 4.5,
93
+ downloads: "3k+",
94
+ price: "Free",
95
+ tags: ["Notifications"],
96
+ installed: false,
97
+ },
98
+ ];
99
+
100
+ export function MarketplaceManager() {
101
+ const [extensions, setExtensions] = useState<Extension[]>(mockExtensions);
102
+ const [searchQuery, setSearchQuery] = useState("");
103
+ const [activeCategory, setActiveCategory] = useState("All");
104
+ const { confirm, alert } = useUIStore();
105
+
106
+ const categories = ["All", "Official", "E-commerce", "SEO", "AI", "Utility"];
107
+
108
+ const handleInstallRequest = (ext: Extension) => {
109
+ confirm({
110
+ title: `Integrate ${ext.name}?`,
111
+ message: `Connect ${ext.name} to your dashboard? It will have scoped access to your data.`,
112
+ confirmLabel: "Connect Extension",
113
+ onConfirm: async () => {
114
+ await new Promise(resolve => setTimeout(resolve, 1500));
115
+ setExtensions((prev) =>
116
+ prev.map((e) => e.id === ext.id ? { ...e, installed: true } : e)
117
+ );
118
+ toast.success(`Extension initialized: ${ext.name}`);
119
+ }
120
+ });
121
+ };
122
+
123
+ const filteredExtensions = extensions.filter((ext) => {
124
+ const matchesSearch =
125
+ ext.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
126
+ ext.description.toLowerCase().includes(searchQuery.toLowerCase());
127
+ const matchesCategory =
128
+ activeCategory === "All" ||
129
+ ext.tags.includes(activeCategory) ||
130
+ (activeCategory === "Official" && ext.developer.includes("Kyro"));
131
+ return matchesSearch && matchesCategory;
132
+ });
133
+
134
+ return (
135
+ <div className="w-full space-y-6 animate-in fade-in duration-700">
136
+ {/* Header Panel */}
137
+ <div className="surface-tile p-5 flex flex-col md:flex-row md:items-center justify-between gap-6">
138
+ <div>
139
+ <div className="flex items-center gap-2 mb-1">
140
+ <div className="w-2 h-2 rounded-full bg-amber-500 shadow-[0_0_8px_rgba(245,158,11,0.5)]" />
141
+ <h1 className="text-lg font-bold tracking-tight text-[var(--kyro-text-primary)]">
142
+ Extension Marketplace
143
+ </h1>
144
+ </div>
145
+ <p className="text-[10px] font-bold text-[var(--kyro-text-secondary)] opacity-50 tracking-widest uppercase">
146
+ Expand your ecosystem with community extensions
147
+ </p>
148
+ </div>
149
+
150
+ <div className="flex items-center gap-3">
151
+ <div className="relative group">
152
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-[var(--kyro-text-secondary)] opacity-40 group-focus-within:opacity-100 transition-opacity" />
153
+ <input
154
+ type="text"
155
+ placeholder="Search marketplace..."
156
+ value={searchQuery}
157
+ onChange={(e) => setSearchQuery(e.target.value)}
158
+ className="pl-9 pr-4 py-2 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-xl text-xs font-bold w-64 focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] transition-all"
159
+ />
160
+ </div>
161
+ </div>
162
+ </div>
163
+
164
+ {/* Categories */}
165
+ <div className="flex items-center gap-1.5 overflow-x-auto pb-1 no-scrollbar">
166
+ {categories.map((category) => (
167
+ <button
168
+ key={category}
169
+ onClick={() => setActiveCategory(category)}
170
+ className={`px-4 py-1.5 rounded-xl text-[10px] font-bold tracking-widest uppercase transition-all border ${activeCategory === category
171
+ ? "bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] border-[var(--kyro-sidebar-active)] shadow-lg"
172
+ : "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] border-[var(--kyro-border)] hover:bg-[var(--kyro-surface)]"
173
+ }`}
174
+ >
175
+ {category}
176
+ </button>
177
+ ))}
178
+ </div>
179
+
180
+ {/* Extension Grid */}
181
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
182
+ {filteredExtensions.length > 0 ? (
183
+ filteredExtensions.map((ext) => (
184
+ <div
185
+ key={ext.id}
186
+ className="surface-tile p-5 group hover:shadow-xl transition-all duration-300 relative flex flex-col"
187
+ >
188
+ <div className="flex items-start justify-between mb-4">
189
+ <div className="w-10 h-10 rounded-xl bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] flex items-center justify-center text-[var(--kyro-text-primary)] group-hover:scale-105 transition-transform">
190
+ <DownloadCloud className="w-5 h-5 opacity-70" />
191
+ </div>
192
+ {ext.featured && (
193
+ <Badge variant="warning" className="text-[8px] font-bold tracking-widest uppercase">
194
+ FEATURED
195
+ </Badge>
196
+ )}
197
+ </div>
198
+
199
+ <div className="mb-4">
200
+ <h3 className="text-xs font-bold text-[var(--kyro-text-primary)] mb-1 group-hover:text-[var(--kyro-primary)] transition-colors">
201
+ {ext.name}
202
+ </h3>
203
+ <p className="text-[9px] font-bold text-[var(--kyro-text-secondary)] opacity-50 tracking-widest uppercase mb-3">
204
+ BY {ext.developer}
205
+ </p>
206
+ <p className="text-[11px] text-[var(--kyro-text-secondary)] line-clamp-2 leading-relaxed">
207
+ {ext.description}
208
+ </p>
209
+ </div>
210
+
211
+ <div className="flex items-center gap-4 mb-5">
212
+ <div className="flex items-center gap-1">
213
+ <Star className="w-3 h-3 text-amber-500 fill-amber-500" />
214
+ <span className="text-[10px] font-bold text-[var(--kyro-text-primary)]">{ext.rating}</span>
215
+ </div>
216
+ <div className="flex items-center gap-1 text-[var(--kyro-text-secondary)] opacity-50">
217
+ <TrendingUp className="w-3 h-3" />
218
+ <span className="text-[10px] font-bold">{ext.downloads}</span>
219
+ </div>
220
+ </div>
221
+
222
+ <div className="mt-auto pt-4 border-t border-[var(--kyro-border)] flex items-center justify-between">
223
+ <span className="text-[10px] font-bold text-[var(--kyro-text-primary)] uppercase tracking-widest">
224
+ {ext.price}
225
+ </span>
226
+ {ext.installed ? (
227
+ <div className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-green-500/10 text-green-500 text-[9px] font-bold tracking-widest uppercase">
228
+ <CheckCircle2 className="w-3 h-3" />
229
+ Active
230
+ </div>
231
+ ) : (
232
+ <button
233
+ type="button"
234
+ onClick={() => handleInstallRequest(ext)}
235
+ className="flex items-center gap-1.5 text-[10px] font-bold text-[var(--kyro-primary)] hover:translate-x-1 transition-transform"
236
+ >
237
+ Install <ChevronRight className="w-3 h-3" />
238
+ </button>
239
+ )}
240
+ </div>
241
+ </div>
242
+ ))
243
+ ) : (
244
+ <div className="col-span-full py-20 text-center surface-tile">
245
+ <Search className="w-10 h-10 mx-auto mb-4 text-[var(--kyro-text-secondary)] opacity-20" />
246
+ <p className="text-xs font-bold text-[var(--kyro-text-secondary)] opacity-50 tracking-widest uppercase">
247
+ No results match your search
248
+ </p>
249
+ </div>
250
+ )}
251
+ </div>
252
+ </div>
253
+ );
254
+ }