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,311 +0,0 @@
1
- // file_description: Library tab component for profile picture dialog with category tabs and image grid
2
- // section: client_directive
3
- "use client";
4
-
5
- // section: imports
6
- import { useState, useEffect } from "react";
7
- import { Switch } from "../../../ui/switch";
8
- import { Label } from "../../../ui/label";
9
- import { Avatar, AvatarFallback } from "../../../ui/avatar";
10
- import { VerticalTabs, VerticalTabsList, VerticalTabsTrigger, VerticalTabsContent } from "../../../ui/vertical-tabs";
11
- import { Loader2 } from "lucide-react";
12
- import { HazoUITooltip } from "../../../ui/hazo_ui_tooltip";
13
-
14
- // section: types
15
- export type ProfilePictureLibraryTabProps = {
16
- useLibrary: boolean;
17
- onUseLibraryChange: (use: boolean) => void;
18
- onPhotoSelect: (photoUrl: string) => void;
19
- disabled?: boolean;
20
- libraryPhotoPath: string;
21
- currentPhotoUrl?: string;
22
- libraryTooltipMessage: string;
23
- tooltipIconSizeSmall: number;
24
- libraryPhotoGridColumns: number;
25
- libraryPhotoPreviewSize: number;
26
- };
27
-
28
- // section: component
29
- /**
30
- * Library tab component for profile picture dialog
31
- * Two columns: left = vertical category tabs, right = image grid + preview
32
- * Lazy loads thumbnails when category is selected
33
- * @param props - Component props including library state, photo selection handler, and configuration
34
- * @returns Library tab component
35
- */
36
- export function ProfilePictureLibraryTab({
37
- useLibrary,
38
- onUseLibraryChange,
39
- onPhotoSelect,
40
- disabled = false,
41
- libraryPhotoPath,
42
- currentPhotoUrl,
43
- libraryTooltipMessage,
44
- tooltipIconSizeSmall,
45
- libraryPhotoGridColumns,
46
- libraryPhotoPreviewSize,
47
- }: ProfilePictureLibraryTabProps) {
48
- const [categories, setCategories] = useState<string[]>([]);
49
- const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
50
- const [photos, setPhotos] = useState<string[]>([]);
51
- const [selectedPhoto, setSelectedPhoto] = useState<string | null>(currentPhotoUrl || null);
52
- const [loadingCategories, setLoadingCategories] = useState(false);
53
- const [loadingPhotos, setLoadingPhotos] = useState(false);
54
-
55
- // Load categories on mount
56
- useEffect(() => {
57
- const loadCategories = async () => {
58
- setLoadingCategories(true);
59
- try {
60
- const response = await fetch("/api/hazo_auth/library_photos");
61
- const data = await response.json();
62
- if (data.success && data.categories) {
63
- setCategories(data.categories);
64
- // Select first category if available
65
- if (data.categories.length > 0) {
66
- setSelectedCategory(data.categories[0]);
67
- }
68
- }
69
- } catch (error) {
70
- // Client-side error handling - show toast notification
71
- import("sonner").then(({ toast }) => {
72
- toast.error("Failed to load photo categories. Please try again.");
73
- });
74
- } finally {
75
- setLoadingCategories(false);
76
- }
77
- };
78
-
79
- void loadCategories();
80
- }, []);
81
-
82
- // Sync selectedPhoto with currentPhotoUrl when it changes
83
- useEffect(() => {
84
- if (currentPhotoUrl && currentPhotoUrl !== selectedPhoto) {
85
- setSelectedPhoto(currentPhotoUrl);
86
- }
87
- }, [currentPhotoUrl]);
88
-
89
- // Load photos when category is selected
90
- useEffect(() => {
91
- if (!selectedCategory) {
92
- setPhotos([]);
93
- if (!currentPhotoUrl) {
94
- setSelectedPhoto(null);
95
- }
96
- return;
97
- }
98
-
99
- const loadPhotos = async () => {
100
- setLoadingPhotos(true);
101
- try {
102
- const response = await fetch(`/api/hazo_auth/library_photos?category=${encodeURIComponent(selectedCategory)}`);
103
- const data = await response.json();
104
- if (data.success && data.photos) {
105
- setPhotos(data.photos);
106
-
107
- // If we have a current photo URL and it's in this category, select it
108
- if (currentPhotoUrl && data.photos.includes(currentPhotoUrl)) {
109
- setSelectedPhoto(currentPhotoUrl);
110
- onPhotoSelect(currentPhotoUrl);
111
- } else if (data.photos.length > 0) {
112
- // Otherwise, select first photo if available and notify parent
113
- const firstPhoto = data.photos[0];
114
- setSelectedPhoto(firstPhoto);
115
- onPhotoSelect(firstPhoto);
116
- } else if (!currentPhotoUrl) {
117
- // Clear selection if no photos and no current photo
118
- setSelectedPhoto(null);
119
- }
120
- }
121
- } catch (error) {
122
- // Client-side error handling - show toast notification
123
- import("sonner").then(({ toast }) => {
124
- toast.error("Failed to load photos. Please try again.");
125
- });
126
- } finally {
127
- setLoadingPhotos(false);
128
- }
129
- };
130
-
131
- void loadPhotos();
132
- }, [selectedCategory, onPhotoSelect, currentPhotoUrl]);
133
-
134
- const handlePhotoClick = (photoUrl: string) => {
135
- setSelectedPhoto(photoUrl);
136
- onPhotoSelect(photoUrl);
137
- };
138
-
139
- const getInitials = (): string => {
140
- return "L";
141
- };
142
-
143
- // Map column count to Tailwind grid class
144
- const getGridColumnsClass = (columns: number): string => {
145
- const columnMap: Record<number, string> = {
146
- 1: "grid-cols-1",
147
- 2: "grid-cols-2",
148
- 3: "grid-cols-3",
149
- 4: "grid-cols-4",
150
- 5: "grid-cols-5",
151
- 6: "grid-cols-6",
152
- 7: "grid-cols-7",
153
- 8: "grid-cols-8",
154
- };
155
- return columnMap[columns] || "grid-cols-4";
156
- };
157
-
158
- return (
159
- <div className="cls_profile_picture_library_tab flex flex-col gap-4">
160
- {/* Switch */}
161
- <div className="cls_profile_picture_library_tab_switch flex items-center gap-3">
162
- <Switch
163
- id="use-library"
164
- checked={useLibrary}
165
- onCheckedChange={onUseLibraryChange}
166
- disabled={disabled}
167
- className="cls_profile_picture_library_tab_switch_input"
168
- aria-label="Use library photo"
169
- />
170
- <Label
171
- htmlFor="use-library"
172
- className="cls_profile_picture_library_tab_switch_label text-sm font-medium text-slate-700 cursor-pointer"
173
- >
174
- Use library photo
175
- <HazoUITooltip
176
- message={libraryTooltipMessage}
177
- iconSize={tooltipIconSizeSmall}
178
- side="top"
179
- />
180
- </Label>
181
- </div>
182
-
183
- {/* Three columns: category tabs (25%), photo grid (50%), preview (25%) */}
184
- <div className="cls_profile_picture_library_tab_content grid grid-cols-12 gap-4">
185
- {/* Left column: Category tabs (25% - 3 columns) */}
186
- <div className="cls_profile_picture_library_tab_categories_container flex flex-col gap-2 col-span-3">
187
- <Label className="cls_profile_picture_library_tab_categories_label text-sm font-medium text-slate-700">
188
- Categories
189
- </Label>
190
- {loadingCategories ? (
191
- <div className="cls_profile_picture_library_tab_loading flex items-center justify-center p-8 border border-slate-200 rounded-lg bg-slate-50 min-h-[400px]">
192
- <Loader2 className="h-6 w-6 text-slate-400 animate-spin" aria-hidden="true" />
193
- </div>
194
- ) : categories.length > 0 ? (
195
- <VerticalTabs
196
- value={selectedCategory || categories[0]}
197
- onValueChange={setSelectedCategory}
198
- className="cls_profile_picture_library_tab_vertical_tabs"
199
- >
200
- <VerticalTabsList className="cls_profile_picture_library_tab_vertical_tabs_list w-full">
201
- {categories.map((category) => (
202
- <VerticalTabsTrigger
203
- key={category}
204
- value={category}
205
- className="cls_profile_picture_library_tab_vertical_tabs_trigger w-full justify-start"
206
- >
207
- {category}
208
- </VerticalTabsTrigger>
209
- ))}
210
- </VerticalTabsList>
211
- </VerticalTabs>
212
- ) : (
213
- <div className="cls_profile_picture_library_tab_no_categories flex items-center justify-center p-8 border border-slate-200 rounded-lg bg-slate-50 min-h-[400px]">
214
- <p className="cls_profile_picture_library_tab_no_categories_text text-sm text-slate-600">
215
- No categories available
216
- </p>
217
- </div>
218
- )}
219
- </div>
220
-
221
- {/* Middle column: Photo grid (50% - 6 columns) */}
222
- <div className="cls_profile_picture_library_tab_photos_container flex flex-col gap-2 col-span-6">
223
- <Label className="cls_profile_picture_library_tab_photos_label text-sm font-medium text-slate-700">
224
- Photos
225
- </Label>
226
- {loadingPhotos ? (
227
- <div className="cls_profile_picture_library_tab_photos_loading flex items-center justify-center p-8 border border-slate-200 rounded-lg bg-slate-50 min-h-[400px]">
228
- <Loader2 className="h-6 w-6 text-slate-400 animate-spin" aria-hidden="true" />
229
- </div>
230
- ) : photos.length > 0 ? (
231
- <div className={`cls_profile_picture_library_tab_photos_grid grid ${getGridColumnsClass(libraryPhotoGridColumns)} gap-3 overflow-y-auto p-4 border border-slate-200 rounded-lg bg-slate-50 min-h-[400px] max-h-[400px]`}>
232
- {photos.map((photoUrl) => (
233
- <button
234
- key={photoUrl}
235
- type="button"
236
- onClick={() => handlePhotoClick(photoUrl)}
237
- className={`
238
- cls_profile_picture_library_tab_photo_thumbnail
239
- aspect-square rounded-lg overflow-hidden border-2 transition-colors cursor-pointer
240
- ${selectedPhoto === photoUrl ? "border-blue-500 ring-2 ring-blue-200" : "border-slate-200 hover:border-slate-300"}
241
- `}
242
- aria-label={`Select photo ${photoUrl.split('/').pop()}`}
243
- >
244
- <img
245
- src={photoUrl}
246
- alt={`Library photo ${photoUrl.split('/').pop()}`}
247
- className="cls_profile_picture_library_tab_photo_thumbnail_image w-full h-full object-cover"
248
- loading="lazy"
249
- onError={(e) => {
250
- // Fallback if image fails to load
251
- const target = e.target as HTMLImageElement;
252
- target.style.display = 'none';
253
- }}
254
- />
255
- </button>
256
- ))}
257
- </div>
258
- ) : (
259
- <div className="cls_profile_picture_library_tab_no_photos flex items-center justify-center p-8 border border-slate-200 rounded-lg bg-slate-50 min-h-[400px]">
260
- <p className="cls_profile_picture_library_tab_no_photos_text text-sm text-slate-600">
261
- No photos in this category
262
- </p>
263
- </div>
264
- )}
265
- </div>
266
-
267
- {/* Right column: Preview (25% - 3 columns) */}
268
- <div className="cls_profile_picture_library_tab_preview_container flex flex-col gap-2 col-span-3">
269
- <Label className="cls_profile_picture_library_tab_preview_label text-sm font-medium text-slate-700">
270
- Preview
271
- </Label>
272
- {selectedPhoto ? (
273
- <div className="cls_profile_picture_library_tab_preview flex flex-col items-center gap-4 p-4 border border-slate-200 rounded-lg bg-slate-50 min-h-[400px] justify-center">
274
- <div className="cls_profile_picture_library_tab_preview_image_wrapper w-full flex items-center justify-center">
275
- <img
276
- src={selectedPhoto}
277
- alt="Selected library photo preview"
278
- className="cls_profile_picture_library_tab_preview_image max-w-full max-h-[350px] rounded-lg object-contain"
279
- onError={(e) => {
280
- // Fallback if preview image fails to load
281
- const target = e.target as HTMLImageElement;
282
- target.style.display = 'none';
283
- const wrapper = target.parentElement;
284
- if (wrapper) {
285
- wrapper.innerHTML = '<p class="text-sm text-red-500">Failed to load preview</p>';
286
- }
287
- }}
288
- />
289
- </div>
290
- <p className="cls_profile_picture_library_tab_preview_text text-sm text-slate-600 text-center">
291
- Selected photo preview
292
- </p>
293
- </div>
294
- ) : (
295
- <div className="cls_profile_picture_library_tab_preview_empty flex flex-col items-center justify-center gap-2 p-8 border border-slate-200 rounded-lg bg-slate-50 min-h-[400px]">
296
- <Avatar className="cls_profile_picture_library_tab_preview_empty_avatar h-32 w-32">
297
- <AvatarFallback className="cls_profile_picture_library_tab_preview_empty_avatar_fallback bg-slate-200 text-slate-600 text-3xl">
298
- {getInitials()}
299
- </AvatarFallback>
300
- </Avatar>
301
- <p className="cls_profile_picture_library_tab_preview_empty_text text-sm text-slate-500 text-center">
302
- Select a photo to see preview
303
- </p>
304
- </div>
305
- )}
306
- </div>
307
- </div>
308
- </div>
309
- );
310
- }
311
-
@@ -1,341 +0,0 @@
1
- // file_description: Upload tab component for profile picture dialog with dropzone and preview
2
- // section: client_directive
3
- "use client";
4
-
5
- // section: imports
6
- import { useState, useCallback, useEffect } from "react";
7
- import { Switch } from "../../../ui/switch";
8
- import { Label } from "../../../ui/label";
9
- import { Avatar, AvatarImage, AvatarFallback } from "../../../ui/avatar";
10
- import { Upload, X, Loader2, Info } from "lucide-react";
11
- import { Button } from "../../../ui/button";
12
- import imageCompression from "browser-image-compression";
13
-
14
- // section: types
15
- export type ProfilePictureUploadTabProps = {
16
- useUpload: boolean;
17
- onUseUploadChange: (use: boolean) => void;
18
- onFileSelect: (file: File) => Promise<void>;
19
- maxSize: number; // in bytes
20
- uploadEnabled: boolean;
21
- disabled?: boolean;
22
- currentPreview?: string;
23
- photoUploadDisabledMessage?: string;
24
- imageCompressionMaxDimension?: number;
25
- uploadFileHardLimitBytes?: number;
26
- allowedImageMimeTypes?: string[];
27
- };
28
-
29
- // section: component
30
- /**
31
- * Upload tab component for profile picture dialog
32
- * Two columns: left = dropzone, right = preview
33
- * Uses browser-image-compression for client-side compression
34
- * @param props - Component props including upload state, file handler, and configuration
35
- * @returns Upload tab component
36
- */
37
- export function ProfilePictureUploadTab({
38
- useUpload,
39
- onUseUploadChange,
40
- onFileSelect,
41
- maxSize,
42
- uploadEnabled,
43
- disabled = false,
44
- currentPreview,
45
- photoUploadDisabledMessage,
46
- imageCompressionMaxDimension = 200,
47
- uploadFileHardLimitBytes = 10485760, // 10MB default
48
- allowedImageMimeTypes = ["image/jpeg", "image/jpg", "image/png"],
49
- }: ProfilePictureUploadTabProps) {
50
- const [dragActive, setDragActive] = useState(false);
51
- const [preview, setPreview] = useState<string | null>(currentPreview || null);
52
- const [isNewImage, setIsNewImage] = useState(false); // Track if preview is showing a newly uploaded image
53
- const [uploading, setUploading] = useState(false);
54
- const [compressing, setCompressing] = useState(false);
55
- const [error, setError] = useState<string | null>(null);
56
-
57
- // Update preview when currentPreview changes (e.g., when dialog opens)
58
- useEffect(() => {
59
- if (currentPreview) {
60
- setPreview(currentPreview);
61
- setIsNewImage(false); // Reset to current when dialog opens or currentPreview changes
62
- } else {
63
- // If no current preview, only clear if we're not showing a new image
64
- if (!isNewImage) {
65
- setPreview(null);
66
- }
67
- }
68
- // eslint-disable-next-line react-hooks/exhaustive-deps
69
- }, [currentPreview]); // Only depend on currentPreview to avoid loops, isNewImage check is intentional
70
-
71
- const handleFile = useCallback(async (file: File) => {
72
- // Validate file type
73
- if (!allowedImageMimeTypes.includes(file.type)) {
74
- setError(`Invalid file type. Only ${allowedImageMimeTypes.map(t => t.split("/")[1].toUpperCase()).join(", ")} files are allowed.`);
75
- return;
76
- }
77
-
78
- // Hard limit: reject files larger than configured limit (too large to process efficiently)
79
- if (file.size > uploadFileHardLimitBytes) {
80
- setError(`File is too large. Maximum size is ${Math.round(maxSize / 1024)}KB.`);
81
- return;
82
- }
83
-
84
- setError(null);
85
- setCompressing(false);
86
- setUploading(false);
87
-
88
- // If file is larger than maxSize, compress it
89
- if (file.size > maxSize) {
90
- setCompressing(true);
91
- try {
92
- // Compress image
93
- const options = {
94
- maxSizeMB: maxSize / (1024 * 1024), // Convert bytes to MB
95
- maxWidthOrHeight: imageCompressionMaxDimension,
96
- useWebWorker: true,
97
- fileType: file.type,
98
- };
99
-
100
- const compressedFile = await imageCompression(file, options);
101
- setCompressing(false);
102
-
103
- // Check if compressed file is still too large
104
- if (compressedFile.size > maxSize) {
105
- setError(`File is too large. Maximum size is ${Math.round(maxSize / 1024)}KB. After compression, file is ${Math.round(compressedFile.size / 1024)}KB.`);
106
- return;
107
- }
108
-
109
- // Create preview
110
- const reader = new FileReader();
111
- reader.onloadend = () => {
112
- setPreview(reader.result as string);
113
- setIsNewImage(true); // Mark as new image
114
- };
115
- reader.readAsDataURL(compressedFile);
116
-
117
- // Upload the compressed file
118
- setUploading(true);
119
- await onFileSelect(compressedFile);
120
- setUploading(false);
121
- } catch (error) {
122
- setCompressing(false);
123
- const errorMessage = error instanceof Error ? error.message : "Failed to compress image";
124
- setError(errorMessage);
125
- }
126
- } else {
127
- // File is already small enough, just upload it
128
- setUploading(true);
129
- try {
130
- // Create preview
131
- const reader = new FileReader();
132
- reader.onloadend = () => {
133
- setPreview(reader.result as string);
134
- setIsNewImage(true); // Mark as new image
135
- };
136
- reader.readAsDataURL(file);
137
-
138
- // Call onFileSelect with original file
139
- await onFileSelect(file);
140
- } catch (error) {
141
- const errorMessage = error instanceof Error ? error.message : "Failed to process image";
142
- setError(errorMessage);
143
- } finally {
144
- setUploading(false);
145
- }
146
- }
147
- }, [maxSize, onFileSelect]);
148
-
149
- const handleDrag = useCallback((e: React.DragEvent<HTMLDivElement>) => {
150
- e.preventDefault();
151
- e.stopPropagation();
152
- if (e.type === "dragenter" || e.type === "dragover") {
153
- setDragActive(true);
154
- } else if (e.type === "dragleave") {
155
- setDragActive(false);
156
- }
157
- }, []);
158
-
159
- const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
160
- e.preventDefault();
161
- e.stopPropagation();
162
- setDragActive(false);
163
-
164
- if (!uploadEnabled || disabled) {
165
- setError("Photo upload is not enabled");
166
- return;
167
- }
168
-
169
- if (e.dataTransfer.files && e.dataTransfer.files[0]) {
170
- void handleFile(e.dataTransfer.files[0]);
171
- }
172
- }, [uploadEnabled, disabled, handleFile]);
173
-
174
- const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
175
- e.preventDefault();
176
- if (!uploadEnabled || disabled) {
177
- setError("Photo upload is not enabled");
178
- return;
179
- }
180
-
181
- if (e.target.files && e.target.files[0]) {
182
- void handleFile(e.target.files[0]);
183
- }
184
- }, [uploadEnabled, disabled, handleFile]);
185
-
186
- const handleRemove = useCallback(() => {
187
- setPreview(currentPreview || null); // Reset to current preview if available
188
- setIsNewImage(false); // Reset to showing current image
189
- setError(null);
190
- }, [currentPreview]);
191
-
192
- const getInitials = (): string => {
193
- return "U";
194
- };
195
-
196
- return (
197
- <div className="cls_profile_picture_upload_tab flex flex-col gap-4">
198
- {/* Switch */}
199
- <div className="cls_profile_picture_upload_tab_switch flex items-center gap-3">
200
- <Switch
201
- id="use-upload"
202
- checked={useUpload}
203
- onCheckedChange={onUseUploadChange}
204
- disabled={disabled || !uploadEnabled}
205
- className="cls_profile_picture_upload_tab_switch_input"
206
- aria-label="Use uploaded photo"
207
- />
208
- <Label
209
- htmlFor="use-upload"
210
- className="cls_profile_picture_upload_tab_switch_label text-sm font-medium text-slate-700 cursor-pointer"
211
- >
212
- Use uploaded photo
213
- </Label>
214
- </div>
215
-
216
- {!uploadEnabled && (
217
- <div className="cls_profile_picture_upload_tab_disabled flex items-center gap-2 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
218
- <Info className="h-4 w-4 text-yellow-600" aria-hidden="true" />
219
- <p className="cls_profile_picture_upload_tab_disabled_text text-sm text-yellow-800">
220
- {photoUploadDisabledMessage}
221
- </p>
222
- </div>
223
- )}
224
-
225
- {/* Two columns: dropzone and preview */}
226
- <div className="cls_profile_picture_upload_tab_content grid grid-cols-1 md:grid-cols-2 gap-6">
227
- {/* Left column: Dropzone */}
228
- <div className="cls_profile_picture_upload_tab_dropzone_container flex flex-col gap-2">
229
- <Label className="cls_profile_picture_upload_tab_dropzone_label text-sm font-medium text-slate-700">
230
- Upload Photo
231
- </Label>
232
- <div
233
- className={`
234
- cls_profile_picture_upload_tab_dropzone
235
- flex flex-col items-center justify-center
236
- border-2 border-dashed rounded-lg p-8
237
- transition-colors
238
- ${dragActive ? "border-blue-500 bg-blue-50" : "border-slate-300 bg-slate-50"}
239
- ${disabled || !uploadEnabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer hover:border-slate-400"}
240
- `}
241
- onDragEnter={handleDrag}
242
- onDragLeave={handleDrag}
243
- onDragOver={handleDrag}
244
- onDrop={handleDrop}
245
- onClick={() => {
246
- if (!disabled && uploadEnabled) {
247
- document.getElementById("file-upload-input")?.click();
248
- }
249
- }}
250
- >
251
- <input
252
- id="file-upload-input"
253
- type="file"
254
- accept={allowedImageMimeTypes.join(",")}
255
- onChange={handleChange}
256
- disabled={disabled || !uploadEnabled}
257
- className="hidden"
258
- aria-label="Upload profile picture"
259
- />
260
- <Upload className="h-8 w-8 text-slate-400 mb-2" aria-hidden="true" />
261
- <p className="cls_profile_picture_upload_tab_dropzone_text text-sm text-slate-600 text-center">
262
- Drag and drop an image here, or click to select
263
- </p>
264
- <p className="cls_profile_picture_upload_tab_dropzone_hint text-xs text-slate-500 text-center mt-1">
265
- JPG or PNG, max {Math.round(maxSize / 1024)}KB
266
- </p>
267
- </div>
268
- {error && (
269
- <p className="cls_profile_picture_upload_tab_error text-sm text-red-600" role="alert">
270
- {error}
271
- </p>
272
- )}
273
- </div>
274
-
275
- {/* Right column: Preview */}
276
- <div className="cls_profile_picture_upload_tab_preview_container flex flex-col gap-2">
277
- <Label className="cls_profile_picture_upload_tab_preview_label text-sm font-medium text-slate-700">
278
- {isNewImage ? "Preview (new)" : "Preview (current)"}
279
- </Label>
280
- <div className="cls_profile_picture_upload_tab_preview_content flex flex-col items-center justify-center border border-slate-200 rounded-lg p-6 bg-slate-50 min-h-[200px]">
281
- {compressing ? (
282
- <div className="cls_profile_picture_upload_tab_compressing flex flex-col items-center gap-2">
283
- <Loader2 className="h-8 w-8 text-slate-400 animate-spin" aria-hidden="true" />
284
- <p className="cls_profile_picture_upload_tab_compressing_text text-sm text-slate-600">
285
- Compressing image...
286
- </p>
287
- </div>
288
- ) : uploading ? (
289
- <div className="cls_profile_picture_upload_tab_uploading flex flex-col items-center gap-2">
290
- <Loader2 className="h-8 w-8 text-slate-400 animate-spin" aria-hidden="true" />
291
- <p className="cls_profile_picture_upload_tab_uploading_text text-sm text-slate-600">
292
- Uploading...
293
- </p>
294
- </div>
295
- ) : preview ? (
296
- <div className="cls_profile_picture_upload_tab_preview_image_container flex flex-col items-center gap-4">
297
- <div className="cls_profile_picture_upload_tab_preview_image_wrapper relative">
298
- <Avatar className="cls_profile_picture_upload_tab_preview_avatar h-32 w-32">
299
- <AvatarImage
300
- src={preview}
301
- alt="Uploaded profile picture preview"
302
- className="cls_profile_picture_upload_tab_preview_avatar_image"
303
- />
304
- <AvatarFallback className="cls_profile_picture_upload_tab_preview_avatar_fallback bg-slate-200 text-slate-600 text-3xl">
305
- {getInitials()}
306
- </AvatarFallback>
307
- </Avatar>
308
- <Button
309
- type="button"
310
- onClick={handleRemove}
311
- variant="ghost"
312
- size="icon"
313
- className="cls_profile_picture_upload_tab_preview_remove absolute -top-2 -right-2 rounded-full h-6 w-6 border border-slate-300 bg-white hover:bg-slate-50"
314
- aria-label="Remove preview"
315
- >
316
- <X className="h-4 w-4" aria-hidden="true" />
317
- </Button>
318
- </div>
319
- <p className="cls_profile_picture_upload_tab_preview_success_text text-sm text-slate-600 text-center">
320
- Preview of your uploaded photo
321
- </p>
322
- </div>
323
- ) : (
324
- <div className="cls_profile_picture_upload_tab_preview_empty flex flex-col items-center gap-2">
325
- <Avatar className="cls_profile_picture_upload_tab_preview_empty_avatar h-32 w-32">
326
- <AvatarFallback className="cls_profile_picture_upload_tab_preview_empty_avatar_fallback bg-slate-200 text-slate-600 text-3xl">
327
- {getInitials()}
328
- </AvatarFallback>
329
- </Avatar>
330
- <p className="cls_profile_picture_upload_tab_preview_empty_text text-sm text-slate-500 text-center">
331
- Upload an image to see preview
332
- </p>
333
- </div>
334
- )}
335
- </div>
336
- </div>
337
- </div>
338
- </div>
339
- );
340
- }
341
-