hazo_auth 1.4.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/SETUP_CHECKLIST.md +708 -0
  2. package/dist/app/api/hazo_auth/change_password/route.d.ts +8 -0
  3. package/dist/app/api/hazo_auth/change_password/route.d.ts.map +1 -0
  4. package/dist/app/api/hazo_auth/change_password/route.js +98 -0
  5. package/dist/app/api/hazo_auth/forgot_password/route.d.ts +8 -0
  6. package/dist/app/api/hazo_auth/forgot_password/route.d.ts.map +1 -0
  7. package/dist/app/api/hazo_auth/forgot_password/route.js +78 -0
  8. package/dist/app/api/hazo_auth/get_auth/route.d.ts +10 -0
  9. package/dist/app/api/hazo_auth/get_auth/route.d.ts.map +1 -0
  10. package/dist/app/api/hazo_auth/get_auth/route.js +63 -0
  11. package/dist/app/api/hazo_auth/invalidate_cache/route.d.ts +14 -0
  12. package/dist/app/api/hazo_auth/invalidate_cache/route.d.ts.map +1 -0
  13. package/dist/app/api/hazo_auth/invalidate_cache/route.js +96 -0
  14. package/dist/app/api/hazo_auth/library_photos/route.d.ts +13 -0
  15. package/dist/app/api/hazo_auth/library_photos/route.d.ts.map +1 -0
  16. package/dist/app/api/hazo_auth/library_photos/route.js +55 -0
  17. package/dist/app/api/hazo_auth/login/route.d.ts +12 -0
  18. package/dist/app/api/hazo_auth/login/route.d.ts.map +1 -0
  19. package/dist/app/api/hazo_auth/login/route.js +140 -0
  20. package/dist/app/api/hazo_auth/logout/route.d.ts +8 -0
  21. package/dist/app/api/hazo_auth/logout/route.d.ts.map +1 -0
  22. package/dist/app/api/hazo_auth/logout/route.js +71 -0
  23. package/dist/app/api/hazo_auth/me/route.d.ts +3 -0
  24. package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -0
  25. package/dist/app/api/hazo_auth/me/route.js +34 -0
  26. package/dist/app/api/hazo_auth/profile_picture/[filename]/route.d.ts +7 -0
  27. package/dist/app/api/hazo_auth/profile_picture/[filename]/route.d.ts.map +1 -0
  28. package/dist/app/api/hazo_auth/profile_picture/[filename]/route.js +43 -0
  29. package/dist/app/api/hazo_auth/register/route.d.ts +9 -0
  30. package/dist/app/api/hazo_auth/register/route.d.ts.map +1 -0
  31. package/dist/app/api/hazo_auth/register/route.js +80 -0
  32. package/dist/app/api/hazo_auth/remove_profile_picture/route.d.ts +8 -0
  33. package/dist/app/api/hazo_auth/remove_profile_picture/route.d.ts.map +1 -0
  34. package/dist/app/api/hazo_auth/remove_profile_picture/route.js +64 -0
  35. package/dist/app/api/hazo_auth/resend_verification/route.d.ts +8 -0
  36. package/dist/app/api/hazo_auth/resend_verification/route.d.ts.map +1 -0
  37. package/dist/app/api/hazo_auth/resend_verification/route.js +79 -0
  38. package/dist/app/api/hazo_auth/reset_password/route.d.ts +8 -0
  39. package/dist/app/api/hazo_auth/reset_password/route.d.ts.map +1 -0
  40. package/dist/app/api/hazo_auth/reset_password/route.js +76 -0
  41. package/dist/app/api/hazo_auth/update_user/route.d.ts +9 -0
  42. package/dist/app/api/hazo_auth/update_user/route.d.ts.map +1 -0
  43. package/dist/app/api/hazo_auth/update_user/route.js +95 -0
  44. package/dist/app/api/hazo_auth/upload_profile_picture/route.d.ts +9 -0
  45. package/dist/app/api/hazo_auth/upload_profile_picture/route.d.ts.map +1 -0
  46. package/dist/app/api/hazo_auth/upload_profile_picture/route.js +204 -0
  47. package/dist/app/api/hazo_auth/validate_reset_token/route.d.ts +6 -0
  48. package/dist/app/api/hazo_auth/validate_reset_token/route.d.ts.map +1 -0
  49. package/dist/app/api/hazo_auth/validate_reset_token/route.js +58 -0
  50. package/dist/app/api/hazo_auth/verify_email/route.d.ts +11 -0
  51. package/dist/app/api/hazo_auth/verify_email/route.d.ts.map +1 -0
  52. package/dist/app/api/hazo_auth/verify_email/route.js +63 -0
  53. package/dist/cli/generate.d.ts +2 -0
  54. package/dist/cli/generate.d.ts.map +1 -0
  55. package/dist/cli/generate.js +117 -0
  56. package/dist/cli/index.d.ts +3 -0
  57. package/dist/cli/index.d.ts.map +1 -0
  58. package/dist/cli/index.js +120 -0
  59. package/dist/cli/validate.d.ts +15 -0
  60. package/dist/cli/validate.d.ts.map +1 -0
  61. package/dist/cli/validate.js +509 -0
  62. package/dist/components/ui/card.d.ts +9 -0
  63. package/dist/components/ui/card.d.ts.map +1 -0
  64. package/dist/components/ui/card.js +45 -0
  65. package/dist/hooks/use-mobile.d.ts.map +1 -1
  66. package/dist/hooks/use-mobile.js +17 -3
  67. package/dist/server/routes/change_password.d.ts +2 -0
  68. package/dist/server/routes/change_password.d.ts.map +1 -0
  69. package/dist/server/routes/change_password.js +2 -0
  70. package/dist/server/routes/forgot_password.d.ts +2 -0
  71. package/dist/server/routes/forgot_password.d.ts.map +1 -0
  72. package/dist/server/routes/forgot_password.js +2 -0
  73. package/dist/server/routes/get_auth.d.ts +2 -0
  74. package/dist/server/routes/get_auth.d.ts.map +1 -0
  75. package/dist/server/routes/get_auth.js +2 -0
  76. package/dist/server/routes/index.d.ts +18 -0
  77. package/dist/server/routes/index.d.ts.map +1 -0
  78. package/dist/server/routes/index.js +24 -0
  79. package/dist/server/routes/invalidate_cache.d.ts +2 -0
  80. package/dist/server/routes/invalidate_cache.d.ts.map +1 -0
  81. package/dist/server/routes/invalidate_cache.js +2 -0
  82. package/dist/server/routes/library_photos.d.ts +2 -0
  83. package/dist/server/routes/library_photos.d.ts.map +1 -0
  84. package/dist/server/routes/library_photos.js +2 -0
  85. package/dist/server/routes/login.d.ts +2 -0
  86. package/dist/server/routes/login.d.ts.map +1 -0
  87. package/dist/server/routes/login.js +2 -0
  88. package/dist/server/routes/logout.d.ts +2 -0
  89. package/dist/server/routes/logout.d.ts.map +1 -0
  90. package/dist/server/routes/logout.js +2 -0
  91. package/dist/server/routes/me.d.ts +2 -0
  92. package/dist/server/routes/me.d.ts.map +1 -0
  93. package/dist/server/routes/me.js +2 -0
  94. package/dist/server/routes/profile_picture_filename.d.ts +2 -0
  95. package/dist/server/routes/profile_picture_filename.d.ts.map +1 -0
  96. package/dist/server/routes/profile_picture_filename.js +3 -0
  97. package/dist/server/routes/register.d.ts +2 -0
  98. package/dist/server/routes/register.d.ts.map +1 -0
  99. package/dist/server/routes/register.js +2 -0
  100. package/dist/server/routes/remove_profile_picture.d.ts +2 -0
  101. package/dist/server/routes/remove_profile_picture.d.ts.map +1 -0
  102. package/dist/server/routes/remove_profile_picture.js +2 -0
  103. package/dist/server/routes/resend_verification.d.ts +2 -0
  104. package/dist/server/routes/resend_verification.d.ts.map +1 -0
  105. package/dist/server/routes/resend_verification.js +2 -0
  106. package/dist/server/routes/reset_password.d.ts +2 -0
  107. package/dist/server/routes/reset_password.d.ts.map +1 -0
  108. package/dist/server/routes/reset_password.js +2 -0
  109. package/dist/server/routes/update_user.d.ts +2 -0
  110. package/dist/server/routes/update_user.d.ts.map +1 -0
  111. package/dist/server/routes/update_user.js +2 -0
  112. package/dist/server/routes/upload_profile_picture.d.ts +2 -0
  113. package/dist/server/routes/upload_profile_picture.d.ts.map +1 -0
  114. package/dist/server/routes/upload_profile_picture.js +2 -0
  115. package/dist/server/routes/validate_reset_token.d.ts +2 -0
  116. package/dist/server/routes/validate_reset_token.d.ts.map +1 -0
  117. package/dist/server/routes/validate_reset_token.js +2 -0
  118. package/dist/server/routes/verify_email.d.ts +2 -0
  119. package/dist/server/routes/verify_email.d.ts.map +1 -0
  120. package/dist/server/routes/verify_email.js +2 -0
  121. package/package.json +12 -17
  122. package/components.json +0 -22
  123. package/instrumentation.ts +0 -32
  124. package/migrations/001_add_token_type_to_refresh_tokens.sql +0 -14
  125. package/migrations/002_add_name_to_hazo_users.sql +0 -7
  126. package/migrations/003_add_url_on_logon_to_hazo_users.sql +0 -8
  127. package/next.config.mjs +0 -67
  128. package/postcss.config.mjs +0 -8
  129. package/public/file.svg +0 -1
  130. package/public/globe.svg +0 -1
  131. package/public/next.svg +0 -1
  132. package/public/vercel.svg +0 -1
  133. package/public/window.svg +0 -1
  134. package/scripts/apply_migration.ts +0 -118
  135. package/scripts/init_users.ts +0 -378
  136. package/src/app/api/hazo_auth/auth/upload_profile_picture/route.ts +0 -268
  137. package/src/app/api/hazo_auth/change_password/route.ts +0 -132
  138. package/src/app/api/hazo_auth/forgot_password/route.ts +0 -107
  139. package/src/app/api/hazo_auth/get_auth/route.ts +0 -89
  140. package/src/app/api/hazo_auth/invalidate_cache/route.ts +0 -139
  141. package/src/app/api/hazo_auth/library_photos/route.ts +0 -73
  142. package/src/app/api/hazo_auth/login/route.ts +0 -181
  143. package/src/app/api/hazo_auth/logout/route.ts +0 -89
  144. package/src/app/api/hazo_auth/me/route.ts +0 -47
  145. package/src/app/api/hazo_auth/profile_picture/[filename]/route.ts +0 -67
  146. package/src/app/api/hazo_auth/register/route.ts +0 -109
  147. package/src/app/api/hazo_auth/remove_profile_picture/route.ts +0 -86
  148. package/src/app/api/hazo_auth/resend_verification/route.ts +0 -108
  149. package/src/app/api/hazo_auth/reset_password/route.ts +0 -107
  150. package/src/app/api/hazo_auth/update_user/route.ts +0 -126
  151. package/src/app/api/hazo_auth/upload_profile_picture/route.ts +0 -268
  152. package/src/app/api/hazo_auth/user_management/permissions/route.ts +0 -367
  153. package/src/app/api/hazo_auth/user_management/roles/route.ts +0 -442
  154. package/src/app/api/hazo_auth/user_management/users/roles/route.ts +0 -367
  155. package/src/app/api/hazo_auth/user_management/users/route.ts +0 -239
  156. package/src/app/api/hazo_auth/validate_reset_token/route.ts +0 -83
  157. package/src/app/api/hazo_auth/verify_email/route.ts +0 -88
  158. package/src/app/api/migrations/apply/route.ts +0 -91
  159. package/src/app/favicon.ico +0 -0
  160. package/src/app/fonts/GeistMonoVF.woff +0 -0
  161. package/src/app/fonts/GeistVF.woff +0 -0
  162. package/src/app/globals.css +0 -89
  163. package/src/app/hazo_auth/forgot_password/forgot_password_page_client.tsx +0 -60
  164. package/src/app/hazo_auth/forgot_password/page.tsx +0 -24
  165. package/src/app/hazo_auth/login/login_page_client.tsx +0 -86
  166. package/src/app/hazo_auth/login/page.tsx +0 -38
  167. package/src/app/hazo_auth/my_settings/my_settings_page_client.tsx +0 -120
  168. package/src/app/hazo_auth/my_settings/page.tsx +0 -40
  169. package/src/app/hazo_auth/register/page.tsx +0 -36
  170. package/src/app/hazo_auth/register/register_page_client.tsx +0 -81
  171. package/src/app/hazo_auth/reset_password/page.tsx +0 -29
  172. package/src/app/hazo_auth/reset_password/reset_password_page_client.tsx +0 -81
  173. package/src/app/hazo_auth/user_management/page.tsx +0 -14
  174. package/src/app/hazo_auth/user_management/user_management_page_client.tsx +0 -16
  175. package/src/app/hazo_auth/verify_email/page.tsx +0 -24
  176. package/src/app/hazo_auth/verify_email/verify_email_page_client.tsx +0 -60
  177. package/src/app/hazo_connect/api/sqlite/data/route.ts +0 -203
  178. package/src/app/hazo_connect/api/sqlite/schema/route.ts +0 -45
  179. package/src/app/hazo_connect/api/sqlite/tables/route.ts +0 -36
  180. package/src/app/hazo_connect/sqlite_admin/page.tsx +0 -51
  181. package/src/app/hazo_connect/sqlite_admin/sqlite-admin-client.tsx +0 -984
  182. package/src/app/layout.tsx +0 -43
  183. package/src/app/page.tsx +0 -170
  184. package/src/components/index.ts +0 -7
  185. package/src/components/layouts/email_verification/config/email_verification_field_config.ts +0 -86
  186. package/src/components/layouts/email_verification/hooks/use_email_verification.ts +0 -297
  187. package/src/components/layouts/email_verification/index.tsx +0 -297
  188. package/src/components/layouts/forgot_password/config/forgot_password_field_config.ts +0 -58
  189. package/src/components/layouts/forgot_password/hooks/use_forgot_password_form.ts +0 -179
  190. package/src/components/layouts/forgot_password/index.tsx +0 -168
  191. package/src/components/layouts/index.ts +0 -26
  192. package/src/components/layouts/login/config/login_field_config.ts +0 -67
  193. package/src/components/layouts/login/hooks/use_login_form.ts +0 -286
  194. package/src/components/layouts/login/index.tsx +0 -252
  195. package/src/components/layouts/my_settings/components/editable_field.tsx +0 -177
  196. package/src/components/layouts/my_settings/components/password_change_dialog.tsx +0 -301
  197. package/src/components/layouts/my_settings/components/profile_picture_dialog.tsx +0 -385
  198. package/src/components/layouts/my_settings/components/profile_picture_display.tsx +0 -66
  199. package/src/components/layouts/my_settings/components/profile_picture_gravatar_tab.tsx +0 -143
  200. package/src/components/layouts/my_settings/components/profile_picture_library_tab.tsx +0 -311
  201. package/src/components/layouts/my_settings/components/profile_picture_upload_tab.tsx +0 -341
  202. package/src/components/layouts/my_settings/config/my_settings_field_config.ts +0 -61
  203. package/src/components/layouts/my_settings/hooks/use_my_settings.ts +0 -458
  204. package/src/components/layouts/my_settings/index.tsx +0 -351
  205. package/src/components/layouts/register/config/register_field_config.ts +0 -101
  206. package/src/components/layouts/register/hooks/use_register_form.ts +0 -275
  207. package/src/components/layouts/register/index.tsx +0 -226
  208. package/src/components/layouts/reset_password/config/reset_password_field_config.ts +0 -86
  209. package/src/components/layouts/reset_password/hooks/use_reset_password_form.ts +0 -276
  210. package/src/components/layouts/reset_password/index.tsx +0 -294
  211. package/src/components/layouts/shared/components/already_logged_in_guard.tsx +0 -95
  212. package/src/components/layouts/shared/components/auth_page_shell.tsx +0 -36
  213. package/src/components/layouts/shared/components/field_error_message.tsx +0 -29
  214. package/src/components/layouts/shared/components/form_action_buttons.tsx +0 -64
  215. package/src/components/layouts/shared/components/form_field_wrapper.tsx +0 -44
  216. package/src/components/layouts/shared/components/form_header.tsx +0 -36
  217. package/src/components/layouts/shared/components/logout_button.tsx +0 -76
  218. package/src/components/layouts/shared/components/password_field.tsx +0 -72
  219. package/src/components/layouts/shared/components/profile_pic_menu.tsx +0 -321
  220. package/src/components/layouts/shared/components/profile_pic_menu_wrapper.tsx +0 -40
  221. package/src/components/layouts/shared/components/sidebar_layout_wrapper.tsx +0 -214
  222. package/src/components/layouts/shared/components/standalone_layout_wrapper.tsx +0 -53
  223. package/src/components/layouts/shared/components/two_column_auth_layout.tsx +0 -44
  224. package/src/components/layouts/shared/components/unauthorized_guard.tsx +0 -78
  225. package/src/components/layouts/shared/components/visual_panel.tsx +0 -41
  226. package/src/components/layouts/shared/config/layout_customization.ts +0 -95
  227. package/src/components/layouts/shared/data/layout_data_client.ts +0 -19
  228. package/src/components/layouts/shared/hooks/use_auth_status.ts +0 -103
  229. package/src/components/layouts/shared/hooks/use_hazo_auth.ts +0 -158
  230. package/src/components/layouts/shared/index.ts +0 -34
  231. package/src/components/layouts/shared/utils/ip_address.ts +0 -37
  232. package/src/components/layouts/shared/utils/validation.ts +0 -66
  233. package/src/components/layouts/user_management/components/roles_matrix.tsx +0 -607
  234. package/src/components/layouts/user_management/index.tsx +0 -1295
  235. package/src/components/ui/alert-dialog.tsx +0 -141
  236. package/src/components/ui/avatar.tsx +0 -50
  237. package/src/components/ui/button.tsx +0 -57
  238. package/src/components/ui/checkbox.tsx +0 -30
  239. package/src/components/ui/dialog.tsx +0 -122
  240. package/src/components/ui/dropdown-menu.tsx +0 -201
  241. package/src/components/ui/hazo_ui_tooltip.tsx +0 -67
  242. package/src/components/ui/index.ts +0 -22
  243. package/src/components/ui/input.tsx +0 -22
  244. package/src/components/ui/label.tsx +0 -26
  245. package/src/components/ui/separator.tsx +0 -31
  246. package/src/components/ui/sheet.tsx +0 -139
  247. package/src/components/ui/sidebar.tsx +0 -773
  248. package/src/components/ui/skeleton.tsx +0 -15
  249. package/src/components/ui/sonner.tsx +0 -31
  250. package/src/components/ui/switch.tsx +0 -29
  251. package/src/components/ui/table.tsx +0 -120
  252. package/src/components/ui/tabs.tsx +0 -55
  253. package/src/components/ui/tooltip.tsx +0 -32
  254. package/src/components/ui/vertical-tabs.tsx +0 -59
  255. package/src/hooks/use-mobile.tsx +0 -19
  256. package/src/index.ts +0 -7
  257. package/src/lib/already_logged_in_config.server.ts +0 -46
  258. package/src/lib/app_logger.ts +0 -24
  259. package/src/lib/auth/auth_cache.ts +0 -220
  260. package/src/lib/auth/auth_rate_limiter.ts +0 -121
  261. package/src/lib/auth/auth_types.ts +0 -65
  262. package/src/lib/auth/auth_utils.server.ts +0 -196
  263. package/src/lib/auth/hazo_get_auth.server.ts +0 -333
  264. package/src/lib/auth/index.ts +0 -23
  265. package/src/lib/auth/server_auth.ts +0 -88
  266. package/src/lib/auth_utility_config.server.ts +0 -136
  267. package/src/lib/config/config_loader.server.ts +0 -164
  268. package/src/lib/email_verification_config.server.ts +0 -32
  269. package/src/lib/file_types_config.server.ts +0 -25
  270. package/src/lib/forgot_password_config.server.ts +0 -32
  271. package/src/lib/hazo_connect_instance.server.ts +0 -101
  272. package/src/lib/hazo_connect_setup.server.ts +0 -194
  273. package/src/lib/hazo_connect_setup.ts +0 -54
  274. package/src/lib/index.ts +0 -44
  275. package/src/lib/login_config.server.ts +0 -71
  276. package/src/lib/messages_config.server.ts +0 -45
  277. package/src/lib/migrations/apply_migration.ts +0 -105
  278. package/src/lib/my_settings_config.server.ts +0 -135
  279. package/src/lib/password_requirements_config.server.ts +0 -39
  280. package/src/lib/profile_pic_menu_config.server.ts +0 -138
  281. package/src/lib/profile_picture_config.server.ts +0 -56
  282. package/src/lib/register_config.server.ts +0 -73
  283. package/src/lib/reset_password_config.server.ts +0 -75
  284. package/src/lib/services/email_service.ts +0 -581
  285. package/src/lib/services/email_verification_service.ts +0 -270
  286. package/src/lib/services/index.ts +0 -15
  287. package/src/lib/services/login_service.ts +0 -134
  288. package/src/lib/services/password_change_service.ts +0 -154
  289. package/src/lib/services/password_reset_service.ts +0 -405
  290. package/src/lib/services/profile_picture_remove_service.ts +0 -120
  291. package/src/lib/services/profile_picture_service.ts +0 -215
  292. package/src/lib/services/profile_picture_source_mapper.ts +0 -62
  293. package/src/lib/services/registration_service.ts +0 -184
  294. package/src/lib/services/token_service.ts +0 -240
  295. package/src/lib/services/user_profiles_service.ts +0 -143
  296. package/src/lib/services/user_update_service.ts +0 -141
  297. package/src/lib/ui_shell_config.server.ts +0 -73
  298. package/src/lib/ui_sizes_config.server.ts +0 -37
  299. package/src/lib/user_fields_config.server.ts +0 -31
  300. package/src/lib/user_management_config.server.ts +0 -39
  301. package/src/lib/utils/api_route_helpers.ts +0 -60
  302. package/src/lib/utils/error_sanitizer.ts +0 -75
  303. package/src/lib/utils.ts +0 -11
  304. package/src/middleware.ts +0 -94
  305. package/src/routes/index.ts +0 -34
  306. package/src/server/config/config_loader.ts +0 -496
  307. package/src/server/index.ts +0 -38
  308. package/src/server/logging/logger_service.ts +0 -56
  309. package/src/server/routes/root_router.ts +0 -16
  310. package/src/server/server.ts +0 -28
  311. package/src/server/types/app_types.ts +0 -74
  312. package/src/server/types/express.d.ts +0 -16
  313. package/src/stories/email_verification_layout.stories.tsx +0 -137
  314. package/src/stories/forgot_password_layout.stories.tsx +0 -85
  315. package/src/stories/login_layout.stories.tsx +0 -85
  316. package/src/stories/project_overview.stories.tsx +0 -33
  317. package/src/stories/register_layout.stories.tsx +0 -107
  318. package/tailwind.config.ts +0 -77
  319. package/tsconfig.build.json +0 -36
  320. package/tsconfig.json +0 -28
@@ -1,1295 +0,0 @@
1
- // file_description: User Management layout component with three tabs for managing users, roles, and permissions
2
- // section: client_directive
3
- "use client";
4
-
5
- // section: imports
6
- import { useState, useEffect, useCallback } from "react";
7
- import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs";
8
- import { use_hazo_auth } from "../shared/hooks/use_hazo_auth";
9
- import {
10
- Table,
11
- TableBody,
12
- TableCell,
13
- TableHead,
14
- TableHeader,
15
- TableRow,
16
- } from "../../ui/table";
17
- import { Button } from "../../ui/button";
18
- import { Avatar, AvatarImage, AvatarFallback } from "../../ui/avatar";
19
- import {
20
- AlertDialog,
21
- AlertDialogAction,
22
- AlertDialogCancel,
23
- AlertDialogContent,
24
- AlertDialogDescription,
25
- AlertDialogFooter,
26
- AlertDialogHeader,
27
- AlertDialogTitle,
28
- } from "../../ui/alert-dialog";
29
- import {
30
- Dialog,
31
- DialogContent,
32
- DialogDescription,
33
- DialogFooter,
34
- DialogHeader,
35
- DialogTitle,
36
- } from "../../ui/dialog";
37
- import { Input } from "../../ui/input";
38
- import { Label } from "../../ui/label";
39
- import { RolesMatrix } from "./components/roles_matrix";
40
- import { UserX, KeyRound, Edit, Trash2, Loader2, CircleCheck, CircleX, Plus, UserPlus } from "lucide-react";
41
- import { toast } from "sonner";
42
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../ui/tooltip";
43
-
44
- // section: types
45
- export type UserManagementLayoutProps = {
46
- className?: string;
47
- };
48
-
49
- type User = {
50
- id: string;
51
- name: string | null;
52
- email_address: string;
53
- email_verified: boolean;
54
- is_active: boolean;
55
- last_logon: string | null;
56
- created_at: string | null;
57
- profile_picture_url: string | null;
58
- profile_source: string | null;
59
- };
60
-
61
- type Permission = {
62
- id: number;
63
- permission_name: string;
64
- description: string;
65
- source: "db" | "config";
66
- };
67
-
68
- // section: component
69
- /**
70
- * User Management layout component with three tabs
71
- * Tab 1: Manage Users - data table with user details and actions
72
- * Tab 2: Roles - roles-permissions matrix
73
- * Tab 3: Permissions - manage permissions from DB and config
74
- * @param props - Component props
75
- * @returns User Management layout component
76
- */
77
- export function UserManagementLayout({ className }: UserManagementLayoutProps) {
78
- // Permission checks
79
- const authResult = use_hazo_auth();
80
- const hasUserManagementPermission = authResult.authenticated &&
81
- authResult.permissions.includes("admin_user_management");
82
- const hasRoleManagementPermission = authResult.authenticated &&
83
- authResult.permissions.includes("admin_role_management");
84
- const hasPermissionManagementPermission = authResult.authenticated &&
85
- authResult.permissions.includes("admin_permission_management");
86
-
87
- // Determine which tabs to show
88
- const showUsersTab = hasUserManagementPermission;
89
- const showRolesTab = hasRoleManagementPermission;
90
- const showPermissionsTab = hasPermissionManagementPermission;
91
- const hasAnyPermission = showUsersTab || showRolesTab || showPermissionsTab;
92
-
93
- // Tab 1: Users state
94
- const [users, setUsers] = useState<User[]>([]);
95
- const [usersLoading, setUsersLoading] = useState(true);
96
- const [deactivateDialogOpen, setDeactivateDialogOpen] = useState(false);
97
- const [resetPasswordDialogOpen, setResetPasswordDialogOpen] = useState(false);
98
- const [userDetailDialogOpen, setUserDetailDialogOpen] = useState(false);
99
- const [assignRolesDialogOpen, setAssignRolesDialogOpen] = useState(false);
100
- const [selectedUser, setSelectedUser] = useState<User | null>(null);
101
- const [usersActionLoading, setUsersActionLoading] = useState(false);
102
-
103
- // Tab 3: Permissions state
104
- const [permissions, setPermissions] = useState<Permission[]>([]);
105
- const [permissionsLoading, setPermissionsLoading] = useState(true);
106
- const [editPermissionDialogOpen, setEditPermissionDialogOpen] = useState(false);
107
- const [addPermissionDialogOpen, setAddPermissionDialogOpen] = useState(false);
108
- const [editingPermission, setEditingPermission] = useState<Permission | null>(null);
109
- const [editDescription, setEditDescription] = useState("");
110
- const [newPermissionName, setNewPermissionName] = useState("");
111
- const [newPermissionDescription, setNewPermissionDescription] = useState("");
112
- const [permissionsActionLoading, setPermissionsActionLoading] = useState(false);
113
- const [migrateLoading, setMigrateLoading] = useState(false);
114
-
115
- // Load users function (reusable)
116
- const loadUsers = useCallback(async () => {
117
- setUsersLoading(true);
118
- try {
119
- const response = await fetch("/api/hazo_auth/user_management/users");
120
- const data = await response.json();
121
-
122
- if (data.success) {
123
- setUsers(data.users);
124
- } else {
125
- toast.error("Failed to load users");
126
- }
127
- } catch (error) {
128
- toast.error("Failed to load users");
129
- } finally {
130
- setUsersLoading(false);
131
- }
132
- }, []);
133
-
134
- // Load users (only if user has permission)
135
- useEffect(() => {
136
- if (!showUsersTab) {
137
- setUsersLoading(false);
138
- return;
139
- }
140
-
141
- void loadUsers();
142
- }, [showUsersTab, loadUsers]);
143
-
144
- // Load permissions (only if user has permission)
145
- useEffect(() => {
146
- if (!showPermissionsTab) {
147
- setPermissionsLoading(false);
148
- return;
149
- }
150
-
151
- const loadPermissions = async () => {
152
- setPermissionsLoading(true);
153
- try {
154
- const response = await fetch("/api/hazo_auth/user_management/permissions");
155
- const data = await response.json();
156
-
157
- if (data.success) {
158
- const db_perms: Permission[] = data.db_permissions.map((p: {
159
- id: number;
160
- permission_name: string;
161
- description: string;
162
- }) => ({
163
- id: p.id,
164
- permission_name: p.permission_name,
165
- description: p.description,
166
- source: "db" as const,
167
- }));
168
-
169
- const config_perms: Permission[] = data.config_permissions.map((name: string) => ({
170
- id: 0, // Temporary ID for config permissions
171
- permission_name: name,
172
- description: "",
173
- source: "config" as const,
174
- }));
175
-
176
- setPermissions([...db_perms, ...config_perms]);
177
- } else {
178
- toast.error("Failed to load permissions");
179
- }
180
- } catch (error) {
181
- toast.error("Failed to load permissions");
182
- } finally {
183
- setPermissionsLoading(false);
184
- }
185
- };
186
-
187
- void loadPermissions();
188
- }, [showPermissionsTab]);
189
-
190
- // Handle deactivate user
191
- const handleDeactivateUser = async () => {
192
- if (!selectedUser) return;
193
-
194
- setUsersActionLoading(true);
195
- try {
196
- const response = await fetch("/api/user_management/users", {
197
- method: "PATCH",
198
- headers: {
199
- "Content-Type": "application/json",
200
- },
201
- body: JSON.stringify({
202
- user_id: selectedUser.id,
203
- is_active: false,
204
- }),
205
- });
206
-
207
- const data = await response.json();
208
-
209
- if (data.success) {
210
- toast.success("User deactivated successfully");
211
- setDeactivateDialogOpen(false);
212
- setSelectedUser(null);
213
-
214
- // Reload users
215
- const reload_response = await fetch("/api/user_management/users");
216
- const reload_data = await reload_response.json();
217
- if (reload_data.success) {
218
- setUsers(reload_data.users);
219
- }
220
- } else {
221
- toast.error(data.error || "Failed to deactivate user");
222
- }
223
- } catch (error) {
224
- toast.error("Failed to deactivate user");
225
- } finally {
226
- setUsersActionLoading(false);
227
- }
228
- };
229
-
230
- // Handle reset password
231
- const handleResetPassword = async () => {
232
- if (!selectedUser) return;
233
-
234
- setUsersActionLoading(true);
235
- try {
236
- const response = await fetch("/api/user_management/users", {
237
- method: "POST",
238
- headers: {
239
- "Content-Type": "application/json",
240
- },
241
- body: JSON.stringify({
242
- user_id: selectedUser.id,
243
- }),
244
- });
245
-
246
- const data = await response.json();
247
-
248
- if (data.success) {
249
- toast.success("Password reset email sent successfully");
250
- setResetPasswordDialogOpen(false);
251
- setSelectedUser(null);
252
- } else {
253
- toast.error(data.error || "Failed to send password reset email");
254
- }
255
- } catch (error) {
256
- toast.error("Failed to send password reset email");
257
- } finally {
258
- setUsersActionLoading(false);
259
- }
260
- };
261
-
262
-
263
- // Handle migrate permissions
264
- const handleMigratePermissions = async () => {
265
- setMigrateLoading(true);
266
- try {
267
- const response = await fetch("/api/hazo_auth/user_management/permissions?action=migrate", {
268
- method: "POST",
269
- });
270
-
271
- const data = await response.json();
272
-
273
- if (data.success) {
274
- const created_count = data.created?.length || 0;
275
- const skipped_count = data.skipped?.length || 0;
276
-
277
- if (created_count > 0) {
278
- toast.success(
279
- `Migrated ${created_count} permission(s) to database. ${skipped_count} already existed.`
280
- );
281
- } else {
282
- toast.info(`All permissions already exist in database. ${skipped_count} skipped.`);
283
- }
284
-
285
- // Show detailed list in toast if there are changes
286
- if (data.created && data.created.length > 0) {
287
- toast.info(`Created: ${data.created.join(", ")}`);
288
- }
289
- if (data.skipped && data.skipped.length > 0) {
290
- toast.info(`Skipped: ${data.skipped.join(", ")}`);
291
- }
292
-
293
- // Reload permissions
294
- const reload_response = await fetch("/api/user_management/permissions");
295
- const reload_data = await reload_response.json();
296
- if (reload_data.success) {
297
- const db_perms: Permission[] = reload_data.db_permissions.map((p: {
298
- id: number;
299
- permission_name: string;
300
- description: string;
301
- }) => ({
302
- id: p.id,
303
- permission_name: p.permission_name,
304
- description: p.description,
305
- source: "db" as const,
306
- }));
307
-
308
- const config_perms: Permission[] = reload_data.config_permissions.map((name: string) => ({
309
- id: 0,
310
- permission_name: name,
311
- description: "",
312
- source: "config" as const,
313
- }));
314
-
315
- setPermissions([...db_perms, ...config_perms]);
316
- }
317
- } else {
318
- toast.error(data.error || "Failed to migrate permissions");
319
- }
320
- } catch (error) {
321
- toast.error("Failed to migrate permissions");
322
- } finally {
323
- setMigrateLoading(false);
324
- }
325
- };
326
-
327
- // Handle edit permission
328
- const handleEditPermission = async () => {
329
- if (!editingPermission || editingPermission.source === "config") return;
330
-
331
- setPermissionsActionLoading(true);
332
- try {
333
- const response = await fetch("/api/user_management/permissions", {
334
- method: "PUT",
335
- headers: {
336
- "Content-Type": "application/json",
337
- },
338
- body: JSON.stringify({
339
- permission_id: editingPermission.id,
340
- description: editDescription,
341
- }),
342
- });
343
-
344
- const data = await response.json();
345
-
346
- if (data.success) {
347
- toast.success("Permission updated successfully");
348
- setEditPermissionDialogOpen(false);
349
- setEditingPermission(null);
350
- setEditDescription("");
351
-
352
- // Reload permissions
353
- const reload_response = await fetch("/api/user_management/permissions");
354
- const reload_data = await reload_response.json();
355
- if (reload_data.success) {
356
- const db_perms: Permission[] = reload_data.db_permissions.map((p: {
357
- id: number;
358
- permission_name: string;
359
- description: string;
360
- }) => ({
361
- id: p.id,
362
- permission_name: p.permission_name,
363
- description: p.description,
364
- source: "db" as const,
365
- }));
366
-
367
- const config_perms: Permission[] = reload_data.config_permissions.map((name: string) => ({
368
- id: 0,
369
- permission_name: name,
370
- description: "",
371
- source: "config" as const,
372
- }));
373
-
374
- setPermissions([...db_perms, ...config_perms]);
375
- }
376
- } else {
377
- toast.error(data.error || "Failed to update permission");
378
- }
379
- } catch (error) {
380
- toast.error("Failed to update permission");
381
- } finally {
382
- setPermissionsActionLoading(false);
383
- }
384
- };
385
-
386
- // Handle add permission
387
- const handleAddPermission = async () => {
388
- if (!newPermissionName.trim()) {
389
- toast.error("Permission name is required");
390
- return;
391
- }
392
-
393
- setPermissionsActionLoading(true);
394
- try {
395
- const response = await fetch("/api/user_management/permissions", {
396
- method: "POST",
397
- headers: {
398
- "Content-Type": "application/json",
399
- },
400
- body: JSON.stringify({
401
- permission_name: newPermissionName.trim(),
402
- description: newPermissionDescription.trim(),
403
- }),
404
- });
405
-
406
- const data = await response.json();
407
-
408
- if (data.success) {
409
- toast.success("Permission created successfully");
410
- setAddPermissionDialogOpen(false);
411
- setNewPermissionName("");
412
- setNewPermissionDescription("");
413
-
414
- // Reload permissions
415
- const reload_response = await fetch("/api/user_management/permissions");
416
- const reload_data = await reload_response.json();
417
- if (reload_data.success) {
418
- const db_perms: Permission[] = reload_data.db_permissions.map((p: {
419
- id: number;
420
- permission_name: string;
421
- description: string;
422
- }) => ({
423
- id: p.id,
424
- permission_name: p.permission_name,
425
- description: p.description,
426
- source: "db" as const,
427
- }));
428
-
429
- const config_perms: Permission[] = reload_data.config_permissions.map((name: string) => ({
430
- id: 0,
431
- permission_name: name,
432
- description: "",
433
- source: "config" as const,
434
- }));
435
-
436
- setPermissions([...db_perms, ...config_perms]);
437
- }
438
- } else {
439
- toast.error(data.error || "Failed to create permission");
440
- }
441
- } catch (error) {
442
- toast.error("Failed to create permission");
443
- } finally {
444
- setPermissionsActionLoading(false);
445
- }
446
- };
447
-
448
- // Handle delete permission
449
- const handleDeletePermission = async (permission: Permission) => {
450
- if (permission.source === "config") return;
451
-
452
- setPermissionsActionLoading(true);
453
- try {
454
- const response = await fetch(
455
- `/api/hazo_auth/user_management/permissions?permission_id=${permission.id}`,
456
- {
457
- method: "DELETE",
458
- }
459
- );
460
-
461
- const data = await response.json();
462
-
463
- if (data.success) {
464
- toast.success("Permission deleted successfully");
465
-
466
- // Reload permissions
467
- const reload_response = await fetch("/api/user_management/permissions");
468
- const reload_data = await reload_response.json();
469
- if (reload_data.success) {
470
- const db_perms: Permission[] = reload_data.db_permissions.map((p: {
471
- id: number;
472
- permission_name: string;
473
- description: string;
474
- }) => ({
475
- id: p.id,
476
- permission_name: p.permission_name,
477
- description: p.description,
478
- source: "db" as const,
479
- }));
480
-
481
- const config_perms: Permission[] = reload_data.config_permissions.map((name: string) => ({
482
- id: 0,
483
- permission_name: name,
484
- description: "",
485
- source: "config" as const,
486
- }));
487
-
488
- setPermissions([...db_perms, ...config_perms]);
489
- }
490
- } else {
491
- toast.error(data.error || "Failed to delete permission");
492
- }
493
- } catch (error) {
494
- toast.error("Failed to delete permission");
495
- } finally {
496
- setPermissionsActionLoading(false);
497
- }
498
- };
499
-
500
- // Get user initials for avatar fallback
501
- const getUserInitials = (user: User): string => {
502
- if (user.name) {
503
- const parts = user.name.trim().split(" ");
504
- if (parts.length >= 2) {
505
- return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
506
- }
507
- return user.name[0]?.toUpperCase() || "";
508
- }
509
- if (user.email_address) {
510
- return user.email_address[0]?.toUpperCase() || "";
511
- }
512
- return "?";
513
- };
514
-
515
- return (
516
- <div className={`cls_user_management_layout flex flex-col gap-4 w-full ${className || ""}`}>
517
- {/* Show loading spinner while checking permissions */}
518
- {authResult.loading ? (
519
- <div className="cls_user_management_permissions_loading flex items-center justify-center p-8">
520
- <Loader2 className="h-6 w-6 animate-spin text-slate-400" />
521
- </div>
522
- ) : !hasAnyPermission ? (
523
- <div className="cls_user_management_no_permissions flex flex-col items-center justify-center p-8 gap-4">
524
- <p className="text-lg font-semibold text-slate-700">
525
- Access Denied
526
- </p>
527
- <p className="text-sm text-muted-foreground text-center">
528
- You don&apos;t have permission to access User Management. Please contact your administrator.
529
- </p>
530
- </div>
531
- ) : (
532
- <Tabs
533
- defaultValue={
534
- showUsersTab ? "users" :
535
- showRolesTab ? "roles" :
536
- showPermissionsTab ? "permissions" : "users"
537
- }
538
- className="cls_user_management_tabs w-full"
539
- >
540
- <TabsList className="cls_user_management_tabs_list flex w-full">
541
- {showUsersTab && (
542
- <TabsTrigger value="users" className="cls_user_management_tabs_trigger flex-1">
543
- Manage Users
544
- </TabsTrigger>
545
- )}
546
- {showRolesTab && (
547
- <TabsTrigger value="roles" className="cls_user_management_tabs_trigger flex-1">
548
- Roles
549
- </TabsTrigger>
550
- )}
551
- {showPermissionsTab && (
552
- <TabsTrigger value="permissions" className="cls_user_management_tabs_trigger flex-1">
553
- Permissions
554
- </TabsTrigger>
555
- )}
556
- </TabsList>
557
-
558
- {/* Tab 1: Manage Users */}
559
- {showUsersTab && (
560
- <TabsContent value="users" className="cls_user_management_tab_users w-full">
561
- {usersLoading ? (
562
- <div className="cls_user_management_users_loading flex items-center justify-center p-8">
563
- <Loader2 className="h-6 w-6 animate-spin text-slate-400" />
564
- </div>
565
- ) : (
566
- <div className="cls_user_management_users_table_container border rounded-lg overflow-auto w-full">
567
- <Table className="cls_user_management_users_table w-full">
568
- <TableHeader className="cls_user_management_users_table_header">
569
- <TableRow className="cls_user_management_users_table_header_row">
570
- <TableHead className="cls_user_management_users_table_header_profile_pic w-16">
571
- Photo
572
- </TableHead>
573
- <TableHead className="cls_user_management_users_table_header_id">ID</TableHead>
574
- <TableHead className="cls_user_management_users_table_header_name">
575
- Name
576
- </TableHead>
577
- <TableHead className="cls_user_management_users_table_header_email">
578
- Email
579
- </TableHead>
580
- <TableHead className="cls_user_management_users_table_header_email_verified">
581
- Email Verified
582
- </TableHead>
583
- <TableHead className="cls_user_management_users_table_header_is_active">
584
- Active
585
- </TableHead>
586
- <TableHead className="cls_user_management_users_table_header_last_logon">
587
- Last Logon
588
- </TableHead>
589
- <TableHead className="cls_user_management_users_table_header_created_at">
590
- Created At
591
- </TableHead>
592
- <TableHead className="cls_user_management_users_table_header_actions text-right">
593
- Actions
594
- </TableHead>
595
- </TableRow>
596
- </TableHeader>
597
- <TableBody className="cls_user_management_users_table_body">
598
- {users.length === 0 ? (
599
- <TableRow className="cls_user_management_users_table_row_empty">
600
- <TableCell colSpan={9} className="text-center text-muted-foreground py-8">
601
- No users found.
602
- </TableCell>
603
- </TableRow>
604
- ) : (
605
- users.map((user) => (
606
- <TableRow
607
- key={user.id}
608
- className="cls_user_management_users_table_row cursor-pointer hover:bg-muted/50"
609
- onClick={() => {
610
- setSelectedUser(user);
611
- setUserDetailDialogOpen(true);
612
- }}
613
- >
614
- <TableCell className="cls_user_management_users_table_cell_profile_pic">
615
- <Avatar className="cls_user_management_users_table_avatar h-8 w-8">
616
- <AvatarImage
617
- src={user.profile_picture_url || undefined}
618
- alt={user.name ? `Profile picture of ${user.name}` : "Profile picture"}
619
- className="cls_user_management_users_table_avatar_image"
620
- />
621
- <AvatarFallback className="cls_user_management_users_table_avatar_fallback bg-slate-200 text-slate-600 text-xs">
622
- {getUserInitials(user)}
623
- </AvatarFallback>
624
- </Avatar>
625
- </TableCell>
626
- <TableCell className="cls_user_management_users_table_cell_id font-mono text-xs">
627
- {user.id.substring(0, 8)}...
628
- </TableCell>
629
- <TableCell className="cls_user_management_users_table_cell_name">
630
- {user.name || "-"}
631
- </TableCell>
632
- <TableCell className="cls_user_management_users_table_cell_email">
633
- {user.email_address}
634
- </TableCell>
635
- <TableCell className="cls_user_management_users_table_cell_email_verified">
636
- {user.email_verified ? (
637
- <span className="text-green-600">Yes</span>
638
- ) : (
639
- <span className="text-red-600">No</span>
640
- )}
641
- </TableCell>
642
- <TableCell className="cls_user_management_users_table_cell_is_active">
643
- {user.is_active ? (
644
- <span className="text-green-600">Active</span>
645
- ) : (
646
- <span className="text-red-600">Inactive</span>
647
- )}
648
- </TableCell>
649
- <TableCell className="cls_user_management_users_table_cell_last_logon">
650
- {user.last_logon
651
- ? new Date(user.last_logon).toLocaleDateString()
652
- : "-"}
653
- </TableCell>
654
- <TableCell className="cls_user_management_users_table_cell_created_at">
655
- {user.created_at
656
- ? new Date(user.created_at).toLocaleDateString()
657
- : "-"}
658
- </TableCell>
659
- <TableCell className="cls_user_management_users_table_cell_actions text-right">
660
- <TooltipProvider>
661
- <div
662
- className="cls_user_management_users_table_actions flex items-center justify-end gap-2"
663
- onClick={(e) => e.stopPropagation()}
664
- >
665
- <Tooltip>
666
- <TooltipTrigger asChild>
667
- <Button
668
- onClick={() => {
669
- setSelectedUser(user);
670
- setAssignRolesDialogOpen(true);
671
- }}
672
- variant="outline"
673
- size="sm"
674
- className="cls_user_management_users_table_action_assign_roles"
675
- >
676
- <UserPlus className="h-4 w-4" />
677
- </Button>
678
- </TooltipTrigger>
679
- <TooltipContent>
680
- <p>Assign Roles</p>
681
- </TooltipContent>
682
- </Tooltip>
683
- {user.is_active && (
684
- <Tooltip>
685
- <TooltipTrigger asChild>
686
- <Button
687
- onClick={() => {
688
- setSelectedUser(user);
689
- setDeactivateDialogOpen(true);
690
- }}
691
- variant="outline"
692
- size="sm"
693
- className="cls_user_management_users_table_action_deactivate"
694
- >
695
- <UserX className="h-4 w-4" />
696
- </Button>
697
- </TooltipTrigger>
698
- <TooltipContent>
699
- <p>Deactivate</p>
700
- </TooltipContent>
701
- </Tooltip>
702
- )}
703
- <Tooltip>
704
- <TooltipTrigger asChild>
705
- <Button
706
- onClick={() => {
707
- setSelectedUser(user);
708
- setResetPasswordDialogOpen(true);
709
- }}
710
- variant="outline"
711
- size="sm"
712
- className="cls_user_management_users_table_action_reset_password"
713
- >
714
- <KeyRound className="h-4 w-4" />
715
- </Button>
716
- </TooltipTrigger>
717
- <TooltipContent>
718
- <p>Reset Password</p>
719
- </TooltipContent>
720
- </Tooltip>
721
- </div>
722
- </TooltipProvider>
723
- </TableCell>
724
- </TableRow>
725
- ))
726
- )}
727
- </TableBody>
728
- </Table>
729
- </div>
730
- )}
731
- </TabsContent>
732
- )}
733
-
734
- {/* Tab 2: Roles */}
735
- {showRolesTab && (
736
- <TabsContent value="roles" className="cls_user_management_tab_roles w-full">
737
- <RolesMatrix
738
- add_button_enabled={true}
739
- role_name_selection_enabled={false}
740
- onSave={(data) => {
741
- // Data is already saved by RolesMatrix component
742
- console.log("Roles saved:", data);
743
- }}
744
- />
745
- </TabsContent>
746
- )}
747
-
748
- {/* Tab 3: Permissions */}
749
- {showPermissionsTab && (
750
- <TabsContent value="permissions" className="cls_user_management_tab_permissions w-full">
751
- <div className="cls_user_management_permissions_container flex flex-col gap-4 w-full">
752
- {/* Header buttons */}
753
- <div className="cls_user_management_permissions_header flex items-center justify-between">
754
- <div className="cls_user_management_permissions_header_left flex items-center gap-2">
755
- <Button
756
- onClick={() => setAddPermissionDialogOpen(true)}
757
- variant="default"
758
- size="sm"
759
- className="cls_user_management_permissions_add_button"
760
- >
761
- <Plus className="h-4 w-4 mr-2" />
762
- Add Permission
763
- </Button>
764
- </div>
765
- <div className="cls_user_management_permissions_header_right">
766
- <Button
767
- onClick={handleMigratePermissions}
768
- disabled={migrateLoading}
769
- variant="default"
770
- size="sm"
771
- className="cls_user_management_permissions_migrate_button"
772
- >
773
- {migrateLoading ? (
774
- <>
775
- <Loader2 className="h-4 w-4 mr-2 animate-spin" />
776
- Migrating...
777
- </>
778
- ) : (
779
- "Migrate config to database"
780
- )}
781
- </Button>
782
- </div>
783
- </div>
784
-
785
- {/* Permissions table */}
786
- {permissionsLoading ? (
787
- <div className="cls_user_management_permissions_loading flex items-center justify-center p-8">
788
- <Loader2 className="h-6 w-6 animate-spin text-slate-400" />
789
- </div>
790
- ) : (
791
- <div className="cls_user_management_permissions_table_container border rounded-lg overflow-auto w-full">
792
- <Table className="cls_user_management_permissions_table w-full">
793
- <TableHeader className="cls_user_management_permissions_table_header">
794
- <TableRow className="cls_user_management_permissions_table_header_row">
795
- <TableHead className="cls_user_management_permissions_table_header_name">
796
- Permission Name
797
- </TableHead>
798
- <TableHead className="cls_user_management_permissions_table_header_description">
799
- Description
800
- </TableHead>
801
- <TableHead className="cls_user_management_permissions_table_header_source">
802
- Source
803
- </TableHead>
804
- <TableHead className="cls_user_management_permissions_table_header_actions text-right">
805
- Actions
806
- </TableHead>
807
- </TableRow>
808
- </TableHeader>
809
- <TableBody className="cls_user_management_permissions_table_body">
810
- {permissions.length === 0 ? (
811
- <TableRow className="cls_user_management_permissions_table_row_empty">
812
- <TableCell colSpan={4} className="text-center text-muted-foreground py-8">
813
- No permissions found.
814
- </TableCell>
815
- </TableRow>
816
- ) : (
817
- permissions.map((permission) => (
818
- <TableRow
819
- key={`${permission.source}-${permission.id}-${permission.permission_name}`}
820
- className="cls_user_management_permissions_table_row"
821
- >
822
- <TableCell
823
- className={`cls_user_management_permissions_table_cell_name font-medium ${
824
- permission.source === "db" ? "text-blue-600" : "text-purple-600"
825
- }`}
826
- >
827
- {permission.permission_name}
828
- </TableCell>
829
- <TableCell className="cls_user_management_permissions_table_cell_description">
830
- {permission.description || "-"}
831
- </TableCell>
832
- <TableCell className="cls_user_management_permissions_table_cell_source">
833
- <span
834
- className={`px-2 py-1 rounded text-xs font-medium ${
835
- permission.source === "db"
836
- ? "bg-blue-100 text-blue-700"
837
- : "bg-purple-100 text-purple-700"
838
- }`}
839
- >
840
- {permission.source === "db" ? "Database" : "Config"}
841
- </span>
842
- </TableCell>
843
- <TableCell className="cls_user_management_permissions_table_cell_actions text-right">
844
- <div className="cls_user_management_permissions_table_actions flex items-center justify-end gap-2">
845
- {permission.source === "db" && (
846
- <>
847
- <Button
848
- onClick={() => {
849
- setEditingPermission(permission);
850
- setEditDescription(permission.description);
851
- setEditPermissionDialogOpen(true);
852
- }}
853
- variant="outline"
854
- size="sm"
855
- className="cls_user_management_permissions_table_action_edit"
856
- >
857
- <Edit className="h-4 w-4 mr-1" />
858
- Edit
859
- </Button>
860
- <Button
861
- onClick={() => handleDeletePermission(permission)}
862
- disabled={permissionsActionLoading}
863
- variant="outline"
864
- size="sm"
865
- className="cls_user_management_permissions_table_action_delete text-destructive"
866
- >
867
- <Trash2 className="h-4 w-4 mr-1" />
868
- Delete
869
- </Button>
870
- </>
871
- )}
872
- </div>
873
- </TableCell>
874
- </TableRow>
875
- ))
876
- )}
877
- </TableBody>
878
- </Table>
879
- </div>
880
- )}
881
- </div>
882
- </TabsContent>
883
- )}
884
- </Tabs>
885
- )}
886
-
887
- {/* Deactivate User Dialog */}
888
- <AlertDialog open={deactivateDialogOpen} onOpenChange={setDeactivateDialogOpen}>
889
- <AlertDialogContent className="cls_user_management_deactivate_dialog">
890
- <AlertDialogHeader>
891
- <AlertDialogTitle>Deactivate User</AlertDialogTitle>
892
- <AlertDialogDescription>
893
- Are you sure you want to deactivate {selectedUser?.name || selectedUser?.email_address}? They will not be able to log in until reactivated.
894
- </AlertDialogDescription>
895
- </AlertDialogHeader>
896
- <AlertDialogFooter className="cls_user_management_deactivate_dialog_footer">
897
- <AlertDialogAction
898
- onClick={handleDeactivateUser}
899
- disabled={usersActionLoading}
900
- className="cls_user_management_deactivate_dialog_confirm"
901
- >
902
- {usersActionLoading ? (
903
- <>
904
- <Loader2 className="h-4 w-4 mr-2 animate-spin" />
905
- Deactivating...
906
- </>
907
- ) : (
908
- "Deactivate"
909
- )}
910
- </AlertDialogAction>
911
- <AlertDialogCancel
912
- onClick={() => {
913
- setDeactivateDialogOpen(false);
914
- setSelectedUser(null);
915
- }}
916
- className="cls_user_management_deactivate_dialog_cancel"
917
- >
918
- Cancel
919
- </AlertDialogCancel>
920
- </AlertDialogFooter>
921
- </AlertDialogContent>
922
- </AlertDialog>
923
-
924
- {/* Reset Password Dialog */}
925
- <AlertDialog open={resetPasswordDialogOpen} onOpenChange={setResetPasswordDialogOpen}>
926
- <AlertDialogContent className="cls_user_management_reset_password_dialog">
927
- <AlertDialogHeader>
928
- <AlertDialogTitle>Reset Password</AlertDialogTitle>
929
- <AlertDialogDescription>
930
- Send a password reset email to {selectedUser?.email_address}? They will receive a link to reset their password.
931
- </AlertDialogDescription>
932
- </AlertDialogHeader>
933
- <AlertDialogFooter className="cls_user_management_reset_password_dialog_footer">
934
- <AlertDialogAction
935
- onClick={handleResetPassword}
936
- disabled={usersActionLoading}
937
- className="cls_user_management_reset_password_dialog_confirm"
938
- >
939
- {usersActionLoading ? (
940
- <>
941
- <Loader2 className="h-4 w-4 mr-2 animate-spin" />
942
- Sending...
943
- </>
944
- ) : (
945
- "Send Reset Email"
946
- )}
947
- </AlertDialogAction>
948
- <AlertDialogCancel
949
- onClick={() => {
950
- setResetPasswordDialogOpen(false);
951
- setSelectedUser(null);
952
- }}
953
- className="cls_user_management_reset_password_dialog_cancel"
954
- >
955
- Cancel
956
- </AlertDialogCancel>
957
- </AlertDialogFooter>
958
- </AlertDialogContent>
959
- </AlertDialog>
960
-
961
- {/* Edit Permission Dialog */}
962
- <Dialog open={editPermissionDialogOpen} onOpenChange={setEditPermissionDialogOpen}>
963
- <DialogContent className="cls_user_management_edit_permission_dialog">
964
- <DialogHeader>
965
- <DialogTitle>Edit Permission</DialogTitle>
966
- <DialogDescription>
967
- Update the description for permission: {editingPermission?.permission_name}
968
- </DialogDescription>
969
- </DialogHeader>
970
- <div className="cls_user_management_edit_permission_dialog_content flex flex-col gap-4 py-4">
971
- <div className="cls_user_management_edit_permission_dialog_field flex flex-col gap-2">
972
- <Label htmlFor="permission_description" className="cls_user_management_edit_permission_dialog_label">
973
- Description
974
- </Label>
975
- <Input
976
- id="permission_description"
977
- value={editDescription}
978
- onChange={(e) => setEditDescription(e.target.value)}
979
- placeholder="Enter permission description"
980
- className="cls_user_management_edit_permission_dialog_input"
981
- />
982
- </div>
983
- </div>
984
- <DialogFooter className="cls_user_management_edit_permission_dialog_footer">
985
- <Button
986
- onClick={handleEditPermission}
987
- disabled={permissionsActionLoading}
988
- variant="default"
989
- className="cls_user_management_edit_permission_dialog_save"
990
- >
991
- {permissionsActionLoading ? (
992
- <>
993
- <Loader2 className="h-4 w-4 mr-2 animate-spin" />
994
- Saving...
995
- </>
996
- ) : (
997
- <>
998
- <CircleCheck className="h-4 w-4 mr-2" />
999
- Save
1000
- </>
1001
- )}
1002
- </Button>
1003
- <Button
1004
- onClick={() => {
1005
- setEditPermissionDialogOpen(false);
1006
- setEditingPermission(null);
1007
- setEditDescription("");
1008
- }}
1009
- variant="outline"
1010
- className="cls_user_management_edit_permission_dialog_cancel"
1011
- >
1012
- <CircleX className="h-4 w-4 mr-2" />
1013
- Cancel
1014
- </Button>
1015
- </DialogFooter>
1016
- </DialogContent>
1017
- </Dialog>
1018
-
1019
- {/* Add Permission Dialog */}
1020
- <Dialog open={addPermissionDialogOpen} onOpenChange={setAddPermissionDialogOpen}>
1021
- <DialogContent className="cls_user_management_add_permission_dialog">
1022
- <DialogHeader>
1023
- <DialogTitle>Add New Permission</DialogTitle>
1024
- <DialogDescription>
1025
- Create a new permission that can be assigned to roles.
1026
- </DialogDescription>
1027
- </DialogHeader>
1028
- <div className="cls_user_management_add_permission_dialog_content flex flex-col gap-4 py-4">
1029
- <div className="cls_user_management_add_permission_dialog_field flex flex-col gap-2">
1030
- <Label htmlFor="new_permission_name" className="cls_user_management_add_permission_dialog_label">
1031
- Permission Name *
1032
- </Label>
1033
- <Input
1034
- id="new_permission_name"
1035
- value={newPermissionName}
1036
- onChange={(e) => setNewPermissionName(e.target.value)}
1037
- placeholder="Enter permission name (e.g., READ_USERS)"
1038
- className="cls_user_management_add_permission_dialog_input"
1039
- onKeyDown={(e) => {
1040
- if (e.key === "Enter") {
1041
- handleAddPermission();
1042
- }
1043
- }}
1044
- />
1045
- </div>
1046
- <div className="cls_user_management_add_permission_dialog_field flex flex-col gap-2">
1047
- <Label htmlFor="new_permission_description" className="cls_user_management_add_permission_dialog_label">
1048
- Description
1049
- </Label>
1050
- <Input
1051
- id="new_permission_description"
1052
- value={newPermissionDescription}
1053
- onChange={(e) => setNewPermissionDescription(e.target.value)}
1054
- placeholder="Enter permission description (optional)"
1055
- className="cls_user_management_add_permission_dialog_input"
1056
- onKeyDown={(e) => {
1057
- if (e.key === "Enter") {
1058
- handleAddPermission();
1059
- }
1060
- }}
1061
- />
1062
- </div>
1063
- </div>
1064
- <DialogFooter className="cls_user_management_add_permission_dialog_footer">
1065
- <Button
1066
- onClick={handleAddPermission}
1067
- disabled={permissionsActionLoading || !newPermissionName.trim()}
1068
- variant="default"
1069
- className="cls_user_management_add_permission_dialog_save"
1070
- >
1071
- {permissionsActionLoading ? (
1072
- <>
1073
- <Loader2 className="h-4 w-4 mr-2 animate-spin" />
1074
- Creating...
1075
- </>
1076
- ) : (
1077
- <>
1078
- <CircleCheck className="h-4 w-4 mr-2" />
1079
- Create Permission
1080
- </>
1081
- )}
1082
- </Button>
1083
- <Button
1084
- onClick={() => {
1085
- setAddPermissionDialogOpen(false);
1086
- setNewPermissionName("");
1087
- setNewPermissionDescription("");
1088
- }}
1089
- variant="outline"
1090
- className="cls_user_management_add_permission_dialog_cancel"
1091
- >
1092
- <CircleX className="h-4 w-4 mr-2" />
1093
- Cancel
1094
- </Button>
1095
- </DialogFooter>
1096
- </DialogContent>
1097
- </Dialog>
1098
-
1099
- {/* User Detail Dialog */}
1100
- <Dialog open={userDetailDialogOpen} onOpenChange={setUserDetailDialogOpen}>
1101
- <DialogContent className="cls_user_management_user_detail_dialog max-w-2xl">
1102
- <DialogHeader>
1103
- <DialogTitle>User Details</DialogTitle>
1104
- <DialogDescription>
1105
- Complete information for {selectedUser?.name || selectedUser?.email_address}
1106
- </DialogDescription>
1107
- </DialogHeader>
1108
- <div className="cls_user_management_user_detail_dialog_content flex flex-col gap-4 py-4">
1109
- {selectedUser && (
1110
- <div className="cls_user_management_user_detail_fields grid grid-cols-1 gap-4">
1111
- {/* Profile Picture */}
1112
- <div className="cls_user_management_user_detail_field_profile_pic flex flex-col gap-2">
1113
- <Label className="cls_user_management_user_detail_label font-semibold">
1114
- Profile Picture
1115
- </Label>
1116
- <div className="cls_user_management_user_detail_profile_pic_container flex items-center gap-4">
1117
- <Avatar className="cls_user_management_user_detail_avatar h-16 w-16">
1118
- <AvatarImage
1119
- src={selectedUser.profile_picture_url || undefined}
1120
- alt={selectedUser.name ? `Profile picture of ${selectedUser.name}` : "Profile picture"}
1121
- className="cls_user_management_user_detail_avatar_image"
1122
- />
1123
- <AvatarFallback className="cls_user_management_user_detail_avatar_fallback bg-slate-200 text-slate-600 text-lg">
1124
- {getUserInitials(selectedUser)}
1125
- </AvatarFallback>
1126
- </Avatar>
1127
- <div className="cls_user_management_user_detail_profile_pic_info flex flex-col gap-1">
1128
- <span className="cls_user_management_user_detail_profile_pic_url text-sm text-muted-foreground">
1129
- {selectedUser.profile_picture_url || "No profile picture"}
1130
- </span>
1131
- <span className="cls_user_management_user_detail_profile_pic_source text-xs text-muted-foreground">
1132
- Source: {selectedUser.profile_source || "N/A"}
1133
- </span>
1134
- </div>
1135
- </div>
1136
- </div>
1137
-
1138
- {/* User ID */}
1139
- <div className="cls_user_management_user_detail_field_id flex flex-col gap-2">
1140
- <Label className="cls_user_management_user_detail_label font-semibold">
1141
- User ID
1142
- </Label>
1143
- <div className="cls_user_management_user_detail_id_value font-mono text-sm bg-muted p-2 rounded">
1144
- {selectedUser.id}
1145
- </div>
1146
- </div>
1147
-
1148
- {/* Name */}
1149
- <div className="cls_user_management_user_detail_field_name flex flex-col gap-2">
1150
- <Label className="cls_user_management_user_detail_label font-semibold">
1151
- Name
1152
- </Label>
1153
- <div className="cls_user_management_user_detail_name_value text-sm">
1154
- {selectedUser.name || "-"}
1155
- </div>
1156
- </div>
1157
-
1158
- {/* Email */}
1159
- <div className="cls_user_management_user_detail_field_email flex flex-col gap-2">
1160
- <Label className="cls_user_management_user_detail_label font-semibold">
1161
- Email Address
1162
- </Label>
1163
- <div className="cls_user_management_user_detail_email_value text-sm">
1164
- {selectedUser.email_address}
1165
- </div>
1166
- </div>
1167
-
1168
- {/* Email Verified */}
1169
- <div className="cls_user_management_user_detail_field_email_verified flex flex-col gap-2">
1170
- <Label className="cls_user_management_user_detail_label font-semibold">
1171
- Email Verified
1172
- </Label>
1173
- <div className="cls_user_management_user_detail_email_verified_value">
1174
- {selectedUser.email_verified ? (
1175
- <span className="text-green-600 font-medium">Yes</span>
1176
- ) : (
1177
- <span className="text-red-600 font-medium">No</span>
1178
- )}
1179
- </div>
1180
- </div>
1181
-
1182
- {/* Active Status */}
1183
- <div className="cls_user_management_user_detail_field_is_active flex flex-col gap-2">
1184
- <Label className="cls_user_management_user_detail_label font-semibold">
1185
- Active Status
1186
- </Label>
1187
- <div className="cls_user_management_user_detail_is_active_value">
1188
- {selectedUser.is_active ? (
1189
- <span className="text-green-600 font-medium">Active</span>
1190
- ) : (
1191
- <span className="text-red-600 font-medium">Inactive</span>
1192
- )}
1193
- </div>
1194
- </div>
1195
-
1196
- {/* Last Logon */}
1197
- <div className="cls_user_management_user_detail_field_last_logon flex flex-col gap-2">
1198
- <Label className="cls_user_management_user_detail_label font-semibold">
1199
- Last Logon
1200
- </Label>
1201
- <div className="cls_user_management_user_detail_last_logon_value text-sm">
1202
- {selectedUser.last_logon ? (
1203
- <span className="font-medium">
1204
- {new Date(selectedUser.last_logon).toLocaleString(undefined, {
1205
- year: "numeric",
1206
- month: "long",
1207
- day: "numeric",
1208
- hour: "2-digit",
1209
- minute: "2-digit",
1210
- second: "2-digit",
1211
- timeZoneName: "short",
1212
- })}
1213
- </span>
1214
- ) : (
1215
- <span className="text-muted-foreground">Never</span>
1216
- )}
1217
- </div>
1218
- </div>
1219
-
1220
- {/* Created At */}
1221
- <div className="cls_user_management_user_detail_field_created_at flex flex-col gap-2">
1222
- <Label className="cls_user_management_user_detail_label font-semibold">
1223
- Created At
1224
- </Label>
1225
- <div className="cls_user_management_user_detail_created_at_value text-sm">
1226
- {selectedUser.created_at ? (
1227
- <span className="font-medium">
1228
- {new Date(selectedUser.created_at).toLocaleString(undefined, {
1229
- year: "numeric",
1230
- month: "long",
1231
- day: "numeric",
1232
- hour: "2-digit",
1233
- minute: "2-digit",
1234
- second: "2-digit",
1235
- timeZoneName: "short",
1236
- })}
1237
- </span>
1238
- ) : (
1239
- <span className="text-muted-foreground">-</span>
1240
- )}
1241
- </div>
1242
- </div>
1243
- </div>
1244
- )}
1245
- </div>
1246
- <DialogFooter className="cls_user_management_user_detail_dialog_footer">
1247
- <Button
1248
- onClick={() => setUserDetailDialogOpen(false)}
1249
- variant="outline"
1250
- className="cls_user_management_user_detail_dialog_close"
1251
- >
1252
- Close
1253
- </Button>
1254
- </DialogFooter>
1255
- </DialogContent>
1256
- </Dialog>
1257
-
1258
- {/* Assign Roles Dialog */}
1259
- <Dialog open={assignRolesDialogOpen} onOpenChange={setAssignRolesDialogOpen}>
1260
- <DialogContent className="cls_user_management_assign_roles_dialog max-w-4xl max-h-[80vh] overflow-y-auto">
1261
- <DialogHeader>
1262
- <DialogTitle>Assign Roles to User</DialogTitle>
1263
- <DialogDescription>
1264
- Select roles to assign to {selectedUser?.name || selectedUser?.email_address}.
1265
- Check the roles you want to assign, then click Save.
1266
- </DialogDescription>
1267
- </DialogHeader>
1268
- <div className="cls_user_management_assign_roles_dialog_content py-4">
1269
- <RolesMatrix
1270
- add_button_enabled={false}
1271
- role_name_selection_enabled={true}
1272
- permissions_read_only={true}
1273
- show_save_cancel={true}
1274
- user_id={selectedUser?.id}
1275
- onSave={(data) => {
1276
- // Data is already saved by RolesMatrix component
1277
- console.log("User roles saved:", data);
1278
- // Refresh users list to show updated roles
1279
- void loadUsers();
1280
- setAssignRolesDialogOpen(false);
1281
- setSelectedUser(null);
1282
- }}
1283
- onCancel={() => {
1284
- setAssignRolesDialogOpen(false);
1285
- setSelectedUser(null);
1286
- }}
1287
- className="cls_user_management_assign_roles_matrix"
1288
- />
1289
- </div>
1290
- </DialogContent>
1291
- </Dialog>
1292
- </div>
1293
- );
1294
- }
1295
-