@m5kdev/web-ui 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/src/animations/card.motion.d.ts +3 -0
- package/dist/src/animations/card.motion.d.ts.map +1 -0
- package/dist/src/animations/card.motion.js +7 -0
- package/dist/src/components/AvatarUpload.d.ts +8 -0
- package/dist/src/components/AvatarUpload.d.ts.map +1 -0
- package/dist/src/components/AvatarUpload.js +67 -0
- package/dist/src/components/Button.d.ts +6 -0
- package/dist/src/components/Button.d.ts.map +1 -0
- package/dist/src/components/Button.js +5 -0
- package/dist/src/components/Calendar.d.ts +36 -0
- package/dist/src/components/Calendar.d.ts.map +1 -0
- package/dist/src/components/Calendar.js +10 -0
- package/dist/src/components/CardsSelect.d.ts +24 -0
- package/dist/src/components/CardsSelect.d.ts.map +1 -0
- package/dist/src/components/CardsSelect.js +46 -0
- package/dist/src/components/CollapsibleSidebarMenuItem.d.ts +10 -0
- package/dist/src/components/CollapsibleSidebarMenuItem.d.ts.map +1 -0
- package/dist/src/components/CollapsibleSidebarMenuItem.js +11 -0
- package/dist/src/components/ColorPicker.d.ts +5 -0
- package/dist/src/components/ColorPicker.d.ts.map +1 -0
- package/dist/src/components/ColorPicker.js +21 -0
- package/dist/src/components/CopyButton.d.ts +7 -0
- package/dist/src/components/CopyButton.d.ts.map +1 -0
- package/dist/src/components/CopyButton.js +24 -0
- package/dist/src/components/CropDialog.d.ts +11 -0
- package/dist/src/components/CropDialog.d.ts.map +1 -0
- package/dist/src/components/CropDialog.js +67 -0
- package/dist/src/components/DialogProvider.d.ts +16 -0
- package/dist/src/components/DialogProvider.d.ts.map +1 -0
- package/dist/src/components/DialogProvider.js +50 -0
- package/dist/src/components/ErrorFallback.d.ts +5 -0
- package/dist/src/components/ErrorFallback.d.ts.map +1 -0
- package/dist/src/components/ErrorFallback.js +5 -0
- package/dist/src/components/FileDropzone.d.ts +7 -0
- package/dist/src/components/FileDropzone.d.ts.map +1 -0
- package/dist/src/components/FileDropzone.js +63 -0
- package/dist/src/components/MultiSelectDropdown.d.ts +27 -0
- package/dist/src/components/MultiSelectDropdown.d.ts.map +1 -0
- package/dist/src/components/MultiSelectDropdown.js +53 -0
- package/dist/src/components/Orb.d.ts +7 -0
- package/dist/src/components/Orb.d.ts.map +1 -0
- package/dist/src/components/Orb.js +259 -0
- package/dist/src/components/PageAlert.d.ts +19 -0
- package/dist/src/components/PageAlert.d.ts.map +1 -0
- package/dist/src/components/PageAlert.js +48 -0
- package/dist/src/components/SelectChips.d.ts +11 -0
- package/dist/src/components/SelectChips.d.ts.map +1 -0
- package/dist/src/components/SelectChips.js +11 -0
- package/dist/src/components/SidebarItem.d.ts +8 -0
- package/dist/src/components/SidebarItem.d.ts.map +1 -0
- package/dist/src/components/SidebarItem.js +6 -0
- package/dist/src/components/Steps.d.ts +20 -0
- package/dist/src/components/Steps.d.ts.map +1 -0
- package/dist/src/components/Steps.js +34 -0
- package/dist/src/components/TablerIconPicker.d.ts +3 -0
- package/dist/src/components/TablerIconPicker.d.ts.map +1 -0
- package/dist/src/components/TablerIconPicker.js +4238 -0
- package/dist/src/components/app-header.d.ts +7 -0
- package/dist/src/components/app-header.d.ts.map +1 -0
- package/dist/src/components/app-header.js +8 -0
- package/dist/src/components/blur-card.d.ts +14 -0
- package/dist/src/components/blur-card.d.ts.map +1 -0
- package/dist/src/components/blur-card.js +34 -0
- package/dist/src/components/features-section-demo-1.d.ts +7 -0
- package/dist/src/components/features-section-demo-1.d.ts.map +1 -0
- package/dist/src/components/features-section-demo-1.js +53 -0
- package/dist/src/components/features-section-demo-2.d.ts +2 -0
- package/dist/src/components/features-section-demo-2.d.ts.map +1 -0
- package/dist/src/components/features-section-demo-2.js +51 -0
- package/dist/src/components/features-section-demo-3.d.ts +9 -0
- package/dist/src/components/features-section-demo-3.d.ts.map +1 -0
- package/dist/src/components/features-section-demo-3.js +116 -0
- package/dist/src/components/mode-toggle.d.ts +2 -0
- package/dist/src/components/mode-toggle.d.ts.map +1 -0
- package/dist/src/components/mode-toggle.js +9 -0
- package/dist/src/components/nav-main.d.ts +14 -0
- package/dist/src/components/nav-main.d.ts.map +1 -0
- package/dist/src/components/nav-main.js +9 -0
- package/dist/src/components/pricing-cards.d.ts +2 -0
- package/dist/src/components/pricing-cards.d.ts.map +1 -0
- package/dist/src/components/pricing-cards.js +27 -0
- package/dist/src/components/shared/ButtonCopy.d.ts +7 -0
- package/dist/src/components/shared/ButtonCopy.d.ts.map +1 -0
- package/dist/src/components/shared/ButtonCopy.js +24 -0
- package/dist/src/components/team-switcher.d.ts +9 -0
- package/dist/src/components/team-switcher.d.ts.map +1 -0
- package/dist/src/components/team-switcher.js +10 -0
- package/dist/src/components/theme-provider.d.ts +14 -0
- package/dist/src/components/theme-provider.d.ts.map +1 -0
- package/dist/src/components/theme-provider.js +41 -0
- package/dist/src/components/typewriter.d.ts +19 -0
- package/dist/src/components/typewriter.d.ts.map +1 -0
- package/dist/src/components/typewriter.js +38 -0
- package/dist/src/components/ui/alert-dialog.d.ts +15 -0
- package/dist/src/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/src/components/ui/alert-dialog.js +38 -0
- package/dist/src/components/ui/alert.d.ts +10 -0
- package/dist/src/components/ui/alert.d.ts.map +1 -0
- package/dist/src/components/ui/alert.js +24 -0
- package/dist/src/components/ui/avatar.d.ts +7 -0
- package/dist/src/components/ui/avatar.d.ts.map +1 -0
- package/dist/src/components/ui/avatar.js +12 -0
- package/dist/src/components/ui/badge.d.ts +10 -0
- package/dist/src/components/ui/badge.d.ts.map +1 -0
- package/dist/src/components/ui/badge.js +20 -0
- package/dist/src/components/ui/bento-grid.d.ts +12 -0
- package/dist/src/components/ui/bento-grid.d.ts.map +1 -0
- package/dist/src/components/ui/bento-grid.js +8 -0
- package/dist/src/components/ui/bento-grid2.d.ts +16 -0
- package/dist/src/components/ui/bento-grid2.d.ts.map +1 -0
- package/dist/src/components/ui/bento-grid2.js +13 -0
- package/dist/src/components/ui/breadcrumb.d.ts +20 -0
- package/dist/src/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/src/components/ui/breadcrumb.js +23 -0
- package/dist/src/components/ui/button.d.ts +12 -0
- package/dist/src/components/ui/button.d.ts.map +1 -0
- package/dist/src/components/ui/button.js +33 -0
- package/dist/src/components/ui/card.d.ts +9 -0
- package/dist/src/components/ui/card.d.ts.map +1 -0
- package/dist/src/components/ui/card.js +16 -0
- package/dist/src/components/ui/checkbox.d.ts +5 -0
- package/dist/src/components/ui/checkbox.d.ts.map +1 -0
- package/dist/src/components/ui/checkbox.js +8 -0
- package/dist/src/components/ui/collapsible.d.ts +6 -0
- package/dist/src/components/ui/collapsible.d.ts.map +1 -0
- package/dist/src/components/ui/collapsible.js +5 -0
- package/dist/src/components/ui/dialog.d.ts +14 -0
- package/dist/src/components/ui/dialog.d.ts.map +1 -0
- package/dist/src/components/ui/dialog.js +35 -0
- package/dist/src/components/ui/dropdown-menu.d.ts +28 -0
- package/dist/src/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/src/components/ui/dropdown-menu.js +32 -0
- package/dist/src/components/ui/floating-navbar.d.ts +10 -0
- package/dist/src/components/ui/floating-navbar.d.ts.map +1 -0
- package/dist/src/components/ui/floating-navbar.js +38 -0
- package/dist/src/components/ui/form.d.ts +24 -0
- package/dist/src/components/ui/form.d.ts.map +1 -0
- package/dist/src/components/ui/form.js +60 -0
- package/dist/src/components/ui/image.d.ts +7 -0
- package/dist/src/components/ui/image.d.ts.map +1 -0
- package/dist/src/components/ui/image.js +15 -0
- package/dist/src/components/ui/input.d.ts +4 -0
- package/dist/src/components/ui/input.d.ts.map +1 -0
- package/dist/src/components/ui/input.js +8 -0
- package/dist/src/components/ui/label.d.ts +6 -0
- package/dist/src/components/ui/label.d.ts.map +1 -0
- package/dist/src/components/ui/label.js +9 -0
- package/dist/src/components/ui/pagination.d.ts +14 -0
- package/dist/src/components/ui/pagination.d.ts.map +1 -0
- package/dist/src/components/ui/pagination.js +29 -0
- package/dist/src/components/ui/progress.d.ts +5 -0
- package/dist/src/components/ui/progress.d.ts.map +1 -0
- package/dist/src/components/ui/progress.js +7 -0
- package/dist/src/components/ui/resizable-navbar.d.ts +57 -0
- package/dist/src/components/ui/resizable-navbar.d.ts.map +1 -0
- package/dist/src/components/ui/resizable-navbar.js +86 -0
- package/dist/src/components/ui/segment-control.d.ts +10 -0
- package/dist/src/components/ui/segment-control.d.ts.map +1 -0
- package/dist/src/components/ui/segment-control.js +42 -0
- package/dist/src/components/ui/select.d.ts +14 -0
- package/dist/src/components/ui/select.d.ts.map +1 -0
- package/dist/src/components/ui/select.js +26 -0
- package/dist/src/components/ui/separator.d.ts +5 -0
- package/dist/src/components/ui/separator.d.ts.map +1 -0
- package/dist/src/components/ui/separator.js +7 -0
- package/dist/src/components/ui/sheet.d.ts +26 -0
- package/dist/src/components/ui/sheet.d.ts.map +1 -0
- package/dist/src/components/ui/sheet.js +37 -0
- package/dist/src/components/ui/sidebar.d.ts +67 -0
- package/dist/src/components/ui/sidebar.d.ts.map +1 -0
- package/dist/src/components/ui/sidebar.js +222 -0
- package/dist/src/components/ui/skeleton.d.ts +3 -0
- package/dist/src/components/ui/skeleton.d.ts.map +1 -0
- package/dist/src/components/ui/skeleton.js +6 -0
- package/dist/src/components/ui/slider.d.ts +5 -0
- package/dist/src/components/ui/slider.d.ts.map +1 -0
- package/dist/src/components/ui/slider.js +7 -0
- package/dist/src/components/ui/sonner.d.ts +5 -0
- package/dist/src/components/ui/sonner.d.ts.map +1 -0
- package/dist/src/components/ui/sonner.js +15 -0
- package/dist/src/components/ui/spinner.d.ts +15 -0
- package/dist/src/components/ui/spinner.d.ts.map +1 -0
- package/dist/src/components/ui/spinner.js +30 -0
- package/dist/src/components/ui/switch.d.ts +5 -0
- package/dist/src/components/ui/switch.d.ts.map +1 -0
- package/dist/src/components/ui/switch.js +7 -0
- package/dist/src/components/ui/table.d.ts +11 -0
- package/dist/src/components/ui/table.d.ts.map +1 -0
- package/dist/src/components/ui/table.js +27 -0
- package/dist/src/components/ui/tabs.d.ts +8 -0
- package/dist/src/components/ui/tabs.d.ts.map +1 -0
- package/dist/src/components/ui/tabs.js +16 -0
- package/dist/src/components/ui/textarea.d.ts +4 -0
- package/dist/src/components/ui/textarea.d.ts.map +1 -0
- package/dist/src/components/ui/textarea.js +6 -0
- package/dist/src/components/ui/timeline.d.ts +12 -0
- package/dist/src/components/ui/timeline.d.ts.map +1 -0
- package/dist/src/components/ui/timeline.js +27 -0
- package/dist/src/components/ui/toast.d.ts +16 -0
- package/dist/src/components/ui/toast.d.ts.map +1 -0
- package/dist/src/components/ui/toast.js +33 -0
- package/dist/src/components/ui/tooltip.d.ts +8 -0
- package/dist/src/components/ui/tooltip.d.ts.map +1 -0
- package/dist/src/components/ui/tooltip.js +16 -0
- package/dist/src/components/ui/typewriter-effect.d.ts +17 -0
- package/dist/src/components/ui/typewriter-effect.d.ts.map +1 -0
- package/dist/src/components/ui/typewriter-effect.js +76 -0
- package/dist/src/hooks/use-mobile.d.ts +2 -0
- package/dist/src/hooks/use-mobile.d.ts.map +1 -0
- package/dist/src/hooks/use-mobile.js +15 -0
- package/dist/src/hooks/useDialog.d.ts +5 -0
- package/dist/src/hooks/useDialog.d.ts.map +1 -0
- package/dist/src/hooks/useDialog.js +22 -0
- package/dist/src/icons/GoogleIcon.d.ts +6 -0
- package/dist/src/icons/GoogleIcon.d.ts.map +1 -0
- package/dist/src/icons/GoogleIcon.js +4 -0
- package/dist/src/icons/LinkedInIcon.d.ts +6 -0
- package/dist/src/icons/LinkedInIcon.d.ts.map +1 -0
- package/dist/src/icons/LinkedInIcon.js +4 -0
- package/dist/src/icons/MicrosoftIcon.d.ts +6 -0
- package/dist/src/icons/MicrosoftIcon.d.ts.map +1 -0
- package/dist/src/icons/MicrosoftIcon.js +4 -0
- package/dist/src/lib/chatwoot.d.ts +12 -0
- package/dist/src/lib/chatwoot.d.ts.map +1 -0
- package/dist/src/lib/chatwoot.js +28 -0
- package/dist/src/lib/utils.d.ts +3 -0
- package/dist/src/lib/utils.d.ts.map +1 -0
- package/dist/src/lib/utils.js +5 -0
- package/dist/src/modules/app/components/AppLoader.d.ts +3 -0
- package/dist/src/modules/app/components/AppLoader.d.ts.map +1 -0
- package/dist/src/modules/app/components/AppLoader.js +5 -0
- package/dist/src/modules/app/components/AppShell.d.ts +8 -0
- package/dist/src/modules/app/components/AppShell.d.ts.map +1 -0
- package/dist/src/modules/app/components/AppShell.js +7 -0
- package/dist/src/modules/app/components/AppSidebar.d.ts +8 -0
- package/dist/src/modules/app/components/AppSidebar.d.ts.map +1 -0
- package/dist/src/modules/app/components/AppSidebar.js +5 -0
- package/dist/src/modules/app/components/AppSidebarContent.d.ts +17 -0
- package/dist/src/modules/app/components/AppSidebarContent.d.ts.map +1 -0
- package/dist/src/modules/app/components/AppSidebarContent.js +7 -0
- package/dist/src/modules/app/components/AppSidebarHeader.d.ts +9 -0
- package/dist/src/modules/app/components/AppSidebarHeader.d.ts.map +1 -0
- package/dist/src/modules/app/components/AppSidebarHeader.js +10 -0
- package/dist/src/modules/app/components/AppSidebarInvites.d.ts +4 -0
- package/dist/src/modules/app/components/AppSidebarInvites.d.ts.map +1 -0
- package/dist/src/modules/app/components/AppSidebarInvites.js +12 -0
- package/dist/src/modules/app/components/AppSidebarUser.d.ts +12 -0
- package/dist/src/modules/app/components/AppSidebarUser.d.ts.map +1 -0
- package/dist/src/modules/app/components/AppSidebarUser.js +16 -0
- package/dist/src/modules/auth/components/AdminUserManagement.d.ts +7 -0
- package/dist/src/modules/auth/components/AdminUserManagement.d.ts.map +1 -0
- package/dist/src/modules/auth/components/AdminUserManagement.js +422 -0
- package/dist/src/modules/auth/components/AdminWaitlist.d.ts +7 -0
- package/dist/src/modules/auth/components/AdminWaitlist.d.ts.map +1 -0
- package/dist/src/modules/auth/components/AdminWaitlist.js +118 -0
- package/dist/src/modules/auth/components/AuthLayout.d.ts +5 -0
- package/dist/src/modules/auth/components/AuthLayout.d.ts.map +1 -0
- package/dist/src/modules/auth/components/AuthLayout.js +5 -0
- package/dist/src/modules/auth/components/AuthProviders.d.ts +7 -0
- package/dist/src/modules/auth/components/AuthProviders.d.ts.map +1 -0
- package/dist/src/modules/auth/components/AuthProviders.js +45 -0
- package/dist/src/modules/auth/components/AuthRouter.d.ts +10 -0
- package/dist/src/modules/auth/components/AuthRouter.d.ts.map +1 -0
- package/dist/src/modules/auth/components/AuthRouter.js +12 -0
- package/dist/src/modules/auth/components/ClaimAccountRoute.d.ts +5 -0
- package/dist/src/modules/auth/components/ClaimAccountRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/ClaimAccountRoute.js +143 -0
- package/dist/src/modules/auth/components/ErrorAuthRoute.d.ts +2 -0
- package/dist/src/modules/auth/components/ErrorAuthRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/ErrorAuthRoute.js +93 -0
- package/dist/src/modules/auth/components/ForgotPasswordForm.d.ts +2 -0
- package/dist/src/modules/auth/components/ForgotPasswordForm.d.ts.map +1 -0
- package/dist/src/modules/auth/components/ForgotPasswordForm.js +27 -0
- package/dist/src/modules/auth/components/ForgotPasswordRoute.d.ts +2 -0
- package/dist/src/modules/auth/components/ForgotPasswordRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/ForgotPasswordRoute.js +9 -0
- package/dist/src/modules/auth/components/InviteFriends.d.ts +6 -0
- package/dist/src/modules/auth/components/InviteFriends.d.ts.map +1 -0
- package/dist/src/modules/auth/components/InviteFriends.js +74 -0
- package/dist/src/modules/auth/components/LastUsedBadge.d.ts +6 -0
- package/dist/src/modules/auth/components/LastUsedBadge.d.ts.map +1 -0
- package/dist/src/modules/auth/components/LastUsedBadge.js +9 -0
- package/dist/src/modules/auth/components/LoginForm.d.ts +4 -0
- package/dist/src/modules/auth/components/LoginForm.d.ts.map +1 -0
- package/dist/src/modules/auth/components/LoginForm.js +44 -0
- package/dist/src/modules/auth/components/LoginRoute.d.ts +4 -0
- package/dist/src/modules/auth/components/LoginRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/LoginRoute.js +11 -0
- package/dist/src/modules/auth/components/LogoutRoute.d.ts +2 -0
- package/dist/src/modules/auth/components/LogoutRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/LogoutRoute.js +15 -0
- package/dist/src/modules/auth/components/OrganizationAcceptInvitationRoute.d.ts +10 -0
- package/dist/src/modules/auth/components/OrganizationAcceptInvitationRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/OrganizationAcceptInvitationRoute.js +102 -0
- package/dist/src/modules/auth/components/OrganizationMembersRoute.d.ts +52 -0
- package/dist/src/modules/auth/components/OrganizationMembersRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/OrganizationMembersRoute.js +359 -0
- package/dist/src/modules/auth/components/OrganizationSettingsRoute.d.ts +21 -0
- package/dist/src/modules/auth/components/OrganizationSettingsRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/OrganizationSettingsRoute.js +153 -0
- package/dist/src/modules/auth/components/OrganizationSwitcher.d.ts +8 -0
- package/dist/src/modules/auth/components/OrganizationSwitcher.d.ts.map +1 -0
- package/dist/src/modules/auth/components/OrganizationSwitcher.js +74 -0
- package/dist/src/modules/auth/components/ProfileRoute.d.ts +2 -0
- package/dist/src/modules/auth/components/ProfileRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/ProfileRoute.js +42 -0
- package/dist/src/modules/auth/components/RangeNuqsDatePicker.d.ts +32 -0
- package/dist/src/modules/auth/components/RangeNuqsDatePicker.d.ts.map +1 -0
- package/dist/src/modules/auth/components/RangeNuqsDatePicker.js +236 -0
- package/dist/src/modules/auth/components/ResetPasswordForm.d.ts +2 -0
- package/dist/src/modules/auth/components/ResetPasswordForm.d.ts.map +1 -0
- package/dist/src/modules/auth/components/ResetPasswordForm.js +39 -0
- package/dist/src/modules/auth/components/ResetPasswordRoute.d.ts +2 -0
- package/dist/src/modules/auth/components/ResetPasswordRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/ResetPasswordRoute.js +9 -0
- package/dist/src/modules/auth/components/SignupFormRoute.d.ts +6 -0
- package/dist/src/modules/auth/components/SignupFormRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/SignupFormRoute.js +106 -0
- package/dist/src/modules/auth/components/SignupRoute.d.ts +8 -0
- package/dist/src/modules/auth/components/SignupRoute.d.ts.map +1 -0
- package/dist/src/modules/auth/components/SignupRoute.js +16 -0
- package/dist/src/modules/auth/components/UserPreferences.d.ts +31 -0
- package/dist/src/modules/auth/components/UserPreferences.d.ts.map +1 -0
- package/dist/src/modules/auth/components/UserPreferences.js +60 -0
- package/dist/src/modules/auth/components/WaitlistCard.d.ts +7 -0
- package/dist/src/modules/auth/components/WaitlistCard.d.ts.map +1 -0
- package/dist/src/modules/auth/components/WaitlistCard.js +32 -0
- package/dist/src/modules/auth/components/WaitlistCodeValidation.d.ts +8 -0
- package/dist/src/modules/auth/components/WaitlistCodeValidation.d.ts.map +1 -0
- package/dist/src/modules/auth/components/WaitlistCodeValidation.js +43 -0
- package/dist/src/modules/billing/components/BillingBetaPage.d.ts +9 -0
- package/dist/src/modules/billing/components/BillingBetaPage.d.ts.map +1 -0
- package/dist/src/modules/billing/components/BillingBetaPage.js +11 -0
- package/dist/src/modules/billing/components/BillingInvoicePage.d.ts +8 -0
- package/dist/src/modules/billing/components/BillingInvoicePage.d.ts.map +1 -0
- package/dist/src/modules/billing/components/BillingInvoicePage.js +32 -0
- package/dist/src/modules/billing/components/BillingPlanSelect.d.ts +7 -0
- package/dist/src/modules/billing/components/BillingPlanSelect.d.ts.map +1 -0
- package/dist/src/modules/billing/components/BillingPlanSelect.js +8 -0
- package/dist/src/modules/billing/components/BillingRouter.d.ts +10 -0
- package/dist/src/modules/billing/components/BillingRouter.d.ts.map +1 -0
- package/dist/src/modules/billing/components/BillingRouter.js +8 -0
- package/dist/src/modules/billing/components/BillingSinglePlanSelect.d.ts +9 -0
- package/dist/src/modules/billing/components/BillingSinglePlanSelect.d.ts.map +1 -0
- package/dist/src/modules/billing/components/BillingSinglePlanSelect.js +46 -0
- package/dist/src/modules/table/components/ColumnOrderAndVisibility.d.ts +11 -0
- package/dist/src/modules/table/components/ColumnOrderAndVisibility.d.ts.map +1 -0
- package/dist/src/modules/table/components/ColumnOrderAndVisibility.js +42 -0
- package/dist/src/modules/table/components/NuqsTable.d.ts +24 -0
- package/dist/src/modules/table/components/NuqsTable.d.ts.map +1 -0
- package/dist/src/modules/table/components/NuqsTable.js +198 -0
- package/dist/src/modules/table/components/TableFiltering.d.ts +24 -0
- package/dist/src/modules/table/components/TableFiltering.d.ts.map +1 -0
- package/dist/src/modules/table/components/TableFiltering.js +236 -0
- package/dist/src/modules/table/components/TablePagination.d.ts +10 -0
- package/dist/src/modules/table/components/TablePagination.d.ts.map +1 -0
- package/dist/src/modules/table/components/TablePagination.js +21 -0
- package/dist/src/modules/table/components/table.types.d.ts +13 -0
- package/dist/src/modules/table/components/table.types.d.ts.map +1 -0
- package/dist/src/modules/table/components/table.types.js +1 -0
- package/dist/src/modules/table/filterTransformers.d.ts +54 -0
- package/dist/src/modules/table/filterTransformers.d.ts.map +1 -0
- package/dist/src/modules/table/filterTransformers.js +276 -0
- package/dist/src/types.d.ts +4 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/tsconfig.json +4 -1
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Input, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, Select, SelectItem, Spinner, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow, Tooltip, } from "@heroui/react";
|
|
3
|
+
import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
|
|
4
|
+
import * as authAdmin from "@m5kdev/frontend/modules/auth/hooks/useAuthAdmin";
|
|
5
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
|
+
import { BarChart3, CalendarClock, ChevronDown, ChevronUp, Copy, Filter, Info, Link2, List, MoreHorizontal, Search, UserPlus, X, } from "lucide-react";
|
|
7
|
+
import { useCallback, useEffect, useId, useState } from "react";
|
|
8
|
+
import { useNavigate } from "react-router";
|
|
9
|
+
import { toast } from "sonner";
|
|
10
|
+
export function AdminUserManagement({ useTRPC }) {
|
|
11
|
+
const banReasonInputId = useId();
|
|
12
|
+
const customDurationId = useId();
|
|
13
|
+
const nameInputId = useId();
|
|
14
|
+
const emailInputId = useId();
|
|
15
|
+
const passwordInputId = useId();
|
|
16
|
+
const roleSelectId = useId();
|
|
17
|
+
const [page, setPage] = useState(0);
|
|
18
|
+
const [limit] = useState(10);
|
|
19
|
+
const [userToDelete, setUserToDelete] = useState(null);
|
|
20
|
+
const [userToBan, setUserToBan] = useState(null);
|
|
21
|
+
const [banReason, setBanReason] = useState("");
|
|
22
|
+
const [banExpiry, setBanExpiry] = useState("never"); // "never", "1d", "7d", "30d", "custom"
|
|
23
|
+
const [customBanDays, setCustomBanDays] = useState(1);
|
|
24
|
+
const [isBanningUser, setIsBanningUser] = useState(false);
|
|
25
|
+
const [isUnbanningUser, setIsUnbanningUser] = useState({});
|
|
26
|
+
const [isImpersonatingUser, setIsImpersonatingUser] = useState(false);
|
|
27
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
28
|
+
const [statusFilter, setStatusFilter] = useState("all");
|
|
29
|
+
const [sortField, setSortField] = useState("createdAt");
|
|
30
|
+
const [sortOrder, setSortOrder] = useState("desc");
|
|
31
|
+
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState("");
|
|
32
|
+
const [isCreateUserModalOpen, setIsCreateUserModalOpen] = useState(false);
|
|
33
|
+
const [newUserData, setNewUserData] = useState({
|
|
34
|
+
name: "",
|
|
35
|
+
email: "",
|
|
36
|
+
password: "",
|
|
37
|
+
role: "user",
|
|
38
|
+
});
|
|
39
|
+
const [isCreatingUser, setIsCreatingUser] = useState(false);
|
|
40
|
+
const [userForUsage, setUserForUsage] = useState(null);
|
|
41
|
+
const [userForMagicLink, setUserForMagicLink] = useState(null);
|
|
42
|
+
const [claimEmail, setClaimEmail] = useState("");
|
|
43
|
+
const [generatedMagicLink, setGeneratedMagicLink] = useState(null);
|
|
44
|
+
const [isGeneratingMagicLink, setIsGeneratingMagicLink] = useState(false);
|
|
45
|
+
const navigate = useNavigate();
|
|
46
|
+
const magicLinkEmailInputId = useId();
|
|
47
|
+
const generatedMagicLinkInputId = useId();
|
|
48
|
+
// AI Usage query
|
|
49
|
+
const trpc = useTRPC?.();
|
|
50
|
+
const queryClient = useQueryClient();
|
|
51
|
+
const usageQueryEnabled = !!userForUsage && !!trpc;
|
|
52
|
+
const usageQueryOptions = trpc?.ai.getUserUsage.queryOptions({ userId: userForUsage?.id ?? "" });
|
|
53
|
+
const usageQuery = useQuery({
|
|
54
|
+
...(usageQueryOptions ?? {
|
|
55
|
+
queryKey: [["ai", "getUserUsage"], { input: { userId: "" } }],
|
|
56
|
+
queryFn: () => Promise.resolve({ inputTokens: null, outputTokens: null, totalTokens: null, cost: null }),
|
|
57
|
+
}),
|
|
58
|
+
enabled: usageQueryEnabled,
|
|
59
|
+
});
|
|
60
|
+
const usageData = usageQuery.data;
|
|
61
|
+
const isLoadingUsage = usageQuery.isLoading;
|
|
62
|
+
const refetchUsage = () => {
|
|
63
|
+
if (usageQueryOptions) {
|
|
64
|
+
queryClient.invalidateQueries({ queryKey: usageQueryOptions.queryKey });
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
// Reset page on filter change
|
|
68
|
+
const resetPage = useCallback(() => {
|
|
69
|
+
setPage(0);
|
|
70
|
+
}, []);
|
|
71
|
+
// Debounce search query
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const timer = setTimeout(() => {
|
|
74
|
+
setDebouncedSearchQuery(searchQuery);
|
|
75
|
+
resetPage();
|
|
76
|
+
}, 300);
|
|
77
|
+
return () => clearTimeout(timer);
|
|
78
|
+
}, [searchQuery, resetPage]);
|
|
79
|
+
// Handle status filter change
|
|
80
|
+
const handleStatusFilterChange = useCallback((value) => {
|
|
81
|
+
setStatusFilter(value);
|
|
82
|
+
resetPage();
|
|
83
|
+
}, [resetPage]);
|
|
84
|
+
const { data: listUsers = {
|
|
85
|
+
users: [],
|
|
86
|
+
total: 0,
|
|
87
|
+
}, isLoading, refetch, } = authAdmin.useListUsers({
|
|
88
|
+
query: {
|
|
89
|
+
searchField: "name",
|
|
90
|
+
searchOperator: "contains",
|
|
91
|
+
searchValue: debouncedSearchQuery,
|
|
92
|
+
limit,
|
|
93
|
+
offset: page * limit,
|
|
94
|
+
sortBy: sortField,
|
|
95
|
+
sortDirection: sortOrder,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
const { users, total: totalUsers } = listUsers;
|
|
99
|
+
const { mutate: deleteUser, isPending: isDeleting } = authAdmin.useRemoveUser({
|
|
100
|
+
onSuccess: () => {
|
|
101
|
+
toast.success("User deleted successfully");
|
|
102
|
+
refetch();
|
|
103
|
+
},
|
|
104
|
+
onError: (error) => {
|
|
105
|
+
toast.error(`Error deleting user: ${error.message}`);
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
const { mutate: updateUser } = authAdmin.useUpdateUser({
|
|
109
|
+
onSuccess: () => {
|
|
110
|
+
toast.success("User updated successfully");
|
|
111
|
+
refetch();
|
|
112
|
+
},
|
|
113
|
+
onError: (error) => {
|
|
114
|
+
toast.error(`Error updating user: ${error.message}`);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
const totalPages = totalUsers ? Math.ceil(totalUsers / limit) : 0;
|
|
118
|
+
const confirmDelete = (userId) => {
|
|
119
|
+
setUserToDelete(userId);
|
|
120
|
+
};
|
|
121
|
+
const handleDelete = () => {
|
|
122
|
+
if (userToDelete) {
|
|
123
|
+
deleteUser({ id: userToDelete });
|
|
124
|
+
setUserToDelete(null);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const openBanModal = (userId, userName) => {
|
|
128
|
+
setUserToBan({ id: userId, name: userName });
|
|
129
|
+
setBanReason("");
|
|
130
|
+
setBanExpiry("never");
|
|
131
|
+
setCustomBanDays(1);
|
|
132
|
+
};
|
|
133
|
+
const getBanExpiryInSeconds = () => {
|
|
134
|
+
switch (banExpiry) {
|
|
135
|
+
case "never":
|
|
136
|
+
return undefined; // No expiry
|
|
137
|
+
case "1d":
|
|
138
|
+
return 60 * 60 * 24; // 1 day in seconds
|
|
139
|
+
case "7d":
|
|
140
|
+
return 60 * 60 * 24 * 7; // 7 days in seconds
|
|
141
|
+
case "30d":
|
|
142
|
+
return 60 * 60 * 24 * 30; // 30 days in seconds
|
|
143
|
+
case "custom":
|
|
144
|
+
return 60 * 60 * 24 * customBanDays; // Custom days in seconds
|
|
145
|
+
default:
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const formatBanExpiry = (expiryTimestamp) => {
|
|
150
|
+
if (!expiryTimestamp)
|
|
151
|
+
return "Never";
|
|
152
|
+
const expiryDate = new Date(expiryTimestamp);
|
|
153
|
+
const now = new Date();
|
|
154
|
+
// If it's expired, return that
|
|
155
|
+
if (expiryDate < now)
|
|
156
|
+
return "Expired";
|
|
157
|
+
// Format the date
|
|
158
|
+
const dateString = expiryDate.toLocaleDateString(undefined, {
|
|
159
|
+
year: "numeric",
|
|
160
|
+
month: "short",
|
|
161
|
+
day: "numeric",
|
|
162
|
+
});
|
|
163
|
+
// Calculate time remaining
|
|
164
|
+
const timeRemaining = expiryDate.getTime() - now.getTime();
|
|
165
|
+
const daysRemaining = Math.ceil(timeRemaining / (1000 * 60 * 60 * 24));
|
|
166
|
+
return `${dateString} (${daysRemaining} days)`;
|
|
167
|
+
};
|
|
168
|
+
const handleBanUser = async () => {
|
|
169
|
+
if (!userToBan)
|
|
170
|
+
return;
|
|
171
|
+
try {
|
|
172
|
+
setIsBanningUser(true);
|
|
173
|
+
await authClient.admin.banUser({
|
|
174
|
+
userId: userToBan.id,
|
|
175
|
+
banReason: banReason || "No reason provided",
|
|
176
|
+
banExpiresIn: getBanExpiryInSeconds(),
|
|
177
|
+
});
|
|
178
|
+
toast.success(`User ${userToBan.name} has been banned`);
|
|
179
|
+
setUserToBan(null);
|
|
180
|
+
await refetch();
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
toast.error(`Failed to ban user: ${error instanceof Error ? error.message : String(error)}`);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
setIsBanningUser(false);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const handleUnbanUser = async (userId) => {
|
|
190
|
+
try {
|
|
191
|
+
setIsUnbanningUser((prev) => ({ ...prev, [userId]: true }));
|
|
192
|
+
await authClient.admin.unbanUser({
|
|
193
|
+
userId,
|
|
194
|
+
});
|
|
195
|
+
toast.success("User has been unbanned");
|
|
196
|
+
await refetch();
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
toast.error(`Failed to unban user: ${error instanceof Error ? error.message : String(error)}`);
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
setIsUnbanningUser((prev) => ({ ...prev, [userId]: false }));
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const handleSetOnboardingUser = async (userId) => {
|
|
206
|
+
const onboardingStep = window.prompt(`Set onboarding step for user ${userId}`, "4");
|
|
207
|
+
const onboardingStepNumber = Number.parseInt(onboardingStep || "0");
|
|
208
|
+
if (onboardingStepNumber) {
|
|
209
|
+
updateUser({
|
|
210
|
+
userId,
|
|
211
|
+
data: { onboarding: onboardingStepNumber },
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const handleImpersonateUser = async (userId) => {
|
|
216
|
+
try {
|
|
217
|
+
setIsImpersonatingUser(true);
|
|
218
|
+
await authClient.admin.impersonateUser({
|
|
219
|
+
userId,
|
|
220
|
+
});
|
|
221
|
+
toast.success("Now impersonating user");
|
|
222
|
+
window.location.assign("/"); // Force full reload to apply impersonated session immediately
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
setIsImpersonatingUser(false);
|
|
226
|
+
toast.error(`Failed to impersonate user: ${error instanceof Error ? error.message : String(error)}`);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
const handlePageChange = (newPage) => {
|
|
230
|
+
if (newPage >= 0 && newPage < totalPages) {
|
|
231
|
+
setPage(newPage);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const handleSort = (field) => {
|
|
235
|
+
if (sortField === field) {
|
|
236
|
+
// Toggle order if same field
|
|
237
|
+
setSortOrder(sortOrder === "asc" ? "desc" : "asc");
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// Set new field and default to ascending
|
|
241
|
+
setSortField(field);
|
|
242
|
+
setSortOrder("asc");
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const renderSortIcon = (field) => {
|
|
246
|
+
if (sortField !== field)
|
|
247
|
+
return null;
|
|
248
|
+
return sortOrder === "asc" ? (_jsx(ChevronUp, { className: "h-4 w-4 inline ml-1" })) : (_jsx(ChevronDown, { className: "h-4 w-4 inline ml-1" }));
|
|
249
|
+
};
|
|
250
|
+
const clearFilters = () => {
|
|
251
|
+
setSearchQuery("");
|
|
252
|
+
setStatusFilter("all");
|
|
253
|
+
setSortField("createdAt");
|
|
254
|
+
setSortOrder("desc");
|
|
255
|
+
};
|
|
256
|
+
const openCreateUserModal = () => {
|
|
257
|
+
setNewUserData({
|
|
258
|
+
name: "",
|
|
259
|
+
email: "",
|
|
260
|
+
password: "",
|
|
261
|
+
role: "user",
|
|
262
|
+
});
|
|
263
|
+
setIsCreateUserModalOpen(true);
|
|
264
|
+
};
|
|
265
|
+
const handleCreateUser = async () => {
|
|
266
|
+
if (!newUserData.name || !newUserData.email || !newUserData.password) {
|
|
267
|
+
toast.error("Please fill in all required fields");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
setIsCreatingUser(true);
|
|
272
|
+
await authClient.admin.createUser({
|
|
273
|
+
name: newUserData.name,
|
|
274
|
+
email: newUserData.email,
|
|
275
|
+
password: newUserData.password,
|
|
276
|
+
role: newUserData.role,
|
|
277
|
+
});
|
|
278
|
+
toast.success(`User ${newUserData.name} has been created`);
|
|
279
|
+
setIsCreateUserModalOpen(false);
|
|
280
|
+
await refetch();
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
toast.error(`Failed to create user: ${error instanceof Error ? error.message : String(error)}`);
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
setIsCreatingUser(false);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
const handleNewUserDataChange = (field, value) => {
|
|
290
|
+
setNewUserData((prev) => ({
|
|
291
|
+
...prev,
|
|
292
|
+
[field]: value,
|
|
293
|
+
}));
|
|
294
|
+
};
|
|
295
|
+
const openUsageModal = (userId, userName) => {
|
|
296
|
+
setUserForUsage({ id: userId, name: userName });
|
|
297
|
+
refetchUsage();
|
|
298
|
+
};
|
|
299
|
+
const formatTokenCount = (count) => {
|
|
300
|
+
if (count === null || count === undefined)
|
|
301
|
+
return "—";
|
|
302
|
+
return count.toLocaleString();
|
|
303
|
+
};
|
|
304
|
+
const formatCost = (cost) => {
|
|
305
|
+
if (cost === null || cost === undefined)
|
|
306
|
+
return "—";
|
|
307
|
+
return `$${cost.toFixed(4)}`;
|
|
308
|
+
};
|
|
309
|
+
const createAccountClaimCodeMutation = useMutation(trpc
|
|
310
|
+
? trpc.auth.createAccountClaimCode.mutationOptions()
|
|
311
|
+
: {
|
|
312
|
+
mutationFn: async () => {
|
|
313
|
+
throw new Error("Account claim actions are not available in this app");
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
const generateAccountClaimMagicLinkMutation = useMutation(trpc
|
|
317
|
+
? trpc.auth.generateAccountClaimMagicLink.mutationOptions()
|
|
318
|
+
: {
|
|
319
|
+
mutationFn: async () => {
|
|
320
|
+
throw new Error("Account claim actions are not available in this app");
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
const openMagicLinkModal = (userId, userName, userEmail) => {
|
|
324
|
+
setUserForMagicLink({ id: userId, name: userName, email: userEmail });
|
|
325
|
+
setClaimEmail(userEmail ?? "");
|
|
326
|
+
setGeneratedMagicLink(null);
|
|
327
|
+
};
|
|
328
|
+
const handleGenerateMagicLink = async () => {
|
|
329
|
+
if (!trpc) {
|
|
330
|
+
toast.error("Account claim actions are not available in this app");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (!userForMagicLink)
|
|
334
|
+
return;
|
|
335
|
+
const normalizedEmail = claimEmail.trim().toLowerCase();
|
|
336
|
+
if (!normalizedEmail) {
|
|
337
|
+
toast.error("Email is required");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
341
|
+
if (!emailRegex.test(normalizedEmail)) {
|
|
342
|
+
toast.error("Please enter a valid email address");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
setIsGeneratingMagicLink(true);
|
|
347
|
+
const claim = await createAccountClaimCodeMutation.mutateAsync({
|
|
348
|
+
userId: userForMagicLink.id,
|
|
349
|
+
});
|
|
350
|
+
const link = await generateAccountClaimMagicLinkMutation.mutateAsync({
|
|
351
|
+
claimId: claim.id,
|
|
352
|
+
email: normalizedEmail,
|
|
353
|
+
});
|
|
354
|
+
setGeneratedMagicLink(link.url);
|
|
355
|
+
toast.success("Magic login link generated");
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
toast.error(`Failed to generate magic link: ${error instanceof Error ? error.message : String(error)}`);
|
|
359
|
+
}
|
|
360
|
+
finally {
|
|
361
|
+
setIsGeneratingMagicLink(false);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
const copyGeneratedMagicLink = async () => {
|
|
365
|
+
if (!generatedMagicLink)
|
|
366
|
+
return;
|
|
367
|
+
try {
|
|
368
|
+
await navigator.clipboard.writeText(generatedMagicLink);
|
|
369
|
+
toast.success("Magic link copied");
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
toast.error("Failed to copy link");
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
if (isLoading) {
|
|
376
|
+
return (_jsx("div", { className: "flex justify-center p-8", children: _jsx(Spinner, {}) }));
|
|
377
|
+
}
|
|
378
|
+
const openWaitlistModal = () => {
|
|
379
|
+
navigate("/admin/waitlist");
|
|
380
|
+
};
|
|
381
|
+
const hasActiveFilters = debouncedSearchQuery ||
|
|
382
|
+
statusFilter !== "all" ||
|
|
383
|
+
sortField !== "createdAt" ||
|
|
384
|
+
sortOrder !== "desc";
|
|
385
|
+
return (_jsxs("div", { className: "space-y-4 p-4", children: [_jsxs("div", { className: "flex justify-between items-center", children: [_jsx("h2", { className: "text-xl font-semibold", children: "User Management" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { onPress: openWaitlistModal, size: "sm", children: [_jsx(List, { className: "h-4 w-4 mr-2" }), "Waitlist"] }), _jsxs(Button, { onPress: openCreateUserModal, size: "sm", children: [_jsx(UserPlus, { className: "h-4 w-4 mr-2" }), "Create User"] }), _jsxs("div", { className: "text-sm text-muted-foreground", children: ["Total users: ", totalUsers || 0] })] })] }), _jsxs("div", { className: "flex flex-col sm:flex-row gap-3 items-start sm:items-center", children: [_jsxs("form", { className: "relative flex-1", onSubmit: (e) => {
|
|
386
|
+
e.preventDefault();
|
|
387
|
+
setDebouncedSearchQuery(searchQuery);
|
|
388
|
+
resetPage();
|
|
389
|
+
}, children: [_jsx(Search, { className: "absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" }), _jsx(Input, { "aria-label": "Search users", placeholder: "Search users by name or email...", className: "pl-8 w-full", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), variant: "bordered" }), searchQuery && (_jsx("button", { type: "button", onClick: () => setSearchQuery(""), className: "absolute right-2.5 top-2.5 text-muted-foreground hover:text-foreground", "aria-label": "Clear search", children: _jsx(X, { className: "h-4 w-4" }) }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Select, { "aria-label": "Status filter", selectedKeys: new Set([statusFilter]), onSelectionChange: (keys) => handleStatusFilterChange(Array.from(keys)[0]), className: "w-[160px]", startContent: _jsx(Filter, { className: "mr-1 h-4 w-4" }), children: [_jsx(SelectItem, { children: "All Users" }, "all"), _jsx(SelectItem, { children: "Active Only" }, "active"), _jsx(SelectItem, { children: "Banned Only" }, "banned")] }), hasActiveFilters && (_jsxs(Button, { variant: "bordered", size: "sm", onPress: clearFilters, children: [_jsx(X, { className: "mr-1 h-4 w-4" }), "Clear Filters"] }))] })] }), _jsx("div", { className: "border rounded-lg overflow-hidden", children: _jsxs(Table, { "aria-label": "Users table", removeWrapper: true, children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "ID" }), _jsxs(TableColumn, { className: "cursor-pointer", onClick: () => handleSort("name"), children: ["Name ", renderSortIcon("name")] }), _jsxs(TableColumn, { className: "cursor-pointer", onClick: () => handleSort("email"), children: ["Email ", renderSortIcon("email")] }), _jsxs(TableColumn, { className: "cursor-pointer", onClick: () => handleSort("role"), children: ["Role ", renderSortIcon("role")] }), _jsx(TableColumn, { children: "Status" }), _jsx(TableColumn, { children: "Onboarding" }), _jsxs(TableColumn, { className: "cursor-pointer", onClick: () => handleSort("createdAt"), children: ["Created At ", renderSortIcon("createdAt")] }), _jsx(TableColumn, { className: "text-right", children: "Actions" })] }), _jsx(TableBody, { items: users ?? [], emptyContent: hasActiveFilters ? "No users found matching your filters" : "No users found", children: (user) => {
|
|
390
|
+
const onboarding = user.onboarding;
|
|
391
|
+
return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: user.id }), _jsx(TableCell, { children: user.name }), _jsx(TableCell, { children: user.email }), _jsx(TableCell, { children: user.role || "user" }), _jsx(TableCell, { children: user.banned ? (_jsx(Tooltip, { content: _jsxs("div", { className: "space-y-1 text-xs", children: [_jsxs("p", { children: [_jsx("strong", { children: "Reason:" }), " ", user.banReason || "No reason provided"] }), user.banExpires && (_jsxs("p", { className: "flex items-center gap-1", children: [_jsx(CalendarClock, { className: "h-3 w-3" }), _jsxs("span", { children: [_jsx("strong", { children: "Expires:" }), " ", formatBanExpiry(user.banExpires.getTime())] })] }))] }), children: _jsxs("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", children: ["Banned", user.banReason && _jsx(Info, { className: "h-3 w-3" })] }) })) : (_jsx("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800", children: "Active" })) }), _jsx(TableCell, { children: onboarding ?? "—" }), _jsx(TableCell, { children: user.createdAt ? new Date(user.createdAt).toLocaleDateString() : "N/A" }), _jsx(TableCell, { className: "text-right", children: _jsxs(Dropdown, { placement: "bottom-end", children: [_jsx(DropdownTrigger, { children: _jsx(Button, { variant: "light", size: "sm", isIconOnly: true, children: _jsx(MoreHorizontal, { className: "h-4 w-4" }) }) }), _jsxs(DropdownMenu, { "aria-label": "User actions", children: [_jsx(DropdownItem, { onClick: () => handleSetOnboardingUser(user.id), children: "Set Onboarding" }, "onboarding"), _jsx(DropdownItem, { onClick: () => openUsageModal(user.id, user.name), className: useTRPC ? "" : "hidden", children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(BarChart3, { className: "h-4 w-4" }), "View AI Usage"] }) }, "usage"), _jsx(DropdownItem, { onClick: () => handleImpersonateUser(user.id), isDisabled: user.banned || isImpersonatingUser, children: isImpersonatingUser ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { className: "mr-2 h-3 w-3" }), "Impersonating..."] })) : ("Impersonate") }, "impersonate"), _jsx(DropdownItem, { onClick: () => openMagicLinkModal(user.id, user.name, user.email ?? null), className: trpc ? "" : "hidden", children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(Link2, { className: "h-4 w-4" }), "Generate Magic Login Link"] }) }, "magic-link"), user.banned ? (_jsx(DropdownItem, { onClick: () => handleUnbanUser(user.id), isDisabled: isUnbanningUser[user.id], children: isUnbanningUser[user.id] ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { className: "mr-2 h-3 w-3" }), "Unbanning..."] })) : ("Unban") }, "unban")) : (_jsx(DropdownItem, { onClick: () => openBanModal(user.id, user.name), children: "Ban" }, "ban")), _jsx(DropdownItem, { onClick: () => confirmDelete(user.id), children: "Remove" }, "remove")] })] }) })] }, user.id));
|
|
392
|
+
} })] }) }), totalPages > 0 && (_jsxs("div", { className: "flex items-center justify-end space-x-2 py-4", children: [_jsx(Button, { variant: "bordered", size: "sm", onClick: () => handlePageChange(page - 1), isDisabled: page === 0, children: "Previous" }), _jsxs("div", { className: "text-sm text-muted-foreground", children: ["Page ", page + 1, " of ", totalPages] }), _jsx(Button, { variant: "bordered", size: "sm", onClick: () => handlePageChange(page + 1), isDisabled: page === totalPages - 1, children: "Next" })] })), _jsx(Modal, { isOpen: !!userToDelete, onOpenChange: (open) => {
|
|
393
|
+
if (!open)
|
|
394
|
+
setUserToDelete(null);
|
|
395
|
+
}, children: _jsx(ModalContent, { children: (onClose) => (_jsxs(_Fragment, { children: [_jsxs(ModalHeader, { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-lg font-semibold", children: "Are you sure?" }), _jsx("p", { className: "text-sm text-default-600", children: "This action cannot be undone. This will permanently delete the user and all their data." })] }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "bordered", onPress: onClose, children: "Cancel" }), _jsx(Button, { color: "danger", onPress: handleDelete, isLoading: isDeleting, children: "Delete" })] })] })) }) }), _jsx(Modal, { isOpen: !!userToBan, onOpenChange: (open) => {
|
|
396
|
+
if (!open)
|
|
397
|
+
setUserToBan(null);
|
|
398
|
+
}, children: _jsx(ModalContent, { children: (onClose) => (_jsxs("form", { onSubmit: (e) => {
|
|
399
|
+
e.preventDefault();
|
|
400
|
+
handleBanUser();
|
|
401
|
+
}, className: "space-y-4", children: [_jsxs(ModalHeader, { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-lg font-semibold", children: "Ban User" }), _jsx("p", { className: "text-sm text-default-600", children: userToBan &&
|
|
402
|
+
`You are about to ban ${userToBan.name}. This will prevent them from signing in.` })] }), _jsxs(ModalBody, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { htmlFor: banReasonInputId, className: "text-sm font-medium", children: "Ban Reason" }), _jsx(Input, { id: banReasonInputId, placeholder: "Enter reason for ban", value: banReason, onChange: (e) => setBanReason(e.target.value), variant: "bordered", labelPlacement: "outside", label: "Ban Reason" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium", children: "Ban Duration" }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsx(Button, { type: "button", variant: banExpiry === "never" ? "solid" : "bordered", onClick: () => setBanExpiry("never"), children: "Permanent" }), _jsx(Button, { type: "button", variant: banExpiry === "1d" ? "solid" : "bordered", onClick: () => setBanExpiry("1d"), children: "1 Day" }), _jsx(Button, { type: "button", variant: banExpiry === "7d" ? "solid" : "bordered", onClick: () => setBanExpiry("7d"), children: "7 Days" }), _jsx(Button, { type: "button", variant: banExpiry === "30d" ? "solid" : "bordered", onClick: () => setBanExpiry("30d"), children: "30 Days" })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { type: "checkbox", id: customDurationId, checked: banExpiry === "custom", onChange: (e) => e.target.checked ? setBanExpiry("custom") : setBanExpiry("never") }), _jsx("label", { htmlFor: customDurationId, className: "text-sm font-medium", children: "Custom Duration" })] }), banExpiry === "custom" && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { type: "number", min: "1", value: customBanDays.toString(), onChange: (e) => setCustomBanDays(Number(e.target.value)), variant: "bordered", labelPlacement: "outside", label: "Custom duration (days)" }), _jsx("span", { className: "text-sm", children: "Days" })] }))] })] }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "bordered", type: "button", onPress: onClose, children: "Cancel" }), _jsxs(Button, { color: "danger", type: "submit", isDisabled: isBanningUser, children: [isBanningUser ? _jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null, isBanningUser ? "Banning..." : "Ban User"] })] })] })) }) }), _jsx(Modal, { isOpen: isCreateUserModalOpen, onOpenChange: (open) => {
|
|
403
|
+
if (!open)
|
|
404
|
+
setIsCreateUserModalOpen(false);
|
|
405
|
+
}, children: _jsx(ModalContent, { children: (onClose) => (_jsxs("form", { onSubmit: (e) => {
|
|
406
|
+
e.preventDefault();
|
|
407
|
+
handleCreateUser();
|
|
408
|
+
}, className: "space-y-4", children: [_jsxs(ModalHeader, { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-lg font-semibold", children: "Create New User" }), _jsx("p", { className: "text-sm text-default-600", children: "Fill in the details below to create a new user account." })] }), _jsxs(ModalBody, { className: "space-y-4", children: [_jsx("div", { className: "space-y-2", children: _jsx(Input, { id: nameInputId, label: "Name *", labelPlacement: "outside", placeholder: "Enter user's name", value: newUserData.name, onChange: (e) => handleNewUserDataChange("name", e.target.value), variant: "bordered" }) }), _jsx("div", { className: "space-y-2", children: _jsx(Input, { id: emailInputId, label: "Email *", labelPlacement: "outside", type: "email", placeholder: "Enter user's email", value: newUserData.email, onChange: (e) => handleNewUserDataChange("email", e.target.value), variant: "bordered" }) }), _jsx("div", { className: "space-y-2", children: _jsx(Input, { id: passwordInputId, label: "Password *", labelPlacement: "outside", type: "password", placeholder: "Enter password", value: newUserData.password, onChange: (e) => handleNewUserDataChange("password", e.target.value), variant: "bordered" }) }), _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium", id: roleSelectId, children: "Role" }), _jsxs(Select, { "aria-label": "Select role", "aria-labelledby": roleSelectId, selectedKeys: new Set([newUserData.role]), onSelectionChange: (keys) => handleNewUserDataChange("role", Array.from(keys)[0]), children: [_jsx(SelectItem, { children: "User" }, "user"), _jsx(SelectItem, { children: "Admin" }, "admin")] })] })] }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "bordered", type: "button", onPress: onClose, children: "Cancel" }), _jsxs(Button, { type: "submit", color: "primary", isDisabled: isCreatingUser, children: [isCreatingUser ? _jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null, isCreatingUser ? "Creating..." : "Create User"] })] })] })) }) }), _jsx(Modal, { isOpen: !!userForUsage, onOpenChange: (open) => {
|
|
409
|
+
if (!open)
|
|
410
|
+
setUserForUsage(null);
|
|
411
|
+
}, children: _jsx(ModalContent, { children: (onClose) => (_jsxs(_Fragment, { children: [_jsxs(ModalHeader, { className: "flex flex-col gap-1", children: [_jsxs("p", { className: "text-lg font-semibold flex items-center gap-2", children: [_jsx(BarChart3, { className: "h-5 w-5" }), "AI Usage"] }), _jsx("p", { className: "text-sm text-default-600", children: userForUsage && `Usage statistics for ${userForUsage.name}` })] }), _jsx(ModalBody, { children: isLoadingUsage ? (_jsx("div", { className: "flex justify-center py-8", children: _jsx(Spinner, {}) })) : usageData ? (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "p-4 rounded-lg bg-default-100", children: [_jsx("p", { className: "text-sm text-default-600", children: "Input Tokens" }), _jsx("p", { className: "text-2xl font-semibold", children: formatTokenCount(usageData.inputTokens) })] }), _jsxs("div", { className: "p-4 rounded-lg bg-default-100", children: [_jsx("p", { className: "text-sm text-default-600", children: "Output Tokens" }), _jsx("p", { className: "text-2xl font-semibold", children: formatTokenCount(usageData.outputTokens) })] }), _jsxs("div", { className: "p-4 rounded-lg bg-default-100", children: [_jsx("p", { className: "text-sm text-default-600", children: "Total Tokens" }), _jsx("p", { className: "text-2xl font-semibold", children: formatTokenCount(usageData.totalTokens) })] }), _jsxs("div", { className: "p-4 rounded-lg bg-primary-100", children: [_jsx("p", { className: "text-sm text-primary-600", children: "Estimated Cost" }), _jsx("p", { className: "text-2xl font-semibold text-primary", children: formatCost(usageData.cost) })] })] }) })) : (_jsx("div", { className: "text-center py-8 text-default-600", children: "No usage data available" })) }), _jsx(ModalFooter, { children: _jsx(Button, { variant: "bordered", onPress: onClose, children: "Close" }) })] })) }) }), _jsx(Modal, { isOpen: !!userForMagicLink, onOpenChange: (open) => {
|
|
412
|
+
if (!open) {
|
|
413
|
+
setUserForMagicLink(null);
|
|
414
|
+
setGeneratedMagicLink(null);
|
|
415
|
+
setClaimEmail("");
|
|
416
|
+
}
|
|
417
|
+
}, children: _jsx(ModalContent, { children: (onClose) => (_jsxs("form", { onSubmit: (e) => {
|
|
418
|
+
e.preventDefault();
|
|
419
|
+
handleGenerateMagicLink();
|
|
420
|
+
}, className: "space-y-4", children: [_jsxs(ModalHeader, { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-lg font-semibold", children: "Generate Magic Login Link" }), _jsx("p", { className: "text-sm text-default-600", children: userForMagicLink &&
|
|
421
|
+
`Generate a one-time claim sign-in link for ${userForMagicLink.name}.` })] }), _jsxs(ModalBody, { className: "space-y-4", children: [_jsx(Input, { id: magicLinkEmailInputId, label: "Claim email", labelPlacement: "outside", type: "email", placeholder: "person@example.com", value: claimEmail, onChange: (e) => setClaimEmail(e.target.value), variant: "bordered", description: "This email is used for provider linking and future sign-ins." }), _jsx(Input, { id: generatedMagicLinkInputId, label: "Generated link", labelPlacement: "outside", value: generatedMagicLink ?? "", readOnly: true, variant: "bordered", placeholder: "Generate link to see it here" })] }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "bordered", type: "button", onPress: onClose, children: "Close" }), _jsxs(Button, { variant: "bordered", type: "button", onPress: copyGeneratedMagicLink, isDisabled: !generatedMagicLink, children: [_jsx(Copy, { className: "h-4 w-4 mr-2" }), "Copy Link"] }), _jsxs(Button, { color: "primary", type: "submit", isDisabled: isGeneratingMagicLink, children: [isGeneratingMagicLink ? _jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null, isGeneratingMagicLink ? "Generating..." : "Generate Link"] })] })] })) }) })] }));
|
|
422
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { UseBackendTRPC } from "#types";
|
|
2
|
+
interface AdminWaitlistProps {
|
|
3
|
+
useTRPC: UseBackendTRPC;
|
|
4
|
+
}
|
|
5
|
+
export declare function AdminWaitlist({ useTRPC }: AdminWaitlistProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=AdminWaitlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdminWaitlist.d.ts","sourceRoot":"","sources":["../../../../../src/modules/auth/components/AdminWaitlist.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,UAAU,kBAAkB;IAC1B,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,EAAE,kBAAkB,2CAuU5D"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Input, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, Spinner, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow, } from "@heroui/react";
|
|
3
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { Mail, MoreHorizontal, Search, Trash2, UserPlus, X } from "lucide-react";
|
|
5
|
+
import { useEffect, useId, useState } from "react";
|
|
6
|
+
import { toast } from "sonner";
|
|
7
|
+
export function AdminWaitlist({ useTRPC }) {
|
|
8
|
+
const trpc = useTRPC();
|
|
9
|
+
const queryClient = useQueryClient();
|
|
10
|
+
const { data: waitlist = [], isLoading } = useQuery(trpc.auth.listAdminWaitlist.queryOptions());
|
|
11
|
+
const { mutate: invite, isPending: isInviting } = useMutation(trpc.auth.inviteFromWaitlist.mutationOptions({
|
|
12
|
+
onSuccess: () => {
|
|
13
|
+
queryClient.invalidateQueries({ queryKey: trpc.auth.listAdminWaitlist.queryKey() });
|
|
14
|
+
toast.success("Invitation sent successfully");
|
|
15
|
+
},
|
|
16
|
+
onError: (error) => {
|
|
17
|
+
toast.error(`Failed to send invitation: ${error instanceof Error ? error.message : String(error)}`);
|
|
18
|
+
},
|
|
19
|
+
}));
|
|
20
|
+
const { mutate: remove, isPending: isRemoving } = useMutation(trpc.auth.removeFromWaitlist.mutationOptions({
|
|
21
|
+
onSuccess: () => {
|
|
22
|
+
queryClient.invalidateQueries({ queryKey: trpc.auth.listAdminWaitlist.queryKey() });
|
|
23
|
+
toast.success("Removed from waitlist successfully");
|
|
24
|
+
},
|
|
25
|
+
onError: (error) => {
|
|
26
|
+
toast.error(`Failed to remove: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
|
+
},
|
|
28
|
+
onSettled: () => {
|
|
29
|
+
setItemToDelete(null);
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
const { mutate: add, isPending: isAdding } = useMutation(trpc.auth.addToWaitlist.mutationOptions({
|
|
33
|
+
onSuccess: () => {
|
|
34
|
+
queryClient.invalidateQueries({ queryKey: trpc.auth.listAdminWaitlist.queryKey() });
|
|
35
|
+
toast.success("Added to waitlist successfully");
|
|
36
|
+
},
|
|
37
|
+
onError: (error) => {
|
|
38
|
+
toast.error(`Failed to add to waitlist: ${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
const emailInputId = useId();
|
|
42
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
43
|
+
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState("");
|
|
44
|
+
const [itemToDelete, setItemToDelete] = useState(null);
|
|
45
|
+
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
|
46
|
+
const [newEmail, setNewEmail] = useState("");
|
|
47
|
+
// Debounce search query
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const timer = setTimeout(() => {
|
|
50
|
+
setDebouncedSearchQuery(searchQuery);
|
|
51
|
+
}, 300);
|
|
52
|
+
return () => clearTimeout(timer);
|
|
53
|
+
}, [searchQuery]);
|
|
54
|
+
const filteredWaitlist = waitlist.filter((item) => item.email?.toLowerCase().includes(debouncedSearchQuery?.toLowerCase() ?? ""));
|
|
55
|
+
const handleInvite = (id) => {
|
|
56
|
+
invite({ id });
|
|
57
|
+
};
|
|
58
|
+
const handleRemove = () => {
|
|
59
|
+
if (itemToDelete) {
|
|
60
|
+
remove({ id: itemToDelete });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const handleAdd = async () => {
|
|
64
|
+
if (!newEmail.trim()) {
|
|
65
|
+
toast.error("Please enter an email address");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Basic email validation
|
|
69
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
70
|
+
if (!emailRegex.test(newEmail.trim())) {
|
|
71
|
+
toast.error("Please enter a valid email address");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
add({ email: newEmail.trim() });
|
|
75
|
+
setNewEmail("");
|
|
76
|
+
setIsAddModalOpen(false);
|
|
77
|
+
};
|
|
78
|
+
const formatDate = (date) => {
|
|
79
|
+
if (!date)
|
|
80
|
+
return "N/A";
|
|
81
|
+
const dateObj = typeof date === "string" ? new Date(date) : date;
|
|
82
|
+
return dateObj.toLocaleDateString(undefined, {
|
|
83
|
+
year: "numeric",
|
|
84
|
+
month: "short",
|
|
85
|
+
day: "numeric",
|
|
86
|
+
hour: "2-digit",
|
|
87
|
+
minute: "2-digit",
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const getStatusBadge = (status) => {
|
|
91
|
+
const statusLower = status.toLowerCase();
|
|
92
|
+
if (statusLower === "invited" || statusLower === "active") {
|
|
93
|
+
return (_jsx("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800", children: status }));
|
|
94
|
+
}
|
|
95
|
+
if (statusLower === "pending") {
|
|
96
|
+
return (_jsx("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800", children: status }));
|
|
97
|
+
}
|
|
98
|
+
return (_jsx("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800", children: status }));
|
|
99
|
+
};
|
|
100
|
+
if (isLoading) {
|
|
101
|
+
return (_jsx("div", { className: "flex justify-center p-8", children: _jsx(Spinner, {}) }));
|
|
102
|
+
}
|
|
103
|
+
return (_jsxs("div", { className: "space-y-4 p-4", children: [_jsxs("div", { className: "flex justify-between items-center", children: [_jsx("h2", { className: "text-xl font-semibold", children: "Waitlist Management" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { onPress: () => setIsAddModalOpen(true), size: "sm", children: [_jsx(UserPlus, { className: "h-4 w-4 mr-2" }), "Add to Waitlist"] }), _jsxs("div", { className: "text-sm text-muted-foreground", children: ["Total: ", waitlist.length] })] })] }), _jsx("div", { className: "flex flex-col sm:flex-row gap-3 items-start sm:items-center", children: _jsxs("form", { className: "relative flex-1", onSubmit: (e) => {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
setDebouncedSearchQuery(searchQuery);
|
|
106
|
+
}, children: [_jsx(Search, { className: "absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" }), _jsx(Input, { "aria-label": "Search by email", placeholder: "Search by email...", className: "pl-8 w-full", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), variant: "bordered" }), searchQuery && (_jsx("button", { type: "button", onClick: () => setSearchQuery(""), className: "absolute right-2.5 top-2.5 text-muted-foreground hover:text-foreground", "aria-label": "Clear search", children: _jsx(X, { className: "h-4 w-4" }) }))] }) }), _jsx("div", { className: "border rounded-lg overflow-hidden", children: _jsxs(Table, { "aria-label": "Waitlist table", removeWrapper: true, children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "Email" }), _jsx(TableColumn, { children: "Status" }), _jsx(TableColumn, { children: "Created At" }), _jsx(TableColumn, { children: "Updated At" }), _jsx(TableColumn, { className: "text-right", children: "Actions" })] }), _jsx(TableBody, { items: filteredWaitlist, emptyContent: searchQuery
|
|
107
|
+
? "No waitlist entries found matching your search"
|
|
108
|
+
: "No waitlist entries found", children: (item) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: item.email }), _jsx(TableCell, { children: getStatusBadge(item.status) }), _jsx(TableCell, { children: formatDate(item.createdAt) }), _jsx(TableCell, { children: formatDate(item.updatedAt) }), _jsx(TableCell, { className: "text-right", children: _jsxs(Dropdown, { placement: "bottom-end", children: [_jsx(DropdownTrigger, { children: _jsx(Button, { variant: "light", size: "sm", isIconOnly: true, children: _jsx(MoreHorizontal, { className: "h-4 w-4" }) }) }), _jsxs(DropdownMenu, { "aria-label": "Waitlist actions", children: [_jsx(DropdownItem, { onPress: () => handleInvite(item.id), isDisabled: isInviting, children: isInviting ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { className: "mr-2 h-3 w-3" }), "Inviting..."] })) : (_jsxs(_Fragment, { children: [_jsx(Mail, { className: "mr-2 h-4 w-4" }), "Send Invitation"] })) }, "invite"), _jsxs(DropdownItem, { className: "text-danger", onClick: () => setItemToDelete(item.id), children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Remove"] }, "remove")] })] }) })] }, item.id)) })] }) }), _jsx(Modal, { isOpen: !!itemToDelete, onOpenChange: (open) => {
|
|
109
|
+
if (!open)
|
|
110
|
+
setItemToDelete(null);
|
|
111
|
+
}, children: _jsx(ModalContent, { children: (onClose) => (_jsxs(_Fragment, { children: [_jsxs(ModalHeader, { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-lg font-semibold", children: "Are you sure?" }), _jsx("p", { className: "text-sm text-default-600", children: "This action cannot be undone. This will permanently remove this entry from the waitlist." })] }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "bordered", onPress: onClose, children: "Cancel" }), _jsx(Button, { color: "danger", onPress: handleRemove, isLoading: isRemoving, children: "Remove" })] })] })) }) }), _jsx(Modal, { isOpen: isAddModalOpen, onOpenChange: (open) => {
|
|
112
|
+
if (!open)
|
|
113
|
+
setIsAddModalOpen(false);
|
|
114
|
+
}, children: _jsx(ModalContent, { children: (onClose) => (_jsxs("form", { onSubmit: (e) => {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
handleAdd();
|
|
117
|
+
}, className: "space-y-4", children: [_jsxs(ModalHeader, { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-lg font-semibold", children: "Add to Waitlist" }), _jsx("p", { className: "text-sm text-default-600", children: "Enter an email address to add someone to the waitlist." })] }), _jsx(ModalBody, { className: "space-y-4", children: _jsx("div", { className: "space-y-2", children: _jsx(Input, { id: emailInputId, label: "Email *", labelPlacement: "outside", type: "email", placeholder: "Enter email address", value: newEmail, onChange: (e) => setNewEmail(e.target.value), variant: "bordered" }) }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "bordered", type: "button", onPress: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", color: "primary", isDisabled: isAdding, isLoading: isAdding, children: isAdding ? "Adding..." : "Add to Waitlist" })] })] })) }) })] }));
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthLayout.d.ts","sourceRoot":"","sources":["../../../../../src/modules/auth/components/AuthLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,2CAS3D"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Outlet } from "react-router";
|
|
3
|
+
export function AuthLayout({ header }) {
|
|
4
|
+
return (_jsx("div", { className: "flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10", children: _jsxs("div", { className: "flex w-full max-w-sm flex-col gap-6", children: [_jsx("div", { className: "flex items-center gap-2 self-center font-medium", children: header }), _jsx(Outlet, {})] }) }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function AuthProviders({ providers, lastMethod, code, requestSignUp, }: {
|
|
2
|
+
providers?: string[];
|
|
3
|
+
code?: string | null;
|
|
4
|
+
requestSignUp?: boolean;
|
|
5
|
+
lastMethod?: string | null;
|
|
6
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
7
|
+
//# sourceMappingURL=AuthProviders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthProviders.d.ts","sourceRoot":"","sources":["../../../../../src/modules/auth/components/AuthProviders.tsx"],"names":[],"mappings":"AASA,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,UAAU,EACV,IAAI,EACJ,aAAqB,GACtB,EAAE;IACD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,kDAqFA"}
|