@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.
- package/dist/src/animations/card.motion.d.ts +0 -1
- package/dist/src/components/AvatarUpload.d.ts +0 -1
- package/dist/src/components/Button.d.ts +0 -1
- package/dist/src/components/Calendar.d.ts +0 -1
- package/dist/src/components/CardsSelect.d.ts +0 -1
- package/dist/src/components/CollapsibleSidebarMenuItem.d.ts +0 -1
- package/dist/src/components/ColorPicker.d.ts +0 -1
- package/dist/src/components/CopyButton.d.ts +0 -1
- package/dist/src/components/CropDialog.d.ts +0 -1
- package/dist/src/components/DialogProvider.d.ts +0 -1
- package/dist/src/components/ErrorFallback.d.ts +0 -1
- package/dist/src/components/FileDropzone.d.ts +0 -1
- package/dist/src/components/MultiSelectDropdown.d.ts +0 -1
- package/dist/src/components/Orb.d.ts +0 -1
- package/dist/src/components/PageAlert.d.ts +0 -1
- package/dist/src/components/SelectChips.d.ts +0 -1
- package/dist/src/components/SidebarItem.d.ts +0 -1
- package/dist/src/components/Steps.d.ts +0 -1
- package/dist/src/components/TablerIconPicker.d.ts +0 -1
- package/dist/src/components/app-header.d.ts +0 -1
- package/dist/src/components/blur-card.d.ts +0 -1
- package/dist/src/components/features-section-demo-1.d.ts +0 -1
- package/dist/src/components/features-section-demo-2.d.ts +0 -1
- package/dist/src/components/features-section-demo-3.d.ts +0 -1
- package/dist/src/components/mode-toggle.d.ts +0 -1
- package/dist/src/components/nav-main.d.ts +0 -1
- package/dist/src/components/pricing-cards.d.ts +0 -1
- package/dist/src/components/shared/ButtonCopy.d.ts +0 -1
- package/dist/src/components/team-switcher.d.ts +0 -1
- package/dist/src/components/theme-provider.d.ts +0 -1
- package/dist/src/components/typewriter.d.ts +0 -1
- package/dist/src/components/ui/alert-dialog.d.ts +0 -1
- package/dist/src/components/ui/alert.d.ts +0 -1
- package/dist/src/components/ui/avatar.d.ts +0 -1
- package/dist/src/components/ui/badge.d.ts +0 -1
- package/dist/src/components/ui/bento-grid.d.ts +0 -1
- package/dist/src/components/ui/bento-grid2.d.ts +0 -1
- package/dist/src/components/ui/breadcrumb.d.ts +0 -1
- package/dist/src/components/ui/button.d.ts +0 -1
- package/dist/src/components/ui/card.d.ts +0 -1
- package/dist/src/components/ui/checkbox.d.ts +0 -1
- package/dist/src/components/ui/collapsible.d.ts +0 -1
- package/dist/src/components/ui/dialog.d.ts +0 -1
- package/dist/src/components/ui/dropdown-menu.d.ts +0 -1
- package/dist/src/components/ui/floating-navbar.d.ts +0 -1
- package/dist/src/components/ui/form.d.ts +0 -1
- package/dist/src/components/ui/image.d.ts +0 -1
- package/dist/src/components/ui/input.d.ts +0 -1
- package/dist/src/components/ui/label.d.ts +0 -1
- package/dist/src/components/ui/pagination.d.ts +0 -1
- package/dist/src/components/ui/progress.d.ts +0 -1
- package/dist/src/components/ui/resizable-navbar.d.ts +0 -1
- package/dist/src/components/ui/segment-control.d.ts +0 -1
- package/dist/src/components/ui/select.d.ts +0 -1
- package/dist/src/components/ui/separator.d.ts +0 -1
- package/dist/src/components/ui/sheet.d.ts +0 -1
- package/dist/src/components/ui/sidebar.d.ts +0 -1
- package/dist/src/components/ui/skeleton.d.ts +0 -1
- package/dist/src/components/ui/slider.d.ts +0 -1
- package/dist/src/components/ui/sonner.d.ts +0 -1
- package/dist/src/components/ui/spinner.d.ts +0 -1
- package/dist/src/components/ui/switch.d.ts +0 -1
- package/dist/src/components/ui/table.d.ts +0 -1
- package/dist/src/components/ui/tabs.d.ts +0 -1
- package/dist/src/components/ui/textarea.d.ts +0 -1
- package/dist/src/components/ui/timeline.d.ts +0 -1
- package/dist/src/components/ui/toast.d.ts +0 -1
- package/dist/src/components/ui/tooltip.d.ts +0 -1
- package/dist/src/components/ui/typewriter-effect.d.ts +0 -1
- package/dist/src/hooks/use-mobile.d.ts +0 -1
- package/dist/src/hooks/useDialog.d.ts +0 -1
- package/dist/src/icons/GoogleIcon.d.ts +0 -1
- package/dist/src/icons/LinkedInIcon.d.ts +0 -1
- package/dist/src/icons/MicrosoftIcon.d.ts +0 -1
- package/dist/src/lib/chatwoot.d.ts +0 -1
- package/dist/src/lib/utils.d.ts +0 -1
- package/dist/src/modules/app/components/AppLoader.d.ts +0 -1
- package/dist/src/modules/app/components/AppShell.d.ts +0 -1
- package/dist/src/modules/app/components/AppSidebar.d.ts +0 -1
- package/dist/src/modules/app/components/AppSidebarContent.d.ts +0 -1
- package/dist/src/modules/app/components/AppSidebarHeader.d.ts +0 -1
- package/dist/src/modules/app/components/AppSidebarInvites.d.ts +0 -1
- package/dist/src/modules/app/components/AppSidebarUser.d.ts +0 -1
- package/dist/src/modules/auth/components/AdminUserManagement.d.ts +0 -1
- package/dist/src/modules/auth/components/AdminWaitlist.d.ts +0 -1
- package/dist/src/modules/auth/components/AuthLayout.d.ts +0 -1
- package/dist/src/modules/auth/components/AuthProviders.d.ts +0 -1
- package/dist/src/modules/auth/components/AuthRouter.d.ts +0 -1
- package/dist/src/modules/auth/components/ClaimAccountRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/ErrorAuthRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/ForgotPasswordForm.d.ts +0 -1
- package/dist/src/modules/auth/components/ForgotPasswordRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/InviteFriends.d.ts +0 -1
- package/dist/src/modules/auth/components/LastUsedBadge.d.ts +0 -1
- package/dist/src/modules/auth/components/LoginForm.d.ts +0 -1
- package/dist/src/modules/auth/components/LoginRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/LogoutRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/OrganizationAcceptInvitationRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/OrganizationMembersRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/OrganizationSettingsRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/OrganizationSwitcher.d.ts +0 -1
- package/dist/src/modules/auth/components/ProfileRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/RangeNuqsDatePicker.d.ts +0 -1
- package/dist/src/modules/auth/components/ResetPasswordForm.d.ts +0 -1
- package/dist/src/modules/auth/components/ResetPasswordRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/SignupFormRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/SignupRoute.d.ts +0 -1
- package/dist/src/modules/auth/components/UserPreferences.d.ts +0 -1
- package/dist/src/modules/auth/components/WaitlistCard.d.ts +0 -1
- package/dist/src/modules/auth/components/WaitlistCodeValidation.d.ts +0 -1
- package/dist/src/modules/billing/components/BillingBetaPage.d.ts +0 -1
- package/dist/src/modules/billing/components/BillingInvoicePage.d.ts +0 -1
- package/dist/src/modules/billing/components/BillingPlanSelect.d.ts +0 -1
- package/dist/src/modules/billing/components/BillingRouter.d.ts +0 -1
- package/dist/src/modules/billing/components/BillingSinglePlanSelect.d.ts +0 -1
- package/dist/src/modules/table/components/ColumnOrderAndVisibility.d.ts +0 -1
- package/dist/src/modules/table/components/NuqsTable.d.ts +0 -1
- package/dist/src/modules/table/components/TableFiltering.d.ts +0 -1
- package/dist/src/modules/table/components/TablePagination.d.ts +0 -1
- package/dist/src/modules/table/components/table.types.d.ts +0 -1
- package/dist/src/modules/table/filterTransformers.d.ts +0 -1
- package/dist/src/types.d.ts +0 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +8 -5
- package/.cursor/rules/web-ui.mdc +0 -126
- package/.turbo/turbo-build.log +0 -5
- package/.turbo/turbo-check-types.log +0 -5
- package/.turbo/turbo-lint$colon$fix.log +0 -381
- package/.turbo/turbo-lint.log +0 -361
- package/CHANGELOG.md +0 -25
- package/components.json +0 -21
- package/dist/src/animations/card.motion.d.ts.map +0 -1
- package/dist/src/components/AvatarUpload.d.ts.map +0 -1
- package/dist/src/components/Button.d.ts.map +0 -1
- package/dist/src/components/Calendar.d.ts.map +0 -1
- package/dist/src/components/CardsSelect.d.ts.map +0 -1
- package/dist/src/components/CollapsibleSidebarMenuItem.d.ts.map +0 -1
- package/dist/src/components/ColorPicker.d.ts.map +0 -1
- package/dist/src/components/CopyButton.d.ts.map +0 -1
- package/dist/src/components/CropDialog.d.ts.map +0 -1
- package/dist/src/components/DialogProvider.d.ts.map +0 -1
- package/dist/src/components/ErrorFallback.d.ts.map +0 -1
- package/dist/src/components/FileDropzone.d.ts.map +0 -1
- package/dist/src/components/MultiSelectDropdown.d.ts.map +0 -1
- package/dist/src/components/Orb.d.ts.map +0 -1
- package/dist/src/components/PageAlert.d.ts.map +0 -1
- package/dist/src/components/SelectChips.d.ts.map +0 -1
- package/dist/src/components/SidebarItem.d.ts.map +0 -1
- package/dist/src/components/Steps.d.ts.map +0 -1
- package/dist/src/components/TablerIconPicker.d.ts.map +0 -1
- package/dist/src/components/app-header.d.ts.map +0 -1
- package/dist/src/components/blur-card.d.ts.map +0 -1
- package/dist/src/components/features-section-demo-1.d.ts.map +0 -1
- package/dist/src/components/features-section-demo-2.d.ts.map +0 -1
- package/dist/src/components/features-section-demo-3.d.ts.map +0 -1
- package/dist/src/components/mode-toggle.d.ts.map +0 -1
- package/dist/src/components/nav-main.d.ts.map +0 -1
- package/dist/src/components/pricing-cards.d.ts.map +0 -1
- package/dist/src/components/shared/ButtonCopy.d.ts.map +0 -1
- package/dist/src/components/team-switcher.d.ts.map +0 -1
- package/dist/src/components/theme-provider.d.ts.map +0 -1
- package/dist/src/components/typewriter.d.ts.map +0 -1
- package/dist/src/components/ui/alert-dialog.d.ts.map +0 -1
- package/dist/src/components/ui/alert.d.ts.map +0 -1
- package/dist/src/components/ui/avatar.d.ts.map +0 -1
- package/dist/src/components/ui/badge.d.ts.map +0 -1
- package/dist/src/components/ui/bento-grid.d.ts.map +0 -1
- package/dist/src/components/ui/bento-grid2.d.ts.map +0 -1
- package/dist/src/components/ui/breadcrumb.d.ts.map +0 -1
- package/dist/src/components/ui/button.d.ts.map +0 -1
- package/dist/src/components/ui/card.d.ts.map +0 -1
- package/dist/src/components/ui/checkbox.d.ts.map +0 -1
- package/dist/src/components/ui/collapsible.d.ts.map +0 -1
- package/dist/src/components/ui/dialog.d.ts.map +0 -1
- package/dist/src/components/ui/dropdown-menu.d.ts.map +0 -1
- package/dist/src/components/ui/floating-navbar.d.ts.map +0 -1
- package/dist/src/components/ui/form.d.ts.map +0 -1
- package/dist/src/components/ui/image.d.ts.map +0 -1
- package/dist/src/components/ui/input.d.ts.map +0 -1
- package/dist/src/components/ui/label.d.ts.map +0 -1
- package/dist/src/components/ui/pagination.d.ts.map +0 -1
- package/dist/src/components/ui/progress.d.ts.map +0 -1
- package/dist/src/components/ui/resizable-navbar.d.ts.map +0 -1
- package/dist/src/components/ui/segment-control.d.ts.map +0 -1
- package/dist/src/components/ui/select.d.ts.map +0 -1
- package/dist/src/components/ui/separator.d.ts.map +0 -1
- package/dist/src/components/ui/sheet.d.ts.map +0 -1
- package/dist/src/components/ui/sidebar.d.ts.map +0 -1
- package/dist/src/components/ui/skeleton.d.ts.map +0 -1
- package/dist/src/components/ui/slider.d.ts.map +0 -1
- package/dist/src/components/ui/sonner.d.ts.map +0 -1
- package/dist/src/components/ui/spinner.d.ts.map +0 -1
- package/dist/src/components/ui/switch.d.ts.map +0 -1
- package/dist/src/components/ui/table.d.ts.map +0 -1
- package/dist/src/components/ui/tabs.d.ts.map +0 -1
- package/dist/src/components/ui/textarea.d.ts.map +0 -1
- package/dist/src/components/ui/timeline.d.ts.map +0 -1
- package/dist/src/components/ui/toast.d.ts.map +0 -1
- package/dist/src/components/ui/tooltip.d.ts.map +0 -1
- package/dist/src/components/ui/typewriter-effect.d.ts.map +0 -1
- package/dist/src/hooks/use-mobile.d.ts.map +0 -1
- package/dist/src/hooks/useDialog.d.ts.map +0 -1
- package/dist/src/icons/GoogleIcon.d.ts.map +0 -1
- package/dist/src/icons/LinkedInIcon.d.ts.map +0 -1
- package/dist/src/icons/MicrosoftIcon.d.ts.map +0 -1
- package/dist/src/lib/chatwoot.d.ts.map +0 -1
- package/dist/src/lib/utils.d.ts.map +0 -1
- package/dist/src/modules/app/components/AppLoader.d.ts.map +0 -1
- package/dist/src/modules/app/components/AppShell.d.ts.map +0 -1
- package/dist/src/modules/app/components/AppSidebar.d.ts.map +0 -1
- package/dist/src/modules/app/components/AppSidebarContent.d.ts.map +0 -1
- package/dist/src/modules/app/components/AppSidebarHeader.d.ts.map +0 -1
- package/dist/src/modules/app/components/AppSidebarInvites.d.ts.map +0 -1
- package/dist/src/modules/app/components/AppSidebarUser.d.ts.map +0 -1
- package/dist/src/modules/auth/components/AdminUserManagement.d.ts.map +0 -1
- package/dist/src/modules/auth/components/AdminWaitlist.d.ts.map +0 -1
- package/dist/src/modules/auth/components/AuthLayout.d.ts.map +0 -1
- package/dist/src/modules/auth/components/AuthProviders.d.ts.map +0 -1
- package/dist/src/modules/auth/components/AuthRouter.d.ts.map +0 -1
- package/dist/src/modules/auth/components/ClaimAccountRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/ErrorAuthRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/ForgotPasswordForm.d.ts.map +0 -1
- package/dist/src/modules/auth/components/ForgotPasswordRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/InviteFriends.d.ts.map +0 -1
- package/dist/src/modules/auth/components/LastUsedBadge.d.ts.map +0 -1
- package/dist/src/modules/auth/components/LoginForm.d.ts.map +0 -1
- package/dist/src/modules/auth/components/LoginRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/LogoutRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/OrganizationAcceptInvitationRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/OrganizationMembersRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/OrganizationSettingsRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/OrganizationSwitcher.d.ts.map +0 -1
- package/dist/src/modules/auth/components/ProfileRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/RangeNuqsDatePicker.d.ts.map +0 -1
- package/dist/src/modules/auth/components/ResetPasswordForm.d.ts.map +0 -1
- package/dist/src/modules/auth/components/ResetPasswordRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/SignupFormRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/SignupRoute.d.ts.map +0 -1
- package/dist/src/modules/auth/components/UserPreferences.d.ts.map +0 -1
- package/dist/src/modules/auth/components/WaitlistCard.d.ts.map +0 -1
- package/dist/src/modules/auth/components/WaitlistCodeValidation.d.ts.map +0 -1
- package/dist/src/modules/billing/components/BillingBetaPage.d.ts.map +0 -1
- package/dist/src/modules/billing/components/BillingInvoicePage.d.ts.map +0 -1
- package/dist/src/modules/billing/components/BillingPlanSelect.d.ts.map +0 -1
- package/dist/src/modules/billing/components/BillingRouter.d.ts.map +0 -1
- package/dist/src/modules/billing/components/BillingSinglePlanSelect.d.ts.map +0 -1
- package/dist/src/modules/table/components/ColumnOrderAndVisibility.d.ts.map +0 -1
- package/dist/src/modules/table/components/NuqsTable.d.ts.map +0 -1
- package/dist/src/modules/table/components/TableFiltering.d.ts.map +0 -1
- package/dist/src/modules/table/components/TablePagination.d.ts.map +0 -1
- package/dist/src/modules/table/components/table.types.d.ts.map +0 -1
- package/dist/src/modules/table/filterTransformers.d.ts.map +0 -1
- package/dist/src/types.d.ts.map +0 -1
- package/src/animations/card.motion.ts +0 -9
- package/src/components/AvatarUpload.tsx +0 -133
- package/src/components/Button.tsx +0 -14
- package/src/components/Calendar.css +0 -684
- package/src/components/Calendar.tsx +0 -32
- package/src/components/CardsSelect.tsx +0 -155
- package/src/components/CollapsibleSidebarMenuItem.tsx +0 -57
- package/src/components/ColorPicker.tsx +0 -56
- package/src/components/CopyButton.tsx +0 -45
- package/src/components/CropDialog.tsx +0 -154
- package/src/components/DialogProvider.tsx +0 -105
- package/src/components/ErrorFallback.tsx +0 -17
- package/src/components/FileDropzone.tsx +0 -120
- package/src/components/MultiSelectDropdown.tsx +0 -233
- package/src/components/Orb.tsx +0 -288
- package/src/components/PageAlert.tsx +0 -121
- package/src/components/SelectChips.tsx +0 -40
- package/src/components/SidebarItem.tsx +0 -26
- package/src/components/Steps.tsx +0 -340
- package/src/components/TablerIconPicker.tsx +0 -4260
- package/src/components/app-header.tsx +0 -40
- package/src/components/blur-card.tsx +0 -132
- package/src/components/features-section-demo-1.tsx +0 -127
- package/src/components/features-section-demo-2.tsx +0 -102
- package/src/components/features-section-demo-3.tsx +0 -272
- package/src/components/mode-toggle.tsx +0 -31
- package/src/components/nav-main.tsx +0 -69
- package/src/components/pricing-cards.tsx +0 -133
- package/src/components/shared/ButtonCopy.tsx +0 -50
- package/src/components/team-switcher.tsx +0 -83
- package/src/components/theme-provider.tsx +0 -74
- package/src/components/typewriter.tsx +0 -90
- package/src/components/ui/alert-dialog.tsx +0 -133
- package/src/components/ui/alert.tsx +0 -60
- package/src/components/ui/avatar.tsx +0 -47
- package/src/components/ui/badge.tsx +0 -33
- package/src/components/ui/bento-grid.tsx +0 -54
- package/src/components/ui/bento-grid2.tsx +0 -66
- package/src/components/ui/breadcrumb.tsx +0 -101
- package/src/components/ui/button.tsx +0 -50
- package/src/components/ui/card.tsx +0 -55
- package/src/components/ui/checkbox.tsx +0 -26
- package/src/components/ui/collapsible.tsx +0 -9
- package/src/components/ui/dialog.tsx +0 -119
- package/src/components/ui/dropdown-menu.tsx +0 -186
- package/src/components/ui/floating-navbar.tsx +0 -78
- package/src/components/ui/form.tsx +0 -167
- package/src/components/ui/image.tsx +0 -55
- package/src/components/ui/input.tsx +0 -22
- package/src/components/ui/label.tsx +0 -19
- package/src/components/ui/pagination.tsx +0 -105
- package/src/components/ui/progress.tsx +0 -23
- package/src/components/ui/resizable-navbar.tsx +0 -260
- package/src/components/ui/segment-control.tsx +0 -143
- package/src/components/ui/select.tsx +0 -153
- package/src/components/ui/separator.tsx +0 -24
- package/src/components/ui/sheet.tsx +0 -121
- package/src/components/ui/sidebar.tsx +0 -736
- package/src/components/ui/skeleton.tsx +0 -7
- package/src/components/ui/slider.tsx +0 -23
- package/src/components/ui/sonner.tsx +0 -27
- package/src/components/ui/spinner.tsx +0 -45
- package/src/components/ui/switch.tsx +0 -27
- package/src/components/ui/table.tsx +0 -90
- package/src/components/ui/tabs.tsx +0 -52
- package/src/components/ui/textarea.tsx +0 -18
- package/src/components/ui/timeline.tsx +0 -95
- package/src/components/ui/toast.tsx +0 -126
- package/src/components/ui/tooltip.tsx +0 -55
- package/src/components/ui/typewriter-effect.tsx +0 -181
- package/src/hooks/use-mobile.ts +0 -19
- package/src/hooks/useDialog.ts +0 -25
- package/src/icons/GoogleIcon.tsx +0 -32
- package/src/icons/LinkedInIcon.tsx +0 -30
- package/src/icons/MicrosoftIcon.tsx +0 -21
- package/src/lib/chatwoot.ts +0 -51
- package/src/lib/utils.ts +0 -6
- package/src/modules/app/components/AppLoader.tsx +0 -9
- package/src/modules/app/components/AppShell.tsx +0 -21
- package/src/modules/app/components/AppSidebar.tsx +0 -26
- package/src/modules/app/components/AppSidebarContent.tsx +0 -73
- package/src/modules/app/components/AppSidebarHeader.tsx +0 -57
- package/src/modules/app/components/AppSidebarInvites.tsx +0 -32
- package/src/modules/app/components/AppSidebarUser.tsx +0 -128
- package/src/modules/auth/components/AdminUserManagement.tsx +0 -1136
- package/src/modules/auth/components/AdminWaitlist.tsx +0 -358
- package/src/modules/auth/components/AuthLayout.tsx +0 -13
- package/src/modules/auth/components/AuthProviders.tsx +0 -105
- package/src/modules/auth/components/AuthRouter.tsx +0 -29
- package/src/modules/auth/components/ClaimAccountRoute.tsx +0 -242
- package/src/modules/auth/components/ErrorAuthRoute.tsx +0 -121
- package/src/modules/auth/components/ForgotPasswordForm.tsx +0 -58
- package/src/modules/auth/components/ForgotPasswordRoute.tsx +0 -27
- package/src/modules/auth/components/InviteFriends.tsx +0 -273
- package/src/modules/auth/components/LastUsedBadge.tsx +0 -22
- package/src/modules/auth/components/LoginForm.tsx +0 -104
- package/src/modules/auth/components/LoginRoute.tsx +0 -31
- package/src/modules/auth/components/LogoutRoute.tsx +0 -21
- package/src/modules/auth/components/OrganizationAcceptInvitationRoute.tsx +0 -161
- package/src/modules/auth/components/OrganizationMembersRoute.tsx +0 -730
- package/src/modules/auth/components/OrganizationSettingsRoute.tsx +0 -280
- package/src/modules/auth/components/OrganizationSwitcher.tsx +0 -148
- package/src/modules/auth/components/ProfileRoute.tsx +0 -104
- package/src/modules/auth/components/RangeNuqsDatePicker.tsx +0 -365
- package/src/modules/auth/components/ResetPasswordForm.tsx +0 -103
- package/src/modules/auth/components/ResetPasswordRoute.tsx +0 -27
- package/src/modules/auth/components/SignupFormRoute.tsx +0 -189
- package/src/modules/auth/components/SignupRoute.tsx +0 -53
- package/src/modules/auth/components/UserPreferences.tsx +0 -144
- package/src/modules/auth/components/WaitlistCard.tsx +0 -78
- package/src/modules/auth/components/WaitlistCodeValidation.tsx +0 -79
- package/src/modules/billing/components/BillingBetaPage.tsx +0 -124
- package/src/modules/billing/components/BillingInvoicePage.tsx +0 -180
- package/src/modules/billing/components/BillingPlanSelect.tsx +0 -14
- package/src/modules/billing/components/BillingRouter.tsx +0 -20
- package/src/modules/billing/components/BillingSinglePlanSelect.tsx +0 -172
- package/src/modules/table/components/ColumnOrderAndVisibility.tsx +0 -127
- package/src/modules/table/components/NuqsTable.tsx +0 -396
- package/src/modules/table/components/TableFiltering.tsx +0 -520
- package/src/modules/table/components/TablePagination.tsx +0 -59
- package/src/modules/table/components/table.types.ts +0 -11
- package/src/modules/table/filterTransformers.ts +0 -323
- package/src/types.ts +0 -4
- package/src/vite-env.d.ts +0 -1
- package/translations/en/web-ui.json +0 -192
- 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
|
-
}
|