@m5kdev/web-ui 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. package/dist/src/animations/card.motion.d.ts +0 -1
  2. package/dist/src/components/AvatarUpload.d.ts +0 -1
  3. package/dist/src/components/Button.d.ts +0 -1
  4. package/dist/src/components/Calendar.d.ts +0 -1
  5. package/dist/src/components/CardsSelect.d.ts +0 -1
  6. package/dist/src/components/CollapsibleSidebarMenuItem.d.ts +0 -1
  7. package/dist/src/components/ColorPicker.d.ts +0 -1
  8. package/dist/src/components/CopyButton.d.ts +0 -1
  9. package/dist/src/components/CropDialog.d.ts +0 -1
  10. package/dist/src/components/DialogProvider.d.ts +0 -1
  11. package/dist/src/components/ErrorFallback.d.ts +0 -1
  12. package/dist/src/components/FileDropzone.d.ts +0 -1
  13. package/dist/src/components/MultiSelectDropdown.d.ts +0 -1
  14. package/dist/src/components/Orb.d.ts +0 -1
  15. package/dist/src/components/PageAlert.d.ts +0 -1
  16. package/dist/src/components/SelectChips.d.ts +0 -1
  17. package/dist/src/components/SidebarItem.d.ts +0 -1
  18. package/dist/src/components/Steps.d.ts +0 -1
  19. package/dist/src/components/TablerIconPicker.d.ts +0 -1
  20. package/dist/src/components/app-header.d.ts +0 -1
  21. package/dist/src/components/blur-card.d.ts +0 -1
  22. package/dist/src/components/features-section-demo-1.d.ts +0 -1
  23. package/dist/src/components/features-section-demo-2.d.ts +0 -1
  24. package/dist/src/components/features-section-demo-3.d.ts +0 -1
  25. package/dist/src/components/mode-toggle.d.ts +0 -1
  26. package/dist/src/components/nav-main.d.ts +0 -1
  27. package/dist/src/components/pricing-cards.d.ts +0 -1
  28. package/dist/src/components/shared/ButtonCopy.d.ts +0 -1
  29. package/dist/src/components/team-switcher.d.ts +0 -1
  30. package/dist/src/components/theme-provider.d.ts +0 -1
  31. package/dist/src/components/typewriter.d.ts +0 -1
  32. package/dist/src/components/ui/alert-dialog.d.ts +0 -1
  33. package/dist/src/components/ui/alert.d.ts +0 -1
  34. package/dist/src/components/ui/avatar.d.ts +0 -1
  35. package/dist/src/components/ui/badge.d.ts +0 -1
  36. package/dist/src/components/ui/bento-grid.d.ts +0 -1
  37. package/dist/src/components/ui/bento-grid2.d.ts +0 -1
  38. package/dist/src/components/ui/breadcrumb.d.ts +0 -1
  39. package/dist/src/components/ui/button.d.ts +0 -1
  40. package/dist/src/components/ui/card.d.ts +0 -1
  41. package/dist/src/components/ui/checkbox.d.ts +0 -1
  42. package/dist/src/components/ui/collapsible.d.ts +0 -1
  43. package/dist/src/components/ui/dialog.d.ts +0 -1
  44. package/dist/src/components/ui/dropdown-menu.d.ts +0 -1
  45. package/dist/src/components/ui/floating-navbar.d.ts +0 -1
  46. package/dist/src/components/ui/form.d.ts +0 -1
  47. package/dist/src/components/ui/image.d.ts +0 -1
  48. package/dist/src/components/ui/input.d.ts +0 -1
  49. package/dist/src/components/ui/label.d.ts +0 -1
  50. package/dist/src/components/ui/pagination.d.ts +0 -1
  51. package/dist/src/components/ui/progress.d.ts +0 -1
  52. package/dist/src/components/ui/resizable-navbar.d.ts +0 -1
  53. package/dist/src/components/ui/segment-control.d.ts +0 -1
  54. package/dist/src/components/ui/select.d.ts +0 -1
  55. package/dist/src/components/ui/separator.d.ts +0 -1
  56. package/dist/src/components/ui/sheet.d.ts +0 -1
  57. package/dist/src/components/ui/sidebar.d.ts +0 -1
  58. package/dist/src/components/ui/skeleton.d.ts +0 -1
  59. package/dist/src/components/ui/slider.d.ts +0 -1
  60. package/dist/src/components/ui/sonner.d.ts +0 -1
  61. package/dist/src/components/ui/spinner.d.ts +0 -1
  62. package/dist/src/components/ui/switch.d.ts +0 -1
  63. package/dist/src/components/ui/table.d.ts +0 -1
  64. package/dist/src/components/ui/tabs.d.ts +0 -1
  65. package/dist/src/components/ui/textarea.d.ts +0 -1
  66. package/dist/src/components/ui/timeline.d.ts +0 -1
  67. package/dist/src/components/ui/toast.d.ts +0 -1
  68. package/dist/src/components/ui/tooltip.d.ts +0 -1
  69. package/dist/src/components/ui/typewriter-effect.d.ts +0 -1
  70. package/dist/src/hooks/use-mobile.d.ts +0 -1
  71. package/dist/src/hooks/useDialog.d.ts +0 -1
  72. package/dist/src/icons/GoogleIcon.d.ts +0 -1
  73. package/dist/src/icons/LinkedInIcon.d.ts +0 -1
  74. package/dist/src/icons/MicrosoftIcon.d.ts +0 -1
  75. package/dist/src/lib/chatwoot.d.ts +0 -1
  76. package/dist/src/lib/utils.d.ts +0 -1
  77. package/dist/src/modules/app/components/AppLoader.d.ts +0 -1
  78. package/dist/src/modules/app/components/AppShell.d.ts +0 -1
  79. package/dist/src/modules/app/components/AppSidebar.d.ts +0 -1
  80. package/dist/src/modules/app/components/AppSidebarContent.d.ts +0 -1
  81. package/dist/src/modules/app/components/AppSidebarHeader.d.ts +0 -1
  82. package/dist/src/modules/app/components/AppSidebarInvites.d.ts +0 -1
  83. package/dist/src/modules/app/components/AppSidebarUser.d.ts +0 -1
  84. package/dist/src/modules/auth/components/AdminUserManagement.d.ts +0 -1
  85. package/dist/src/modules/auth/components/AdminWaitlist.d.ts +0 -1
  86. package/dist/src/modules/auth/components/AuthLayout.d.ts +0 -1
  87. package/dist/src/modules/auth/components/AuthProviders.d.ts +0 -1
  88. package/dist/src/modules/auth/components/AuthRouter.d.ts +0 -1
  89. package/dist/src/modules/auth/components/ClaimAccountRoute.d.ts +0 -1
  90. package/dist/src/modules/auth/components/ErrorAuthRoute.d.ts +0 -1
  91. package/dist/src/modules/auth/components/ForgotPasswordForm.d.ts +0 -1
  92. package/dist/src/modules/auth/components/ForgotPasswordRoute.d.ts +0 -1
  93. package/dist/src/modules/auth/components/InviteFriends.d.ts +0 -1
  94. package/dist/src/modules/auth/components/LastUsedBadge.d.ts +0 -1
  95. package/dist/src/modules/auth/components/LoginForm.d.ts +0 -1
  96. package/dist/src/modules/auth/components/LoginRoute.d.ts +0 -1
  97. package/dist/src/modules/auth/components/LogoutRoute.d.ts +0 -1
  98. package/dist/src/modules/auth/components/OrganizationAcceptInvitationRoute.d.ts +0 -1
  99. package/dist/src/modules/auth/components/OrganizationMembersRoute.d.ts +0 -1
  100. package/dist/src/modules/auth/components/OrganizationSettingsRoute.d.ts +0 -1
  101. package/dist/src/modules/auth/components/OrganizationSwitcher.d.ts +0 -1
  102. package/dist/src/modules/auth/components/ProfileRoute.d.ts +0 -1
  103. package/dist/src/modules/auth/components/RangeNuqsDatePicker.d.ts +0 -1
  104. package/dist/src/modules/auth/components/ResetPasswordForm.d.ts +0 -1
  105. package/dist/src/modules/auth/components/ResetPasswordRoute.d.ts +0 -1
  106. package/dist/src/modules/auth/components/SignupFormRoute.d.ts +0 -1
  107. package/dist/src/modules/auth/components/SignupRoute.d.ts +0 -1
  108. package/dist/src/modules/auth/components/UserPreferences.d.ts +0 -1
  109. package/dist/src/modules/auth/components/WaitlistCard.d.ts +0 -1
  110. package/dist/src/modules/auth/components/WaitlistCodeValidation.d.ts +0 -1
  111. package/dist/src/modules/billing/components/BillingBetaPage.d.ts +0 -1
  112. package/dist/src/modules/billing/components/BillingInvoicePage.d.ts +0 -1
  113. package/dist/src/modules/billing/components/BillingPlanSelect.d.ts +0 -1
  114. package/dist/src/modules/billing/components/BillingRouter.d.ts +0 -1
  115. package/dist/src/modules/billing/components/BillingSinglePlanSelect.d.ts +0 -1
  116. package/dist/src/modules/table/components/ColumnOrderAndVisibility.d.ts +0 -1
  117. package/dist/src/modules/table/components/NuqsTable.d.ts +0 -1
  118. package/dist/src/modules/table/components/TableFiltering.d.ts +0 -1
  119. package/dist/src/modules/table/components/TablePagination.d.ts +0 -1
  120. package/dist/src/modules/table/components/table.types.d.ts +0 -1
  121. package/dist/src/modules/table/filterTransformers.d.ts +0 -1
  122. package/dist/src/types.d.ts +0 -1
  123. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  124. package/package.json +8 -5
  125. package/.cursor/rules/web-ui.mdc +0 -126
  126. package/.turbo/turbo-build.log +0 -5
  127. package/.turbo/turbo-check-types.log +0 -5
  128. package/.turbo/turbo-lint$colon$fix.log +0 -381
  129. package/.turbo/turbo-lint.log +0 -361
  130. package/CHANGELOG.md +0 -25
  131. package/components.json +0 -21
  132. package/dist/src/animations/card.motion.d.ts.map +0 -1
  133. package/dist/src/components/AvatarUpload.d.ts.map +0 -1
  134. package/dist/src/components/Button.d.ts.map +0 -1
  135. package/dist/src/components/Calendar.d.ts.map +0 -1
  136. package/dist/src/components/CardsSelect.d.ts.map +0 -1
  137. package/dist/src/components/CollapsibleSidebarMenuItem.d.ts.map +0 -1
  138. package/dist/src/components/ColorPicker.d.ts.map +0 -1
  139. package/dist/src/components/CopyButton.d.ts.map +0 -1
  140. package/dist/src/components/CropDialog.d.ts.map +0 -1
  141. package/dist/src/components/DialogProvider.d.ts.map +0 -1
  142. package/dist/src/components/ErrorFallback.d.ts.map +0 -1
  143. package/dist/src/components/FileDropzone.d.ts.map +0 -1
  144. package/dist/src/components/MultiSelectDropdown.d.ts.map +0 -1
  145. package/dist/src/components/Orb.d.ts.map +0 -1
  146. package/dist/src/components/PageAlert.d.ts.map +0 -1
  147. package/dist/src/components/SelectChips.d.ts.map +0 -1
  148. package/dist/src/components/SidebarItem.d.ts.map +0 -1
  149. package/dist/src/components/Steps.d.ts.map +0 -1
  150. package/dist/src/components/TablerIconPicker.d.ts.map +0 -1
  151. package/dist/src/components/app-header.d.ts.map +0 -1
  152. package/dist/src/components/blur-card.d.ts.map +0 -1
  153. package/dist/src/components/features-section-demo-1.d.ts.map +0 -1
  154. package/dist/src/components/features-section-demo-2.d.ts.map +0 -1
  155. package/dist/src/components/features-section-demo-3.d.ts.map +0 -1
  156. package/dist/src/components/mode-toggle.d.ts.map +0 -1
  157. package/dist/src/components/nav-main.d.ts.map +0 -1
  158. package/dist/src/components/pricing-cards.d.ts.map +0 -1
  159. package/dist/src/components/shared/ButtonCopy.d.ts.map +0 -1
  160. package/dist/src/components/team-switcher.d.ts.map +0 -1
  161. package/dist/src/components/theme-provider.d.ts.map +0 -1
  162. package/dist/src/components/typewriter.d.ts.map +0 -1
  163. package/dist/src/components/ui/alert-dialog.d.ts.map +0 -1
  164. package/dist/src/components/ui/alert.d.ts.map +0 -1
  165. package/dist/src/components/ui/avatar.d.ts.map +0 -1
  166. package/dist/src/components/ui/badge.d.ts.map +0 -1
  167. package/dist/src/components/ui/bento-grid.d.ts.map +0 -1
  168. package/dist/src/components/ui/bento-grid2.d.ts.map +0 -1
  169. package/dist/src/components/ui/breadcrumb.d.ts.map +0 -1
  170. package/dist/src/components/ui/button.d.ts.map +0 -1
  171. package/dist/src/components/ui/card.d.ts.map +0 -1
  172. package/dist/src/components/ui/checkbox.d.ts.map +0 -1
  173. package/dist/src/components/ui/collapsible.d.ts.map +0 -1
  174. package/dist/src/components/ui/dialog.d.ts.map +0 -1
  175. package/dist/src/components/ui/dropdown-menu.d.ts.map +0 -1
  176. package/dist/src/components/ui/floating-navbar.d.ts.map +0 -1
  177. package/dist/src/components/ui/form.d.ts.map +0 -1
  178. package/dist/src/components/ui/image.d.ts.map +0 -1
  179. package/dist/src/components/ui/input.d.ts.map +0 -1
  180. package/dist/src/components/ui/label.d.ts.map +0 -1
  181. package/dist/src/components/ui/pagination.d.ts.map +0 -1
  182. package/dist/src/components/ui/progress.d.ts.map +0 -1
  183. package/dist/src/components/ui/resizable-navbar.d.ts.map +0 -1
  184. package/dist/src/components/ui/segment-control.d.ts.map +0 -1
  185. package/dist/src/components/ui/select.d.ts.map +0 -1
  186. package/dist/src/components/ui/separator.d.ts.map +0 -1
  187. package/dist/src/components/ui/sheet.d.ts.map +0 -1
  188. package/dist/src/components/ui/sidebar.d.ts.map +0 -1
  189. package/dist/src/components/ui/skeleton.d.ts.map +0 -1
  190. package/dist/src/components/ui/slider.d.ts.map +0 -1
  191. package/dist/src/components/ui/sonner.d.ts.map +0 -1
  192. package/dist/src/components/ui/spinner.d.ts.map +0 -1
  193. package/dist/src/components/ui/switch.d.ts.map +0 -1
  194. package/dist/src/components/ui/table.d.ts.map +0 -1
  195. package/dist/src/components/ui/tabs.d.ts.map +0 -1
  196. package/dist/src/components/ui/textarea.d.ts.map +0 -1
  197. package/dist/src/components/ui/timeline.d.ts.map +0 -1
  198. package/dist/src/components/ui/toast.d.ts.map +0 -1
  199. package/dist/src/components/ui/tooltip.d.ts.map +0 -1
  200. package/dist/src/components/ui/typewriter-effect.d.ts.map +0 -1
  201. package/dist/src/hooks/use-mobile.d.ts.map +0 -1
  202. package/dist/src/hooks/useDialog.d.ts.map +0 -1
  203. package/dist/src/icons/GoogleIcon.d.ts.map +0 -1
  204. package/dist/src/icons/LinkedInIcon.d.ts.map +0 -1
  205. package/dist/src/icons/MicrosoftIcon.d.ts.map +0 -1
  206. package/dist/src/lib/chatwoot.d.ts.map +0 -1
  207. package/dist/src/lib/utils.d.ts.map +0 -1
  208. package/dist/src/modules/app/components/AppLoader.d.ts.map +0 -1
  209. package/dist/src/modules/app/components/AppShell.d.ts.map +0 -1
  210. package/dist/src/modules/app/components/AppSidebar.d.ts.map +0 -1
  211. package/dist/src/modules/app/components/AppSidebarContent.d.ts.map +0 -1
  212. package/dist/src/modules/app/components/AppSidebarHeader.d.ts.map +0 -1
  213. package/dist/src/modules/app/components/AppSidebarInvites.d.ts.map +0 -1
  214. package/dist/src/modules/app/components/AppSidebarUser.d.ts.map +0 -1
  215. package/dist/src/modules/auth/components/AdminUserManagement.d.ts.map +0 -1
  216. package/dist/src/modules/auth/components/AdminWaitlist.d.ts.map +0 -1
  217. package/dist/src/modules/auth/components/AuthLayout.d.ts.map +0 -1
  218. package/dist/src/modules/auth/components/AuthProviders.d.ts.map +0 -1
  219. package/dist/src/modules/auth/components/AuthRouter.d.ts.map +0 -1
  220. package/dist/src/modules/auth/components/ClaimAccountRoute.d.ts.map +0 -1
  221. package/dist/src/modules/auth/components/ErrorAuthRoute.d.ts.map +0 -1
  222. package/dist/src/modules/auth/components/ForgotPasswordForm.d.ts.map +0 -1
  223. package/dist/src/modules/auth/components/ForgotPasswordRoute.d.ts.map +0 -1
  224. package/dist/src/modules/auth/components/InviteFriends.d.ts.map +0 -1
  225. package/dist/src/modules/auth/components/LastUsedBadge.d.ts.map +0 -1
  226. package/dist/src/modules/auth/components/LoginForm.d.ts.map +0 -1
  227. package/dist/src/modules/auth/components/LoginRoute.d.ts.map +0 -1
  228. package/dist/src/modules/auth/components/LogoutRoute.d.ts.map +0 -1
  229. package/dist/src/modules/auth/components/OrganizationAcceptInvitationRoute.d.ts.map +0 -1
  230. package/dist/src/modules/auth/components/OrganizationMembersRoute.d.ts.map +0 -1
  231. package/dist/src/modules/auth/components/OrganizationSettingsRoute.d.ts.map +0 -1
  232. package/dist/src/modules/auth/components/OrganizationSwitcher.d.ts.map +0 -1
  233. package/dist/src/modules/auth/components/ProfileRoute.d.ts.map +0 -1
  234. package/dist/src/modules/auth/components/RangeNuqsDatePicker.d.ts.map +0 -1
  235. package/dist/src/modules/auth/components/ResetPasswordForm.d.ts.map +0 -1
  236. package/dist/src/modules/auth/components/ResetPasswordRoute.d.ts.map +0 -1
  237. package/dist/src/modules/auth/components/SignupFormRoute.d.ts.map +0 -1
  238. package/dist/src/modules/auth/components/SignupRoute.d.ts.map +0 -1
  239. package/dist/src/modules/auth/components/UserPreferences.d.ts.map +0 -1
  240. package/dist/src/modules/auth/components/WaitlistCard.d.ts.map +0 -1
  241. package/dist/src/modules/auth/components/WaitlistCodeValidation.d.ts.map +0 -1
  242. package/dist/src/modules/billing/components/BillingBetaPage.d.ts.map +0 -1
  243. package/dist/src/modules/billing/components/BillingInvoicePage.d.ts.map +0 -1
  244. package/dist/src/modules/billing/components/BillingPlanSelect.d.ts.map +0 -1
  245. package/dist/src/modules/billing/components/BillingRouter.d.ts.map +0 -1
  246. package/dist/src/modules/billing/components/BillingSinglePlanSelect.d.ts.map +0 -1
  247. package/dist/src/modules/table/components/ColumnOrderAndVisibility.d.ts.map +0 -1
  248. package/dist/src/modules/table/components/NuqsTable.d.ts.map +0 -1
  249. package/dist/src/modules/table/components/TableFiltering.d.ts.map +0 -1
  250. package/dist/src/modules/table/components/TablePagination.d.ts.map +0 -1
  251. package/dist/src/modules/table/components/table.types.d.ts.map +0 -1
  252. package/dist/src/modules/table/filterTransformers.d.ts.map +0 -1
  253. package/dist/src/types.d.ts.map +0 -1
  254. package/src/animations/card.motion.ts +0 -9
  255. package/src/components/AvatarUpload.tsx +0 -133
  256. package/src/components/Button.tsx +0 -14
  257. package/src/components/Calendar.css +0 -684
  258. package/src/components/Calendar.tsx +0 -32
  259. package/src/components/CardsSelect.tsx +0 -155
  260. package/src/components/CollapsibleSidebarMenuItem.tsx +0 -57
  261. package/src/components/ColorPicker.tsx +0 -56
  262. package/src/components/CopyButton.tsx +0 -45
  263. package/src/components/CropDialog.tsx +0 -154
  264. package/src/components/DialogProvider.tsx +0 -105
  265. package/src/components/ErrorFallback.tsx +0 -17
  266. package/src/components/FileDropzone.tsx +0 -120
  267. package/src/components/MultiSelectDropdown.tsx +0 -233
  268. package/src/components/Orb.tsx +0 -288
  269. package/src/components/PageAlert.tsx +0 -121
  270. package/src/components/SelectChips.tsx +0 -40
  271. package/src/components/SidebarItem.tsx +0 -26
  272. package/src/components/Steps.tsx +0 -340
  273. package/src/components/TablerIconPicker.tsx +0 -4260
  274. package/src/components/app-header.tsx +0 -40
  275. package/src/components/blur-card.tsx +0 -132
  276. package/src/components/features-section-demo-1.tsx +0 -127
  277. package/src/components/features-section-demo-2.tsx +0 -102
  278. package/src/components/features-section-demo-3.tsx +0 -272
  279. package/src/components/mode-toggle.tsx +0 -31
  280. package/src/components/nav-main.tsx +0 -69
  281. package/src/components/pricing-cards.tsx +0 -133
  282. package/src/components/shared/ButtonCopy.tsx +0 -50
  283. package/src/components/team-switcher.tsx +0 -83
  284. package/src/components/theme-provider.tsx +0 -74
  285. package/src/components/typewriter.tsx +0 -90
  286. package/src/components/ui/alert-dialog.tsx +0 -133
  287. package/src/components/ui/alert.tsx +0 -60
  288. package/src/components/ui/avatar.tsx +0 -47
  289. package/src/components/ui/badge.tsx +0 -33
  290. package/src/components/ui/bento-grid.tsx +0 -54
  291. package/src/components/ui/bento-grid2.tsx +0 -66
  292. package/src/components/ui/breadcrumb.tsx +0 -101
  293. package/src/components/ui/button.tsx +0 -50
  294. package/src/components/ui/card.tsx +0 -55
  295. package/src/components/ui/checkbox.tsx +0 -26
  296. package/src/components/ui/collapsible.tsx +0 -9
  297. package/src/components/ui/dialog.tsx +0 -119
  298. package/src/components/ui/dropdown-menu.tsx +0 -186
  299. package/src/components/ui/floating-navbar.tsx +0 -78
  300. package/src/components/ui/form.tsx +0 -167
  301. package/src/components/ui/image.tsx +0 -55
  302. package/src/components/ui/input.tsx +0 -22
  303. package/src/components/ui/label.tsx +0 -19
  304. package/src/components/ui/pagination.tsx +0 -105
  305. package/src/components/ui/progress.tsx +0 -23
  306. package/src/components/ui/resizable-navbar.tsx +0 -260
  307. package/src/components/ui/segment-control.tsx +0 -143
  308. package/src/components/ui/select.tsx +0 -153
  309. package/src/components/ui/separator.tsx +0 -24
  310. package/src/components/ui/sheet.tsx +0 -121
  311. package/src/components/ui/sidebar.tsx +0 -736
  312. package/src/components/ui/skeleton.tsx +0 -7
  313. package/src/components/ui/slider.tsx +0 -23
  314. package/src/components/ui/sonner.tsx +0 -27
  315. package/src/components/ui/spinner.tsx +0 -45
  316. package/src/components/ui/switch.tsx +0 -27
  317. package/src/components/ui/table.tsx +0 -90
  318. package/src/components/ui/tabs.tsx +0 -52
  319. package/src/components/ui/textarea.tsx +0 -18
  320. package/src/components/ui/timeline.tsx +0 -95
  321. package/src/components/ui/toast.tsx +0 -126
  322. package/src/components/ui/tooltip.tsx +0 -55
  323. package/src/components/ui/typewriter-effect.tsx +0 -181
  324. package/src/hooks/use-mobile.ts +0 -19
  325. package/src/hooks/useDialog.ts +0 -25
  326. package/src/icons/GoogleIcon.tsx +0 -32
  327. package/src/icons/LinkedInIcon.tsx +0 -30
  328. package/src/icons/MicrosoftIcon.tsx +0 -21
  329. package/src/lib/chatwoot.ts +0 -51
  330. package/src/lib/utils.ts +0 -6
  331. package/src/modules/app/components/AppLoader.tsx +0 -9
  332. package/src/modules/app/components/AppShell.tsx +0 -21
  333. package/src/modules/app/components/AppSidebar.tsx +0 -26
  334. package/src/modules/app/components/AppSidebarContent.tsx +0 -73
  335. package/src/modules/app/components/AppSidebarHeader.tsx +0 -57
  336. package/src/modules/app/components/AppSidebarInvites.tsx +0 -32
  337. package/src/modules/app/components/AppSidebarUser.tsx +0 -128
  338. package/src/modules/auth/components/AdminUserManagement.tsx +0 -1136
  339. package/src/modules/auth/components/AdminWaitlist.tsx +0 -358
  340. package/src/modules/auth/components/AuthLayout.tsx +0 -13
  341. package/src/modules/auth/components/AuthProviders.tsx +0 -105
  342. package/src/modules/auth/components/AuthRouter.tsx +0 -29
  343. package/src/modules/auth/components/ClaimAccountRoute.tsx +0 -242
  344. package/src/modules/auth/components/ErrorAuthRoute.tsx +0 -121
  345. package/src/modules/auth/components/ForgotPasswordForm.tsx +0 -58
  346. package/src/modules/auth/components/ForgotPasswordRoute.tsx +0 -27
  347. package/src/modules/auth/components/InviteFriends.tsx +0 -273
  348. package/src/modules/auth/components/LastUsedBadge.tsx +0 -22
  349. package/src/modules/auth/components/LoginForm.tsx +0 -104
  350. package/src/modules/auth/components/LoginRoute.tsx +0 -31
  351. package/src/modules/auth/components/LogoutRoute.tsx +0 -21
  352. package/src/modules/auth/components/OrganizationAcceptInvitationRoute.tsx +0 -161
  353. package/src/modules/auth/components/OrganizationMembersRoute.tsx +0 -730
  354. package/src/modules/auth/components/OrganizationSettingsRoute.tsx +0 -280
  355. package/src/modules/auth/components/OrganizationSwitcher.tsx +0 -148
  356. package/src/modules/auth/components/ProfileRoute.tsx +0 -104
  357. package/src/modules/auth/components/RangeNuqsDatePicker.tsx +0 -365
  358. package/src/modules/auth/components/ResetPasswordForm.tsx +0 -103
  359. package/src/modules/auth/components/ResetPasswordRoute.tsx +0 -27
  360. package/src/modules/auth/components/SignupFormRoute.tsx +0 -189
  361. package/src/modules/auth/components/SignupRoute.tsx +0 -53
  362. package/src/modules/auth/components/UserPreferences.tsx +0 -144
  363. package/src/modules/auth/components/WaitlistCard.tsx +0 -78
  364. package/src/modules/auth/components/WaitlistCodeValidation.tsx +0 -79
  365. package/src/modules/billing/components/BillingBetaPage.tsx +0 -124
  366. package/src/modules/billing/components/BillingInvoicePage.tsx +0 -180
  367. package/src/modules/billing/components/BillingPlanSelect.tsx +0 -14
  368. package/src/modules/billing/components/BillingRouter.tsx +0 -20
  369. package/src/modules/billing/components/BillingSinglePlanSelect.tsx +0 -172
  370. package/src/modules/table/components/ColumnOrderAndVisibility.tsx +0 -127
  371. package/src/modules/table/components/NuqsTable.tsx +0 -396
  372. package/src/modules/table/components/TableFiltering.tsx +0 -520
  373. package/src/modules/table/components/TablePagination.tsx +0 -59
  374. package/src/modules/table/components/table.types.ts +0 -11
  375. package/src/modules/table/filterTransformers.ts +0 -323
  376. package/src/types.ts +0 -4
  377. package/src/vite-env.d.ts +0 -1
  378. package/translations/en/web-ui.json +0 -192
  379. package/tsconfig.json +0 -30
@@ -1,1136 +0,0 @@
1
- import {
2
- Button,
3
- Dropdown,
4
- DropdownItem,
5
- DropdownMenu,
6
- DropdownTrigger,
7
- Input,
8
- Modal,
9
- ModalBody,
10
- ModalContent,
11
- ModalFooter,
12
- ModalHeader,
13
- Select,
14
- SelectItem,
15
- Spinner,
16
- Table,
17
- TableBody,
18
- TableCell,
19
- TableColumn,
20
- TableHeader,
21
- TableRow,
22
- Tooltip,
23
- } from "@heroui/react";
24
- import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
25
- import * as authAdmin from "@m5kdev/frontend/modules/auth/hooks/useAuthAdmin";
26
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
27
- import {
28
- BarChart3,
29
- CalendarClock,
30
- ChevronDown,
31
- ChevronUp,
32
- Copy,
33
- Filter,
34
- Info,
35
- Link2,
36
- List,
37
- MoreHorizontal,
38
- Search,
39
- UserPlus,
40
- X,
41
- } from "lucide-react";
42
- import { useCallback, useEffect, useId, useState } from "react";
43
- import { useNavigate } from "react-router";
44
- import { toast } from "sonner";
45
- import type { UseBackendTRPC } from "#types";
46
-
47
- type SortField = "name" | "email" | "role" | "createdAt";
48
- type SortOrder = "asc" | "desc";
49
- type StatusFilter = "all" | "banned" | "active";
50
-
51
- interface AdminUserManagementProps {
52
- useTRPC?: UseBackendTRPC;
53
- }
54
-
55
- export function AdminUserManagement({ useTRPC }: AdminUserManagementProps) {
56
- const banReasonInputId = useId();
57
- const customDurationId = useId();
58
- const nameInputId = useId();
59
- const emailInputId = useId();
60
- const passwordInputId = useId();
61
- const roleSelectId = useId();
62
- const [page, setPage] = useState(0);
63
- const [limit] = useState(10);
64
- const [userToDelete, setUserToDelete] = useState<string | null>(null);
65
- const [userToBan, setUserToBan] = useState<{ id: string; name: string } | null>(null);
66
- const [banReason, setBanReason] = useState("");
67
- const [banExpiry, setBanExpiry] = useState("never"); // "never", "1d", "7d", "30d", "custom"
68
- const [customBanDays, setCustomBanDays] = useState(1);
69
- const [isBanningUser, setIsBanningUser] = useState(false);
70
- const [isUnbanningUser, setIsUnbanningUser] = useState<Record<string, boolean>>({});
71
- const [isImpersonatingUser, setIsImpersonatingUser] = useState(false);
72
- const [searchQuery, setSearchQuery] = useState("");
73
- const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
74
- const [sortField, setSortField] = useState<SortField>("createdAt");
75
- const [sortOrder, setSortOrder] = useState<SortOrder>("desc");
76
- const [debouncedSearchQuery, setDebouncedSearchQuery] = useState("");
77
- const [isCreateUserModalOpen, setIsCreateUserModalOpen] = useState(false);
78
- const [newUserData, setNewUserData] = useState({
79
- name: "",
80
- email: "",
81
- password: "",
82
- role: "user",
83
- });
84
- const [isCreatingUser, setIsCreatingUser] = useState(false);
85
- const [userForUsage, setUserForUsage] = useState<{ id: string; name: string } | null>(null);
86
- const [userForMagicLink, setUserForMagicLink] = useState<{
87
- id: string;
88
- name: string;
89
- email: string | null;
90
- } | null>(null);
91
- const [claimEmail, setClaimEmail] = useState("");
92
- const [generatedMagicLink, setGeneratedMagicLink] = useState<string | null>(null);
93
- const [isGeneratingMagicLink, setIsGeneratingMagicLink] = useState(false);
94
- const navigate = useNavigate();
95
- const magicLinkEmailInputId = useId();
96
- const generatedMagicLinkInputId = useId();
97
-
98
- // AI Usage query
99
- const trpc = useTRPC?.();
100
- const queryClient = useQueryClient();
101
- const usageQueryEnabled = !!userForUsage && !!trpc;
102
- const usageQueryOptions = trpc?.ai.getUserUsage.queryOptions({ userId: userForUsage?.id ?? "" });
103
- const usageQuery = useQuery({
104
- ...(usageQueryOptions ?? {
105
- queryKey: [["ai", "getUserUsage"], { input: { userId: "" } }] as const,
106
- queryFn: () =>
107
- Promise.resolve({ inputTokens: null, outputTokens: null, totalTokens: null, cost: null }),
108
- }),
109
- enabled: usageQueryEnabled,
110
- });
111
- const usageData = usageQuery.data;
112
- const isLoadingUsage = usageQuery.isLoading;
113
-
114
- const refetchUsage = () => {
115
- if (usageQueryOptions) {
116
- queryClient.invalidateQueries({ queryKey: usageQueryOptions.queryKey });
117
- }
118
- };
119
-
120
- // Reset page on filter change
121
- const resetPage = useCallback(() => {
122
- setPage(0);
123
- }, []);
124
-
125
- // Debounce search query
126
- useEffect(() => {
127
- const timer = setTimeout(() => {
128
- setDebouncedSearchQuery(searchQuery);
129
- resetPage();
130
- }, 300);
131
-
132
- return () => clearTimeout(timer);
133
- }, [searchQuery, resetPage]);
134
-
135
- // Handle status filter change
136
- const handleStatusFilterChange = useCallback(
137
- (value: StatusFilter) => {
138
- setStatusFilter(value);
139
- resetPage();
140
- },
141
- [resetPage]
142
- );
143
-
144
- const {
145
- data: listUsers = {
146
- users: [],
147
- total: 0,
148
- },
149
- isLoading,
150
- refetch,
151
- } = authAdmin.useListUsers({
152
- query: {
153
- searchField: "name",
154
- searchOperator: "contains",
155
- searchValue: debouncedSearchQuery,
156
- limit,
157
- offset: page * limit,
158
- sortBy: sortField,
159
- sortDirection: sortOrder,
160
- },
161
- });
162
-
163
- const { users, total: totalUsers } = listUsers;
164
-
165
- const { mutate: deleteUser, isPending: isDeleting } = authAdmin.useRemoveUser({
166
- onSuccess: () => {
167
- toast.success("User deleted successfully");
168
- refetch();
169
- },
170
- onError: (error) => {
171
- toast.error(`Error deleting user: ${error.message}`);
172
- },
173
- });
174
-
175
- const { mutate: updateUser } = authAdmin.useUpdateUser({
176
- onSuccess: () => {
177
- toast.success("User updated successfully");
178
- refetch();
179
- },
180
- onError: (error) => {
181
- toast.error(`Error updating user: ${error.message}`);
182
- },
183
- });
184
-
185
- const totalPages = totalUsers ? Math.ceil(totalUsers / limit) : 0;
186
-
187
- const confirmDelete = (userId: string) => {
188
- setUserToDelete(userId);
189
- };
190
-
191
- const handleDelete = () => {
192
- if (userToDelete) {
193
- deleteUser({ id: userToDelete });
194
- setUserToDelete(null);
195
- }
196
- };
197
-
198
- const openBanModal = (userId: string, userName: string) => {
199
- setUserToBan({ id: userId, name: userName });
200
- setBanReason("");
201
- setBanExpiry("never");
202
- setCustomBanDays(1);
203
- };
204
-
205
- const getBanExpiryInSeconds = () => {
206
- switch (banExpiry) {
207
- case "never":
208
- return undefined; // No expiry
209
- case "1d":
210
- return 60 * 60 * 24; // 1 day in seconds
211
- case "7d":
212
- return 60 * 60 * 24 * 7; // 7 days in seconds
213
- case "30d":
214
- return 60 * 60 * 24 * 30; // 30 days in seconds
215
- case "custom":
216
- return 60 * 60 * 24 * customBanDays; // Custom days in seconds
217
- default:
218
- return undefined;
219
- }
220
- };
221
-
222
- const formatBanExpiry = (expiryTimestamp: number | null) => {
223
- if (!expiryTimestamp) return "Never";
224
-
225
- const expiryDate = new Date(expiryTimestamp);
226
- const now = new Date();
227
-
228
- // If it's expired, return that
229
- if (expiryDate < now) return "Expired";
230
-
231
- // Format the date
232
- const dateString = expiryDate.toLocaleDateString(undefined, {
233
- year: "numeric",
234
- month: "short",
235
- day: "numeric",
236
- });
237
-
238
- // Calculate time remaining
239
- const timeRemaining = expiryDate.getTime() - now.getTime();
240
- const daysRemaining = Math.ceil(timeRemaining / (1000 * 60 * 60 * 24));
241
-
242
- return `${dateString} (${daysRemaining} days)`;
243
- };
244
-
245
- const handleBanUser = async () => {
246
- if (!userToBan) return;
247
-
248
- try {
249
- setIsBanningUser(true);
250
- await authClient.admin.banUser({
251
- userId: userToBan.id,
252
- banReason: banReason || "No reason provided",
253
- banExpiresIn: getBanExpiryInSeconds(),
254
- });
255
-
256
- toast.success(`User ${userToBan.name} has been banned`);
257
- setUserToBan(null);
258
- await refetch();
259
- } catch (error) {
260
- toast.error(`Failed to ban user: ${error instanceof Error ? error.message : String(error)}`);
261
- } finally {
262
- setIsBanningUser(false);
263
- }
264
- };
265
-
266
- const handleUnbanUser = async (userId: string) => {
267
- try {
268
- setIsUnbanningUser((prev) => ({ ...prev, [userId]: true }));
269
- await authClient.admin.unbanUser({
270
- userId,
271
- });
272
-
273
- toast.success("User has been unbanned");
274
- await refetch();
275
- } catch (error) {
276
- toast.error(
277
- `Failed to unban user: ${error instanceof Error ? error.message : String(error)}`
278
- );
279
- } finally {
280
- setIsUnbanningUser((prev) => ({ ...prev, [userId]: false }));
281
- }
282
- };
283
-
284
- const handleSetOnboardingUser = async (userId: string) => {
285
- const onboardingStep = window.prompt(`Set onboarding step for user ${userId}`, "4");
286
- const onboardingStepNumber = Number.parseInt(onboardingStep || "0");
287
- if (onboardingStepNumber) {
288
- updateUser({
289
- userId,
290
- data: { onboarding: onboardingStepNumber },
291
- });
292
- }
293
- };
294
-
295
- const handleImpersonateUser = async (userId: string) => {
296
- try {
297
- setIsImpersonatingUser(true);
298
- await authClient.admin.impersonateUser({
299
- userId,
300
- });
301
-
302
- toast.success("Now impersonating user");
303
- window.location.assign("/"); // Force full reload to apply impersonated session immediately
304
- } catch (error) {
305
- setIsImpersonatingUser(false);
306
- toast.error(
307
- `Failed to impersonate user: ${error instanceof Error ? error.message : String(error)}`
308
- );
309
- }
310
- };
311
-
312
- const handlePageChange = (newPage: number) => {
313
- if (newPage >= 0 && newPage < totalPages) {
314
- setPage(newPage);
315
- }
316
- };
317
-
318
- const handleSort = (field: SortField) => {
319
- if (sortField === field) {
320
- // Toggle order if same field
321
- setSortOrder(sortOrder === "asc" ? "desc" : "asc");
322
- } else {
323
- // Set new field and default to ascending
324
- setSortField(field);
325
- setSortOrder("asc");
326
- }
327
- };
328
-
329
- const renderSortIcon = (field: SortField) => {
330
- if (sortField !== field) return null;
331
-
332
- return sortOrder === "asc" ? (
333
- <ChevronUp className="h-4 w-4 inline ml-1" />
334
- ) : (
335
- <ChevronDown className="h-4 w-4 inline ml-1" />
336
- );
337
- };
338
-
339
- const clearFilters = () => {
340
- setSearchQuery("");
341
- setStatusFilter("all");
342
- setSortField("createdAt");
343
- setSortOrder("desc");
344
- };
345
-
346
- const openCreateUserModal = () => {
347
- setNewUserData({
348
- name: "",
349
- email: "",
350
- password: "",
351
- role: "user",
352
- });
353
- setIsCreateUserModalOpen(true);
354
- };
355
-
356
- const handleCreateUser = async () => {
357
- if (!newUserData.name || !newUserData.email || !newUserData.password) {
358
- toast.error("Please fill in all required fields");
359
- return;
360
- }
361
-
362
- try {
363
- setIsCreatingUser(true);
364
- await authClient.admin.createUser({
365
- name: newUserData.name,
366
- email: newUserData.email,
367
- password: newUserData.password,
368
- role: newUserData.role as "user" | "admin",
369
- });
370
-
371
- toast.success(`User ${newUserData.name} has been created`);
372
- setIsCreateUserModalOpen(false);
373
- await refetch();
374
- } catch (error) {
375
- toast.error(
376
- `Failed to create user: ${error instanceof Error ? error.message : String(error)}`
377
- );
378
- } finally {
379
- setIsCreatingUser(false);
380
- }
381
- };
382
-
383
- const handleNewUserDataChange = (field: keyof typeof newUserData, value: string) => {
384
- setNewUserData((prev) => ({
385
- ...prev,
386
- [field]: value,
387
- }));
388
- };
389
-
390
- const openUsageModal = (userId: string, userName: string) => {
391
- setUserForUsage({ id: userId, name: userName });
392
- refetchUsage();
393
- };
394
-
395
- const formatTokenCount = (count: number | null | undefined): string => {
396
- if (count === null || count === undefined) return "—";
397
- return count.toLocaleString();
398
- };
399
-
400
- const formatCost = (cost: number | null | undefined): string => {
401
- if (cost === null || cost === undefined) return "—";
402
- return `$${cost.toFixed(4)}`;
403
- };
404
-
405
- const createAccountClaimCodeMutation = useMutation(
406
- trpc
407
- ? trpc.auth.createAccountClaimCode.mutationOptions()
408
- : {
409
- mutationFn: async () => {
410
- throw new Error("Account claim actions are not available in this app");
411
- },
412
- }
413
- );
414
-
415
- const generateAccountClaimMagicLinkMutation = useMutation(
416
- trpc
417
- ? trpc.auth.generateAccountClaimMagicLink.mutationOptions()
418
- : {
419
- mutationFn: async () => {
420
- throw new Error("Account claim actions are not available in this app");
421
- },
422
- }
423
- );
424
-
425
- const openMagicLinkModal = (userId: string, userName: string, userEmail: string | null) => {
426
- setUserForMagicLink({ id: userId, name: userName, email: userEmail });
427
- setClaimEmail(userEmail ?? "");
428
- setGeneratedMagicLink(null);
429
- };
430
-
431
- const handleGenerateMagicLink = async () => {
432
- if (!trpc) {
433
- toast.error("Account claim actions are not available in this app");
434
- return;
435
- }
436
-
437
- if (!userForMagicLink) return;
438
-
439
- const normalizedEmail = claimEmail.trim().toLowerCase();
440
- if (!normalizedEmail) {
441
- toast.error("Email is required");
442
- return;
443
- }
444
-
445
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
446
- if (!emailRegex.test(normalizedEmail)) {
447
- toast.error("Please enter a valid email address");
448
- return;
449
- }
450
-
451
- try {
452
- setIsGeneratingMagicLink(true);
453
- const claim = await createAccountClaimCodeMutation.mutateAsync({
454
- userId: userForMagicLink.id,
455
- });
456
- const link = await generateAccountClaimMagicLinkMutation.mutateAsync({
457
- claimId: claim.id,
458
- email: normalizedEmail,
459
- });
460
- setGeneratedMagicLink(link.url);
461
- toast.success("Magic login link generated");
462
- } catch (error) {
463
- toast.error(
464
- `Failed to generate magic link: ${error instanceof Error ? error.message : String(error)}`
465
- );
466
- } finally {
467
- setIsGeneratingMagicLink(false);
468
- }
469
- };
470
-
471
- const copyGeneratedMagicLink = async () => {
472
- if (!generatedMagicLink) return;
473
-
474
- try {
475
- await navigator.clipboard.writeText(generatedMagicLink);
476
- toast.success("Magic link copied");
477
- } catch {
478
- toast.error("Failed to copy link");
479
- }
480
- };
481
-
482
- if (isLoading) {
483
- return (
484
- <div className="flex justify-center p-8">
485
- <Spinner />
486
- </div>
487
- );
488
- }
489
-
490
- const openWaitlistModal = () => {
491
- navigate("/admin/waitlist");
492
- };
493
-
494
- const hasActiveFilters =
495
- debouncedSearchQuery ||
496
- statusFilter !== "all" ||
497
- sortField !== "createdAt" ||
498
- sortOrder !== "desc";
499
-
500
- return (
501
- <div className="space-y-4 p-4">
502
- <div className="flex justify-between items-center">
503
- <h2 className="text-xl font-semibold">User Management</h2>
504
- <div className="flex items-center gap-2">
505
- <Button onPress={openWaitlistModal} size="sm">
506
- <List className="h-4 w-4 mr-2" />
507
- Waitlist
508
- </Button>
509
- <Button onPress={openCreateUserModal} size="sm">
510
- <UserPlus className="h-4 w-4 mr-2" />
511
- Create User
512
- </Button>
513
- <div className="text-sm text-muted-foreground">Total users: {totalUsers || 0}</div>
514
- </div>
515
- </div>
516
-
517
- {/* Search and filters */}
518
- <div className="flex flex-col sm:flex-row gap-3 items-start sm:items-center">
519
- <form
520
- className="relative flex-1"
521
- onSubmit={(e) => {
522
- e.preventDefault();
523
- setDebouncedSearchQuery(searchQuery);
524
- resetPage();
525
- }}
526
- >
527
- <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
528
- <Input
529
- aria-label="Search users"
530
- placeholder="Search users by name or email..."
531
- className="pl-8 w-full"
532
- value={searchQuery}
533
- onChange={(e) => setSearchQuery(e.target.value)}
534
- variant="bordered"
535
- />
536
- {searchQuery && (
537
- <button
538
- type="button"
539
- onClick={() => setSearchQuery("")}
540
- className="absolute right-2.5 top-2.5 text-muted-foreground hover:text-foreground"
541
- aria-label="Clear search"
542
- >
543
- <X className="h-4 w-4" />
544
- </button>
545
- )}
546
- </form>
547
-
548
- <div className="flex gap-2">
549
- <Select
550
- aria-label="Status filter"
551
- selectedKeys={new Set([statusFilter])}
552
- onSelectionChange={(keys) =>
553
- handleStatusFilterChange(Array.from(keys)[0] as StatusFilter)
554
- }
555
- className="w-[160px]"
556
- startContent={<Filter className="mr-1 h-4 w-4" />}
557
- >
558
- <SelectItem key="all">All Users</SelectItem>
559
- <SelectItem key="active">Active Only</SelectItem>
560
- <SelectItem key="banned">Banned Only</SelectItem>
561
- </Select>
562
-
563
- {hasActiveFilters && (
564
- <Button variant="bordered" size="sm" onPress={clearFilters}>
565
- <X className="mr-1 h-4 w-4" />
566
- Clear Filters
567
- </Button>
568
- )}
569
- </div>
570
- </div>
571
-
572
- <div className="border rounded-lg overflow-hidden">
573
- <Table aria-label="Users table" removeWrapper>
574
- <TableHeader>
575
- <TableColumn>ID</TableColumn>
576
- <TableColumn className="cursor-pointer" onClick={() => handleSort("name")}>
577
- Name {renderSortIcon("name")}
578
- </TableColumn>
579
- <TableColumn className="cursor-pointer" onClick={() => handleSort("email")}>
580
- Email {renderSortIcon("email")}
581
- </TableColumn>
582
- <TableColumn className="cursor-pointer" onClick={() => handleSort("role")}>
583
- Role {renderSortIcon("role")}
584
- </TableColumn>
585
- <TableColumn>Status</TableColumn>
586
- <TableColumn>Onboarding</TableColumn>
587
- <TableColumn className="cursor-pointer" onClick={() => handleSort("createdAt")}>
588
- Created At {renderSortIcon("createdAt")}
589
- </TableColumn>
590
- <TableColumn className="text-right">Actions</TableColumn>
591
- </TableHeader>
592
- <TableBody
593
- items={users ?? []}
594
- emptyContent={
595
- hasActiveFilters ? "No users found matching your filters" : "No users found"
596
- }
597
- >
598
- {(user) => {
599
- const onboarding = (user as { onboarding?: string | number }).onboarding;
600
- return (
601
- <TableRow key={user.id}>
602
- <TableCell className="font-mono text-xs">{user.id}</TableCell>
603
- <TableCell>{user.name}</TableCell>
604
- <TableCell>{user.email}</TableCell>
605
- <TableCell>{user.role || "user"}</TableCell>
606
- <TableCell>
607
- {user.banned ? (
608
- <Tooltip
609
- content={
610
- <div className="space-y-1 text-xs">
611
- <p>
612
- <strong>Reason:</strong> {user.banReason || "No reason provided"}
613
- </p>
614
- {user.banExpires && (
615
- <p className="flex items-center gap-1">
616
- <CalendarClock className="h-3 w-3" />
617
- <span>
618
- <strong>Expires:</strong>{" "}
619
- {formatBanExpiry(user.banExpires.getTime())}
620
- </span>
621
- </p>
622
- )}
623
- </div>
624
- }
625
- >
626
- <span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
627
- Banned
628
- {user.banReason && <Info className="h-3 w-3" />}
629
- </span>
630
- </Tooltip>
631
- ) : (
632
- <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
633
- Active
634
- </span>
635
- )}
636
- </TableCell>
637
- <TableCell>{onboarding ?? "—"}</TableCell>
638
- <TableCell>
639
- {user.createdAt ? new Date(user.createdAt).toLocaleDateString() : "N/A"}
640
- </TableCell>
641
- <TableCell className="text-right">
642
- <Dropdown placement="bottom-end">
643
- <DropdownTrigger>
644
- <Button variant="light" size="sm" isIconOnly>
645
- <MoreHorizontal className="h-4 w-4" />
646
- </Button>
647
- </DropdownTrigger>
648
- <DropdownMenu aria-label="User actions">
649
- <DropdownItem
650
- key="onboarding"
651
- onClick={() => handleSetOnboardingUser(user.id)}
652
- >
653
- Set Onboarding
654
- </DropdownItem>
655
- <DropdownItem
656
- key="usage"
657
- onClick={() => openUsageModal(user.id, user.name)}
658
- className={useTRPC ? "" : "hidden"}
659
- >
660
- <span className="flex items-center gap-2">
661
- <BarChart3 className="h-4 w-4" />
662
- View AI Usage
663
- </span>
664
- </DropdownItem>
665
- <DropdownItem
666
- key="impersonate"
667
- onClick={() => handleImpersonateUser(user.id)}
668
- isDisabled={user.banned || isImpersonatingUser}
669
- >
670
- {isImpersonatingUser ? (
671
- <>
672
- <Spinner className="mr-2 h-3 w-3" />
673
- Impersonating...
674
- </>
675
- ) : (
676
- "Impersonate"
677
- )}
678
- </DropdownItem>
679
- <DropdownItem
680
- key="magic-link"
681
- onClick={() => openMagicLinkModal(user.id, user.name, user.email ?? null)}
682
- className={trpc ? "" : "hidden"}
683
- >
684
- <span className="flex items-center gap-2">
685
- <Link2 className="h-4 w-4" />
686
- Generate Magic Login Link
687
- </span>
688
- </DropdownItem>
689
- {user.banned ? (
690
- <DropdownItem
691
- key="unban"
692
- onClick={() => handleUnbanUser(user.id)}
693
- isDisabled={isUnbanningUser[user.id]}
694
- >
695
- {isUnbanningUser[user.id] ? (
696
- <>
697
- <Spinner className="mr-2 h-3 w-3" />
698
- Unbanning...
699
- </>
700
- ) : (
701
- "Unban"
702
- )}
703
- </DropdownItem>
704
- ) : (
705
- <DropdownItem key="ban" onClick={() => openBanModal(user.id, user.name)}>
706
- Ban
707
- </DropdownItem>
708
- )}
709
- <DropdownItem key="remove" onClick={() => confirmDelete(user.id)}>
710
- Remove
711
- </DropdownItem>
712
- </DropdownMenu>
713
- </Dropdown>
714
- </TableCell>
715
- </TableRow>
716
- );
717
- }}
718
- </TableBody>
719
- </Table>
720
- </div>
721
-
722
- {/* Pagination */}
723
- {totalPages > 0 && (
724
- <div className="flex items-center justify-end space-x-2 py-4">
725
- <Button
726
- variant="bordered"
727
- size="sm"
728
- onClick={() => handlePageChange(page - 1)}
729
- isDisabled={page === 0}
730
- >
731
- Previous
732
- </Button>
733
- <div className="text-sm text-muted-foreground">
734
- Page {page + 1} of {totalPages}
735
- </div>
736
- <Button
737
- variant="bordered"
738
- size="sm"
739
- onClick={() => handlePageChange(page + 1)}
740
- isDisabled={page === totalPages - 1}
741
- >
742
- Next
743
- </Button>
744
- </div>
745
- )}
746
-
747
- {/* Delete confirmation modal */}
748
- <Modal
749
- isOpen={!!userToDelete}
750
- onOpenChange={(open) => {
751
- if (!open) setUserToDelete(null);
752
- }}
753
- >
754
- <ModalContent>
755
- {(onClose) => (
756
- <>
757
- <ModalHeader className="flex flex-col gap-1">
758
- <p className="text-lg font-semibold">Are you sure?</p>
759
- <p className="text-sm text-default-600">
760
- This action cannot be undone. This will permanently delete the user and all their
761
- data.
762
- </p>
763
- </ModalHeader>
764
- <ModalFooter>
765
- <Button variant="bordered" onPress={onClose}>
766
- Cancel
767
- </Button>
768
- <Button color="danger" onPress={handleDelete} isLoading={isDeleting}>
769
- Delete
770
- </Button>
771
- </ModalFooter>
772
- </>
773
- )}
774
- </ModalContent>
775
- </Modal>
776
-
777
- {/* Ban user modal */}
778
- <Modal
779
- isOpen={!!userToBan}
780
- onOpenChange={(open) => {
781
- if (!open) setUserToBan(null);
782
- }}
783
- >
784
- <ModalContent>
785
- {(onClose) => (
786
- <form
787
- onSubmit={(e) => {
788
- e.preventDefault();
789
- handleBanUser();
790
- }}
791
- className="space-y-4"
792
- >
793
- <ModalHeader className="flex flex-col gap-1">
794
- <p className="text-lg font-semibold">Ban User</p>
795
- <p className="text-sm text-default-600">
796
- {userToBan &&
797
- `You are about to ban ${userToBan.name}. This will prevent them from signing in.`}
798
- </p>
799
- </ModalHeader>
800
-
801
- <ModalBody className="space-y-4">
802
- <div className="space-y-2">
803
- <label htmlFor={banReasonInputId} className="text-sm font-medium">
804
- Ban Reason
805
- </label>
806
- <Input
807
- id={banReasonInputId}
808
- placeholder="Enter reason for ban"
809
- value={banReason}
810
- onChange={(e) => setBanReason(e.target.value)}
811
- variant="bordered"
812
- labelPlacement="outside"
813
- label="Ban Reason"
814
- />
815
- </div>
816
-
817
- <div className="space-y-2">
818
- <p className="text-sm font-medium">Ban Duration</p>
819
- <div className="grid grid-cols-2 gap-2">
820
- <Button
821
- type="button"
822
- variant={banExpiry === "never" ? "solid" : "bordered"}
823
- onClick={() => setBanExpiry("never")}
824
- >
825
- Permanent
826
- </Button>
827
- <Button
828
- type="button"
829
- variant={banExpiry === "1d" ? "solid" : "bordered"}
830
- onClick={() => setBanExpiry("1d")}
831
- >
832
- 1 Day
833
- </Button>
834
- <Button
835
- type="button"
836
- variant={banExpiry === "7d" ? "solid" : "bordered"}
837
- onClick={() => setBanExpiry("7d")}
838
- >
839
- 7 Days
840
- </Button>
841
- <Button
842
- type="button"
843
- variant={banExpiry === "30d" ? "solid" : "bordered"}
844
- onClick={() => setBanExpiry("30d")}
845
- >
846
- 30 Days
847
- </Button>
848
- </div>
849
- </div>
850
-
851
- <div className="space-y-2">
852
- <div className="flex items-center gap-2">
853
- <input
854
- type="checkbox"
855
- id={customDurationId}
856
- checked={banExpiry === "custom"}
857
- onChange={(e) =>
858
- e.target.checked ? setBanExpiry("custom") : setBanExpiry("never")
859
- }
860
- />
861
- <label htmlFor={customDurationId} className="text-sm font-medium">
862
- Custom Duration
863
- </label>
864
- </div>
865
-
866
- {banExpiry === "custom" && (
867
- <div className="flex items-center gap-2">
868
- <Input
869
- type="number"
870
- min="1"
871
- value={customBanDays.toString()}
872
- onChange={(e) => setCustomBanDays(Number(e.target.value))}
873
- variant="bordered"
874
- labelPlacement="outside"
875
- label="Custom duration (days)"
876
- />
877
- <span className="text-sm">Days</span>
878
- </div>
879
- )}
880
- </div>
881
- </ModalBody>
882
-
883
- <ModalFooter>
884
- <Button variant="bordered" type="button" onPress={onClose}>
885
- Cancel
886
- </Button>
887
- <Button color="danger" type="submit" isDisabled={isBanningUser}>
888
- {isBanningUser ? <Spinner className="mr-2 h-4 w-4" /> : null}
889
- {isBanningUser ? "Banning..." : "Ban User"}
890
- </Button>
891
- </ModalFooter>
892
- </form>
893
- )}
894
- </ModalContent>
895
- </Modal>
896
-
897
- {/* Create user modal */}
898
- <Modal
899
- isOpen={isCreateUserModalOpen}
900
- onOpenChange={(open) => {
901
- if (!open) setIsCreateUserModalOpen(false);
902
- }}
903
- >
904
- <ModalContent>
905
- {(onClose) => (
906
- <form
907
- onSubmit={(e) => {
908
- e.preventDefault();
909
- handleCreateUser();
910
- }}
911
- className="space-y-4"
912
- >
913
- <ModalHeader className="flex flex-col gap-1">
914
- <p className="text-lg font-semibold">Create New User</p>
915
- <p className="text-sm text-default-600">
916
- Fill in the details below to create a new user account.
917
- </p>
918
- </ModalHeader>
919
-
920
- <ModalBody className="space-y-4">
921
- <div className="space-y-2">
922
- <Input
923
- id={nameInputId}
924
- label="Name *"
925
- labelPlacement="outside"
926
- placeholder="Enter user's name"
927
- value={newUserData.name}
928
- onChange={(e) => handleNewUserDataChange("name", e.target.value)}
929
- variant="bordered"
930
- />
931
- </div>
932
-
933
- <div className="space-y-2">
934
- <Input
935
- id={emailInputId}
936
- label="Email *"
937
- labelPlacement="outside"
938
- type="email"
939
- placeholder="Enter user's email"
940
- value={newUserData.email}
941
- onChange={(e) => handleNewUserDataChange("email", e.target.value)}
942
- variant="bordered"
943
- />
944
- </div>
945
-
946
- <div className="space-y-2">
947
- <Input
948
- id={passwordInputId}
949
- label="Password *"
950
- labelPlacement="outside"
951
- type="password"
952
- placeholder="Enter password"
953
- value={newUserData.password}
954
- onChange={(e) => handleNewUserDataChange("password", e.target.value)}
955
- variant="bordered"
956
- />
957
- </div>
958
-
959
- <div className="space-y-2">
960
- <p className="text-sm font-medium" id={roleSelectId}>
961
- Role
962
- </p>
963
- <Select
964
- aria-label="Select role"
965
- aria-labelledby={roleSelectId}
966
- selectedKeys={new Set([newUserData.role])}
967
- onSelectionChange={(keys) =>
968
- handleNewUserDataChange("role", Array.from(keys)[0] as string)
969
- }
970
- >
971
- <SelectItem key="user">User</SelectItem>
972
- <SelectItem key="admin">Admin</SelectItem>
973
- </Select>
974
- </div>
975
- </ModalBody>
976
-
977
- <ModalFooter>
978
- <Button variant="bordered" type="button" onPress={onClose}>
979
- Cancel
980
- </Button>
981
- <Button type="submit" color="primary" isDisabled={isCreatingUser}>
982
- {isCreatingUser ? <Spinner className="mr-2 h-4 w-4" /> : null}
983
- {isCreatingUser ? "Creating..." : "Create User"}
984
- </Button>
985
- </ModalFooter>
986
- </form>
987
- )}
988
- </ModalContent>
989
- </Modal>
990
-
991
- {/* AI Usage modal */}
992
- <Modal
993
- isOpen={!!userForUsage}
994
- onOpenChange={(open) => {
995
- if (!open) setUserForUsage(null);
996
- }}
997
- >
998
- <ModalContent>
999
- {(onClose) => (
1000
- <>
1001
- <ModalHeader className="flex flex-col gap-1">
1002
- <p className="text-lg font-semibold flex items-center gap-2">
1003
- <BarChart3 className="h-5 w-5" />
1004
- AI Usage
1005
- </p>
1006
- <p className="text-sm text-default-600">
1007
- {userForUsage && `Usage statistics for ${userForUsage.name}`}
1008
- </p>
1009
- </ModalHeader>
1010
-
1011
- <ModalBody>
1012
- {isLoadingUsage ? (
1013
- <div className="flex justify-center py-8">
1014
- <Spinner />
1015
- </div>
1016
- ) : usageData ? (
1017
- <div className="space-y-4">
1018
- <div className="grid grid-cols-2 gap-4">
1019
- <div className="p-4 rounded-lg bg-default-100">
1020
- <p className="text-sm text-default-600">Input Tokens</p>
1021
- <p className="text-2xl font-semibold">
1022
- {formatTokenCount(usageData.inputTokens)}
1023
- </p>
1024
- </div>
1025
- <div className="p-4 rounded-lg bg-default-100">
1026
- <p className="text-sm text-default-600">Output Tokens</p>
1027
- <p className="text-2xl font-semibold">
1028
- {formatTokenCount(usageData.outputTokens)}
1029
- </p>
1030
- </div>
1031
- <div className="p-4 rounded-lg bg-default-100">
1032
- <p className="text-sm text-default-600">Total Tokens</p>
1033
- <p className="text-2xl font-semibold">
1034
- {formatTokenCount(usageData.totalTokens)}
1035
- </p>
1036
- </div>
1037
- <div className="p-4 rounded-lg bg-primary-100">
1038
- <p className="text-sm text-primary-600">Estimated Cost</p>
1039
- <p className="text-2xl font-semibold text-primary">
1040
- {formatCost(usageData.cost)}
1041
- </p>
1042
- </div>
1043
- </div>
1044
- </div>
1045
- ) : (
1046
- <div className="text-center py-8 text-default-600">No usage data available</div>
1047
- )}
1048
- </ModalBody>
1049
-
1050
- <ModalFooter>
1051
- <Button variant="bordered" onPress={onClose}>
1052
- Close
1053
- </Button>
1054
- </ModalFooter>
1055
- </>
1056
- )}
1057
- </ModalContent>
1058
- </Modal>
1059
-
1060
- {/* Magic login link modal */}
1061
- <Modal
1062
- isOpen={!!userForMagicLink}
1063
- onOpenChange={(open) => {
1064
- if (!open) {
1065
- setUserForMagicLink(null);
1066
- setGeneratedMagicLink(null);
1067
- setClaimEmail("");
1068
- }
1069
- }}
1070
- >
1071
- <ModalContent>
1072
- {(onClose) => (
1073
- <form
1074
- onSubmit={(e) => {
1075
- e.preventDefault();
1076
- handleGenerateMagicLink();
1077
- }}
1078
- className="space-y-4"
1079
- >
1080
- <ModalHeader className="flex flex-col gap-1">
1081
- <p className="text-lg font-semibold">Generate Magic Login Link</p>
1082
- <p className="text-sm text-default-600">
1083
- {userForMagicLink &&
1084
- `Generate a one-time claim sign-in link for ${userForMagicLink.name}.`}
1085
- </p>
1086
- </ModalHeader>
1087
-
1088
- <ModalBody className="space-y-4">
1089
- <Input
1090
- id={magicLinkEmailInputId}
1091
- label="Claim email"
1092
- labelPlacement="outside"
1093
- type="email"
1094
- placeholder="person@example.com"
1095
- value={claimEmail}
1096
- onChange={(e) => setClaimEmail(e.target.value)}
1097
- variant="bordered"
1098
- description="This email is used for provider linking and future sign-ins."
1099
- />
1100
-
1101
- <Input
1102
- id={generatedMagicLinkInputId}
1103
- label="Generated link"
1104
- labelPlacement="outside"
1105
- value={generatedMagicLink ?? ""}
1106
- readOnly
1107
- variant="bordered"
1108
- placeholder="Generate link to see it here"
1109
- />
1110
- </ModalBody>
1111
-
1112
- <ModalFooter>
1113
- <Button variant="bordered" type="button" onPress={onClose}>
1114
- Close
1115
- </Button>
1116
- <Button
1117
- variant="bordered"
1118
- type="button"
1119
- onPress={copyGeneratedMagicLink}
1120
- isDisabled={!generatedMagicLink}
1121
- >
1122
- <Copy className="h-4 w-4 mr-2" />
1123
- Copy Link
1124
- </Button>
1125
- <Button color="primary" type="submit" isDisabled={isGeneratingMagicLink}>
1126
- {isGeneratingMagicLink ? <Spinner className="mr-2 h-4 w-4" /> : null}
1127
- {isGeneratingMagicLink ? "Generating..." : "Generate Link"}
1128
- </Button>
1129
- </ModalFooter>
1130
- </form>
1131
- )}
1132
- </ModalContent>
1133
- </Modal>
1134
- </div>
1135
- );
1136
- }