@licklist/design 0.78.5-dev.105 → 0.78.5-dev.107

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/bitbucket-pipelines.yml +13 -4
  3. package/dist/index.js +1 -1
  4. package/dist/product-set/form/ProductsControl.d.ts +2 -1
  5. package/dist/product-set/form/ProductsControl.d.ts.map +1 -1
  6. package/dist/product-set/form/ProductsControl.js +0 -24
  7. package/dist/v2/components/DataTable/DataTable.d.ts.map +1 -1
  8. package/dist/v2/components/DataTable/DataTable.js +86 -2
  9. package/dist/v2/components/InputCheckbox/InputCheckbox.scss.js +1 -1
  10. package/dist/v2/components/Modal/DeleteModal.d.ts.map +1 -1
  11. package/dist/v2/components/Modal/DeleteModal.js +13 -11
  12. package/dist/v2/components/Toggle/Toggle.d.ts.map +1 -1
  13. package/dist/v2/components/Toggle/Toggle.js +8 -5
  14. package/dist/v2/components/ZoneCard/ZoneCard.scss.js +1 -1
  15. package/dist/v2/components/index.d.ts +1 -1
  16. package/dist/v2/components/index.d.ts.map +1 -1
  17. package/dist/v2/icons/index.d.ts +4 -0
  18. package/dist/v2/icons/index.d.ts.map +1 -1
  19. package/dist/v2/icons/index.js +47 -16
  20. package/dist/v2/index.d.ts +0 -4
  21. package/dist/v2/index.d.ts.map +1 -1
  22. package/dist/v2/pages/Settings/components/SidebarCustomisation.js +0 -3
  23. package/dist/v2/pages/Settings/components/SidebarNavItem.js +0 -3
  24. package/package.json +6 -6
  25. package/rollup.config.js +16 -2
  26. package/src/iframe/payment/payment-status-page/PaymentStatusPage.tsx +1 -1
  27. package/src/product-set/form/ProductsControl.tsx +2 -1
  28. package/src/v2/components/DataTable/DataTable.tsx +23 -1
  29. package/src/v2/components/InputCheckbox/InputCheckbox.scss +6 -6
  30. package/src/v2/components/Modal/DeleteModal.tsx +12 -20
  31. package/src/v2/components/Toggle/Toggle.tsx +6 -5
  32. package/src/v2/components/ZoneCard/ZoneCard.scss +78 -0
  33. package/src/v2/components/index.ts +1 -0
  34. package/src/v2/icons/index.tsx +10 -0
  35. package/src/v2/index.ts +0 -73
  36. package/src/v2/navigation/Navigation/Navigation.stories.tsx +137 -0
  37. package/dist/v2/shadcn/components/ui/accordion.d.ts +0 -8
  38. package/dist/v2/shadcn/components/ui/accordion.d.ts.map +0 -1
  39. package/dist/v2/shadcn/components/ui/alert-dialog.d.ts +0 -21
  40. package/dist/v2/shadcn/components/ui/alert-dialog.d.ts.map +0 -1
  41. package/dist/v2/shadcn/components/ui/alert.d.ts +0 -9
  42. package/dist/v2/shadcn/components/ui/alert.d.ts.map +0 -1
  43. package/dist/v2/shadcn/components/ui/aspect-ratio.d.ts +0 -4
  44. package/dist/v2/shadcn/components/ui/aspect-ratio.d.ts.map +0 -1
  45. package/dist/v2/shadcn/components/ui/avatar.d.ts +0 -7
  46. package/dist/v2/shadcn/components/ui/avatar.d.ts.map +0 -1
  47. package/dist/v2/shadcn/components/ui/badge.d.ts +0 -10
  48. package/dist/v2/shadcn/components/ui/badge.d.ts.map +0 -1
  49. package/dist/v2/shadcn/components/ui/breadcrumb.d.ts +0 -20
  50. package/dist/v2/shadcn/components/ui/breadcrumb.d.ts.map +0 -1
  51. package/dist/v2/shadcn/components/ui/button.d.ts +0 -14
  52. package/dist/v2/shadcn/components/ui/button.d.ts.map +0 -1
  53. package/dist/v2/shadcn/components/ui/calendar.d.ts +0 -9
  54. package/dist/v2/shadcn/components/ui/calendar.d.ts.map +0 -1
  55. package/dist/v2/shadcn/components/ui/card.d.ts +0 -9
  56. package/dist/v2/shadcn/components/ui/card.d.ts.map +0 -1
  57. package/dist/v2/shadcn/components/ui/carousel.d.ts +0 -19
  58. package/dist/v2/shadcn/components/ui/carousel.d.ts.map +0 -1
  59. package/dist/v2/shadcn/components/ui/checkbox.d.ts +0 -6
  60. package/dist/v2/shadcn/components/ui/checkbox.d.ts.map +0 -1
  61. package/dist/v2/shadcn/components/ui/checkbox.js +0 -115
  62. package/dist/v2/shadcn/components/ui/checkbox.scss.js +0 -6
  63. package/dist/v2/shadcn/components/ui/collapsible.d.ts +0 -6
  64. package/dist/v2/shadcn/components/ui/collapsible.d.ts.map +0 -1
  65. package/dist/v2/shadcn/components/ui/command.d.ts +0 -83
  66. package/dist/v2/shadcn/components/ui/command.d.ts.map +0 -1
  67. package/dist/v2/shadcn/components/ui/context-menu.d.ts +0 -28
  68. package/dist/v2/shadcn/components/ui/context-menu.d.ts.map +0 -1
  69. package/dist/v2/shadcn/components/ui/dialog.d.ts +0 -20
  70. package/dist/v2/shadcn/components/ui/dialog.d.ts.map +0 -1
  71. package/dist/v2/shadcn/components/ui/dialog.js +0 -169
  72. package/dist/v2/shadcn/components/ui/drawer.d.ts +0 -23
  73. package/dist/v2/shadcn/components/ui/drawer.d.ts.map +0 -1
  74. package/dist/v2/shadcn/components/ui/dropdown-menu.d.ts +0 -28
  75. package/dist/v2/shadcn/components/ui/dropdown-menu.d.ts.map +0 -1
  76. package/dist/v2/shadcn/components/ui/form.d.ts +0 -24
  77. package/dist/v2/shadcn/components/ui/form.d.ts.map +0 -1
  78. package/dist/v2/shadcn/components/ui/hover-card.d.ts +0 -7
  79. package/dist/v2/shadcn/components/ui/hover-card.d.ts.map +0 -1
  80. package/dist/v2/shadcn/components/ui/input-otp.d.ts +0 -35
  81. package/dist/v2/shadcn/components/ui/input-otp.d.ts.map +0 -1
  82. package/dist/v2/shadcn/components/ui/input.d.ts +0 -6
  83. package/dist/v2/shadcn/components/ui/input.d.ts.map +0 -1
  84. package/dist/v2/shadcn/components/ui/label.d.ts +0 -6
  85. package/dist/v2/shadcn/components/ui/label.d.ts.map +0 -1
  86. package/dist/v2/shadcn/components/ui/menubar.d.ts +0 -34
  87. package/dist/v2/shadcn/components/ui/menubar.d.ts.map +0 -1
  88. package/dist/v2/shadcn/components/ui/navigation-menu.d.ts +0 -13
  89. package/dist/v2/shadcn/components/ui/navigation-menu.d.ts.map +0 -1
  90. package/dist/v2/shadcn/components/ui/pagination.d.ts +0 -29
  91. package/dist/v2/shadcn/components/ui/pagination.d.ts.map +0 -1
  92. package/dist/v2/shadcn/components/ui/popover.d.ts +0 -7
  93. package/dist/v2/shadcn/components/ui/popover.d.ts.map +0 -1
  94. package/dist/v2/shadcn/components/ui/progress.d.ts +0 -5
  95. package/dist/v2/shadcn/components/ui/progress.d.ts.map +0 -1
  96. package/dist/v2/shadcn/components/ui/radio-card.d.ts +0 -12
  97. package/dist/v2/shadcn/components/ui/radio-card.d.ts.map +0 -1
  98. package/dist/v2/shadcn/components/ui/radio-group.d.ts +0 -6
  99. package/dist/v2/shadcn/components/ui/radio-group.d.ts.map +0 -1
  100. package/dist/v2/shadcn/components/ui/scroll-area.d.ts +0 -6
  101. package/dist/v2/shadcn/components/ui/scroll-area.d.ts.map +0 -1
  102. package/dist/v2/shadcn/components/ui/select.d.ts +0 -14
  103. package/dist/v2/shadcn/components/ui/select.d.ts.map +0 -1
  104. package/dist/v2/shadcn/components/ui/separator.d.ts +0 -5
  105. package/dist/v2/shadcn/components/ui/separator.d.ts.map +0 -1
  106. package/dist/v2/shadcn/components/ui/sheet.d.ts +0 -26
  107. package/dist/v2/shadcn/components/ui/sheet.d.ts.map +0 -1
  108. package/dist/v2/shadcn/components/ui/sidebar.d.ts +0 -67
  109. package/dist/v2/shadcn/components/ui/sidebar.d.ts.map +0 -1
  110. package/dist/v2/shadcn/components/ui/skeleton.d.ts +0 -3
  111. package/dist/v2/shadcn/components/ui/skeleton.d.ts.map +0 -1
  112. package/dist/v2/shadcn/components/ui/slider.d.ts +0 -5
  113. package/dist/v2/shadcn/components/ui/slider.d.ts.map +0 -1
  114. package/dist/v2/shadcn/components/ui/switch.d.ts +0 -6
  115. package/dist/v2/shadcn/components/ui/switch.d.ts.map +0 -1
  116. package/dist/v2/shadcn/components/ui/switch.js +0 -115
  117. package/dist/v2/shadcn/components/ui/switch.scss.js +0 -6
  118. package/dist/v2/shadcn/components/ui/table-pagination.d.ts +0 -11
  119. package/dist/v2/shadcn/components/ui/table-pagination.d.ts.map +0 -1
  120. package/dist/v2/shadcn/components/ui/table.d.ts +0 -11
  121. package/dist/v2/shadcn/components/ui/table.d.ts.map +0 -1
  122. package/dist/v2/shadcn/components/ui/tabs.d.ts +0 -8
  123. package/dist/v2/shadcn/components/ui/tabs.d.ts.map +0 -1
  124. package/dist/v2/shadcn/components/ui/textarea.d.ts +0 -6
  125. package/dist/v2/shadcn/components/ui/textarea.d.ts.map +0 -1
  126. package/dist/v2/shadcn/components/ui/toast.d.ts +0 -16
  127. package/dist/v2/shadcn/components/ui/toast.d.ts.map +0 -1
  128. package/dist/v2/shadcn/components/ui/toaster.d.ts +0 -2
  129. package/dist/v2/shadcn/components/ui/toaster.d.ts.map +0 -1
  130. package/dist/v2/shadcn/components/ui/toggle-group.d.ts +0 -13
  131. package/dist/v2/shadcn/components/ui/toggle-group.d.ts.map +0 -1
  132. package/dist/v2/shadcn/components/ui/toggle.d.ts +0 -13
  133. package/dist/v2/shadcn/components/ui/toggle.d.ts.map +0 -1
  134. package/dist/v2/shadcn/components/ui/tooltip.d.ts +0 -8
  135. package/dist/v2/shadcn/components/ui/tooltip.d.ts.map +0 -1
  136. package/dist/v2/shadcn/components/ui/use-toast.d.ts +0 -3
  137. package/dist/v2/shadcn/components/ui/use-toast.d.ts.map +0 -1
  138. package/dist/v2/shadcn/hooks/use-mobile.d.ts +0 -2
  139. package/dist/v2/shadcn/hooks/use-mobile.d.ts.map +0 -1
  140. package/dist/v2/shadcn/hooks/use-toast.d.ts +0 -45
  141. package/dist/v2/shadcn/hooks/use-toast.d.ts.map +0 -1
  142. package/dist/v2/shadcn/index.d.ts +0 -20
  143. package/dist/v2/shadcn/index.d.ts.map +0 -1
  144. package/dist/v2/shadcn/lib/utils.d.ts +0 -3
  145. package/dist/v2/shadcn/lib/utils.d.ts.map +0 -1
  146. package/dist/v2/shadcn/lib/utils.js +0 -11
  147. package/dist/v2/shadcn/styles/globals.css +0 -112
  148. package/src/v2/shadcn/_reference/AccountManagerCard.tsx +0 -45
  149. package/src/v2/shadcn/_reference/AffiliatesTable.tsx +0 -178
  150. package/src/v2/shadcn/_reference/AuditArchive.tsx +0 -165
  151. package/src/v2/shadcn/_reference/AuditContent.tsx +0 -270
  152. package/src/v2/shadcn/_reference/AutomationsGeneralSettings.tsx +0 -251
  153. package/src/v2/shadcn/_reference/AvatarUpload.tsx +0 -150
  154. package/src/v2/shadcn/_reference/BookingsSummaryCard.tsx +0 -268
  155. package/src/v2/shadcn/_reference/CodeCleanUpAudit.tsx +0 -274
  156. package/src/v2/shadcn/_reference/CompaniesTable.tsx +0 -387
  157. package/src/v2/shadcn/_reference/ComponentAudit.tsx +0 -239
  158. package/src/v2/shadcn/_reference/ConfigureSettingsCard.tsx +0 -95
  159. package/src/v2/shadcn/_reference/CustomerCard.tsx +0 -155
  160. package/src/v2/shadcn/_reference/DashboardCards.tsx +0 -50
  161. package/src/v2/shadcn/_reference/DashboardFooter.tsx +0 -18
  162. package/src/v2/shadcn/_reference/DiarySettings.tsx +0 -187
  163. package/src/v2/shadcn/_reference/DiaryView.tsx +0 -998
  164. package/src/v2/shadcn/_reference/EmptyState.tsx +0 -76
  165. package/src/v2/shadcn/_reference/EntityInfoCard.tsx +0 -48
  166. package/src/v2/shadcn/_reference/ExistingUserAssignments.tsx +0 -131
  167. package/src/v2/shadcn/_reference/FeatureToggle.tsx +0 -72
  168. package/src/v2/shadcn/_reference/FlowCard.tsx +0 -170
  169. package/src/v2/shadcn/_reference/FlowsContent.tsx +0 -688
  170. package/src/v2/shadcn/_reference/FlowsGeneralSettings.tsx +0 -27
  171. package/src/v2/shadcn/_reference/GeneralSettings.tsx +0 -33
  172. package/src/v2/shadcn/_reference/InventoryGeneralSettings.tsx +0 -82
  173. package/src/v2/shadcn/_reference/LanguageSelector.tsx +0 -97
  174. package/src/v2/shadcn/_reference/LoadingScreen.tsx +0 -25
  175. package/src/v2/shadcn/_reference/LoadingSpinner.tsx +0 -41
  176. package/src/v2/shadcn/_reference/ManagedClientsList.tsx +0 -121
  177. package/src/v2/shadcn/_reference/NPSScore.tsx +0 -379
  178. package/src/v2/shadcn/_reference/NPSSummaryCard.tsx +0 -181
  179. package/src/v2/shadcn/_reference/NotificationBanner.tsx +0 -129
  180. package/src/v2/shadcn/_reference/NotificationPanel.tsx +0 -208
  181. package/src/v2/shadcn/_reference/OnlineUsersCard.tsx +0 -73
  182. package/src/v2/shadcn/_reference/ProtectedRoute.tsx +0 -39
  183. package/src/v2/shadcn/_reference/ProvidersTable.tsx +0 -353
  184. package/src/v2/shadcn/_reference/QuickAddPanel.tsx +0 -1057
  185. package/src/v2/shadcn/_reference/QuickFilters.tsx +0 -112
  186. package/src/v2/shadcn/_reference/ScheduleView.tsx +0 -410
  187. package/src/v2/shadcn/_reference/ScrollToTop.tsx +0 -14
  188. package/src/v2/shadcn/_reference/SecondaryNav.tsx +0 -50
  189. package/src/v2/shadcn/_reference/SecuritySettings.tsx +0 -258
  190. package/src/v2/shadcn/_reference/SessionDetailView.tsx +0 -294
  191. package/src/v2/shadcn/_reference/Sidebar.tsx +0 -14
  192. package/src/v2/shadcn/_reference/SidebarAwareLayout.tsx +0 -30
  193. package/src/v2/shadcn/_reference/SidebarLabelCustomization.tsx +0 -285
  194. package/src/v2/shadcn/_reference/SimulationBanner.tsx +0 -57
  195. package/src/v2/shadcn/_reference/SortControls.tsx +0 -65
  196. package/src/v2/shadcn/_reference/StatusBadge.tsx +0 -49
  197. package/src/v2/shadcn/_reference/StyleGuideContent.tsx +0 -331
  198. package/src/v2/shadcn/_reference/TableActionMenu.tsx +0 -126
  199. package/src/v2/shadcn/_reference/ThemeProvider.tsx +0 -119
  200. package/src/v2/shadcn/_reference/ThemeSettings.tsx +0 -73
  201. package/src/v2/shadcn/_reference/TopNavigation.tsx +0 -332
  202. package/src/v2/shadcn/_reference/UserActivityHistory.tsx +0 -209
  203. package/src/v2/shadcn/_reference/UserLanguageSettings.tsx +0 -94
  204. package/src/v2/shadcn/_reference/UserPanel.tsx +0 -472
  205. package/src/v2/shadcn/_reference/UsersTable.tsx +0 -1023
  206. package/src/v2/shadcn/_reference/WaiverForm.tsx +0 -301
  207. package/src/v2/shadcn/_reference/WaiversGeneralSettings.tsx +0 -46
  208. package/src/v2/shadcn/_reference/WaiversTable.tsx +0 -290
  209. package/src/v2/shadcn/_reference/WaiversTemplatesSettings.tsx +0 -416
  210. package/src/v2/shadcn/_reference/ai/AIChatPanel.tsx +0 -313
  211. package/src/v2/shadcn/_reference/ai/AIChatSearchBar.tsx +0 -36
  212. package/src/v2/shadcn/_reference/ai/ChatInteractiveBlock.tsx +0 -298
  213. package/src/v2/shadcn/_reference/ai/ChatMessageContent.tsx +0 -40
  214. package/src/v2/shadcn/_reference/ai/parseInteractiveBlocks.ts +0 -142
  215. package/src/v2/shadcn/_reference/auth/AuthLayout.tsx +0 -55
  216. package/src/v2/shadcn/_reference/auth/CreatePasswordForm.tsx +0 -285
  217. package/src/v2/shadcn/_reference/auth/CreatePasswordPanel.tsx +0 -20
  218. package/src/v2/shadcn/_reference/auth/LoginFooter.tsx +0 -14
  219. package/src/v2/shadcn/_reference/auth/LoginForm.tsx +0 -205
  220. package/src/v2/shadcn/_reference/auth/LoginPanel.tsx +0 -41
  221. package/src/v2/shadcn/_reference/auth/ResetPasswordForm.tsx +0 -102
  222. package/src/v2/shadcn/_reference/auth/ResetPasswordPanel.tsx +0 -20
  223. package/src/v2/shadcn/_reference/auth/VerifyEmailForm.tsx +0 -95
  224. package/src/v2/shadcn/_reference/auth/VerifyEmailPanel.tsx +0 -20
  225. package/src/v2/shadcn/_reference/email/EmailAttachment.tsx +0 -119
  226. package/src/v2/shadcn/_reference/email/EmailAutomation.tsx +0 -92
  227. package/src/v2/shadcn/_reference/email/EmailPlaceholders.tsx +0 -64
  228. package/src/v2/shadcn/_reference/email/UnlayerEmailEditor.tsx +0 -41
  229. package/src/v2/shadcn/_reference/email/emailTemplateData.ts +0 -53
  230. package/src/v2/shadcn/_reference/emptyStateIcons.tsx +0 -103
  231. package/src/v2/shadcn/_reference/games/MazeGame.tsx +0 -394
  232. package/src/v2/shadcn/_reference/games/RunnerGame.tsx +0 -497
  233. package/src/v2/shadcn/_reference/logos/BookedLogoFull.tsx +0 -36
  234. package/src/v2/shadcn/_reference/logos/BookedLogoMark.tsx +0 -31
  235. package/src/v2/shadcn/_reference/logos/BookedLogoNew.tsx +0 -36
  236. package/src/v2/shadcn/_reference/pricing/DynamicPricingRulesEditor.tsx +0 -401
  237. package/src/v2/shadcn/_reference/pricing/DynamicPricingTierCard.tsx +0 -77
  238. package/src/v2/shadcn/_reference/pricing/DynamicPricingTiersList.tsx +0 -218
  239. package/src/v2/shadcn/_reference/pricing/PricingCalendar.tsx +0 -810
  240. package/src/v2/shadcn/_reference/pricing/PricingPeriodCard.tsx +0 -152
  241. package/src/v2/shadcn/_reference/pricing/PricingPeriodForm.tsx +0 -377
  242. package/src/v2/shadcn/_reference/pricing/PricingPeriodsList.tsx +0 -213
  243. package/src/v2/shadcn/_reference/pricing/getRuleSummary.ts +0 -39
  244. package/src/v2/shadcn/_reference/products/AvailabilityRulesSection.tsx +0 -184
  245. package/src/v2/shadcn/_reference/products/AvailabilitySection.tsx +0 -677
  246. package/src/v2/shadcn/_reference/products/BookingTypeConfigOptions.tsx +0 -40
  247. package/src/v2/shadcn/_reference/products/CapacityPeriodsSection.tsx +0 -238
  248. package/src/v2/shadcn/_reference/products/DynamicPricingTiersSection.tsx +0 -131
  249. package/src/v2/shadcn/_reference/products/GiftCardOrdersTab.tsx +0 -192
  250. package/src/v2/shadcn/_reference/products/GiftCardSettings.tsx +0 -342
  251. package/src/v2/shadcn/_reference/products/PackageProductsSection.tsx +0 -322
  252. package/src/v2/shadcn/_reference/products/PricingSection.tsx +0 -173
  253. package/src/v2/shadcn/_reference/products/ProductTypeFields.tsx +0 -353
  254. package/src/v2/shadcn/_reference/products/ProductTypeIcon.tsx +0 -95
  255. package/src/v2/shadcn/_reference/products/VariablePricingSection.tsx +0 -140
  256. package/src/v2/shadcn/_reference/products/productTypeConfig.ts +0 -182
  257. package/src/v2/shadcn/_reference/shared/BackButton.tsx +0 -50
  258. package/src/v2/shadcn/_reference/shared/CancelConfirmationDialog.tsx +0 -18
  259. package/src/v2/shadcn/_reference/shared/ConfirmationDialog.tsx +0 -136
  260. package/src/v2/shadcn/_reference/shared/DeleteConfirmationDialog.tsx +0 -18
  261. package/src/v2/shadcn/_reference/shared/DeleteEntityPage.tsx +0 -221
  262. package/src/v2/shadcn/_reference/shared/SidebarIcons.tsx +0 -108
  263. package/src/v2/shadcn/_reference/shared/UnifiedSidebar.tsx +0 -722
  264. package/src/v2/shadcn/_reference/tables/BulkActionsBar.tsx +0 -68
  265. package/src/v2/shadcn/_reference/tables/DataTable.tsx +0 -221
  266. package/src/v2/shadcn/_reference/tables/TableControls.tsx +0 -94
  267. package/src/v2/shadcn/_reference/tables/index.ts +0 -3
  268. package/src/v2/shadcn/_reference/tables/types.ts +0 -79
  269. package/src/v2/shadcn/_reference/zones/LegacyZoneSettings.tsx +0 -299
  270. package/src/v2/shadcn/components/ui/accordion.stories.tsx +0 -63
  271. package/src/v2/shadcn/components/ui/accordion.tsx +0 -52
  272. package/src/v2/shadcn/components/ui/alert-dialog.stories.tsx +0 -44
  273. package/src/v2/shadcn/components/ui/alert-dialog.tsx +0 -104
  274. package/src/v2/shadcn/components/ui/alert.stories.tsx +0 -44
  275. package/src/v2/shadcn/components/ui/alert.tsx +0 -43
  276. package/src/v2/shadcn/components/ui/aspect-ratio.stories.tsx +0 -46
  277. package/src/v2/shadcn/components/ui/aspect-ratio.tsx +0 -5
  278. package/src/v2/shadcn/components/ui/avatar.stories.tsx +0 -39
  279. package/src/v2/shadcn/components/ui/avatar.tsx +0 -38
  280. package/src/v2/shadcn/components/ui/badge.stories.tsx +0 -17
  281. package/src/v2/shadcn/components/ui/badge.tsx +0 -30
  282. package/src/v2/shadcn/components/ui/breadcrumb.stories.tsx +0 -91
  283. package/src/v2/shadcn/components/ui/breadcrumb.tsx +0 -90
  284. package/src/v2/shadcn/components/ui/button.stories.tsx +0 -20
  285. package/src/v2/shadcn/components/ui/button.tsx +0 -60
  286. package/src/v2/shadcn/components/ui/calendar.stories.tsx +0 -61
  287. package/src/v2/shadcn/components/ui/calendar.tsx +0 -54
  288. package/src/v2/shadcn/components/ui/card.stories.tsx +0 -37
  289. package/src/v2/shadcn/components/ui/card.tsx +0 -43
  290. package/src/v2/shadcn/components/ui/carousel.stories.tsx +0 -92
  291. package/src/v2/shadcn/components/ui/carousel.tsx +0 -224
  292. package/src/v2/shadcn/components/ui/checkbox.scss +0 -38
  293. package/src/v2/shadcn/components/ui/checkbox.stories.tsx +0 -23
  294. package/src/v2/shadcn/components/ui/checkbox.tsx +0 -24
  295. package/src/v2/shadcn/components/ui/collapsible.stories.tsx +0 -59
  296. package/src/v2/shadcn/components/ui/collapsible.tsx +0 -9
  297. package/src/v2/shadcn/components/ui/command.stories.tsx +0 -70
  298. package/src/v2/shadcn/components/ui/command.tsx +0 -132
  299. package/src/v2/shadcn/components/ui/context-menu.stories.tsx +0 -72
  300. package/src/v2/shadcn/components/ui/context-menu.tsx +0 -178
  301. package/src/v2/shadcn/components/ui/dialog.stories.tsx +0 -67
  302. package/src/v2/shadcn/components/ui/dialog.tsx +0 -95
  303. package/src/v2/shadcn/components/ui/drawer.stories.tsx +0 -50
  304. package/src/v2/shadcn/components/ui/drawer.tsx +0 -87
  305. package/src/v2/shadcn/components/ui/dropdown-menu.stories.tsx +0 -73
  306. package/src/v2/shadcn/components/ui/dropdown-menu.tsx +0 -179
  307. package/src/v2/shadcn/components/ui/form.stories.tsx +0 -105
  308. package/src/v2/shadcn/components/ui/form.tsx +0 -129
  309. package/src/v2/shadcn/components/ui/hover-card.stories.tsx +0 -35
  310. package/src/v2/shadcn/components/ui/hover-card.tsx +0 -27
  311. package/src/v2/shadcn/components/ui/input-otp.stories.tsx +0 -72
  312. package/src/v2/shadcn/components/ui/input-otp.tsx +0 -61
  313. package/src/v2/shadcn/components/ui/input.stories.tsx +0 -16
  314. package/src/v2/shadcn/components/ui/input.tsx +0 -25
  315. package/src/v2/shadcn/components/ui/label.stories.tsx +0 -13
  316. package/src/v2/shadcn/components/ui/label.tsx +0 -17
  317. package/src/v2/shadcn/components/ui/menubar.stories.tsx +0 -86
  318. package/src/v2/shadcn/components/ui/menubar.tsx +0 -207
  319. package/src/v2/shadcn/components/ui/navigation-menu.stories.tsx +0 -68
  320. package/src/v2/shadcn/components/ui/navigation-menu.tsx +0 -120
  321. package/src/v2/shadcn/components/ui/pagination.stories.tsx +0 -78
  322. package/src/v2/shadcn/components/ui/pagination.tsx +0 -81
  323. package/src/v2/shadcn/components/ui/popover.stories.tsx +0 -44
  324. package/src/v2/shadcn/components/ui/popover.tsx +0 -29
  325. package/src/v2/shadcn/components/ui/progress.stories.tsx +0 -17
  326. package/src/v2/shadcn/components/ui/progress.tsx +0 -23
  327. package/src/v2/shadcn/components/ui/radio-card.stories.tsx +0 -68
  328. package/src/v2/shadcn/components/ui/radio-card.tsx +0 -52
  329. package/src/v2/shadcn/components/ui/radio-group.stories.tsx +0 -77
  330. package/src/v2/shadcn/components/ui/radio-group.tsx +0 -35
  331. package/src/v2/shadcn/components/ui/scroll-area.stories.tsx +0 -56
  332. package/src/v2/shadcn/components/ui/scroll-area.tsx +0 -38
  333. package/src/v2/shadcn/components/ui/select.stories.tsx +0 -60
  334. package/src/v2/shadcn/components/ui/select.tsx +0 -148
  335. package/src/v2/shadcn/components/ui/separator.stories.tsx +0 -30
  336. package/src/v2/shadcn/components/ui/separator.tsx +0 -20
  337. package/src/v2/shadcn/components/ui/sheet.stories.tsx +0 -115
  338. package/src/v2/shadcn/components/ui/sheet.tsx +0 -107
  339. package/src/v2/shadcn/components/ui/sidebar.stories.tsx +0 -167
  340. package/src/v2/shadcn/components/ui/sidebar.tsx +0 -637
  341. package/src/v2/shadcn/components/ui/skeleton.stories.tsx +0 -36
  342. package/src/v2/shadcn/components/ui/skeleton.tsx +0 -7
  343. package/src/v2/shadcn/components/ui/slider.stories.tsx +0 -16
  344. package/src/v2/shadcn/components/ui/slider.tsx +0 -23
  345. package/src/v2/shadcn/components/ui/switch.scss +0 -63
  346. package/src/v2/shadcn/components/ui/switch.stories.tsx +0 -23
  347. package/src/v2/shadcn/components/ui/switch.tsx +0 -24
  348. package/src/v2/shadcn/components/ui/table-pagination.stories.tsx +0 -81
  349. package/src/v2/shadcn/components/ui/table-pagination.tsx +0 -61
  350. package/src/v2/shadcn/components/ui/table.stories.tsx +0 -40
  351. package/src/v2/shadcn/components/ui/table.tsx +0 -72
  352. package/src/v2/shadcn/components/ui/tabs.stories.tsx +0 -85
  353. package/src/v2/shadcn/components/ui/tabs.tsx +0 -53
  354. package/src/v2/shadcn/components/ui/textarea.stories.tsx +0 -15
  355. package/src/v2/shadcn/components/ui/textarea.tsx +0 -21
  356. package/src/v2/shadcn/components/ui/toast.stories.tsx +0 -77
  357. package/src/v2/shadcn/components/ui/toast.tsx +0 -111
  358. package/src/v2/shadcn/components/ui/toaster.stories.tsx +0 -46
  359. package/src/v2/shadcn/components/ui/toaster.tsx +0 -24
  360. package/src/v2/shadcn/components/ui/toggle-group.stories.tsx +0 -95
  361. package/src/v2/shadcn/components/ui/toggle-group.tsx +0 -49
  362. package/src/v2/shadcn/components/ui/toggle.stories.tsx +0 -18
  363. package/src/v2/shadcn/components/ui/toggle.tsx +0 -37
  364. package/src/v2/shadcn/components/ui/tooltip.stories.tsx +0 -57
  365. package/src/v2/shadcn/components/ui/tooltip.tsx +0 -28
  366. package/src/v2/shadcn/components/ui/use-toast.ts +0 -3
  367. package/src/v2/shadcn/hooks/use-mobile.tsx +0 -19
  368. package/src/v2/shadcn/hooks/use-toast.ts +0 -184
  369. package/src/v2/shadcn/index.ts +0 -76
  370. package/src/v2/shadcn/lib/utils.ts +0 -6
  371. package/src/v2/shadcn/styles/globals.css +0 -112
@@ -1,1023 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { useNavigate } from 'react-router-dom';
3
- import { DataTable } from './tables/DataTable';
4
- import { TableControls } from './tables/TableControls';
5
- import { ColumnConfig, RowAction, SearchConfig, HeaderAction } from './tables/types';
6
- import { BulkActionsBar } from './tables/BulkActionsBar';
7
- import StatusBadge from './StatusBadge';
8
- import { supabase } from '@/integrations/supabase/client';
9
- import LoadingScreen from './LoadingScreen';
10
- import { AlertDialog, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from './ui/alert-dialog';
11
- import { Button } from './ui/button';
12
- import { formatDistanceToNow } from 'date-fns';
13
- import DeleteConfirmationDialog from './shared/DeleteConfirmationDialog';
14
- import { getShortUserId } from '@/lib/userUtils';
15
- import { useAuth } from '@/contexts/AuthContext';
16
- import { usePermissions } from '../hooks/usePermissions';
17
- import { useNotify } from '../hooks/useNotify';
18
- import NotificationBanner, { Notification } from './NotificationBanner';
19
- import IconImportUser from '@/assets/Icon_ImportUser.svg';
20
- import { IconAddUser } from '../../icons';
21
-
22
- interface UserRole {
23
- user_id: string;
24
- role: string;
25
- provider_id: string | null;
26
- company_id: string | null;
27
- }
28
-
29
- interface User {
30
- id: string;
31
- full_name: string | null;
32
- email: string | null;
33
- status: string | null;
34
- created_at: string;
35
- roles: string[];
36
- currentLevelRole: string | null;
37
- otherProviderRoleCount: number;
38
- avatar_url: string | null;
39
- last_seen_at: string | null;
40
- user_number: number | null;
41
- providers: string[];
42
- companies: string[];
43
- }
44
-
45
- export interface UsersTableProps {
46
- /** If provided, scopes users to this provider */
47
- providerId?: string;
48
- /** If provided alongside providerId, also includes company users */
49
- companyId?: string | null;
50
- /** Navigation prefix for row clicks/actions (e.g. "/company/01/venue/01") */
51
- navigatePrefix?: string;
52
- /** Custom header actions (overrides default admin actions) */
53
- headerActions?: HeaderAction[];
54
- /** Whether to show bulk actions (default: true for admin) */
55
- showBulkActions?: boolean;
56
- /** Whether to show delete row action (default: true for admin) */
57
- showDeleteAction?: boolean;
58
- /** Whether to show the "Role & Provider" column or just "Role" (default: true) */
59
- showProviderColumn?: boolean;
60
- /** Filter out super_admin, system_admin, customer roles (default: false) */
61
- filterAdminRoles?: boolean;
62
- }
63
-
64
- const UsersTable: React.FC<UsersTableProps> = ({
65
- providerId,
66
- companyId,
67
- navigatePrefix = '/admin',
68
- headerActions: customHeaderActions,
69
- showBulkActions = true,
70
- showDeleteAction = true,
71
- showProviderColumn = true,
72
- filterAdminRoles = false,
73
- }) => {
74
- const navigate = useNavigate();
75
- const { showSuccess, showError } = useNotify();
76
- const { user: currentUser } = useAuth();
77
- const { hasPermission } = usePermissions();
78
- const [searchQuery, setSearchQuery] = useState('');
79
- const [users, setUsers] = useState<User[]>([]);
80
- const [isLoading, setIsLoading] = useState(true);
81
- const [showDeleteDialog, setShowDeleteDialog] = useState(false);
82
- const [selectedUser, setSelectedUser] = useState<User | null>(null);
83
- const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
84
- const [notification, setNotification] = useState<Notification | null>(null);
85
- const [sortKey, setSortKey] = useState<string>('name');
86
- const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
87
-
88
- useEffect(() => {
89
- fetchUsers();
90
- }, []);
91
-
92
- const fetchUsers = async () => {
93
- try {
94
- setIsLoading(true);
95
-
96
- let assignedUserIds: string[] | null = null;
97
-
98
- // If scoped to a provider, fetch only assigned user IDs
99
- if (providerId) {
100
- const { data: userProviderAssignments, error: assignmentsError } = await supabase
101
- .from('user_providers')
102
- .select('user_id')
103
- .eq('provider_id', providerId);
104
-
105
- if (assignmentsError) throw assignmentsError;
106
-
107
- assignedUserIds = userProviderAssignments?.map(up => up.user_id) || [];
108
-
109
- // Also include company users if provider belongs to a company
110
- if (companyId) {
111
- const { data: companyUserAssignments, error: companyError } = await supabase
112
- .from('user_companies')
113
- .select('user_id')
114
- .eq('company_id', companyId);
115
-
116
- if (companyError) throw companyError;
117
-
118
- const companyUserIds = companyUserAssignments?.map(uc => uc.user_id) || [];
119
- assignedUserIds = [...new Set([...assignedUserIds, ...companyUserIds])];
120
- }
121
-
122
- if (assignedUserIds.length === 0) {
123
- setUsers([]);
124
- setIsLoading(false);
125
- return;
126
- }
127
- } else if (companyId) {
128
- // Company-only scope: get users from user_companies + all providers under this company
129
- const { data: companyUserAssignments, error: companyError } = await supabase
130
- .from('user_companies')
131
- .select('user_id')
132
- .eq('company_id', companyId);
133
-
134
- if (companyError) throw companyError;
135
-
136
- const companyUserIds = companyUserAssignments?.map(uc => uc.user_id) || [];
137
-
138
- // Also get users assigned to any provider under this company
139
- const { data: companyProviders, error: cpError } = await supabase
140
- .from('providers')
141
- .select('id')
142
- .eq('company_id', companyId);
143
-
144
- if (cpError) throw cpError;
145
-
146
- const providerIds = companyProviders?.map(p => p.id) || [];
147
- let providerUserIds: string[] = [];
148
-
149
- if (providerIds.length > 0) {
150
- const { data: providerUserAssignments, error: puError } = await supabase
151
- .from('user_providers')
152
- .select('user_id')
153
- .in('provider_id', providerIds);
154
-
155
- if (puError) throw puError;
156
- providerUserIds = providerUserAssignments?.map(up => up.user_id) || [];
157
- }
158
-
159
- assignedUserIds = [...new Set([...companyUserIds, ...providerUserIds])];
160
-
161
- if (assignedUserIds.length === 0) {
162
- setUsers([]);
163
- setIsLoading(false);
164
- return;
165
- }
166
- }
167
-
168
- // Fetch profiles (scoped or all)
169
- let profilesQuery = supabase.from('profiles').select('*');
170
- if (assignedUserIds) {
171
- profilesQuery = profilesQuery.in('id', assignedUserIds);
172
- }
173
- const { data: profiles, error: profilesError } = await profilesQuery;
174
-
175
- if (profilesError) throw profilesError;
176
-
177
- const userIds = profiles?.map(p => p.id) || [];
178
-
179
- // Fetch user roles with scoping columns
180
- let rolesQuery = supabase.from('user_roles').select('user_id, role, provider_id, company_id');
181
- if (assignedUserIds) {
182
- rolesQuery = rolesQuery.in('user_id', assignedUserIds);
183
- }
184
- const { data: userRoles, error: rolesError } = await rolesQuery;
185
- if (rolesError) throw rolesError;
186
-
187
- // Fetch user provider assignments
188
- let providersQuery = supabase.from('user_providers').select('user_id, provider_id');
189
- if (assignedUserIds) {
190
- providersQuery = providersQuery.in('user_id', assignedUserIds);
191
- }
192
- const { data: userProviders, error: providersError } = await providersQuery;
193
- if (providersError) throw providersError;
194
-
195
- // Fetch all providers
196
- const { data: providers, error: allProvidersError } = await supabase
197
- .from('providers')
198
- .select('id, name');
199
- if (allProvidersError) throw allProvidersError;
200
-
201
- // Fetch user company assignments
202
- let companiesQuery = supabase.from('user_companies').select('user_id, company_id');
203
- if (assignedUserIds) {
204
- companiesQuery = companiesQuery.in('user_id', assignedUserIds);
205
- }
206
- const { data: userCompanies, error: companiesError } = await companiesQuery;
207
- if (companiesError) throw companiesError;
208
-
209
- // Fetch all companies
210
- const { data: companies, error: allCompaniesError } = await supabase
211
- .from('companies')
212
- .select('id, name');
213
- if (allCompaniesError) throw allCompaniesError;
214
-
215
- // Combine profiles with roles, providers, and companies
216
- let usersWithRoles = profiles?.map(profile => {
217
- const allRoles = (userRoles as UserRole[] || [])
218
- .filter(ur => ur.user_id === profile.id);
219
- const roles = allRoles.map(ur => ur.role);
220
-
221
- // Determine current level role based on context
222
- let currentLevelRole: string | null = null;
223
- let otherProviderRoleCount = 0;
224
-
225
- if (providerId) {
226
- // Provider level: show the role scoped to this provider
227
- const providerRole = allRoles.find(r => r.provider_id === providerId);
228
- currentLevelRole = providerRole?.role || null;
229
-
230
- // If user has no provider-scoped role but is in user_providers, they have access but no explicit role
231
- if (!currentLevelRole) {
232
- // Check if they have a company role (company user viewing provider)
233
- const companyRole = companyId ? allRoles.find(r => r.company_id === companyId && !r.provider_id) : null;
234
- currentLevelRole = companyRole?.role || 'No Access';
235
- }
236
- } else if (companyId) {
237
- // Company level: show the role scoped to this company
238
- const companyRole = allRoles.find(r => r.company_id === companyId && !r.provider_id);
239
- currentLevelRole = companyRole?.role || 'No Access';
240
-
241
- // Count all provider-level roles
242
- const providerRoles = allRoles.filter(r => r.provider_id !== null);
243
- otherProviderRoleCount = providerRoles.length;
244
- } else {
245
- // Admin level: show super_admin/system_admin or "Provider User"
246
- const hasAdminRole = allRoles.find(r => r.role === 'super_admin' || r.role === 'system_admin');
247
- currentLevelRole = hasAdminRole?.role || 'provider_user';
248
- }
249
-
250
- const userProviderIds = userProviders
251
- ?.filter(up => up.user_id === profile.id)
252
- .map(up => up.provider_id) || [];
253
- const providerNames = providers
254
- ?.filter(p => userProviderIds.includes(p.id))
255
- .map(p => p.name) || [];
256
-
257
- const userCompanyIds = userCompanies
258
- ?.filter(uc => uc.user_id === profile.id)
259
- .map(uc => uc.company_id) || [];
260
- const companyNames = companies
261
- ?.filter(c => userCompanyIds.includes(c.id))
262
- .map(c => c.name) || [];
263
-
264
- return {
265
- id: profile.id,
266
- full_name: profile.full_name,
267
- email: profile.email,
268
- status: profile.status,
269
- created_at: profile.created_at,
270
- roles: roles,
271
- currentLevelRole,
272
- otherProviderRoleCount,
273
- avatar_url: profile.avatar_url,
274
- last_seen_at: profile.last_seen_at,
275
- user_number: profile.user_number,
276
- providers: providerNames,
277
- companies: companyNames
278
- };
279
- }) || [];
280
-
281
- // Filter out admin/system roles if requested
282
- if (filterAdminRoles) {
283
- usersWithRoles = usersWithRoles.filter(user =>
284
- !user.roles.some(role =>
285
- role === 'super_admin' ||
286
- role === 'system_admin' ||
287
- role === 'customer'
288
- )
289
- );
290
- }
291
-
292
- setUsers(usersWithRoles);
293
- } catch (error) {
294
- console.error('Error fetching users:', error);
295
- } finally {
296
- setIsLoading(false);
297
- }
298
- };
299
-
300
- const handleSort = (key: string) => {
301
- if (sortKey === key) {
302
- setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
303
- } else {
304
- setSortKey(key);
305
- setSortDirection('asc');
306
- }
307
- };
308
-
309
- const filteredUsers = users.filter(user => {
310
- const searchLower = searchQuery.toLowerCase();
311
- const userIdString = getShortUserId(user.user_number);
312
- return (
313
- user.full_name?.toLowerCase().includes(searchLower) ||
314
- user.email?.toLowerCase().includes(searchLower) ||
315
- userIdString.toLowerCase().includes(searchLower) ||
316
- user.providers.some(provider => provider.toLowerCase().includes(searchLower)) ||
317
- user.companies.some(company => company.toLowerCase().includes(searchLower))
318
- );
319
- });
320
-
321
- const sortedUsers = [...filteredUsers].sort((a, b) => {
322
- let aValue: any;
323
- let bValue: any;
324
-
325
- switch (sortKey) {
326
- case 'name':
327
- aValue = a.full_name?.toLowerCase() || '';
328
- bValue = b.full_name?.toLowerCase() || '';
329
- break;
330
- case 'created':
331
- aValue = new Date(a.created_at).getTime();
332
- bValue = new Date(b.created_at).getTime();
333
- break;
334
- case 'last_seen':
335
- aValue = a.last_seen_at ? new Date(a.last_seen_at).getTime() : 0;
336
- bValue = b.last_seen_at ? new Date(b.last_seen_at).getTime() : 0;
337
- break;
338
- default:
339
- return 0;
340
- }
341
-
342
- if (aValue < bValue) return sortDirection === 'asc' ? -1 : 1;
343
- if (aValue > bValue) return sortDirection === 'asc' ? 1 : -1;
344
- return 0;
345
- });
346
-
347
- const searchConfig: SearchConfig = {
348
- placeholder: 'Search by name, email, ID, company or provider',
349
- value: searchQuery,
350
- onChange: setSearchQuery,
351
- label: 'Search Users'
352
- };
353
-
354
- // Auto-cleanup orphaned auth users on mount (one-time, admin only)
355
- useEffect(() => {
356
- if (providerId) return; // Skip cleanup for provider-scoped views
357
- const cleanupOrphanedUsers = async () => {
358
- try {
359
- const { data: { session } } = await supabase.auth.getSession();
360
- if (!session) return; // Skip if not authenticated
361
- await supabase.functions.invoke('cleanup-orphaned-users', { body: {} });
362
- console.log('Orphaned users cleanup completed');
363
- } catch (error) {
364
- console.error('Cleanup error:', error);
365
- }
366
- };
367
- cleanupOrphanedUsers();
368
- }, [providerId]);
369
-
370
- const defaultActions: HeaderAction[] = [
371
- {
372
- label: 'Add New User',
373
- mobileLabel: 'Add',
374
- variant: 'ghost',
375
- withIcon: true,
376
- icon: <IconAddUser className="fill-current" />,
377
- onClick: () => navigate(`${navigatePrefix}/users/add`)
378
- },
379
- {
380
- label: 'Import Users',
381
- mobileLabel: 'Import',
382
- icon: <img src={IconImportUser} alt="" className="w-5 h-5" />,
383
- onClick: () => navigate(`${navigatePrefix}/users/import`),
384
- variant: 'outline'
385
- }
386
- ];
387
-
388
- const actions = customHeaderActions || defaultActions;
389
-
390
- const getInitials = (name: string | null) => {
391
- if (!name) return 'U';
392
- const parts = name.trim().split(' ');
393
- if (parts.length === 1) return parts[0][0].toUpperCase();
394
- return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
395
- };
396
-
397
- const getUserIcon = (user: User) => {
398
- if (user.avatar_url) {
399
- return (
400
- <div className="flex w-10 h-10 justify-center items-center rounded-full overflow-hidden flex-shrink-0">
401
- <img src={user.avatar_url} alt={user.full_name || 'User'} className="w-full h-full object-cover" />
402
- </div>
403
- );
404
- }
405
-
406
- const initials = getInitials(user.full_name);
407
-
408
- return (
409
- <div className="w-10 h-10 relative flex items-center justify-center flex-shrink-0 overflow-hidden rounded-full">
410
- <svg width="40" height="40" viewBox="0 0 32 32" fill="none" className="absolute">
411
- <circle cx="16" cy="16" r="15" className="fill-fill-secondary stroke-fill-secondary" fillOpacity="0.2" strokeWidth="2" />
412
- </svg>
413
- <div className="text-xs font-extrabold leading-3 text-label-secondary relative">
414
- {initials}
415
- </div>
416
- </div>
417
- );
418
- };
419
-
420
- const formatDate = (dateString: string) => {
421
- const date = new Date(dateString);
422
- return date.toLocaleDateString('en-GB', {
423
- day: '2-digit',
424
- month: '2-digit',
425
- year: 'numeric'
426
- });
427
- };
428
-
429
- const formatRole = (user: User) => {
430
- if (!user.currentLevelRole || user.currentLevelRole === 'No Access') return 'No Access';
431
- if (user.currentLevelRole === 'provider_user') return 'Provider User';
432
- return user.currentLevelRole.replace(/_/g, ' ');
433
- };
434
-
435
- const getCompanyProvider = (roles: string[], providers: string[], companies: string[]) => {
436
- // Check if user has System Admin or Super Admin role
437
- const hasAdminRole = roles.some(role =>
438
- role === 'system_admin' || role === 'super_admin'
439
- );
440
-
441
- if (hasAdminRole) {
442
- return 'Booked it';
443
- }
444
-
445
- // Combine companies and providers
446
- const allAssignments = [...companies, ...providers];
447
-
448
- // Handle multiple assignments
449
- if (allAssignments.length === 0) return 'Unassigned';
450
- if (allAssignments.length === 1) return allAssignments[0];
451
- return `${allAssignments[0]} +${allAssignments.length - 1}`;
452
- };
453
-
454
- const formatLastSeen = (lastSeenAt: string | null, status: string | null) => {
455
- // If user is active and last seen is within 5 minutes, show "Active Now"
456
- if (status === 'active' && lastSeenAt) {
457
- const lastSeenDate = new Date(lastSeenAt);
458
- const now = new Date();
459
- const diffMinutes = (now.getTime() - lastSeenDate.getTime()) / (1000 * 60);
460
- if (diffMinutes < 5) {
461
- return 'Active Now';
462
- }
463
- }
464
-
465
- if (!lastSeenAt) return 'N/A';
466
- try {
467
- // Remove "about" from the output
468
- return formatDistanceToNow(new Date(lastSeenAt), { addSuffix: true }).replace('about ', '');
469
- } catch {
470
- return 'N/A';
471
- }
472
- };
473
-
474
- const handleDeleteClick = (user: User) => {
475
- setSelectedUser(user);
476
- setShowDeleteDialog(true);
477
- };
478
-
479
- const handleDeleteConfirm = async () => {
480
- if (!selectedUser) return;
481
-
482
- try {
483
- // Call edge function to properly delete user from auth.users
484
- const { data, error } = await supabase.functions.invoke('delete-user', {
485
- body: { userId: selectedUser.id }
486
- });
487
-
488
- if (error) throw error;
489
- if (data?.error) throw new Error(data.error);
490
-
491
- showSuccess(`User ${selectedUser.full_name || selectedUser.email} deleted successfully`);
492
- fetchUsers(); // Refresh the list
493
- } catch (error) {
494
- console.error('Error deleting user:', error);
495
- showError('Failed to delete user', error instanceof Error ? error.message : 'Please try again.');
496
- } finally {
497
- setShowDeleteDialog(false);
498
- setSelectedUser(null);
499
- }
500
- };
501
-
502
- const handleSuspendToggle = async (user: User) => {
503
- const newStatus = user.status === 'active' ? 'suspended' : 'active';
504
-
505
- try {
506
- const { error } = await supabase
507
- .from('profiles')
508
- .update({ status: newStatus })
509
- .eq('id', user.id);
510
-
511
- if (error) throw error;
512
-
513
- fetchUsers(); // Refresh the list
514
- } catch (error) {
515
- console.error('Error updating user status:', error);
516
- }
517
- };
518
-
519
-
520
- const handleResendInvitation = async (user: User) => {
521
- try {
522
- const { data, error } = await supabase.functions.invoke('resend-invitation', {
523
- body: {
524
- userId: user.id
525
- }
526
- });
527
-
528
- if (error) {
529
- showError(`Failed to resend invitation to ${user.email}`, 'Please try again or contact support if the issue persists.');
530
- console.error('Error resending invitation:', error);
531
- return;
532
- }
533
-
534
- showSuccess(`Invitation email sent to ${user.email}`, 'The user will receive an email with instructions to set up their account.');
535
- fetchUsers(); // Refresh the table
536
- } catch (error) {
537
- showError('Unable to send invitation email', 'An unexpected error occurred. Please try again.');
538
- console.error('Error resending invitation:', error);
539
- }
540
- };
541
-
542
- const handleForcePasswordReset = async (user: User) => {
543
- try {
544
- const { data, error } = await supabase.functions.invoke('force-password-reset', {
545
- body: {
546
- userId: user.id
547
- }
548
- });
549
-
550
- if (error) {
551
- showError('Failed to force password reset', 'Please try again or contact support if the issue persists.');
552
- console.error('Error forcing password reset:', error);
553
- return;
554
- }
555
-
556
- showSuccess(`Password reset required for ${user.full_name || user.email}`, 'The user will be required to reset their password on next login.');
557
- fetchUsers();
558
- } catch (error) {
559
- showError('Unable to force password reset', 'An unexpected error occurred. Please try again.');
560
- console.error('Error forcing password reset:', error);
561
- }
562
- };
563
-
564
- const handleResetToSurnamePassword = async (user: User) => {
565
- try {
566
- const { data, error } = await supabase.functions.invoke('reset-to-surname-password', {
567
- body: {
568
- userId: user.id
569
- }
570
- });
571
-
572
- if (error) {
573
- showError('Failed to reset password', 'Please try again or contact support if the issue persists.');
574
- console.error('Error resetting password:', error);
575
- return;
576
- }
577
-
578
- showSuccess(`Password reset to surname for ${user.full_name || user.email}`, `New password: ${data.password}`);
579
- fetchUsers();
580
- } catch (error) {
581
- showError('Unable to reset password', 'An unexpected error occurred. Please try again.');
582
- console.error('Error resetting password:', error);
583
- }
584
- };
585
-
586
- // Bulk action handlers
587
- const handleClearSelection = () => {
588
- setSelectedUsers([]);
589
- };
590
-
591
- const handleBulkResendInvite = async () => {
592
- const selectedUserData = users.filter(u =>
593
- selectedUsers.includes(u.id) && u.status === 'invited'
594
- );
595
-
596
- if (selectedUserData.length === 0) {
597
- setNotification({
598
- type: 'error',
599
- title: 'No Invited Users Selected',
600
- message: 'Please select users with "invited" status to resend invitations.'
601
- });
602
- return;
603
- }
604
-
605
- let successCount = 0;
606
- let failCount = 0;
607
-
608
- for (const user of selectedUserData) {
609
- try {
610
- const { error } = await supabase.functions.invoke('resend-invitation', {
611
- body: { userId: user.id }
612
- });
613
-
614
- if (error) {
615
- failCount++;
616
- } else {
617
- successCount++;
618
- }
619
- } catch (error) {
620
- failCount++;
621
- }
622
- }
623
-
624
- if (successCount > 0) {
625
- setNotification({
626
- type: 'success',
627
- title: 'Invitations Sent',
628
- message: `Successfully resent ${successCount} invitation${successCount > 1 ? 's' : ''}${failCount > 0 ? `. ${failCount} failed.` : '.'}`
629
- });
630
- } else {
631
- setNotification({
632
- type: 'error',
633
- title: 'Failed to Resend Invitations',
634
- message: 'All invitation attempts failed. Please try again.'
635
- });
636
- }
637
-
638
- setSelectedUsers([]);
639
- fetchUsers();
640
- };
641
-
642
- const handleBulkSuspend = async () => {
643
- // Filter out current user to prevent self-suspension
644
- const selectedUserData = users.filter(u =>
645
- selectedUsers.includes(u.id) && u.status === 'active' && u.id !== currentUser?.id
646
- );
647
-
648
- if (selectedUserData.length === 0) {
649
- setNotification({
650
- type: 'error',
651
- title: 'No Active Users Selected',
652
- message: selectedUsers.includes(currentUser?.id || '')
653
- ? 'You cannot suspend your own account. Please select other users with "active" status to suspend.'
654
- : 'Please select users with "active" status to suspend.'
655
- });
656
- return;
657
- }
658
-
659
- try {
660
- const { error } = await supabase
661
- .from('profiles')
662
- .update({ status: 'suspended' })
663
- .in('id', selectedUserData.map(u => u.id));
664
-
665
- if (error) throw error;
666
-
667
- setNotification({
668
- type: 'success',
669
- title: 'Users Suspended',
670
- message: `Successfully suspended ${selectedUserData.length} user${selectedUserData.length > 1 ? 's' : ''}.`
671
- });
672
- setSelectedUsers([]);
673
- fetchUsers();
674
- } catch (error) {
675
- console.error('Error suspending users:', error);
676
- setNotification({
677
- type: 'error',
678
- title: 'Failed to Suspend Users',
679
- message: 'An error occurred. Please try again.'
680
- });
681
- }
682
- };
683
-
684
- const handleBulkPasswordReset = async () => {
685
- if (selectedUsers.length === 0) return;
686
-
687
- let successCount = 0;
688
- let failCount = 0;
689
-
690
- for (const userId of selectedUsers) {
691
- try {
692
- const { error } = await supabase.functions.invoke('force-password-reset', {
693
- body: { userId }
694
- });
695
-
696
- if (error) {
697
- failCount++;
698
- } else {
699
- successCount++;
700
- }
701
- } catch (error) {
702
- failCount++;
703
- }
704
- }
705
-
706
- if (successCount > 0) {
707
- setNotification({
708
- type: 'success',
709
- title: 'Password Resets Triggered',
710
- message: `Successfully triggered password reset for ${successCount} user${successCount > 1 ? 's' : ''}${failCount > 0 ? `. ${failCount} failed.` : '.'}`
711
- });
712
- } else {
713
- setNotification({
714
- type: 'error',
715
- title: 'Failed to Trigger Password Resets',
716
- message: 'All attempts failed. Please try again.'
717
- });
718
- }
719
-
720
- setSelectedUsers([]);
721
- fetchUsers();
722
- };
723
-
724
- const handleBulkDelete = () => {
725
- // Filter out current user to prevent self-deletion
726
- const filteredUserIds = selectedUsers.filter(id => id !== currentUser?.id);
727
-
728
- if (filteredUserIds.length === 0) {
729
- setNotification({
730
- type: 'error',
731
- title: 'Cannot Delete Your Own Account',
732
- message: 'You cannot delete your own account through bulk actions.'
733
- });
734
- return;
735
- }
736
-
737
- // Navigate to bulk delete page with selected user IDs
738
- navigate(`${navigatePrefix}/users/bulk-delete`, {
739
- state: { userIds: filteredUserIds }
740
- });
741
- };
742
-
743
- const columns: ColumnConfig<User>[] = [
744
- {
745
- label: 'User',
746
- sortKey: 'name',
747
- sortable: true,
748
- render: (user) => (
749
- <div className="flex items-center gap-3 flex-1">
750
- {getUserIcon(user)}
751
- <div className="flex flex-col justify-center items-start gap-0.5 flex-1 min-w-0 relative">
752
- <h3 className="text-label-primary text-sm font-semibold leading-4 truncate w-full">
753
- {user.full_name || 'Unnamed User'}
754
- </h3>
755
- <div className="relative w-full h-4">
756
- <p className="text-label-secondary text-xs font-normal leading-4 truncate w-full absolute inset-0 group-hover:opacity-0 transition-opacity">
757
- {user.email || 'No email'}
758
- </p>
759
- <p className="text-label-secondary text-xs font-normal leading-4 truncate w-full absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity">
760
- ID: {getShortUserId(user.user_number)}
761
- </p>
762
- </div>
763
- </div>
764
- </div>
765
- )
766
- },
767
- ...(showProviderColumn ? [{
768
- label: 'Role',
769
- render: (user: User) => (
770
- <div className="flex flex-col justify-center items-start gap-0.5">
771
- <span className="text-label-primary text-sm font-normal capitalize">
772
- {formatRole(user)}
773
- </span>
774
- {user.otherProviderRoleCount > 0 && (
775
- <span className="text-label-secondary text-xs font-normal">
776
- {user.otherProviderRoleCount} provider role{user.otherProviderRoleCount > 1 ? 's' : ''}
777
- </span>
778
- )}
779
- </div>
780
- ),
781
- width: 'w-[180px]'
782
- }] : [{
783
- label: 'Role',
784
- render: (user: User) => (
785
- <div className="flex flex-col justify-center items-start gap-0.5">
786
- <span className="text-label-primary text-sm font-normal capitalize">
787
- {formatRole(user)}
788
- </span>
789
- {user.otherProviderRoleCount > 0 && (
790
- <span className="text-label-secondary text-xs font-normal">
791
- {user.otherProviderRoleCount} provider role{user.otherProviderRoleCount > 1 ? 's' : ''}
792
- </span>
793
- )}
794
- </div>
795
- ),
796
- width: 'w-[180px]'
797
- }]),
798
- {
799
- label: 'Status',
800
- render: (user) => (
801
- <StatusBadge status={(user.status as 'active' | 'suspended' | 'invited' | 'disabled') || 'invited'} />
802
- ),
803
- width: 'w-[120px]'
804
- },
805
- {
806
- label: 'Last Seen',
807
- sortKey: 'last_seen',
808
- sortable: true,
809
- render: (user) => (
810
- <span className="text-text-secondary">
811
- {formatLastSeen(user.last_seen_at, user.status)}
812
- </span>
813
- ),
814
- width: 'w-[140px]'
815
- }
816
- ];
817
-
818
- const rowActions = (user: User): RowAction<User>[] => {
819
- const isCurrentUser = currentUser?.id === user.id;
820
-
821
- const actions: RowAction<User>[] = [
822
- {
823
- label: 'View User',
824
- onClick: (user) => navigate(`${navigatePrefix}/users/${user.id}`),
825
- variant: 'default'
826
- },
827
- {
828
- label: 'Edit User',
829
- onClick: (user) => navigate(`${navigatePrefix}/users/${user.id}/edit`),
830
- variant: 'default'
831
- },
832
- {
833
- label: 'Configure Permissions',
834
- onClick: (user) => navigate(`${navigatePrefix}/users/${user.id}/permissions`),
835
- variant: 'default'
836
- }
837
- ];
838
-
839
- // Don't allow users to suspend or delete their own account
840
- if (!isCurrentUser) {
841
- // Add Resend Invitation action for invited users with proper permission check
842
- if (user.status === 'invited' && hasPermission('user.resend_invitation')) {
843
- actions.push({
844
- label: 'Resend User Invitation',
845
- onClick: handleResendInvitation,
846
- variant: 'default'
847
- });
848
- }
849
-
850
- // Add Force Password Reset action for active users
851
- if (user.status === 'active') {
852
- actions.push({
853
- label: 'Trigger Password Reset',
854
- onClick: handleForcePasswordReset,
855
- variant: 'default'
856
- });
857
- }
858
-
859
- // Add Reset to Surname Password action
860
- actions.push({
861
- label: 'Reset to Surname Password',
862
- onClick: handleResetToSurnamePassword,
863
- variant: 'default'
864
- });
865
-
866
- // Add Suspend/Activate action only for active or suspended users (not invited)
867
- if (user.status === 'active' || user.status === 'suspended') {
868
- actions.push({
869
- label: user.status === 'active' ? 'Suspend User' : 'Activate User',
870
- onClick: handleSuspendToggle,
871
- variant: 'default'
872
- });
873
- }
874
- }
875
-
876
- return actions;
877
- };
878
-
879
- if (isLoading) {
880
- return <LoadingScreen message="Loading users..." />;
881
- }
882
-
883
- // Get bulk action configuration based on selection
884
- const getBulkActions = () => {
885
- const selectedUserData = users.filter(u => selectedUsers.includes(u.id));
886
- const hasInvited = selectedUserData.some(u => u.status === 'invited');
887
- const hasActive = selectedUserData.some(u => u.status === 'active');
888
- const hasSuspended = selectedUserData.some(u => u.status === 'suspended');
889
-
890
- const actions = [];
891
-
892
- if (hasInvited && hasPermission('user.resend_invitation')) {
893
- actions.push({
894
- label: 'Resend Invite',
895
- onClick: handleBulkResendInvite,
896
- });
897
- }
898
-
899
- if (hasActive) {
900
- actions.push({
901
- label: 'Suspend',
902
- onClick: handleBulkSuspend,
903
- });
904
- }
905
-
906
- if (hasSuspended) {
907
- actions.push({
908
- label: 'Activate',
909
- onClick: async () => {
910
- // Filter out current user to prevent self-activation (shouldn't happen but for safety)
911
- const suspendedUsers = selectedUserData.filter(u => u.status === 'suspended' && u.id !== currentUser?.id);
912
-
913
- if (suspendedUsers.length === 0) {
914
- setNotification({
915
- type: 'error',
916
- title: 'No Suspended Users Selected',
917
- message: 'Please select users with "suspended" status to activate.'
918
- });
919
- return;
920
- }
921
-
922
- try {
923
- const { error } = await supabase
924
- .from('profiles')
925
- .update({ status: 'active' })
926
- .in('id', suspendedUsers.map(u => u.id));
927
-
928
- if (error) throw error;
929
-
930
- setNotification({
931
- type: 'success',
932
- title: 'Users Activated',
933
- message: `Successfully activated ${suspendedUsers.length} user${suspendedUsers.length > 1 ? 's' : ''}.`
934
- });
935
- setSelectedUsers([]);
936
- fetchUsers();
937
- } catch (error) {
938
- console.error('Error activating users:', error);
939
- setNotification({
940
- type: 'error',
941
- title: 'Failed to Activate Users',
942
- message: 'An error occurred. Please try again.'
943
- });
944
- }
945
- },
946
- });
947
- }
948
-
949
- actions.push({
950
- label: 'Trigger Password Reset',
951
- onClick: handleBulkPasswordReset,
952
- });
953
-
954
- actions.push({
955
- label: 'Delete',
956
- icon: 'delete' as const,
957
- onClick: handleBulkDelete,
958
- variant: 'destructive' as const,
959
- });
960
-
961
- return actions;
962
- };
963
-
964
- return (
965
- <>
966
- {notification && (
967
- <div className="w-full mb-4">
968
- <NotificationBanner
969
- notification={notification}
970
- onClose={() => setNotification(null)}
971
- showNotificationsLink={false}
972
- />
973
- </div>
974
- )}
975
-
976
- <div className="flex flex-col gap-6 w-full">
977
- <TableControls
978
- searchConfig={searchConfig}
979
- actions={actions}
980
- />
981
- {showBulkActions && selectedUsers.length > 0 && (
982
- <BulkActionsBar
983
- selectedCount={selectedUsers.length}
984
- onClearSelection={handleClearSelection}
985
- actions={getBulkActions()}
986
- />
987
- )}
988
- <DataTable
989
- data={sortedUsers}
990
- columns={columns}
991
- keyExtractor={(user) => user.id}
992
- rowActions={rowActions}
993
- onRowClick={(user) => navigate(`${navigatePrefix}/users/${user.id}`)}
994
- selectable={showBulkActions}
995
- selectedItems={selectedUsers}
996
- onSelectionChange={setSelectedUsers}
997
- sortKey={sortKey}
998
- sortDirection={sortDirection}
999
- onSort={handleSort}
1000
- pagination={{
1001
- entityName: 'users'
1002
- }}
1003
- emptyState={
1004
- <div className="text-center py-12">
1005
- <p className="text-label-secondary">No users found</p>
1006
- </div>
1007
- }
1008
- />
1009
- </div>
1010
-
1011
- {/* Delete Confirmation Dialog */}
1012
- <DeleteConfirmationDialog
1013
- open={showDeleteDialog}
1014
- onOpenChange={setShowDeleteDialog}
1015
- onConfirm={handleDeleteConfirm}
1016
- title={`Delete ${selectedUser?.full_name || 'User'}?`}
1017
- itemName={selectedUser?.full_name || 'this user'}
1018
- />
1019
- </>
1020
- );
1021
- };
1022
-
1023
- export default UsersTable;