@kyro-cms/admin 0.3.1 → 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
@@ -0,0 +1,112 @@
1
+ // Re-exported from react-icons/lu (Lucide icons via React Icons, ISC license)
2
+ // Allows mixing packs or swapping individual icons without touching consumers.
3
+
4
+ export { LuActivity as IconActivity } from "react-icons/lu";
5
+ export { LuTriangleAlert as IconAlertTriangle } from "react-icons/lu";
6
+ export { LuAlignLeft as IconAlignLeft } from "react-icons/lu";
7
+ export { LuArchive as IconArchive } from "react-icons/lu";
8
+ export { LuArrowDown as IconArrowDown } from "react-icons/lu";
9
+ export { LuArrowRight as IconArrowRight } from "react-icons/lu";
10
+ export { LuArrowUpRight as IconArrowUpRight } from "react-icons/lu";
11
+ export { LuBlocks as IconBlocks } from "react-icons/lu";
12
+ export { LuBold as IconBold } from "react-icons/lu";
13
+ export { LuBook as IconBook } from "react-icons/lu";
14
+ export { LuBox as IconBox } from "react-icons/lu";
15
+ export { LuCircleCheck as IconCheckCircle2 } from "react-icons/lu";
16
+ export { LuCheck as IconCheck } from "react-icons/lu";
17
+ export { LuChevronDown as IconChevronDown } from "react-icons/lu";
18
+ export { LuChevronLeft as IconChevronLeft } from "react-icons/lu";
19
+ export { LuChevronRight as IconChevronRight } from "react-icons/lu";
20
+ export { LuChevronUp as IconChevronUp } from "react-icons/lu";
21
+ export { LuClock as IconClock } from "react-icons/lu";
22
+ export { LuCode as IconCode } from "react-icons/lu";
23
+ export { LuCodeXml as IconCode2 } from "react-icons/lu";
24
+ export { LuColumns3 as IconColumns3 } from "react-icons/lu";
25
+ export { LuCopy as IconCopy } from "react-icons/lu";
26
+ export { LuCrop as IconCrop } from "react-icons/lu";
27
+ export { LuDatabase as IconDatabase } from "react-icons/lu";
28
+ export { LuDownload as IconDownload } from "react-icons/lu";
29
+ export { LuCloudDownload as IconDownloadCloud } from "react-icons/lu";
30
+ export { LuExternalLink as IconExternalLink } from "react-icons/lu";
31
+ export { LuEye as IconEye } from "react-icons/lu";
32
+ export { LuEyeOff as IconEyeOff } from "react-icons/lu";
33
+ export { LuFile as IconFile } from "react-icons/lu";
34
+ export { LuGlobe as IconGlobe } from "react-icons/lu";
35
+ export { LuFilm as IconFilm } from "react-icons/lu";
36
+ export { LuFilter as IconFilter } from "react-icons/lu";
37
+ export { LuFolder as IconFolder } from "react-icons/lu";
38
+ export { LuFolderInput as IconFolderInput } from "react-icons/lu";
39
+ export { LuFolderPlus as IconFolderPlus } from "react-icons/lu";
40
+ export { LuGrid3X3 as IconGrid } from "react-icons/lu";
41
+ export { LuGripVertical as IconGripVertical } from "react-icons/lu";
42
+ export { LuHeading1 as IconHeading1 } from "react-icons/lu";
43
+ export { LuHexagon as IconHexagon } from "react-icons/lu";
44
+ export { LuHouse as IconHome } from "react-icons/lu";
45
+ export { LuImage as IconImage } from "react-icons/lu";
46
+ export { LuInfo as IconInfo } from "react-icons/lu";
47
+ export { LuItalic as IconItalic } from "react-icons/lu";
48
+ export { LuKey as IconKey } from "react-icons/lu";
49
+ export { LuLayoutPanelTop as IconLayout } from "react-icons/lu";
50
+ export { LuLayoutDashboard as IconLayoutDashboard } from "react-icons/lu";
51
+ export { LuLink as IconLink } from "react-icons/lu";
52
+ export { LuLink2 as IconLink2 } from "react-icons/lu";
53
+ export { LuList as IconList } from "react-icons/lu";
54
+ export { LuListOrdered as IconListOrdered } from "react-icons/lu";
55
+ export { LuLoaderCircle as IconLoader2 } from "react-icons/lu";
56
+ export { LuLock as IconLock } from "react-icons/lu";
57
+ export { LuLogOut as IconLogOut } from "react-icons/lu";
58
+ export { LuMail as IconMail } from "react-icons/lu";
59
+ export { LuMaximize2 as IconMaximize2 } from "react-icons/lu";
60
+ export { LuMenu as IconMenu } from "react-icons/lu";
61
+ export { LuMinus as IconMinus } from "react-icons/lu";
62
+ export { LuMonitor as IconMonitor } from "react-icons/lu";
63
+ export { LuMoon as IconMoon } from "react-icons/lu";
64
+ export { LuEllipsisVertical as IconMoreVertical } from "react-icons/lu";
65
+ export { LuMousePointerClick as IconMousePointerClick } from "react-icons/lu";
66
+ export { LuMusic as IconMusic } from "react-icons/lu";
67
+ export { LuNetwork as IconNetwork } from "react-icons/lu";
68
+ export { LuPalette as IconPalette } from "react-icons/lu";
69
+ export { LuPause as IconPause } from "react-icons/lu";
70
+ export { LuPencil as IconPencil } from "react-icons/lu";
71
+ export { LuPlay as IconPlay } from "react-icons/lu";
72
+ export { LuCirclePlay as IconPlayCircle } from "react-icons/lu";
73
+ export { LuPlus as IconPlus } from "react-icons/lu";
74
+ export { LuRedo as IconRedo } from "react-icons/lu";
75
+ export { LuRefreshCcw as IconRefreshCcw } from "react-icons/lu";
76
+ export { LuRefreshCw as IconRefreshCw } from "react-icons/lu";
77
+ export { LuSave as IconSave } from "react-icons/lu";
78
+ export { LuSearch as IconSearch } from "react-icons/lu";
79
+ export { LuSend as IconSend } from "react-icons/lu";
80
+ export { LuSettings as IconSettings } from "react-icons/lu";
81
+ export { LuShield as IconShield } from "react-icons/lu";
82
+ export { LuShieldCheck as IconShieldCheck } from "react-icons/lu";
83
+ export { LuSparkles as IconSparkles } from "react-icons/lu";
84
+ export { LuStar as IconStar } from "react-icons/lu";
85
+ export { LuStrikethrough as IconStrikethrough } from "react-icons/lu";
86
+ export { LuSun as IconSun } from "react-icons/lu";
87
+ export { LuTag as IconTag } from "react-icons/lu";
88
+ export { LuTerminal as IconTerminal } from "react-icons/lu";
89
+ export { LuToggleLeft as IconToggleLeft } from "react-icons/lu";
90
+ export { LuToggleRight as IconToggleRight } from "react-icons/lu";
91
+ export { LuTrash2 as IconTrash2 } from "react-icons/lu";
92
+ export { LuTrendingUp as IconTrendingUp } from "react-icons/lu";
93
+ export { LuType as IconType } from "react-icons/lu";
94
+ export { LuUnderline as IconUnderline } from "react-icons/lu";
95
+ export { LuUndo as IconUndo } from "react-icons/lu";
96
+ export { LuLockOpen as IconUnlock } from "react-icons/lu";
97
+ export { LuUser as IconUser } from "react-icons/lu";
98
+ export { LuUserPlus as IconUserPlus } from "react-icons/lu";
99
+ export { LuUsers as IconUsers } from "react-icons/lu";
100
+ export { LuVideo as IconVideo } from "react-icons/lu";
101
+ export { LuWebhook as IconWebhook } from "react-icons/lu";
102
+ export { LuX as IconX } from "react-icons/lu";
103
+ export { LuZap as IconZap } from "react-icons/lu";
104
+ export { LuDot as IconDot } from "react-icons/lu";
105
+ export { LuShieldAlert as IconShieldAlert } from "react-icons/lu";
106
+ export { LuPencil as IconEdit2 } from "react-icons/lu";
107
+ export { LuCalendar as IconCalendar } from "react-icons/lu";
108
+ export { LuGrid3X3 as IconGrid3X3 } from "react-icons/lu";
109
+
110
+ // Direct re-exports for files that still use original lucide-react names
111
+ export { LuActivity as Activity, LuAlignLeft as AlignLeft, LuArchive as Archive, LuArrowDown as ArrowDown, LuArrowRight as ArrowRight, LuArrowUpRight as ArrowUpRight, LuBlocks as Blocks, LuBox as Box, LuCalendar as Calendar, LuCheck as Check, LuChevronDown as ChevronDown, LuChevronLeft as ChevronLeft, LuChevronRight as ChevronRight, LuChevronUp as ChevronUp, LuClock as Clock, LuCode as Code, LuColumns3 as Columns3, LuCopy as Copy, LuCrop as Crop, LuDownload as Download, LuExternalLink as ExternalLink, LuEye as Eye, LuEyeOff as EyeOff, LuFile as File, LuFile as FileIcon, LuFileText as FileText, LuGlobe as Globe, LuFilm as Film, LuFilter as Filter, LuFolder as Folder, LuFolderInput as FolderInput, LuFolderPlus as FolderPlus, LuGripVertical as GripVertical, LuHeading1 as Heading1, LuImage as Image, LuInfo as Info, LuKey as Key, LuLayoutDashboard as LayoutDashboard, LuLink as Link, LuLink2 as Link2, LuList as List, LuListOrdered as ListOrdered, LuLock as Lock, LuMail as Mail, LuMaximize2 as Maximize2, LuMenu as Menu, LuMinus as Minus, LuMonitor as Monitor, LuMousePointerClick as MousePointerClick, LuMusic as Music, LuPalette as Palette, LuPause as Pause, LuPlay as Play, LuPlus as Plus, LuRefreshCcw as RefreshCcw, LuRefreshCw as RefreshCw, LuSave as Save, LuSearch as Search, LuSend as Send, LuSettings as Settings, LuShield as Shield, LuSparkles as Sparkles, LuStar as Star, LuTag as Tag, LuTerminal as Terminal, LuToggleLeft as ToggleLeft, LuToggleRight as ToggleRight, LuTrash2 as Trash2, LuTrendingUp as TrendingUp, LuType as Type, LuUser as User, LuUserPlus as UserPlus, LuUsers as Users, LuVideo as Video, LuWebhook as Webhook, LuX as X, LuZap as Zap, LuCircleHelp as HelpCircle } from "react-icons/lu";
112
+ export { LuCircleCheck as CheckCircle2, LuGrid3X3 as Grid, LuHouse as Home, LuLayoutPanelTop as Layout, LuLoaderCircle as Loader2, LuLockOpen as Unlock, LuCirclePlay as PlayCircle, LuTriangleAlert as AlertTriangle, LuCodeXml as Code2, LuCloudDownload as DownloadCloud, LuEllipsisVertical as MoreVertical, LuShieldCheck as ShieldCheck, LuShieldAlert as ShieldAlert, LuPencil as Edit2, LuMoon as Moon, LuSun as Sun, LuLogOut as LogOut, LuDatabase as Database, LuHexagon as Hexagon, LuNetwork as Network, LuBook as Book, LuBold as Bold, LuItalic as Italic, LuUnderline as Underline, LuStrikethrough as Strikethrough, LuUndo as Undo, LuRedo as Redo, LuDot as Dot, LuGrid3X3, LuLaptop as Laptop, LuSmartphone as Smartphone } from "react-icons/lu";
@@ -0,0 +1,290 @@
1
+ import React, { useState } from "react";
2
+ import { useUIStore } from "../../lib/stores";
3
+
4
+ interface User {
5
+ id: string;
6
+ name?: string;
7
+ email: string;
8
+ role: string;
9
+ tenantId?: string;
10
+ emailVerified?: boolean;
11
+ locked?: boolean;
12
+ lastLogin?: string;
13
+ createdAt?: string;
14
+ updatedAt?: string;
15
+ failedLoginAttempts?: number;
16
+ }
17
+
18
+ interface UserDetailProps {
19
+ user: User;
20
+ apiPath: string;
21
+ adminPath: string;
22
+ }
23
+
24
+ const roleOptions = [
25
+ "super_admin",
26
+ "admin",
27
+ "editor",
28
+ "author",
29
+ "customer",
30
+ "guest",
31
+ ];
32
+
33
+ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
34
+ const [name, setName] = useState(user.name || "");
35
+ const [role, setRole] = useState(user.role);
36
+ const [saving, setSaving] = useState(false);
37
+ const { confirm, alert } = useUIStore();
38
+ const [deleting, setDeleting] = useState(false);
39
+ const [locking, setLocking] = useState(false);
40
+ const [isLocked, setIsLocked] = useState(user.locked || false);
41
+
42
+ const handleSave = async () => {
43
+ setSaving(true);
44
+ try {
45
+ const body: Record<string, unknown> = {};
46
+
47
+ if (role !== user.role) {
48
+ body.role = role;
49
+ }
50
+ if (name !== user.name) {
51
+ body.name = name.trim() === "" ? null : name.trim();
52
+ }
53
+
54
+ if (Object.keys(body).length === 0) {
55
+ window.location.href = adminPath + "/users";
56
+ return;
57
+ }
58
+
59
+ const res = await fetch(`${apiPath}/users/${user.id}`, {
60
+ method: "PATCH",
61
+ credentials: "include",
62
+ headers: { "Content-Type": "application/json" },
63
+ body: JSON.stringify(body),
64
+ });
65
+
66
+ const data = await res.json();
67
+
68
+ if (res.ok) {
69
+ window.location.href = adminPath + "/users";
70
+ } else {
71
+ console.error("Save failed:", data.error);
72
+ }
73
+ } catch (e) {
74
+ console.error("Failed to save user:", e);
75
+ } finally {
76
+ setSaving(false);
77
+ }
78
+ };
79
+
80
+ const handleLockToggle = () => {
81
+ confirm({
82
+ title: isLocked ? "Unlock User" : "Lock User",
83
+ message: isLocked
84
+ ? `Unlock ${user.email}? They will be able to log in again.`
85
+ : `Lock ${user.email}? They will not be able to log in.`,
86
+ variant: isLocked ? "default" : "danger",
87
+ confirmLabel: isLocked ? "Unlock" : "Lock",
88
+ onConfirm: async () => {
89
+ setLocking(true);
90
+ try {
91
+ const res = await fetch(`${apiPath}/users/${user.id}`, {
92
+ method: "PATCH",
93
+ credentials: "include",
94
+ headers: { "Content-Type": "application/json" },
95
+ body: JSON.stringify({ locked: !isLocked }),
96
+ });
97
+ if (res.ok) {
98
+ setIsLocked(!isLocked);
99
+ }
100
+ } catch (e) {
101
+ console.error("Failed to toggle lock:", e);
102
+ } finally {
103
+ setLocking(false);
104
+ }
105
+ }
106
+ });
107
+ };
108
+
109
+ const handleDelete = () => {
110
+ confirm({
111
+ title: "Delete User",
112
+ message: `Are you sure you want to delete ${user.email}? This action cannot be undone.`,
113
+ variant: "danger",
114
+ onConfirm: async () => {
115
+ setDeleting(true);
116
+ try {
117
+ const res = await fetch(`${apiPath}/users/${user.id}`, {
118
+ method: "DELETE",
119
+ credentials: "include",
120
+ });
121
+ if (res.ok) {
122
+ window.location.href = adminPath + "/users";
123
+ }
124
+ } catch (e) {
125
+ console.error("Failed to delete user:", e);
126
+ } finally {
127
+ setDeleting(false);
128
+ }
129
+ }
130
+ });
131
+ };
132
+
133
+ const formatDate = (dateStr?: string) => {
134
+ if (!dateStr) return "—";
135
+ return new Date(dateStr).toLocaleString();
136
+ };
137
+
138
+ return (
139
+ <div className="flex-1 overflow-y-auto pr-12 space-y-8">
140
+ <div className="surface-tile p-6 flex items-center justify-between">
141
+ <div className="flex items-center gap-4">
142
+ <div className="w-14 h-14 rounded-full bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] flex items-center justify-center font-bold text-xl">
143
+ {(name || user.email).charAt(0).toUpperCase()}
144
+ </div>
145
+ <div>
146
+ <h1 className="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]">
147
+ {name || user.email}
148
+ </h1>
149
+ <p className="text-sm text-[var(--kyro-text-secondary)] font-medium">
150
+ {name ? user.email : `User ID: ${user.id}`}
151
+ </p>
152
+ </div>
153
+ </div>
154
+ <div className="flex gap-2">
155
+ <button
156
+ onClick={handleLockToggle}
157
+ className="px-4 py-2 border border-[var(--kyro-border)] rounded-xl text-sm font-bold text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors"
158
+ >
159
+ {isLocked ? "Unlock User" : "Lock User"}
160
+ </button>
161
+ <button
162
+ onClick={handleDelete}
163
+ className="px-4 py-2 border border-red-200 rounded-xl text-sm font-bold text-red-600 hover:bg-red-50 transition-colors"
164
+ >
165
+ Delete
166
+ </button>
167
+ </div>
168
+ </div>
169
+
170
+ <div className="surface-tile p-6">
171
+ <h2 className="text-lg font-bold text-[var(--kyro-text-primary)] tracking-tighter mb-6">
172
+ Details
173
+ </h2>
174
+ <div className="grid grid-cols-2 gap-6">
175
+ <div>
176
+ <label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
177
+ Name
178
+ </label>
179
+ <input
180
+ type="text"
181
+ value={name}
182
+ onChange={(e) => setName(e.target.value)}
183
+ className="mt-1 w-full px-3 py-2 border border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)] rounded-lg text-sm font-medium text-[var(--kyro-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)]"
184
+ placeholder="Enter name"
185
+ />
186
+ </div>
187
+ <div>
188
+ <label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
189
+ Email
190
+ </label>
191
+ <p className="mt-1 font-medium text-[var(--kyro-text-primary)]">
192
+ {user.email}
193
+ </p>
194
+ </div>
195
+ <div>
196
+ <label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
197
+ Role
198
+ </label>
199
+ <select
200
+ value={role}
201
+ onChange={(e) => setRole(e.target.value)}
202
+ className="mt-1 w-full px-3 py-2 border border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)] rounded-lg text-sm font-medium text-[var(--kyro-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)]"
203
+ >
204
+ {roleOptions.map((r) => (
205
+ <option key={r} value={r}>
206
+ {r}
207
+ </option>
208
+ ))}
209
+ </select>
210
+ </div>
211
+ <div>
212
+ <label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
213
+ Email Verified
214
+ </label>
215
+ <p className="mt-1">
216
+ <span
217
+ className={`inline-flex items-center px-3 py-1 rounded-lg text-xs font-bold ${user.emailVerified
218
+ ? "bg-green-500/10 text-green-500"
219
+ : "bg-yellow-500/10 text-yellow-500"
220
+ }`}
221
+ >
222
+ {user.emailVerified ? "Verified" : "Not verified"}
223
+ </span>
224
+ </p>
225
+ </div>
226
+ <div>
227
+ <label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
228
+ Status
229
+ </label>
230
+ <p className="mt-1">
231
+ <span
232
+ className={`inline-flex items-center px-3 py-1 rounded-lg text-xs font-bold ${isLocked
233
+ ? "bg-red-500/10 text-red-500"
234
+ : "bg-green-500/10 text-green-500"
235
+ }`}
236
+ >
237
+ {isLocked ? "Locked" : "Active"}
238
+ </span>
239
+ </p>
240
+ </div>
241
+ <div>
242
+ <label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
243
+ Last Login
244
+ </label>
245
+ <p className="mt-1 text-sm text-[var(--kyro-text-secondary)]">
246
+ {user.lastLogin
247
+ ? new Date(user.lastLogin).toLocaleString()
248
+ : "Never"}
249
+ </p>
250
+ </div>
251
+ <div>
252
+ <label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
253
+ Failed Attempts
254
+ </label>
255
+ <p className="mt-1 text-sm font-medium text-[var(--kyro-text-primary)]">
256
+ {user.failedLoginAttempts || 0}
257
+ </p>
258
+ </div>
259
+ <div>
260
+ <label className="text-xs font-bold text-[#64748b] tracking-wider">
261
+ Created
262
+ </label>
263
+ <p className="mt-1 text-sm text-[#64748b]">
264
+ {formatDate(user.createdAt)}
265
+ </p>
266
+ </div>
267
+ <div>
268
+ <label className="text-xs font-bold text-[#64748b] tracking-wider">
269
+ Updated
270
+ </label>
271
+ <p className="mt-1 text-sm text-[#64748b]">
272
+ {formatDate(user.updatedAt)}
273
+ </p>
274
+ </div>
275
+ </div>
276
+
277
+ <div className="mt-6 flex justify-end">
278
+ <button
279
+ onClick={handleSave}
280
+ disabled={saving}
281
+ className="px-6 py-2 bg-[#0b1222] text-white rounded-xl text-sm font-bold hover:bg-[#1a2332] transition-colors disabled:opacity-50"
282
+ >
283
+ {saving ? "Saving…" : "Save Changes"}
284
+ </button>
285
+ </div>
286
+ </div>
287
+
288
+ </div>
289
+ );
290
+ }
@@ -0,0 +1,242 @@
1
+ import React, { useState } from "react";
2
+
3
+ interface UserFormProps {
4
+ mode: "create" | "edit";
5
+ apiPath: string;
6
+ adminPath: string;
7
+ user?: {
8
+ id: string;
9
+ name?: string;
10
+ email: string;
11
+ role: string;
12
+ tenantId?: string;
13
+ emailVerified?: boolean;
14
+ locked?: boolean;
15
+ };
16
+ }
17
+
18
+ const roleOptions = [
19
+ "super_admin",
20
+ "admin",
21
+ "editor",
22
+ "author",
23
+ "customer",
24
+ "guest",
25
+ ];
26
+
27
+ export function UserForm({ mode, apiPath, adminPath, user }: UserFormProps) {
28
+ const [email, setEmail] = useState(user?.email || "");
29
+ const [name, setName] = useState(user?.name || "");
30
+ const [password, setPassword] = useState("");
31
+ const [role, setRole] = useState(user?.role || "customer");
32
+ const [tenantId, setTenantId] = useState(user?.tenantId || "");
33
+ const [loading, setLoading] = useState(false);
34
+ const [message, setMessage] = useState<{
35
+ text: string;
36
+ type: "success" | "error";
37
+ } | null>(null);
38
+
39
+ const handleSubmit = async (e: React.FormEvent) => {
40
+ e.preventDefault();
41
+ setLoading(true);
42
+ setMessage(null);
43
+
44
+ const body: Record<string, string> = { email, role };
45
+ if (name.trim()) body.name = name.trim();
46
+ if (mode === "create") body.password = password;
47
+ if (tenantId.trim()) body.tenantId = tenantId.trim();
48
+
49
+ try {
50
+ const url =
51
+ mode === "create" ? `${apiPath}/users` : `${apiPath}/users/${user!.id}`;
52
+ const method = mode === "create" ? "POST" : "PATCH";
53
+
54
+ const res = await fetch(url, {
55
+ method,
56
+ credentials: "include",
57
+ headers: { "Content-Type": "application/json" },
58
+ body: JSON.stringify(body),
59
+ });
60
+
61
+ const data = await res.json();
62
+
63
+ if (res.ok) {
64
+ setMessage({
65
+ text:
66
+ mode === "create"
67
+ ? "User created successfully!"
68
+ : "User updated successfully!",
69
+ type: "success",
70
+ });
71
+ setTimeout(() => {
72
+ window.location.href = `${adminPath}/users`;
73
+ }, 1000);
74
+ } else {
75
+ setMessage({
76
+ text: data.error || "Failed to save user",
77
+ type: "error",
78
+ });
79
+ }
80
+ } catch (err) {
81
+ setMessage({ text: "An unexpected error occurred", type: "error" });
82
+ } finally {
83
+ setLoading(false);
84
+ }
85
+ };
86
+
87
+ return (
88
+ <div className="flex-1 overflow-y-auto pr-12 space-y-8">
89
+ <div className="surface-tile p-6 flex items-center justify-between">
90
+ <div>
91
+ <h1 className="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]">
92
+ {mode === "create" ? "Create User" : "Edit User"}
93
+ </h1>
94
+ <p className="text-sm text-[var(--kyro-text-secondary)] mt-1 font-medium">
95
+ {mode === "create"
96
+ ? "Add a new user to the system"
97
+ : `Editing ${user?.email}`}
98
+ </p>
99
+ </div>
100
+ <a
101
+ href={`${adminPath}/users`}
102
+ className="text-sm font-bold text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] transition-colors"
103
+ >
104
+ ← Back to users
105
+ </a>
106
+ </div>
107
+
108
+ <div className="surface-tile p-6">
109
+ <form onSubmit={handleSubmit} className="space-y-6 max-w-2xl">
110
+ <div>
111
+ <label
112
+ htmlFor="name"
113
+ className="block text-sm font-medium text-[var(--kyro-text-primary)] mb-2"
114
+ >
115
+ Name (optional)
116
+ </label>
117
+ <input
118
+ type="text"
119
+ id="name"
120
+ value={name}
121
+ onChange={(e) => setName(e.target.value)}
122
+ className="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
123
+ placeholder="John Doe"
124
+ />
125
+ </div>
126
+
127
+ <div>
128
+ <label
129
+ htmlFor="email"
130
+ className="block text-sm font-medium text-[var(--kyro-text-primary)] mb-2"
131
+ >
132
+ Email Address
133
+ </label>
134
+ <input
135
+ type="email"
136
+ id="email"
137
+ value={email}
138
+ onChange={(e) => setEmail(e.target.value)}
139
+ required
140
+ className="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
141
+ placeholder="user@example.com"
142
+ />
143
+ </div>
144
+
145
+ {mode === "create" && (
146
+ <div>
147
+ <label
148
+ htmlFor="password"
149
+ className="block text-sm font-medium text-[var(--kyro-text-primary)] mb-2"
150
+ >
151
+ Password
152
+ </label>
153
+ <input
154
+ type="password"
155
+ id="password"
156
+ value={password}
157
+ onChange={(e) => setPassword(e.target.value)}
158
+ required
159
+ minLength={12}
160
+ className="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
161
+ placeholder="Minimum 12 characters"
162
+ />
163
+ <p className="text-xs text-[var(--kyro-text-secondary)] mt-1">
164
+ Must contain , lowercase, numbers, and special
165
+ characters
166
+ </p>
167
+ </div>
168
+ )}
169
+
170
+ <div>
171
+ <label
172
+ htmlFor="role"
173
+ className="block text-sm font-medium text-[var(--kyro-text-primary)] mb-2"
174
+ >
175
+ Role
176
+ </label>
177
+ <select
178
+ id="role"
179
+ value={role}
180
+ onChange={(e) => setRole(e.target.value)}
181
+ className="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
182
+ >
183
+ {roleOptions.map((r) => (
184
+ <option key={r} value={r}>
185
+ {r}
186
+ </option>
187
+ ))}
188
+ </select>
189
+ </div>
190
+
191
+ <div>
192
+ <label
193
+ htmlFor="tenantId"
194
+ className="block text-sm font-medium text-[var(--kyro-text-primary)] mb-2"
195
+ >
196
+ Tenant ID (optional)
197
+ </label>
198
+ <input
199
+ type="text"
200
+ id="tenantId"
201
+ value={tenantId}
202
+ onChange={(e) => setTenantId(e.target.value)}
203
+ className="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
204
+ placeholder="Leave empty for global user"
205
+ />
206
+ </div>
207
+
208
+ <div className="flex items-center justify-end gap-3 pt-4 border-t border-[var(--kyro-border)]">
209
+ <a
210
+ href={`${adminPath}/users`}
211
+ className="px-6 py-3 border border-[var(--kyro-border)] rounded-xl text-sm font-bold text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors"
212
+ >
213
+ Cancel
214
+ </a>
215
+ <button
216
+ type="submit"
217
+ disabled={loading}
218
+ className="px-6 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl text-sm font-bold hover:bg-opacity-90 transition-colors shadow-lg shadow-black/10 disabled:opacity-50"
219
+ >
220
+ {loading
221
+ ? "Saving..."
222
+ : mode === "create"
223
+ ? "Create User"
224
+ : "Save Changes"}
225
+ </button>
226
+ </div>
227
+ </form>
228
+
229
+ {message && (
230
+ <div
231
+ className={`mt-4 p-4 rounded-xl text-sm font-bold ${message.type === "success"
232
+ ? "bg-green-500/10 text-green-500"
233
+ : "bg-red-500/10 text-red-500"
234
+ }`}
235
+ >
236
+ {message.text}
237
+ </div>
238
+ )}
239
+ </div>
240
+ </div>
241
+ );
242
+ }