hazo_auth 1.4.1 → 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 (478) hide show
  1. package/README.md +25 -38
  2. package/SETUP_CHECKLIST.md +708 -0
  3. package/dist/app/api/hazo_auth/change_password/route.d.ts +8 -0
  4. package/dist/app/api/hazo_auth/change_password/route.d.ts.map +1 -0
  5. package/dist/app/api/hazo_auth/change_password/route.js +98 -0
  6. package/dist/app/api/hazo_auth/forgot_password/route.d.ts +8 -0
  7. package/dist/app/api/hazo_auth/forgot_password/route.d.ts.map +1 -0
  8. package/dist/app/api/hazo_auth/forgot_password/route.js +78 -0
  9. package/dist/app/api/hazo_auth/get_auth/route.d.ts +10 -0
  10. package/dist/app/api/hazo_auth/get_auth/route.d.ts.map +1 -0
  11. package/dist/app/api/hazo_auth/get_auth/route.js +63 -0
  12. package/dist/app/api/hazo_auth/invalidate_cache/route.d.ts +14 -0
  13. package/dist/app/api/hazo_auth/invalidate_cache/route.d.ts.map +1 -0
  14. package/dist/app/api/hazo_auth/invalidate_cache/route.js +96 -0
  15. package/dist/app/api/hazo_auth/library_photos/route.d.ts +13 -0
  16. package/dist/app/api/hazo_auth/library_photos/route.d.ts.map +1 -0
  17. package/dist/app/api/hazo_auth/library_photos/route.js +55 -0
  18. package/dist/app/api/hazo_auth/login/route.d.ts +12 -0
  19. package/dist/app/api/hazo_auth/login/route.d.ts.map +1 -0
  20. package/dist/app/api/hazo_auth/login/route.js +140 -0
  21. package/dist/app/api/hazo_auth/logout/route.d.ts +8 -0
  22. package/dist/app/api/hazo_auth/logout/route.d.ts.map +1 -0
  23. package/dist/app/api/hazo_auth/logout/route.js +71 -0
  24. package/dist/app/api/hazo_auth/me/route.d.ts +3 -0
  25. package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -0
  26. package/dist/app/api/hazo_auth/me/route.js +34 -0
  27. package/dist/app/api/hazo_auth/profile_picture/[filename]/route.d.ts +7 -0
  28. package/dist/app/api/hazo_auth/profile_picture/[filename]/route.d.ts.map +1 -0
  29. package/dist/app/api/hazo_auth/profile_picture/[filename]/route.js +43 -0
  30. package/dist/app/api/hazo_auth/register/route.d.ts +9 -0
  31. package/dist/app/api/hazo_auth/register/route.d.ts.map +1 -0
  32. package/dist/app/api/hazo_auth/register/route.js +80 -0
  33. package/dist/app/api/hazo_auth/remove_profile_picture/route.d.ts +8 -0
  34. package/dist/app/api/hazo_auth/remove_profile_picture/route.d.ts.map +1 -0
  35. package/dist/app/api/hazo_auth/remove_profile_picture/route.js +64 -0
  36. package/dist/app/api/hazo_auth/resend_verification/route.d.ts +8 -0
  37. package/dist/app/api/hazo_auth/resend_verification/route.d.ts.map +1 -0
  38. package/dist/app/api/hazo_auth/resend_verification/route.js +79 -0
  39. package/dist/app/api/hazo_auth/reset_password/route.d.ts +8 -0
  40. package/dist/app/api/hazo_auth/reset_password/route.d.ts.map +1 -0
  41. package/dist/app/api/hazo_auth/reset_password/route.js +76 -0
  42. package/dist/app/api/hazo_auth/update_user/route.d.ts +9 -0
  43. package/dist/app/api/hazo_auth/update_user/route.d.ts.map +1 -0
  44. package/dist/app/api/hazo_auth/update_user/route.js +95 -0
  45. package/dist/app/api/hazo_auth/upload_profile_picture/route.d.ts +9 -0
  46. package/dist/app/api/hazo_auth/upload_profile_picture/route.d.ts.map +1 -0
  47. package/dist/app/api/hazo_auth/upload_profile_picture/route.js +204 -0
  48. package/dist/app/api/hazo_auth/validate_reset_token/route.d.ts +6 -0
  49. package/dist/app/api/hazo_auth/validate_reset_token/route.d.ts.map +1 -0
  50. package/dist/app/api/hazo_auth/validate_reset_token/route.js +58 -0
  51. package/dist/app/api/hazo_auth/verify_email/route.d.ts +11 -0
  52. package/dist/app/api/hazo_auth/verify_email/route.d.ts.map +1 -0
  53. package/dist/app/api/hazo_auth/verify_email/route.js +63 -0
  54. package/dist/cli/generate.d.ts +2 -0
  55. package/dist/cli/generate.d.ts.map +1 -0
  56. package/dist/cli/generate.js +117 -0
  57. package/dist/cli/index.d.ts +3 -0
  58. package/dist/cli/index.d.ts.map +1 -0
  59. package/dist/cli/index.js +120 -0
  60. package/dist/cli/validate.d.ts +15 -0
  61. package/dist/cli/validate.d.ts.map +1 -0
  62. package/dist/cli/validate.js +509 -0
  63. package/dist/components/layouts/email_verification/config/email_verification_field_config.d.ts +2 -2
  64. package/dist/components/layouts/email_verification/config/email_verification_field_config.d.ts.map +1 -1
  65. package/dist/components/layouts/email_verification/config/email_verification_field_config.js +1 -1
  66. package/dist/components/layouts/email_verification/hooks/use_email_verification.d.ts +2 -2
  67. package/dist/components/layouts/email_verification/hooks/use_email_verification.d.ts.map +1 -1
  68. package/dist/components/layouts/email_verification/hooks/use_email_verification.js +2 -2
  69. package/dist/components/layouts/email_verification/index.d.ts +3 -3
  70. package/dist/components/layouts/email_verification/index.d.ts.map +1 -1
  71. package/dist/components/layouts/email_verification/index.js +9 -9
  72. package/dist/components/layouts/forgot_password/config/forgot_password_field_config.d.ts +2 -2
  73. package/dist/components/layouts/forgot_password/config/forgot_password_field_config.d.ts.map +1 -1
  74. package/dist/components/layouts/forgot_password/config/forgot_password_field_config.js +1 -1
  75. package/dist/components/layouts/forgot_password/hooks/use_forgot_password_form.d.ts +2 -2
  76. package/dist/components/layouts/forgot_password/hooks/use_forgot_password_form.d.ts.map +1 -1
  77. package/dist/components/layouts/forgot_password/hooks/use_forgot_password_form.js +2 -2
  78. package/dist/components/layouts/forgot_password/index.d.ts +2 -2
  79. package/dist/components/layouts/forgot_password/index.d.ts.map +1 -1
  80. package/dist/components/layouts/forgot_password/index.js +8 -8
  81. package/dist/components/layouts/login/config/login_field_config.d.ts +2 -2
  82. package/dist/components/layouts/login/config/login_field_config.d.ts.map +1 -1
  83. package/dist/components/layouts/login/config/login_field_config.js +1 -1
  84. package/dist/components/layouts/login/hooks/use_login_form.d.ts +2 -2
  85. package/dist/components/layouts/login/hooks/use_login_form.d.ts.map +1 -1
  86. package/dist/components/layouts/login/hooks/use_login_form.js +4 -4
  87. package/dist/components/layouts/login/index.d.ts +2 -2
  88. package/dist/components/layouts/login/index.d.ts.map +1 -1
  89. package/dist/components/layouts/login/index.js +9 -9
  90. package/dist/components/layouts/my_settings/components/editable_field.js +3 -3
  91. package/dist/components/layouts/my_settings/components/password_change_dialog.d.ts +1 -1
  92. package/dist/components/layouts/my_settings/components/password_change_dialog.d.ts.map +1 -1
  93. package/dist/components/layouts/my_settings/components/password_change_dialog.js +4 -4
  94. package/dist/components/layouts/my_settings/components/profile_picture_dialog.js +7 -7
  95. package/dist/components/layouts/my_settings/components/profile_picture_display.js +1 -1
  96. package/dist/components/layouts/my_settings/components/profile_picture_gravatar_tab.js +3 -3
  97. package/dist/components/layouts/my_settings/components/profile_picture_library_tab.js +5 -5
  98. package/dist/components/layouts/my_settings/components/profile_picture_upload_tab.js +4 -4
  99. package/dist/components/layouts/my_settings/config/my_settings_field_config.d.ts +1 -1
  100. package/dist/components/layouts/my_settings/config/my_settings_field_config.d.ts.map +1 -1
  101. package/dist/components/layouts/my_settings/config/my_settings_field_config.js +1 -1
  102. package/dist/components/layouts/my_settings/hooks/use_my_settings.d.ts +1 -1
  103. package/dist/components/layouts/my_settings/hooks/use_my_settings.d.ts.map +1 -1
  104. package/dist/components/layouts/my_settings/hooks/use_my_settings.js +1 -1
  105. package/dist/components/layouts/my_settings/index.d.ts +2 -2
  106. package/dist/components/layouts/my_settings/index.d.ts.map +1 -1
  107. package/dist/components/layouts/my_settings/index.js +9 -9
  108. package/dist/components/layouts/register/config/register_field_config.d.ts +2 -2
  109. package/dist/components/layouts/register/config/register_field_config.d.ts.map +1 -1
  110. package/dist/components/layouts/register/config/register_field_config.js +1 -1
  111. package/dist/components/layouts/register/hooks/use_register_form.d.ts +3 -3
  112. package/dist/components/layouts/register/hooks/use_register_form.d.ts.map +1 -1
  113. package/dist/components/layouts/register/hooks/use_register_form.js +2 -2
  114. package/dist/components/layouts/register/index.d.ts +2 -2
  115. package/dist/components/layouts/register/index.d.ts.map +1 -1
  116. package/dist/components/layouts/register/index.js +9 -9
  117. package/dist/components/layouts/reset_password/config/reset_password_field_config.d.ts +2 -2
  118. package/dist/components/layouts/reset_password/config/reset_password_field_config.d.ts.map +1 -1
  119. package/dist/components/layouts/reset_password/config/reset_password_field_config.js +1 -1
  120. package/dist/components/layouts/reset_password/hooks/use_reset_password_form.d.ts +3 -3
  121. package/dist/components/layouts/reset_password/hooks/use_reset_password_form.d.ts.map +1 -1
  122. package/dist/components/layouts/reset_password/hooks/use_reset_password_form.js +2 -2
  123. package/dist/components/layouts/reset_password/index.d.ts +2 -2
  124. package/dist/components/layouts/reset_password/index.d.ts.map +1 -1
  125. package/dist/components/layouts/reset_password/index.js +8 -8
  126. package/dist/components/layouts/shared/components/already_logged_in_guard.js +4 -4
  127. package/dist/components/layouts/shared/components/auth_page_shell.js +3 -3
  128. package/dist/components/layouts/shared/components/form_action_buttons.d.ts +1 -1
  129. package/dist/components/layouts/shared/components/form_action_buttons.d.ts.map +1 -1
  130. package/dist/components/layouts/shared/components/form_action_buttons.js +1 -1
  131. package/dist/components/layouts/shared/components/form_field_wrapper.js +2 -2
  132. package/dist/components/layouts/shared/components/logout_button.js +2 -2
  133. package/dist/components/layouts/shared/components/password_field.js +3 -3
  134. package/dist/components/layouts/shared/components/profile_pic_menu.d.ts +1 -1
  135. package/dist/components/layouts/shared/components/profile_pic_menu.d.ts.map +1 -1
  136. package/dist/components/layouts/shared/components/profile_pic_menu.js +4 -4
  137. package/dist/components/layouts/shared/components/profile_pic_menu_wrapper.js +2 -2
  138. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +3 -3
  139. package/dist/components/layouts/shared/components/standalone_layout_wrapper.js +1 -1
  140. package/dist/components/layouts/shared/components/two_column_auth_layout.js +1 -1
  141. package/dist/components/layouts/shared/components/unauthorized_guard.js +2 -2
  142. package/dist/components/layouts/shared/hooks/use_hazo_auth.d.ts +1 -1
  143. package/dist/components/layouts/shared/hooks/use_hazo_auth.d.ts.map +1 -1
  144. package/dist/components/layouts/shared/utils/validation.d.ts +1 -1
  145. package/dist/components/layouts/shared/utils/validation.d.ts.map +1 -1
  146. package/dist/components/layouts/user_management/components/roles_matrix.js +7 -7
  147. package/dist/components/layouts/user_management/index.js +11 -11
  148. package/dist/components/ui/alert-dialog.js +2 -2
  149. package/dist/components/ui/avatar.js +1 -1
  150. package/dist/components/ui/button.js +1 -1
  151. package/dist/components/ui/card.d.ts +9 -0
  152. package/dist/components/ui/card.d.ts.map +1 -0
  153. package/dist/components/ui/card.js +45 -0
  154. package/dist/components/ui/checkbox.js +1 -1
  155. package/dist/components/ui/dialog.js +1 -1
  156. package/dist/components/ui/dropdown-menu.js +1 -1
  157. package/dist/components/ui/hazo_ui_tooltip.js +1 -1
  158. package/dist/components/ui/input.js +1 -1
  159. package/dist/components/ui/label.js +1 -1
  160. package/dist/components/ui/separator.js +1 -1
  161. package/dist/components/ui/sheet.js +1 -1
  162. package/dist/components/ui/sidebar.d.ts +2 -2
  163. package/dist/components/ui/sidebar.d.ts.map +1 -1
  164. package/dist/components/ui/sidebar.js +8 -8
  165. package/dist/components/ui/skeleton.js +1 -1
  166. package/dist/components/ui/switch.js +1 -1
  167. package/dist/components/ui/table.js +1 -1
  168. package/dist/components/ui/tabs.js +1 -1
  169. package/dist/components/ui/tooltip.js +1 -1
  170. package/dist/components/ui/vertical-tabs.js +1 -1
  171. package/dist/hooks/use-mobile.d.ts.map +1 -1
  172. package/dist/hooks/use-mobile.js +17 -3
  173. package/dist/lib/already_logged_in_config.server.js +1 -1
  174. package/dist/lib/app_logger.js +1 -1
  175. package/dist/lib/auth/auth_cache.d.ts +1 -1
  176. package/dist/lib/auth/auth_cache.d.ts.map +1 -1
  177. package/dist/lib/auth/auth_utils.server.js +2 -2
  178. package/dist/lib/auth/hazo_get_auth.server.d.ts +1 -1
  179. package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
  180. package/dist/lib/auth/hazo_get_auth.server.js +7 -7
  181. package/dist/lib/auth/server_auth.js +2 -2
  182. package/dist/lib/auth_utility_config.server.js +1 -1
  183. package/dist/lib/config/config_loader.server.js +1 -1
  184. package/dist/lib/email_verification_config.server.js +1 -1
  185. package/dist/lib/file_types_config.server.js +1 -1
  186. package/dist/lib/forgot_password_config.server.js +1 -1
  187. package/dist/lib/hazo_connect_instance.server.js +2 -2
  188. package/dist/lib/hazo_connect_setup.server.js +2 -2
  189. package/dist/lib/login_config.server.js +2 -2
  190. package/dist/lib/messages_config.server.js +1 -1
  191. package/dist/lib/my_settings_config.server.js +7 -7
  192. package/dist/lib/password_requirements_config.server.js +1 -1
  193. package/dist/lib/profile_pic_menu_config.server.js +1 -1
  194. package/dist/lib/profile_picture_config.server.js +2 -2
  195. package/dist/lib/register_config.server.js +4 -4
  196. package/dist/lib/reset_password_config.server.js +3 -3
  197. package/dist/lib/services/email_service.js +2 -2
  198. package/dist/lib/services/email_verification_service.js +3 -3
  199. package/dist/lib/services/login_service.js +3 -3
  200. package/dist/lib/services/password_change_service.js +3 -3
  201. package/dist/lib/services/password_reset_service.js +3 -3
  202. package/dist/lib/services/profile_picture_remove_service.js +3 -3
  203. package/dist/lib/services/profile_picture_service.d.ts +1 -1
  204. package/dist/lib/services/profile_picture_service.d.ts.map +1 -1
  205. package/dist/lib/services/profile_picture_service.js +5 -5
  206. package/dist/lib/services/registration_service.js +8 -8
  207. package/dist/lib/services/token_service.js +2 -2
  208. package/dist/lib/services/user_profiles_service.js +2 -2
  209. package/dist/lib/services/user_update_service.d.ts +1 -1
  210. package/dist/lib/services/user_update_service.d.ts.map +1 -1
  211. package/dist/lib/services/user_update_service.js +4 -4
  212. package/dist/lib/ui_shell_config.server.js +1 -1
  213. package/dist/lib/ui_sizes_config.server.js +1 -1
  214. package/dist/lib/user_fields_config.server.js +1 -1
  215. package/dist/lib/user_management_config.server.js +1 -1
  216. package/dist/lib/utils/error_sanitizer.d.ts +1 -1
  217. package/dist/lib/utils/error_sanitizer.d.ts.map +1 -1
  218. package/dist/server/config/config_loader.d.ts +1 -1
  219. package/dist/server/config/config_loader.d.ts.map +1 -1
  220. package/dist/server/config/config_loader.js +1 -1
  221. package/dist/server/index.js +2 -2
  222. package/dist/server/logging/logger_service.d.ts +1 -1
  223. package/dist/server/logging/logger_service.d.ts.map +1 -1
  224. package/dist/server/routes/change_password.d.ts +2 -0
  225. package/dist/server/routes/change_password.d.ts.map +1 -0
  226. package/dist/server/routes/change_password.js +2 -0
  227. package/dist/server/routes/forgot_password.d.ts +2 -0
  228. package/dist/server/routes/forgot_password.d.ts.map +1 -0
  229. package/dist/server/routes/forgot_password.js +2 -0
  230. package/dist/server/routes/get_auth.d.ts +2 -0
  231. package/dist/server/routes/get_auth.d.ts.map +1 -0
  232. package/dist/server/routes/get_auth.js +2 -0
  233. package/dist/server/routes/index.d.ts +18 -0
  234. package/dist/server/routes/index.d.ts.map +1 -0
  235. package/dist/server/routes/index.js +24 -0
  236. package/dist/server/routes/invalidate_cache.d.ts +2 -0
  237. package/dist/server/routes/invalidate_cache.d.ts.map +1 -0
  238. package/dist/server/routes/invalidate_cache.js +2 -0
  239. package/dist/server/routes/library_photos.d.ts +2 -0
  240. package/dist/server/routes/library_photos.d.ts.map +1 -0
  241. package/dist/server/routes/library_photos.js +2 -0
  242. package/dist/server/routes/login.d.ts +2 -0
  243. package/dist/server/routes/login.d.ts.map +1 -0
  244. package/dist/server/routes/login.js +2 -0
  245. package/dist/server/routes/logout.d.ts +2 -0
  246. package/dist/server/routes/logout.d.ts.map +1 -0
  247. package/dist/server/routes/logout.js +2 -0
  248. package/dist/server/routes/me.d.ts +2 -0
  249. package/dist/server/routes/me.d.ts.map +1 -0
  250. package/dist/server/routes/me.js +2 -0
  251. package/dist/server/routes/profile_picture_filename.d.ts +2 -0
  252. package/dist/server/routes/profile_picture_filename.d.ts.map +1 -0
  253. package/dist/server/routes/profile_picture_filename.js +3 -0
  254. package/dist/server/routes/register.d.ts +2 -0
  255. package/dist/server/routes/register.d.ts.map +1 -0
  256. package/dist/server/routes/register.js +2 -0
  257. package/dist/server/routes/remove_profile_picture.d.ts +2 -0
  258. package/dist/server/routes/remove_profile_picture.d.ts.map +1 -0
  259. package/dist/server/routes/remove_profile_picture.js +2 -0
  260. package/dist/server/routes/resend_verification.d.ts +2 -0
  261. package/dist/server/routes/resend_verification.d.ts.map +1 -0
  262. package/dist/server/routes/resend_verification.js +2 -0
  263. package/dist/server/routes/reset_password.d.ts +2 -0
  264. package/dist/server/routes/reset_password.d.ts.map +1 -0
  265. package/dist/server/routes/reset_password.js +2 -0
  266. package/dist/server/routes/update_user.d.ts +2 -0
  267. package/dist/server/routes/update_user.d.ts.map +1 -0
  268. package/dist/server/routes/update_user.js +2 -0
  269. package/dist/server/routes/upload_profile_picture.d.ts +2 -0
  270. package/dist/server/routes/upload_profile_picture.d.ts.map +1 -0
  271. package/dist/server/routes/upload_profile_picture.js +2 -0
  272. package/dist/server/routes/validate_reset_token.d.ts +2 -0
  273. package/dist/server/routes/validate_reset_token.d.ts.map +1 -0
  274. package/dist/server/routes/validate_reset_token.js +2 -0
  275. package/dist/server/routes/verify_email.d.ts +2 -0
  276. package/dist/server/routes/verify_email.d.ts.map +1 -0
  277. package/dist/server/routes/verify_email.js +2 -0
  278. package/dist/server/server.js +2 -2
  279. package/package.json +14 -115
  280. package/components.json +0 -22
  281. package/instrumentation.ts +0 -32
  282. package/migrations/001_add_token_type_to_refresh_tokens.sql +0 -14
  283. package/migrations/002_add_name_to_hazo_users.sql +0 -7
  284. package/migrations/003_add_url_on_logon_to_hazo_users.sql +0 -8
  285. package/next.config.mjs +0 -67
  286. package/postcss.config.mjs +0 -8
  287. package/public/file.svg +0 -1
  288. package/public/globe.svg +0 -1
  289. package/public/next.svg +0 -1
  290. package/public/vercel.svg +0 -1
  291. package/public/window.svg +0 -1
  292. package/scripts/apply_migration.ts +0 -118
  293. package/scripts/init_users.ts +0 -378
  294. package/src/app/api/hazo_auth/auth/upload_profile_picture/route.ts +0 -268
  295. package/src/app/api/hazo_auth/change_password/route.ts +0 -132
  296. package/src/app/api/hazo_auth/forgot_password/route.ts +0 -107
  297. package/src/app/api/hazo_auth/get_auth/route.ts +0 -89
  298. package/src/app/api/hazo_auth/invalidate_cache/route.ts +0 -139
  299. package/src/app/api/hazo_auth/library_photos/route.ts +0 -73
  300. package/src/app/api/hazo_auth/login/route.ts +0 -181
  301. package/src/app/api/hazo_auth/logout/route.ts +0 -89
  302. package/src/app/api/hazo_auth/me/route.ts +0 -47
  303. package/src/app/api/hazo_auth/profile_picture/[filename]/route.ts +0 -67
  304. package/src/app/api/hazo_auth/register/route.ts +0 -109
  305. package/src/app/api/hazo_auth/remove_profile_picture/route.ts +0 -86
  306. package/src/app/api/hazo_auth/resend_verification/route.ts +0 -108
  307. package/src/app/api/hazo_auth/reset_password/route.ts +0 -107
  308. package/src/app/api/hazo_auth/update_user/route.ts +0 -126
  309. package/src/app/api/hazo_auth/upload_profile_picture/route.ts +0 -268
  310. package/src/app/api/hazo_auth/user_management/permissions/route.ts +0 -367
  311. package/src/app/api/hazo_auth/user_management/roles/route.ts +0 -442
  312. package/src/app/api/hazo_auth/user_management/users/roles/route.ts +0 -367
  313. package/src/app/api/hazo_auth/user_management/users/route.ts +0 -239
  314. package/src/app/api/hazo_auth/validate_reset_token/route.ts +0 -83
  315. package/src/app/api/hazo_auth/verify_email/route.ts +0 -88
  316. package/src/app/api/migrations/apply/route.ts +0 -91
  317. package/src/app/favicon.ico +0 -0
  318. package/src/app/fonts/GeistMonoVF.woff +0 -0
  319. package/src/app/fonts/GeistVF.woff +0 -0
  320. package/src/app/globals.css +0 -89
  321. package/src/app/hazo_auth/forgot_password/forgot_password_page_client.tsx +0 -60
  322. package/src/app/hazo_auth/forgot_password/page.tsx +0 -24
  323. package/src/app/hazo_auth/login/login_page_client.tsx +0 -86
  324. package/src/app/hazo_auth/login/page.tsx +0 -38
  325. package/src/app/hazo_auth/my_settings/my_settings_page_client.tsx +0 -120
  326. package/src/app/hazo_auth/my_settings/page.tsx +0 -40
  327. package/src/app/hazo_auth/register/page.tsx +0 -36
  328. package/src/app/hazo_auth/register/register_page_client.tsx +0 -81
  329. package/src/app/hazo_auth/reset_password/page.tsx +0 -29
  330. package/src/app/hazo_auth/reset_password/reset_password_page_client.tsx +0 -81
  331. package/src/app/hazo_auth/user_management/page.tsx +0 -14
  332. package/src/app/hazo_auth/user_management/user_management_page_client.tsx +0 -16
  333. package/src/app/hazo_auth/verify_email/page.tsx +0 -24
  334. package/src/app/hazo_auth/verify_email/verify_email_page_client.tsx +0 -60
  335. package/src/app/hazo_connect/api/sqlite/data/route.ts +0 -203
  336. package/src/app/hazo_connect/api/sqlite/schema/route.ts +0 -45
  337. package/src/app/hazo_connect/api/sqlite/tables/route.ts +0 -36
  338. package/src/app/hazo_connect/sqlite_admin/page.tsx +0 -51
  339. package/src/app/hazo_connect/sqlite_admin/sqlite-admin-client.tsx +0 -984
  340. package/src/app/layout.tsx +0 -43
  341. package/src/app/page.tsx +0 -170
  342. package/src/components/index.ts +0 -7
  343. package/src/components/layouts/email_verification/config/email_verification_field_config.ts +0 -86
  344. package/src/components/layouts/email_verification/hooks/use_email_verification.ts +0 -297
  345. package/src/components/layouts/email_verification/index.tsx +0 -297
  346. package/src/components/layouts/forgot_password/config/forgot_password_field_config.ts +0 -58
  347. package/src/components/layouts/forgot_password/hooks/use_forgot_password_form.ts +0 -179
  348. package/src/components/layouts/forgot_password/index.tsx +0 -168
  349. package/src/components/layouts/index.ts +0 -26
  350. package/src/components/layouts/login/config/login_field_config.ts +0 -67
  351. package/src/components/layouts/login/hooks/use_login_form.ts +0 -286
  352. package/src/components/layouts/login/index.tsx +0 -252
  353. package/src/components/layouts/my_settings/components/editable_field.tsx +0 -177
  354. package/src/components/layouts/my_settings/components/password_change_dialog.tsx +0 -301
  355. package/src/components/layouts/my_settings/components/profile_picture_dialog.tsx +0 -385
  356. package/src/components/layouts/my_settings/components/profile_picture_display.tsx +0 -66
  357. package/src/components/layouts/my_settings/components/profile_picture_gravatar_tab.tsx +0 -143
  358. package/src/components/layouts/my_settings/components/profile_picture_library_tab.tsx +0 -311
  359. package/src/components/layouts/my_settings/components/profile_picture_upload_tab.tsx +0 -341
  360. package/src/components/layouts/my_settings/config/my_settings_field_config.ts +0 -61
  361. package/src/components/layouts/my_settings/hooks/use_my_settings.ts +0 -458
  362. package/src/components/layouts/my_settings/index.tsx +0 -351
  363. package/src/components/layouts/register/config/register_field_config.ts +0 -101
  364. package/src/components/layouts/register/hooks/use_register_form.ts +0 -275
  365. package/src/components/layouts/register/index.tsx +0 -226
  366. package/src/components/layouts/reset_password/config/reset_password_field_config.ts +0 -86
  367. package/src/components/layouts/reset_password/hooks/use_reset_password_form.ts +0 -276
  368. package/src/components/layouts/reset_password/index.tsx +0 -294
  369. package/src/components/layouts/shared/components/already_logged_in_guard.tsx +0 -95
  370. package/src/components/layouts/shared/components/auth_page_shell.tsx +0 -36
  371. package/src/components/layouts/shared/components/field_error_message.tsx +0 -29
  372. package/src/components/layouts/shared/components/form_action_buttons.tsx +0 -64
  373. package/src/components/layouts/shared/components/form_field_wrapper.tsx +0 -44
  374. package/src/components/layouts/shared/components/form_header.tsx +0 -36
  375. package/src/components/layouts/shared/components/logout_button.tsx +0 -76
  376. package/src/components/layouts/shared/components/password_field.tsx +0 -72
  377. package/src/components/layouts/shared/components/profile_pic_menu.tsx +0 -321
  378. package/src/components/layouts/shared/components/profile_pic_menu_wrapper.tsx +0 -40
  379. package/src/components/layouts/shared/components/sidebar_layout_wrapper.tsx +0 -214
  380. package/src/components/layouts/shared/components/standalone_layout_wrapper.tsx +0 -53
  381. package/src/components/layouts/shared/components/two_column_auth_layout.tsx +0 -44
  382. package/src/components/layouts/shared/components/unauthorized_guard.tsx +0 -78
  383. package/src/components/layouts/shared/components/visual_panel.tsx +0 -41
  384. package/src/components/layouts/shared/config/layout_customization.ts +0 -95
  385. package/src/components/layouts/shared/data/layout_data_client.ts +0 -19
  386. package/src/components/layouts/shared/hooks/use_auth_status.ts +0 -103
  387. package/src/components/layouts/shared/hooks/use_hazo_auth.ts +0 -158
  388. package/src/components/layouts/shared/index.ts +0 -34
  389. package/src/components/layouts/shared/utils/ip_address.ts +0 -37
  390. package/src/components/layouts/shared/utils/validation.ts +0 -66
  391. package/src/components/layouts/user_management/components/roles_matrix.tsx +0 -607
  392. package/src/components/layouts/user_management/index.tsx +0 -1295
  393. package/src/components/ui/alert-dialog.tsx +0 -141
  394. package/src/components/ui/avatar.tsx +0 -50
  395. package/src/components/ui/button.tsx +0 -57
  396. package/src/components/ui/checkbox.tsx +0 -30
  397. package/src/components/ui/dialog.tsx +0 -122
  398. package/src/components/ui/dropdown-menu.tsx +0 -201
  399. package/src/components/ui/hazo_ui_tooltip.tsx +0 -67
  400. package/src/components/ui/index.ts +0 -22
  401. package/src/components/ui/input.tsx +0 -22
  402. package/src/components/ui/label.tsx +0 -26
  403. package/src/components/ui/separator.tsx +0 -31
  404. package/src/components/ui/sheet.tsx +0 -139
  405. package/src/components/ui/sidebar.tsx +0 -773
  406. package/src/components/ui/skeleton.tsx +0 -15
  407. package/src/components/ui/sonner.tsx +0 -31
  408. package/src/components/ui/switch.tsx +0 -29
  409. package/src/components/ui/table.tsx +0 -120
  410. package/src/components/ui/tabs.tsx +0 -55
  411. package/src/components/ui/tooltip.tsx +0 -32
  412. package/src/components/ui/vertical-tabs.tsx +0 -59
  413. package/src/hooks/use-mobile.tsx +0 -19
  414. package/src/index.ts +0 -7
  415. package/src/lib/already_logged_in_config.server.ts +0 -46
  416. package/src/lib/app_logger.ts +0 -24
  417. package/src/lib/auth/auth_cache.ts +0 -220
  418. package/src/lib/auth/auth_rate_limiter.ts +0 -121
  419. package/src/lib/auth/auth_types.ts +0 -65
  420. package/src/lib/auth/auth_utils.server.ts +0 -196
  421. package/src/lib/auth/hazo_get_auth.server.ts +0 -333
  422. package/src/lib/auth/index.ts +0 -23
  423. package/src/lib/auth/server_auth.ts +0 -88
  424. package/src/lib/auth_utility_config.server.ts +0 -136
  425. package/src/lib/config/config_loader.server.ts +0 -164
  426. package/src/lib/email_verification_config.server.ts +0 -32
  427. package/src/lib/file_types_config.server.ts +0 -25
  428. package/src/lib/forgot_password_config.server.ts +0 -32
  429. package/src/lib/hazo_connect_instance.server.ts +0 -101
  430. package/src/lib/hazo_connect_setup.server.ts +0 -194
  431. package/src/lib/hazo_connect_setup.ts +0 -54
  432. package/src/lib/index.ts +0 -44
  433. package/src/lib/login_config.server.ts +0 -71
  434. package/src/lib/messages_config.server.ts +0 -45
  435. package/src/lib/migrations/apply_migration.ts +0 -105
  436. package/src/lib/my_settings_config.server.ts +0 -135
  437. package/src/lib/password_requirements_config.server.ts +0 -39
  438. package/src/lib/profile_pic_menu_config.server.ts +0 -138
  439. package/src/lib/profile_picture_config.server.ts +0 -56
  440. package/src/lib/register_config.server.ts +0 -73
  441. package/src/lib/reset_password_config.server.ts +0 -75
  442. package/src/lib/services/email_service.ts +0 -581
  443. package/src/lib/services/email_verification_service.ts +0 -270
  444. package/src/lib/services/index.ts +0 -15
  445. package/src/lib/services/login_service.ts +0 -134
  446. package/src/lib/services/password_change_service.ts +0 -154
  447. package/src/lib/services/password_reset_service.ts +0 -405
  448. package/src/lib/services/profile_picture_remove_service.ts +0 -120
  449. package/src/lib/services/profile_picture_service.ts +0 -215
  450. package/src/lib/services/profile_picture_source_mapper.ts +0 -62
  451. package/src/lib/services/registration_service.ts +0 -184
  452. package/src/lib/services/token_service.ts +0 -240
  453. package/src/lib/services/user_profiles_service.ts +0 -143
  454. package/src/lib/services/user_update_service.ts +0 -141
  455. package/src/lib/ui_shell_config.server.ts +0 -73
  456. package/src/lib/ui_sizes_config.server.ts +0 -37
  457. package/src/lib/user_fields_config.server.ts +0 -31
  458. package/src/lib/user_management_config.server.ts +0 -39
  459. package/src/lib/utils/api_route_helpers.ts +0 -60
  460. package/src/lib/utils/error_sanitizer.ts +0 -75
  461. package/src/lib/utils.ts +0 -11
  462. package/src/middleware.ts +0 -94
  463. package/src/routes/index.ts +0 -34
  464. package/src/server/config/config_loader.ts +0 -496
  465. package/src/server/index.ts +0 -38
  466. package/src/server/logging/logger_service.ts +0 -56
  467. package/src/server/routes/root_router.ts +0 -16
  468. package/src/server/server.ts +0 -28
  469. package/src/server/types/app_types.ts +0 -74
  470. package/src/server/types/express.d.ts +0 -16
  471. package/src/stories/email_verification_layout.stories.tsx +0 -137
  472. package/src/stories/forgot_password_layout.stories.tsx +0 -85
  473. package/src/stories/login_layout.stories.tsx +0 -85
  474. package/src/stories/project_overview.stories.tsx +0 -33
  475. package/src/stories/register_layout.stories.tsx +0 -107
  476. package/tailwind.config.ts +0 -77
  477. package/tsconfig.build.json +0 -39
  478. package/tsconfig.json +0 -28
@@ -1,984 +0,0 @@
1
- // file_description: SQLite admin UI client component for browsing and editing database tables
2
- "use client"
3
-
4
- import { useEffect, useMemo, useState } from "react"
5
- import {
6
- Filter,
7
- Loader2,
8
- Pencil,
9
- Plus,
10
- RefreshCw,
11
- Save,
12
- Trash2,
13
- X
14
- } from "lucide-react"
15
- import { toast } from "sonner"
16
- import type {
17
- SqliteFilterOperator,
18
- TableColumn,
19
- TableSchema,
20
- TableSummary
21
- } from "hazo_connect/ui"
22
-
23
- type FilterState = {
24
- column?: string
25
- operator: SqliteFilterOperator
26
- value: string
27
- }
28
-
29
- type SqlValue = Record<string, unknown>
30
- type DataResponse = {
31
- data: SqlValue[]
32
- total: number
33
- }
34
-
35
- const DEFAULT_LIMIT = 20
36
- const filterOperators: { label: string; value: SqliteFilterOperator }[] = [
37
- { label: "Equals", value: "eq" },
38
- { label: "Not equal", value: "neq" },
39
- { label: "Greater than", value: "gt" },
40
- { label: "Greater or equal", value: "gte" },
41
- { label: "Less than", value: "lt" },
42
- { label: "Less or equal", value: "lte" },
43
- { label: "Contains", value: "like" },
44
- { label: "Contains (case-insensitive)", value: "ilike" },
45
- { label: "Is / Is Not", value: "is" }
46
- ]
47
-
48
- export default function SqliteAdminClient({
49
- initialTables
50
- }: {
51
- initialTables: TableSummary[]
52
- }) {
53
- const [tables, setTables] = useState<TableSummary[]>(initialTables)
54
- const [selectedTable, setSelectedTable] = useState<TableSummary | null>(
55
- initialTables[0] ?? null
56
- )
57
- const [schema, setSchema] = useState<TableSchema | null>(null)
58
- const [rows, setRows] = useState<SqlValue[]>([])
59
- const [totalRows, setTotalRows] = useState(0)
60
- const [limit, setLimit] = useState(DEFAULT_LIMIT)
61
- const [offset, setOffset] = useState(0)
62
- const [orderBy, setOrderBy] = useState<string | undefined>()
63
- const [orderDirection, setOrderDirection] = useState<"asc" | "desc">("asc")
64
- const [filterState, setFilterState] = useState<FilterState>({
65
- operator: "eq",
66
- value: ""
67
- })
68
-
69
- const [isLoadingSchema, setIsLoadingSchema] = useState(false)
70
- const [isLoadingData, setIsLoadingData] = useState(false)
71
- const [isRefreshingTables, setIsRefreshingTables] = useState(false)
72
- const [isCreateOpen, setIsCreateOpen] = useState(false)
73
- const [editingRow, setEditingRow] = useState<SqlValue | null>(null)
74
-
75
- const columns = useMemo<TableColumn[]>(() => schema?.columns ?? [], [schema])
76
- const filterColumns = useMemo(
77
- () => columns.filter(column => identifierIsFilterable(column.name)),
78
- [columns]
79
- )
80
-
81
- useEffect(() => {
82
- if (!selectedTable) {
83
- return
84
- }
85
- void loadSchemaAndData(selectedTable.name)
86
- // eslint-disable-next-line react-hooks/exhaustive-deps
87
- }, [selectedTable?.name])
88
-
89
- async function loadSchemaAndData(tableName: string) {
90
- setIsLoadingSchema(true)
91
- try {
92
- const schemaResponse = await fetch(`/hazo_connect/api/sqlite/schema?table=${encodeURIComponent(tableName)}`)
93
- if (!schemaResponse.ok) {
94
- const contentType = schemaResponse.headers.get("content-type");
95
- if (contentType && contentType.includes("application/json")) {
96
- const errorData = await schemaResponse.json();
97
- throw new Error(errorData.error || `Failed to fetch schema: ${schemaResponse.statusText}`);
98
- } else {
99
- const errorText = await schemaResponse.text();
100
- throw new Error(`Failed to fetch schema: ${errorText.substring(0, 200)}`);
101
- }
102
- }
103
- const contentType = schemaResponse.headers.get("content-type");
104
- if (!contentType || !contentType.includes("application/json")) {
105
- const text = await schemaResponse.text();
106
- throw new Error(`Expected JSON but received ${contentType || "unknown content type"}. Response: ${text.substring(0, 200)}`);
107
- }
108
- const schemaJson = await schemaResponse.json()
109
- setSchema(schemaJson.data as TableSchema)
110
- const preferredFilterColumn =
111
- filterState.column && schemaJson.data?.columns?.some((col: TableColumn) => col.name === filterState.column)
112
- ? filterState.column
113
- : schemaJson.data?.columns?.[0]?.name
114
- setFilterState(current => ({
115
- ...current,
116
- column: preferredFilterColumn
117
- }))
118
- setLimit(DEFAULT_LIMIT)
119
- setOffset(0)
120
- setOrderBy(undefined)
121
- setOrderDirection("asc")
122
- await loadData(tableName, {
123
- limit: DEFAULT_LIMIT,
124
- offset: 0,
125
- orderBy: undefined,
126
- orderDirection: "asc",
127
- filterOverride: {
128
- ...filterState,
129
- column: preferredFilterColumn
130
- }
131
- })
132
- } catch (error) {
133
- toast.error(
134
- error instanceof Error
135
- ? error.message
136
- : `Failed to load schema for table '${tableName}'`
137
- )
138
- } finally {
139
- setIsLoadingSchema(false)
140
- }
141
- }
142
-
143
- async function loadData(
144
- tableName: string,
145
- overrides?: {
146
- limit?: number
147
- offset?: number
148
- orderBy?: string
149
- orderDirection?: "asc" | "desc"
150
- filterOverride?: FilterState
151
- }
152
- ) {
153
- setIsLoadingData(true)
154
- const nextLimit = overrides?.limit ?? limit
155
- const nextOffset = overrides?.offset ?? offset
156
- const nextOrderBy = overrides?.orderBy ?? orderBy
157
- const nextOrderDirection = overrides?.orderDirection ?? orderDirection
158
- const nextFilter = overrides?.filterOverride ?? filterState
159
-
160
- try {
161
- const params = new URLSearchParams({
162
- table: tableName,
163
- limit: String(nextLimit),
164
- offset: String(nextOffset)
165
- })
166
-
167
- if (nextOrderBy) {
168
- params.set("orderBy", nextOrderBy)
169
- params.set("orderDirection", nextOrderDirection)
170
- }
171
-
172
- if (nextFilter.column && nextFilter.value.trim().length) {
173
- const key =
174
- nextFilter.operator === "eq"
175
- ? `filter[${nextFilter.column}]`
176
- : `filter[${nextFilter.column}][${nextFilter.operator}]`
177
- params.set(key, nextFilter.value)
178
- } else if (nextFilter.operator === "is" && nextFilter.column) {
179
- const key = `filter[${nextFilter.column}][is]`
180
- params.set(key, nextFilter.value || "null")
181
- }
182
-
183
- const response = await fetch(`/hazo_connect/api/sqlite/data?${params.toString()}`)
184
- if (!response.ok) {
185
- const contentType = response.headers.get("content-type");
186
- if (contentType && contentType.includes("application/json")) {
187
- const errorData = await response.json();
188
- throw new Error(errorData.error || `Failed to load data: ${response.statusText}`);
189
- } else {
190
- const errorText = await response.text();
191
- throw new Error(`Failed to load data: ${errorText.substring(0, 200)}`);
192
- }
193
- }
194
-
195
- const contentType = response.headers.get("content-type");
196
- if (!contentType || !contentType.includes("application/json")) {
197
- const text = await response.text();
198
- throw new Error(`Expected JSON but received ${contentType || "unknown content type"}. Response: ${text.substring(0, 200)}`);
199
- }
200
-
201
- const json = (await response.json()) as DataResponse
202
- setRows(json.data ?? [])
203
- setTotalRows(json.total ?? 0)
204
- setLimit(nextLimit)
205
- setOffset(nextOffset)
206
- setOrderBy(nextOrderBy)
207
- setOrderDirection(nextOrderDirection)
208
- setFilterState(nextFilter)
209
- } catch (error) {
210
- toast.error(
211
- error instanceof Error
212
- ? error.message
213
- : `Failed to load data for table '${tableName}'`
214
- )
215
- } finally {
216
- setIsLoadingData(false)
217
- }
218
- }
219
-
220
- async function refreshTables() {
221
- setIsRefreshingTables(true)
222
- try {
223
- const response = await fetch("/hazo_connect/api/sqlite/tables")
224
- if (!response.ok) {
225
- const contentType = response.headers.get("content-type");
226
- if (contentType && contentType.includes("application/json")) {
227
- const errorData = await response.json();
228
- throw new Error(errorData.error || `Failed to refresh tables: ${response.statusText}`);
229
- } else {
230
- const errorText = await response.text();
231
- throw new Error(`Failed to refresh tables: ${errorText.substring(0, 200)}`);
232
- }
233
- }
234
- const contentType = response.headers.get("content-type");
235
- if (!contentType || !contentType.includes("application/json")) {
236
- const text = await response.text();
237
- throw new Error(`Expected JSON but received ${contentType || "unknown content type"}. Response: ${text.substring(0, 200)}`);
238
- }
239
- const json = await response.json()
240
- setTables(json.data ?? [])
241
- toast.success("Tables refreshed")
242
- } catch (error) {
243
- toast.error(
244
- error instanceof Error ? error.message : "Failed to refresh tables"
245
- )
246
- } finally {
247
- setIsRefreshingTables(false)
248
- }
249
- }
250
-
251
- function handleSelectTable(table: TableSummary) {
252
- setSelectedTable(table)
253
- }
254
-
255
- function handleChangePage(nextOffset: number) {
256
- if (!selectedTable) {
257
- return
258
- }
259
- void loadData(selectedTable.name, { offset: Math.max(0, nextOffset) })
260
- }
261
-
262
- function handleChangeLimit(nextLimit: number) {
263
- if (!selectedTable) {
264
- return
265
- }
266
- void loadData(selectedTable.name, { limit: nextLimit, offset: 0 })
267
- }
268
-
269
- function handleChangeOrder(column?: string) {
270
- if (!selectedTable) {
271
- return
272
- }
273
- const nextDirection =
274
- orderBy === column
275
- ? orderDirection === "asc"
276
- ? "desc"
277
- : "asc"
278
- : "asc"
279
- void loadData(selectedTable.name, {
280
- orderBy: column,
281
- orderDirection: nextDirection
282
- })
283
- }
284
-
285
- function handleApplyFilter() {
286
- if (!selectedTable) {
287
- return
288
- }
289
- void loadData(selectedTable.name, { offset: 0 })
290
- }
291
-
292
- async function handleInsertRow(data: Record<string, unknown>) {
293
- if (!selectedTable) {
294
- return
295
- }
296
-
297
- try {
298
- const response = await fetch("/hazo_connect/api/sqlite/data", {
299
- method: "POST",
300
- headers: {
301
- "Content-Type": "application/json"
302
- },
303
- body: JSON.stringify({ table: selectedTable.name, data })
304
- })
305
-
306
- if (!response.ok) {
307
- throw new Error(await response.text())
308
- }
309
-
310
- toast.success("Row inserted")
311
- setIsCreateOpen(false)
312
- await Promise.all([
313
- refreshTables(),
314
- loadData(selectedTable.name, { offset: 0 })
315
- ])
316
- } catch (error) {
317
- toast.error(error instanceof Error ? error.message : "Insert failed")
318
- }
319
- }
320
-
321
- async function handleUpdateRow(
322
- row: SqlValue,
323
- data: Record<string, unknown>
324
- ) {
325
- if (!selectedTable || !schema) {
326
- return
327
- }
328
-
329
- const criteria = buildCriteriaFromRow(row, schema.columns)
330
- try {
331
- const response = await fetch("/hazo_connect/api/sqlite/data", {
332
- method: "PATCH",
333
- headers: {
334
- "Content-Type": "application/json"
335
- },
336
- body: JSON.stringify({
337
- table: selectedTable.name,
338
- data,
339
- criteria
340
- })
341
- })
342
-
343
- if (!response.ok) {
344
- throw new Error(await response.text())
345
- }
346
-
347
- toast.success("Row updated")
348
- setEditingRow(null)
349
- await loadData(selectedTable.name)
350
- } catch (error) {
351
- toast.error(error instanceof Error ? error.message : "Update failed")
352
- }
353
- }
354
-
355
- async function handleDeleteRow(row: SqlValue) {
356
- if (!selectedTable || !schema) {
357
- return
358
- }
359
-
360
- const criteria = buildCriteriaFromRow(row, schema.columns)
361
- if (!Object.keys(criteria).length) {
362
- toast.error("Unable to determine primary key for row deletion")
363
- return
364
- }
365
-
366
- if (!window.confirm("Delete this row? This action cannot be undone.")) {
367
- return
368
- }
369
-
370
- try {
371
- const response = await fetch("/hazo_connect/api/sqlite/data", {
372
- method: "DELETE",
373
- headers: {
374
- "Content-Type": "application/json"
375
- },
376
- body: JSON.stringify({
377
- table: selectedTable.name,
378
- criteria
379
- })
380
- })
381
-
382
- if (!response.ok) {
383
- throw new Error(await response.text())
384
- }
385
-
386
- toast.success("Row deleted")
387
- await loadData(selectedTable.name, { offset: 0 })
388
- await refreshTables()
389
- } catch (error) {
390
- toast.error(error instanceof Error ? error.message : "Delete failed")
391
- }
392
- }
393
-
394
- const totalPages = Math.max(1, Math.ceil(totalRows / limit))
395
- const currentPage = Math.min(totalPages, Math.floor(offset / limit) + 1)
396
-
397
- return (
398
- <div className="flex min-h-[calc(100vh-4rem)] flex-col gap-4 p-6 lg:flex-row">
399
- <aside className="w-full max-w-xs rounded-lg border border-slate-200 bg-white shadow-sm lg:sticky lg:top-6 lg:h-[calc(100vh-6rem)]">
400
- <header className="flex items-center justify-between border-b border-slate-100 px-4 py-3">
401
- <h2 className="text-sm font-medium text-slate-700">Tables</h2>
402
- <button
403
- type="button"
404
- onClick={refreshTables}
405
- disabled={isRefreshingTables}
406
- className="inline-flex items-center gap-1 rounded-md border border-slate-200 bg-white px-2 py-1 text-xs font-medium text-slate-600 transition hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50"
407
- >
408
- {isRefreshingTables ? (
409
- <Loader2 className="h-3 w-3 animate-spin" />
410
- ) : (
411
- <RefreshCw className="h-3 w-3" />
412
- )}
413
- Refresh
414
- </button>
415
- </header>
416
- <nav className="max-h-[calc(100vh-9rem)] overflow-auto px-2 py-3 text-sm">
417
- {tables.length === 0 ? (
418
- <p className="rounded-md bg-slate-50 p-3 text-slate-500">
419
- No tables detected.
420
- </p>
421
- ) : (
422
- <ul className="space-y-1">
423
- {tables.map(table => {
424
- const isActive = selectedTable?.name === table.name
425
- return (
426
- <li key={table.name}>
427
- <button
428
- type="button"
429
- onClick={() => handleSelectTable(table)}
430
- className={[
431
- "flex w-full items-center justify-between rounded-md px-3 py-2 text-left transition",
432
- isActive
433
- ? "bg-slate-900 text-white shadow-sm"
434
- : "bg-white text-slate-700 hover:bg-slate-100"
435
- ].join(" ")}
436
- >
437
- <span className="truncate font-medium">{table.name}</span>
438
- {typeof table.row_count === "number" && (
439
- <span
440
- className={[
441
- "ml-2 inline-flex min-w-[2rem] items-center justify-center rounded-full px-2 text-xs",
442
- isActive
443
- ? "bg-slate-800 text-slate-100"
444
- : "bg-slate-100 text-slate-600"
445
- ].join(" ")}
446
- >
447
- {table.row_count}
448
- </span>
449
- )}
450
- </button>
451
- </li>
452
- )
453
- })}
454
- </ul>
455
- )}
456
- </nav>
457
- </aside>
458
-
459
- <section className="flex-1 space-y-6">
460
- <header className="flex flex-col gap-3 rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
461
- <div className="flex flex-wrap items-center justify-between gap-3">
462
- <div>
463
- <h1 className="text-xl font-semibold text-slate-900">
464
- SQLite Admin
465
- </h1>
466
- <p className="text-sm text-slate-500">
467
- Browse tables, inspect schema, and edit data safely.
468
- </p>
469
- </div>
470
- <div className="flex items-center gap-2">
471
- <button
472
- type="button"
473
- onClick={() => selectedTable && loadSchemaAndData(selectedTable.name)}
474
- disabled={isLoadingSchema || isLoadingData}
475
- className="inline-flex items-center gap-1 rounded-md border border-slate-200 bg-white px-3 py-2 text-sm font-medium text-slate-600 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50"
476
- >
477
- {isLoadingSchema || isLoadingData ? (
478
- <Loader2 className="h-4 w-4 animate-spin" />
479
- ) : (
480
- <RefreshCw className="h-4 w-4" />
481
- )}
482
- Refresh
483
- </button>
484
- <button
485
- type="button"
486
- onClick={() => setIsCreateOpen(true)}
487
- disabled={!selectedTable || !schema}
488
- className="inline-flex items-center gap-1 rounded-md bg-slate-900 px-3 py-2 text-sm font-medium text-white transition hover:bg-slate-800 disabled:cursor-not-allowed disabled:bg-slate-400"
489
- >
490
- <Plus className="h-4 w-4" />
491
- Add Row
492
- </button>
493
- </div>
494
- </div>
495
-
496
- <div className="flex flex-wrap items-center gap-3 rounded-md bg-slate-50 p-3 text-sm text-slate-600">
497
- <Filter className="h-4 w-4 text-slate-500" />
498
- <select
499
- className="rounded-md border border-slate-200 bg-white px-2 py-1 text-sm"
500
- value={filterState.column ?? ""}
501
- onChange={event =>
502
- setFilterState(current => ({
503
- ...current,
504
- column: event.target.value || undefined
505
- }))
506
- }
507
- >
508
- {filterColumns.length === 0 ? (
509
- <option value="">No filterable columns</option>
510
- ) : (
511
- filterColumns.map(column => (
512
- <option key={column.name} value={column.name}>
513
- {column.name}
514
- </option>
515
- ))
516
- )}
517
- </select>
518
- <select
519
- className="rounded-md border border-slate-200 bg-white px-2 py-1 text-sm"
520
- value={filterState.operator}
521
- onChange={event =>
522
- setFilterState(current => ({
523
- ...current,
524
- operator: event.target.value as SqliteFilterOperator
525
- }))
526
- }
527
- >
528
- {filterOperators.map(operator => (
529
- <option key={operator.value} value={operator.value}>
530
- {operator.label}
531
- </option>
532
- ))}
533
- </select>
534
- {filterState.operator === "is" ? (
535
- <select
536
- className="rounded-md border border-slate-200 bg-white px-2 py-1 text-sm"
537
- value={filterState.value}
538
- onChange={event =>
539
- setFilterState(current => ({
540
- ...current,
541
- value: event.target.value
542
- }))
543
- }
544
- >
545
- <option value="null">NULL</option>
546
- <option value="not.null">NOT NULL</option>
547
- </select>
548
- ) : (
549
- <input
550
- type="text"
551
- className="flex-1 rounded-md border border-slate-200 bg-white px-2 py-1 text-sm focus:border-slate-400 focus:outline-none"
552
- placeholder="Filter value…"
553
- value={filterState.value}
554
- onChange={event =>
555
- setFilterState(current => ({
556
- ...current,
557
- value: event.target.value
558
- }))
559
- }
560
- />
561
- )}
562
- <button
563
- type="button"
564
- onClick={handleApplyFilter}
565
- disabled={!selectedTable || !schema}
566
- className="inline-flex items-center gap-1 rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm font-medium text-slate-600 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50"
567
- >
568
- Apply
569
- </button>
570
- </div>
571
- </header>
572
-
573
- {selectedTable ? (
574
- <>
575
- <section className="rounded-lg border border-slate-200 bg-white shadow-sm">
576
- <header className="flex items-center justify-between border-b border-slate-100 px-4 py-3">
577
- <div>
578
- <h2 className="text-base font-semibold text-slate-800">
579
- {selectedTable.name}
580
- </h2>
581
- <p className="text-xs text-slate-500">
582
- {schema?.columns.length ?? 0} columns
583
- </p>
584
- </div>
585
- <div className="flex items-center gap-2">
586
- <label className="flex items-center gap-1 text-xs text-slate-500">
587
- Rows per page
588
- <select
589
- value={limit}
590
- onChange={event =>
591
- handleChangeLimit(Number.parseInt(event.target.value, 10))
592
- }
593
- className="rounded-md border border-slate-200 bg-white px-2 py-1 text-xs shadow-sm"
594
- >
595
- {[10, 20, 50, 100].map(option => (
596
- <option key={option} value={option}>
597
- {option}
598
- </option>
599
- ))}
600
- </select>
601
- </label>
602
- <div className="inline-flex items-center gap-1 rounded-md border border-slate-200 bg-white px-2 py-1 text-xs text-slate-600 shadow-sm">
603
- <button
604
- type="button"
605
- onClick={() => handleChangeOrder(orderBy)}
606
- className="inline-flex items-center gap-1"
607
- disabled={!schema}
608
- >
609
- Order:{" "}
610
- {orderBy ? `${orderBy} • ${orderDirection}` : "not set"}
611
- </button>
612
- {schema && schema.columns.length > 0 && (
613
- <select
614
- value={orderBy ?? ""}
615
- onChange={event =>
616
- handleChangeOrder(event.target.value || undefined)
617
- }
618
- className="rounded border border-slate-200 bg-white px-1 py-0.5 text-xs"
619
- >
620
- <option value="">None</option>
621
- {schema.columns.map(column => (
622
- <option key={column.name} value={column.name}>
623
- {column.name}
624
- </option>
625
- ))}
626
- </select>
627
- )}
628
- </div>
629
- </div>
630
- </header>
631
-
632
- <div className="overflow-auto">
633
- <table className="min-w-full divide-y divide-slate-200 text-sm">
634
- <thead className="bg-slate-50 text-xs uppercase tracking-wide text-slate-500">
635
- <tr>
636
- {columns.map(column => (
637
- <th key={column.name} className="px-3 py-2 text-left">
638
- {column.name}
639
- </th>
640
- ))}
641
- <th className="px-3 py-2 text-right text-slate-400">Actions</th>
642
- </tr>
643
- </thead>
644
- <tbody className="divide-y divide-slate-100 bg-white text-sm text-slate-700">
645
- {isLoadingData ? (
646
- <tr>
647
- <td colSpan={columns.length + 1} className="px-3 py-8 text-center">
648
- <div className="inline-flex items-center gap-2 text-slate-500">
649
- <Loader2 className="h-4 w-4 animate-spin" />
650
- Loading data…
651
- </div>
652
- </td>
653
- </tr>
654
- ) : rows.length === 0 ? (
655
- <tr>
656
- <td colSpan={columns.length + 1} className="px-3 py-8 text-center text-slate-400">
657
- No rows to display.
658
- </td>
659
- </tr>
660
- ) : (
661
- rows.map((row, index) => (
662
- <tr key={index} className="hover:bg-slate-50/60">
663
- {columns.map(column => (
664
- <td key={column.name} className="max-w-[200px] truncate px-3 py-2">
665
- {formatCellValue(row[column.name])}
666
- </td>
667
- ))}
668
- <td className="px-3 py-2 text-right">
669
- <div className="inline-flex items-center gap-1">
670
- <button
671
- type="button"
672
- onClick={() => setEditingRow(row)}
673
- className="inline-flex items-center gap-1 rounded-md border border-slate-200 px-2 py-1 text-xs font-medium text-slate-600 transition hover:bg-slate-100"
674
- >
675
- <Pencil className="h-3 w-3" />
676
- Edit
677
- </button>
678
- <button
679
- type="button"
680
- onClick={() => handleDeleteRow(row)}
681
- className="inline-flex items-center gap-1 rounded-md border border-red-200 px-2 py-1 text-xs font-medium text-red-600 transition hover:bg-red-50"
682
- >
683
- <Trash2 className="h-3 w-3" />
684
- Delete
685
- </button>
686
- </div>
687
- </td>
688
- </tr>
689
- ))
690
- )}
691
- </tbody>
692
- </table>
693
- </div>
694
-
695
- <footer className="flex flex-wrap items-center justify-between gap-3 border-t border-slate-100 px-4 py-3 text-xs text-slate-500">
696
- <span>
697
- Page {currentPage} of {totalPages} • {totalRows} rows
698
- </span>
699
- <div className="flex items-center gap-2">
700
- <button
701
- type="button"
702
- onClick={() => handleChangePage(Math.max(0, offset - limit))}
703
- disabled={offset === 0 || isLoadingData}
704
- className="inline-flex items-center gap-1 rounded-md border border-slate-200 bg-white px-2 py-1 text-xs font-medium text-slate-600 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50"
705
- >
706
- Prev
707
- </button>
708
- <button
709
- type="button"
710
- onClick={() => handleChangePage(offset + limit)}
711
- disabled={offset + limit >= totalRows || isLoadingData}
712
- className="inline-flex items-center gap-1 rounded-md border border-slate-200 bg-white px-2 py-1 text-xs font-medium text-slate-600 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50"
713
- >
714
- Next
715
- </button>
716
- </div>
717
- </footer>
718
- </section>
719
-
720
- <section className="rounded-lg border border-slate-200 bg-white shadow-sm">
721
- <header className="border-b border-slate-100 px-4 py-3">
722
- <h3 className="text-sm font-semibold text-slate-700">Schema</h3>
723
- </header>
724
- <div className="overflow-auto">
725
- <table className="min-w-full divide-y divide-slate-200 text-sm">
726
- <thead className="bg-slate-50 text-xs uppercase tracking-wide text-slate-500">
727
- <tr>
728
- <th className="px-3 py-2 text-left">Column</th>
729
- <th className="px-3 py-2 text-left">Type</th>
730
- <th className="px-3 py-2 text-left">Not null</th>
731
- <th className="px-3 py-2 text-left">Default</th>
732
- <th className="px-3 py-2 text-left">Primary key</th>
733
- </tr>
734
- </thead>
735
- <tbody className="divide-y divide-slate-100 bg-white text-sm text-slate-700">
736
- {columns.map(column => (
737
- <tr key={column.name}>
738
- <td className="px-3 py-2 font-medium">{column.name}</td>
739
- <td className="px-3 py-2">{column.type || "TEXT"}</td>
740
- <td className="px-3 py-2">
741
- {column.notnull ? "Yes" : "No"}
742
- </td>
743
- <td className="px-3 py-2">
744
- {column.default_value === null
745
- ? "NULL"
746
- : String(column.default_value)}
747
- </td>
748
- <td className="px-3 py-2">
749
- {column.primary_key_position
750
- ? `Yes (#${column.primary_key_position})`
751
- : "No"}
752
- </td>
753
- </tr>
754
- ))}
755
- </tbody>
756
- </table>
757
- </div>
758
- </section>
759
- </>
760
- ) : (
761
- <p className="rounded-lg border border-dashed border-slate-300 bg-white p-6 text-center text-sm text-slate-500">
762
- Select a table to begin.
763
- </p>
764
- )}
765
- </section>
766
-
767
- {schema && selectedTable && (
768
- <RowModal
769
- title={`Insert into ${selectedTable.name}`}
770
- open={isCreateOpen}
771
- columns={schema.columns}
772
- onClose={() => setIsCreateOpen(false)}
773
- onSubmit={handleInsertRow}
774
- />
775
- )}
776
-
777
- {schema && selectedTable && editingRow && (
778
- <RowModal
779
- title={`Edit row in ${selectedTable.name}`}
780
- open={Boolean(editingRow)}
781
- columns={schema.columns}
782
- initialValues={editingRow}
783
- onClose={() => setEditingRow(null)}
784
- onSubmit={data => handleUpdateRow(editingRow, data)}
785
- primaryKeys={schema.columns.filter(column => column.primary_key_position > 0)}
786
- />
787
- )}
788
- </div>
789
- )
790
- }
791
-
792
- function RowModal({
793
- title,
794
- open,
795
- columns,
796
- primaryKeys,
797
- initialValues,
798
- onClose,
799
- onSubmit
800
- }: {
801
- title: string
802
- open: boolean
803
- columns: TableColumn[]
804
- primaryKeys?: TableColumn[]
805
- initialValues?: SqlValue
806
- onClose: () => void
807
- onSubmit: (data: Record<string, unknown>) => void
808
- }) {
809
- const [formValues, setFormValues] = useState<Record<string, string>>({})
810
-
811
- useEffect(() => {
812
- if (open) {
813
- const nextValues: Record<string, string> = {}
814
- columns.forEach(column => {
815
- const rawValue = initialValues?.[column.name]
816
- nextValues[column.name] =
817
- rawValue === null || rawValue === undefined ? "" : String(rawValue)
818
- })
819
- setFormValues(nextValues)
820
- }
821
- }, [open, columns, initialValues])
822
-
823
- if (!open) {
824
- return null
825
- }
826
-
827
- function handleChange(column: string, value: string) {
828
- setFormValues(current => ({
829
- ...current,
830
- [column]: value
831
- }))
832
- }
833
-
834
- function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
835
- event.preventDefault()
836
- const payload: Record<string, unknown> = {}
837
-
838
- for (const column of columns) {
839
- const value = formValues[column.name]
840
- payload[column.name] = coerceValue(column, value)
841
- }
842
-
843
- onSubmit(payload)
844
- }
845
-
846
- return (
847
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/30 backdrop-blur">
848
- <div className="relative w-full max-w-2xl rounded-lg border border-slate-200 bg-white p-6 shadow-xl">
849
- <header className="mb-4 flex items-center justify-between">
850
- <div>
851
- <h2 className="text-lg font-semibold text-slate-900">{title}</h2>
852
- {primaryKeys && primaryKeys.length === 0 && (
853
- <p className="mt-1 text-xs text-amber-600">
854
- Warning: table has no primary key defined. Updates/deletes rely on full row
855
- matching.
856
- </p>
857
- )}
858
- </div>
859
- <button
860
- type="button"
861
- onClick={onClose}
862
- className="rounded-full border border-slate-200 p-1 text-slate-500 transition hover:bg-slate-100"
863
- >
864
- <X className="h-4 w-4" />
865
- </button>
866
- </header>
867
-
868
- <form onSubmit={handleSubmit} className="space-y-4">
869
- <div className="grid max-h-[50vh] grid-cols-1 gap-4 overflow-auto pr-2 sm:grid-cols-2">
870
- {columns.map(column => (
871
- <label key={column.name} className="flex flex-col gap-1 text-sm">
872
- <span className="font-medium text-slate-700">
873
- {column.name}
874
- {column.notnull && (
875
- <sup className="ml-1 text-amber-600" title="Required">
876
- *
877
- </sup>
878
- )}
879
- </span>
880
- <input
881
- type="text"
882
- value={formValues[column.name] ?? ""}
883
- onChange={event => handleChange(column.name, event.target.value)}
884
- className="rounded-md border border-slate-200 px-3 py-2 text-sm text-slate-700 shadow-sm focus:border-slate-400 focus:outline-none"
885
- placeholder={
886
- column.default_value === null
887
- ? column.type || "TEXT"
888
- : String(column.default_value)
889
- }
890
- />
891
- <span className="text-xs text-slate-400">
892
- {column.type || "TEXT"}
893
- {column.primary_key_position ? " • Primary key" : ""}
894
- </span>
895
- </label>
896
- ))}
897
- </div>
898
-
899
- <footer className="flex items-center justify-end gap-2">
900
- <button
901
- type="button"
902
- onClick={onClose}
903
- className="inline-flex items-center gap-1 rounded-md border border-slate-200 bg-white px-3 py-2 text-sm font-medium text-slate-600 transition hover:bg-slate-100"
904
- >
905
- Cancel
906
- </button>
907
- <button
908
- type="submit"
909
- className="inline-flex items-center gap-1 rounded-md bg-slate-900 px-3 py-2 text-sm font-medium text-white transition hover:bg-slate-800"
910
- >
911
- <Save className="h-4 w-4" />
912
- Save
913
- </button>
914
- </footer>
915
- </form>
916
- </div>
917
- </div>
918
- )
919
- }
920
-
921
- function buildCriteriaFromRow(row: SqlValue, columns: TableColumn[]) {
922
- const criteria: Record<string, unknown> = {}
923
- const primaryKeys = columns.filter(column => column.primary_key_position > 0)
924
-
925
- if (primaryKeys.length) {
926
- for (const primary of primaryKeys) {
927
- criteria[primary.name] = row[primary.name]
928
- }
929
- return criteria
930
- }
931
-
932
- for (const column of columns) {
933
- criteria[column.name] = row[column.name]
934
- }
935
- return criteria
936
- }
937
-
938
- function coerceValue(column: TableColumn, rawValue: string): unknown {
939
- if (rawValue === "") {
940
- return column.notnull ? "" : null
941
- }
942
-
943
- const normalisedType = (column.type ?? "").toLowerCase()
944
-
945
- if (normalisedType.includes("int")) {
946
- const parsed = Number.parseInt(rawValue, 10)
947
- return Number.isNaN(parsed) ? rawValue : parsed
948
- }
949
-
950
- if (
951
- normalisedType.includes("real") ||
952
- normalisedType.includes("double") ||
953
- normalisedType.includes("float") ||
954
- normalisedType.includes("numeric")
955
- ) {
956
- const parsed = Number.parseFloat(rawValue)
957
- return Number.isNaN(parsed) ? rawValue : parsed
958
- }
959
-
960
- if (normalisedType.includes("bool")) {
961
- return rawValue === "true" || rawValue === "1" ? 1 : 0
962
- }
963
-
964
- return rawValue
965
- }
966
-
967
- function formatCellValue(value: unknown): string {
968
- if (value === null || value === undefined) {
969
- return "NULL"
970
- }
971
- if (typeof value === "object") {
972
- try {
973
- return JSON.stringify(value)
974
- } catch {
975
- return String(value)
976
- }
977
- }
978
- return String(value)
979
- }
980
-
981
- function identifierIsFilterable(name: string): boolean {
982
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name)
983
- }
984
-