@locusai/web 0.1.7 → 0.2.2

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 (346) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/next.config.js +15 -2
  3. package/package.json +26 -3
  4. package/src/app/(auth)/invite/page.tsx +109 -0
  5. package/src/app/(auth)/layout.tsx +19 -0
  6. package/src/app/(auth)/login/page.tsx +65 -0
  7. package/src/app/(auth)/onboarding/workspace/page.tsx +46 -0
  8. package/src/app/(auth)/register/page.tsx +165 -0
  9. package/src/app/(dashboard)/activity/page.tsx +7 -0
  10. package/src/app/(dashboard)/backlog/page.tsx +195 -0
  11. package/src/app/(dashboard)/board/page.tsx +141 -0
  12. package/src/app/(dashboard)/layout.tsx +32 -0
  13. package/src/app/(dashboard)/page.tsx +14 -0
  14. package/src/app/(dashboard)/settings/page.tsx +161 -0
  15. package/src/app/(dashboard)/settings/team/page.tsx +75 -0
  16. package/src/app/globals.css +259 -0
  17. package/src/app/layout.tsx +10 -20
  18. package/src/app/providers.tsx +26 -3
  19. package/src/components/AuthLayoutUI.tsx +53 -0
  20. package/src/components/BoardFilter.tsx +75 -74
  21. package/src/components/CreateModal/CreateModal.tsx +142 -0
  22. package/src/components/CreateModal/index.ts +1 -0
  23. package/src/components/Editor.tsx +279 -0
  24. package/src/components/Header.tsx +99 -12
  25. package/src/components/PageLayout.tsx +69 -0
  26. package/src/components/PropertyItem.tsx +55 -9
  27. package/src/components/Sidebar.tsx +280 -36
  28. package/src/components/SprintCreateModal.tsx +84 -0
  29. package/src/components/TaskCard.tsx +196 -78
  30. package/src/components/TaskCreateModal.tsx +181 -178
  31. package/src/components/TaskPanel.tsx +140 -692
  32. package/src/components/WorkspaceCreateModal.tsx +97 -0
  33. package/src/components/WorkspaceProtected.tsx +91 -0
  34. package/src/components/auth/InviteSteps.tsx +220 -0
  35. package/src/components/auth/LoginSteps.tsx +86 -0
  36. package/src/components/auth/RegisterSteps.tsx +371 -0
  37. package/src/components/auth/index.ts +3 -0
  38. package/src/components/backlog/BacklogList.tsx +92 -0
  39. package/src/components/backlog/BacklogSection.tsx +137 -0
  40. package/src/components/backlog/CompletedSprintItem.tsx +95 -0
  41. package/src/components/backlog/CompletedSprintsSection.tsx +77 -0
  42. package/src/components/backlog/SprintSection.tsx +155 -0
  43. package/src/components/backlog/constants.ts +26 -0
  44. package/src/components/board/BoardColumn.tsx +97 -0
  45. package/src/components/board/BoardContent.tsx +84 -0
  46. package/src/components/board/BoardEmptyState.tsx +82 -0
  47. package/src/components/board/BoardHeader.tsx +47 -0
  48. package/src/components/board/SprintMindmap.tsx +65 -0
  49. package/src/components/board/constants.ts +40 -0
  50. package/src/components/board/index.ts +5 -0
  51. package/src/components/common/ErrorState.tsx +124 -0
  52. package/src/components/common/LoadingState.tsx +83 -0
  53. package/src/components/common/index.ts +40 -0
  54. package/src/components/dashboard/ActivityFeed.tsx +77 -0
  55. package/src/components/dashboard/ActivityItem.tsx +207 -0
  56. package/src/components/dashboard/QuickActions.tsx +50 -0
  57. package/src/components/dashboard/StatCard.tsx +79 -0
  58. package/src/components/dnd/index.tsx +51 -0
  59. package/src/components/docs/DocsEditorArea.tsx +87 -0
  60. package/src/components/docs/DocsHeaderActions.tsx +121 -0
  61. package/src/components/docs/DocsSidebar.tsx +351 -0
  62. package/src/components/index.ts +7 -0
  63. package/src/components/onboarding/StepProgress.tsx +21 -0
  64. package/src/components/onboarding/index.ts +1 -0
  65. package/src/components/settings/ApiKeyConfirmationModal.tsx +81 -0
  66. package/src/components/settings/ApiKeyCreatedModal.tsx +96 -0
  67. package/src/components/settings/ApiKeysList.tsx +143 -0
  68. package/src/components/settings/ApiKeysSettings.tsx +144 -0
  69. package/src/components/settings/InviteMemberModal.tsx +106 -0
  70. package/src/components/settings/ProjectSetupGuide.tsx +147 -0
  71. package/src/components/settings/SettingItem.tsx +32 -0
  72. package/src/components/settings/SettingSection.tsx +50 -0
  73. package/src/components/settings/TeamInvitationsList.tsx +90 -0
  74. package/src/components/settings/TeamMembersList.tsx +95 -0
  75. package/src/components/settings/index.ts +8 -0
  76. package/src/components/task-panel/TaskActivity.tsx +127 -0
  77. package/src/components/task-panel/TaskChecklist.tsx +142 -0
  78. package/src/components/task-panel/TaskDescription.tsx +201 -0
  79. package/src/components/task-panel/TaskDocs.tsx +137 -0
  80. package/src/components/task-panel/TaskHeader.tsx +125 -0
  81. package/src/components/task-panel/TaskProperties.tsx +111 -0
  82. package/src/components/task-panel/index.ts +12 -0
  83. package/src/components/typography/EmptyStateText.tsx +59 -0
  84. package/src/components/typography/MetadataText.tsx +65 -0
  85. package/src/components/typography/SecondaryText.tsx +60 -0
  86. package/src/components/typography/SectionLabel.tsx +60 -0
  87. package/src/components/typography/index.ts +14 -0
  88. package/src/components/typography-scales.tsx +218 -0
  89. package/src/components/ui/Avatar.tsx +123 -0
  90. package/src/components/ui/Badge.tsx +69 -2
  91. package/src/components/ui/Button.tsx +71 -30
  92. package/src/components/ui/Checkbox.tsx +34 -0
  93. package/src/components/ui/Dropdown.tsx +67 -1
  94. package/src/components/ui/EmptyState.tsx +129 -0
  95. package/src/components/ui/Input.tsx +53 -6
  96. package/src/components/ui/Modal.tsx +45 -12
  97. package/src/components/ui/OtpInput.tsx +148 -0
  98. package/src/components/ui/Skeleton.tsx +36 -0
  99. package/src/components/ui/Spinner.tsx +112 -0
  100. package/src/components/ui/Textarea.tsx +28 -3
  101. package/src/components/ui/Toast.tsx +99 -0
  102. package/src/components/ui/Toggle.tsx +63 -0
  103. package/src/components/ui/constants.ts +108 -0
  104. package/src/components/ui/index.ts +7 -0
  105. package/src/context/AuthContext.tsx +140 -0
  106. package/src/context/index.ts +1 -0
  107. package/src/hooks/backlog/index.ts +13 -0
  108. package/src/hooks/backlog/useBacklogActions.ts +144 -0
  109. package/src/hooks/backlog/useBacklogComposite.ts +73 -0
  110. package/src/hooks/backlog/useBacklogData.ts +74 -0
  111. package/src/hooks/backlog/useBacklogDragDrop.ts +118 -0
  112. package/src/hooks/backlog/useBacklogUI.ts +74 -0
  113. package/src/hooks/index.ts +22 -0
  114. package/src/hooks/task-panel/index.ts +13 -0
  115. package/src/hooks/task-panel/useTaskActions.ts +200 -0
  116. package/src/hooks/task-panel/useTaskComputedValues.ts +30 -0
  117. package/src/hooks/task-panel/useTaskData.ts +78 -0
  118. package/src/hooks/task-panel/useTaskPanelComposite.ts +161 -0
  119. package/src/hooks/task-panel/useTaskUIState.ts +80 -0
  120. package/src/hooks/useAuthLayoutLogic.ts +43 -0
  121. package/src/hooks/useAuthenticatedUser.ts +36 -0
  122. package/src/hooks/useAuthenticatedUserWithOrg.ts +41 -0
  123. package/src/hooks/useBacklog.ts +303 -0
  124. package/src/hooks/useBoard.ts +230 -0
  125. package/src/hooks/useDashboardLayout.ts +49 -0
  126. package/src/hooks/useDocs.ts +279 -0
  127. package/src/hooks/useDocsQuery.ts +99 -0
  128. package/src/hooks/useDocsSidebarState.ts +104 -0
  129. package/src/hooks/useFormState.ts +40 -0
  130. package/src/hooks/useGlobalKeydowns.ts +52 -0
  131. package/src/hooks/useInviteForm.ts +122 -0
  132. package/src/hooks/useLoginForm.ts +84 -0
  133. package/src/hooks/useMutationWithToast.ts +56 -0
  134. package/src/hooks/useOrganizationQuery.ts +55 -0
  135. package/src/hooks/useRegisterForm.ts +216 -0
  136. package/src/hooks/useSprintsQuery.ts +38 -0
  137. package/src/hooks/useTaskDescription.ts +102 -0
  138. package/src/hooks/useTaskPanel.ts +341 -0
  139. package/src/hooks/useTasksQuery.ts +39 -0
  140. package/src/hooks/useTeamManagement.ts +92 -0
  141. package/src/hooks/useWorkspaceCreateForm.ts +70 -0
  142. package/src/hooks/useWorkspaceId.ts +29 -0
  143. package/src/lib/api-client.ts +40 -23
  144. package/src/lib/config.ts +25 -0
  145. package/src/lib/constants.ts +83 -0
  146. package/src/lib/options.ts +96 -0
  147. package/src/lib/query-keys.ts +91 -0
  148. package/src/lib/typography.ts +103 -0
  149. package/src/lib/utils.ts +4 -0
  150. package/src/lib/validation.ts +192 -0
  151. package/src/services/index.ts +7 -3
  152. package/src/services/notifications.ts +80 -0
  153. package/src/utils/env.utils.ts +15 -0
  154. package/src/views/ActivityView.tsx +123 -0
  155. package/src/views/Dashboard.tsx +126 -0
  156. package/src/views/Docs.tsx +98 -612
  157. package/tsconfig.tsbuildinfo +1 -1
  158. package/.next/BUILD_ID +0 -1
  159. package/.next/app-build-manifest.json +0 -55
  160. package/.next/app-path-routes-manifest.json +0 -8
  161. package/.next/build-manifest.json +0 -33
  162. package/.next/cache/.previewinfo +0 -1
  163. package/.next/cache/.rscinfo +0 -1
  164. package/.next/cache/.tsbuildinfo +0 -1
  165. package/.next/cache/config.json +0 -7
  166. package/.next/cache/webpack/client-production/0.pack +0 -0
  167. package/.next/cache/webpack/client-production/index.pack +0 -0
  168. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  169. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  170. package/.next/cache/webpack/server-production/0.pack +0 -0
  171. package/.next/cache/webpack/server-production/index.pack +0 -0
  172. package/.next/diagnostics/build-diagnostics.json +0 -6
  173. package/.next/diagnostics/framework.json +0 -1
  174. package/.next/export-detail.json +0 -5
  175. package/.next/export-marker.json +0 -6
  176. package/.next/images-manifest.json +0 -57
  177. package/.next/next-minimal-server.js.nft.json +0 -1
  178. package/.next/next-server.js.nft.json +0 -1
  179. package/.next/package.json +0 -1
  180. package/.next/prerender-manifest.json +0 -137
  181. package/.next/react-loadable-manifest.json +0 -32
  182. package/.next/required-server-files.json +0 -324
  183. package/.next/routes-manifest.json +0 -77
  184. package/.next/server/app/_not-found/page.js +0 -2
  185. package/.next/server/app/_not-found/page.js.nft.json +0 -1
  186. package/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
  187. package/.next/server/app/_not-found.html +0 -1
  188. package/.next/server/app/_not-found.meta +0 -8
  189. package/.next/server/app/_not-found.rsc +0 -21
  190. package/.next/server/app/backlog/page.js +0 -2
  191. package/.next/server/app/backlog/page.js.nft.json +0 -1
  192. package/.next/server/app/backlog/page_client-reference-manifest.js +0 -1
  193. package/.next/server/app/backlog.html +0 -1
  194. package/.next/server/app/backlog.meta +0 -7
  195. package/.next/server/app/backlog.rsc +0 -25
  196. package/.next/server/app/docs/page.js +0 -97
  197. package/.next/server/app/docs/page.js.nft.json +0 -1
  198. package/.next/server/app/docs/page_client-reference-manifest.js +0 -1
  199. package/.next/server/app/docs.html +0 -1
  200. package/.next/server/app/docs.meta +0 -7
  201. package/.next/server/app/docs.rsc +0 -26
  202. package/.next/server/app/favicon.ico/route.js +0 -1
  203. package/.next/server/app/favicon.ico/route.js.nft.json +0 -1
  204. package/.next/server/app/favicon.ico.body +0 -0
  205. package/.next/server/app/favicon.ico.meta +0 -1
  206. package/.next/server/app/index.html +0 -1
  207. package/.next/server/app/index.meta +0 -7
  208. package/.next/server/app/index.rsc +0 -25
  209. package/.next/server/app/page.js +0 -2
  210. package/.next/server/app/page.js.nft.json +0 -1
  211. package/.next/server/app/page_client-reference-manifest.js +0 -1
  212. package/.next/server/app/settings/page.js +0 -2
  213. package/.next/server/app/settings/page.js.nft.json +0 -1
  214. package/.next/server/app/settings/page_client-reference-manifest.js +0 -1
  215. package/.next/server/app/settings.html +0 -1
  216. package/.next/server/app/settings.meta +0 -7
  217. package/.next/server/app/settings.rsc +0 -25
  218. package/.next/server/app-paths-manifest.json +0 -8
  219. package/.next/server/chunks/496.js +0 -6
  220. package/.next/server/chunks/585.js +0 -1
  221. package/.next/server/chunks/665.js +0 -1
  222. package/.next/server/chunks/699.js +0 -22
  223. package/.next/server/chunks/852.js +0 -7
  224. package/.next/server/functions-config-manifest.json +0 -4
  225. package/.next/server/interception-route-rewrite-manifest.js +0 -1
  226. package/.next/server/middleware-build-manifest.js +0 -1
  227. package/.next/server/middleware-manifest.json +0 -6
  228. package/.next/server/middleware-react-loadable-manifest.js +0 -1
  229. package/.next/server/next-font-manifest.js +0 -1
  230. package/.next/server/next-font-manifest.json +0 -1
  231. package/.next/server/pages/404.html +0 -1
  232. package/.next/server/pages/500.html +0 -1
  233. package/.next/server/pages/_app.js +0 -1
  234. package/.next/server/pages/_app.js.nft.json +0 -1
  235. package/.next/server/pages/_document.js +0 -1
  236. package/.next/server/pages/_document.js.nft.json +0 -1
  237. package/.next/server/pages/_error.js +0 -19
  238. package/.next/server/pages/_error.js.nft.json +0 -1
  239. package/.next/server/pages-manifest.json +0 -6
  240. package/.next/server/server-reference-manifest.js +0 -1
  241. package/.next/server/server-reference-manifest.json +0 -1
  242. package/.next/server/webpack-runtime.js +0 -1
  243. package/.next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
  244. package/.next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
  245. package/.next/static/chunks/138.b98511c56423f8bb.js +0 -1
  246. package/.next/static/chunks/146-34259952c594a3b0.js +0 -1
  247. package/.next/static/chunks/337-d3bb75304d130513.js +0 -1
  248. package/.next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
  249. package/.next/static/chunks/487-1808785ba665f784.js +0 -1
  250. package/.next/static/chunks/544.a9569941cc886e9d.js +0 -1
  251. package/.next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
  252. package/.next/static/chunks/902-d6926825a9fe8784.js +0 -1
  253. package/.next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
  254. package/.next/static/chunks/996.e0a334e6ae90900e.js +0 -1
  255. package/.next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
  256. package/.next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
  257. package/.next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
  258. package/.next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
  259. package/.next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
  260. package/.next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
  261. package/.next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
  262. package/.next/static/chunks/main-843ab130fc1be309.js +0 -1
  263. package/.next/static/chunks/main-app-123e879c5a937a00.js +0 -1
  264. package/.next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
  265. package/.next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
  266. package/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  267. package/.next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
  268. package/.next/static/css/13e8617b72f9d3aa.css +0 -1
  269. package/.next/static/css/8aea088cdc4338f0.css +0 -1
  270. package/.next/static/css/b301ab0424111664.css +0 -1
  271. package/.next/static/media/24c15609eaa28576-s.woff2 +0 -0
  272. package/.next/static/media/2c07349e02a7b712-s.woff2 +0 -0
  273. package/.next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
  274. package/.next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
  275. package/.next/static/media/4f77bef990aad698-s.woff2 +0 -0
  276. package/.next/static/media/627d916fd739a539-s.woff2 +0 -0
  277. package/.next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
  278. package/.next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
  279. package/.next/static/media/84602850c8fd81c3-s.woff2 +0 -0
  280. package/.next/trace +0 -46
  281. package/.next/types/app/backlog/page.ts +0 -84
  282. package/.next/types/app/docs/page.ts +0 -84
  283. package/.next/types/app/layout.ts +0 -84
  284. package/.next/types/app/page.ts +0 -84
  285. package/.next/types/app/settings/page.ts +0 -84
  286. package/.next/types/cache-life.d.ts +0 -141
  287. package/.next/types/package.json +0 -1
  288. package/next-env.d.ts +0 -5
  289. package/out/404.html +0 -1
  290. package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
  291. package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
  292. package/out/_next/static/chunks/138.b98511c56423f8bb.js +0 -1
  293. package/out/_next/static/chunks/146-34259952c594a3b0.js +0 -1
  294. package/out/_next/static/chunks/337-d3bb75304d130513.js +0 -1
  295. package/out/_next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
  296. package/out/_next/static/chunks/487-1808785ba665f784.js +0 -1
  297. package/out/_next/static/chunks/544.a9569941cc886e9d.js +0 -1
  298. package/out/_next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
  299. package/out/_next/static/chunks/902-d6926825a9fe8784.js +0 -1
  300. package/out/_next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
  301. package/out/_next/static/chunks/996.e0a334e6ae90900e.js +0 -1
  302. package/out/_next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
  303. package/out/_next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
  304. package/out/_next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
  305. package/out/_next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
  306. package/out/_next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
  307. package/out/_next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
  308. package/out/_next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
  309. package/out/_next/static/chunks/main-843ab130fc1be309.js +0 -1
  310. package/out/_next/static/chunks/main-app-123e879c5a937a00.js +0 -1
  311. package/out/_next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
  312. package/out/_next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
  313. package/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  314. package/out/_next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
  315. package/out/_next/static/css/13e8617b72f9d3aa.css +0 -1
  316. package/out/_next/static/css/8aea088cdc4338f0.css +0 -1
  317. package/out/_next/static/css/b301ab0424111664.css +0 -1
  318. package/out/_next/static/media/24c15609eaa28576-s.woff2 +0 -0
  319. package/out/_next/static/media/2c07349e02a7b712-s.woff2 +0 -0
  320. package/out/_next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
  321. package/out/_next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
  322. package/out/_next/static/media/4f77bef990aad698-s.woff2 +0 -0
  323. package/out/_next/static/media/627d916fd739a539-s.woff2 +0 -0
  324. package/out/_next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
  325. package/out/_next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
  326. package/out/_next/static/media/84602850c8fd81c3-s.woff2 +0 -0
  327. package/out/backlog.html +0 -1
  328. package/out/backlog.txt +0 -25
  329. package/out/docs.html +0 -1
  330. package/out/docs.txt +0 -26
  331. package/out/favicon.ico +0 -0
  332. package/out/index.html +0 -1
  333. package/out/index.txt +0 -25
  334. package/out/logo.png +0 -0
  335. package/out/settings.html +0 -1
  336. package/out/settings.txt +0 -25
  337. package/src/app/backlog/page.tsx +0 -19
  338. package/src/app/page.tsx +0 -16
  339. package/src/app/settings/page.tsx +0 -194
  340. package/src/hooks/useTasks.ts +0 -119
  341. package/src/services/doc.service.ts +0 -27
  342. package/src/services/sprint.service.ts +0 -24
  343. package/src/services/task.service.ts +0 -75
  344. package/src/views/Backlog.tsx +0 -691
  345. package/src/views/Board.tsx +0 -306
  346. /package/src/app/{docs → (dashboard)/docs}/page.tsx +0 -0
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Backlog UI State Hook
3
+ *
4
+ * Manages UI state for the backlog page (modals, selections, expanded sections).
5
+ */
6
+
7
+ "use client";
8
+
9
+ import { useRouter, useSearchParams } from "next/navigation";
10
+ import { useEffect, useState } from "react";
11
+
12
+ export interface BacklogUIState {
13
+ isTaskModalOpen: boolean;
14
+ isSprintModalOpen: boolean;
15
+ selectedTaskId: string | null;
16
+ expandedSections: Set<string>;
17
+ }
18
+
19
+ export interface BacklogUIActions {
20
+ setIsTaskModalOpen: (open: boolean) => void;
21
+ setIsSprintModalOpen: (open: boolean) => void;
22
+ setSelectedTaskId: (id: string | null) => void;
23
+ toggleSection: (section: string) => void;
24
+ }
25
+
26
+ /**
27
+ * Manage backlog UI state including modals and section expansion
28
+ */
29
+ export function useBacklogUI(): BacklogUIState & BacklogUIActions {
30
+ const router = useRouter();
31
+ const searchParams = useSearchParams();
32
+
33
+ const [isTaskModalOpen, setIsTaskModalOpen] = useState(false);
34
+ const [isSprintModalOpen, setIsSprintModalOpen] = useState(false);
35
+ const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
36
+ const [expandedSections, setExpandedSections] = useState<Set<string>>(
37
+ new Set(["active", "planned"])
38
+ );
39
+
40
+ // Handle query parameters for new task/sprint
41
+ useEffect(() => {
42
+ const createTask = searchParams.get("createTask");
43
+ const createSprint = searchParams.get("createSprint");
44
+
45
+ if (createTask === "true") {
46
+ setIsTaskModalOpen(true);
47
+ router.replace("/backlog");
48
+ } else if (createSprint === "true") {
49
+ setIsSprintModalOpen(true);
50
+ router.replace("/backlog");
51
+ }
52
+ }, [searchParams, router]);
53
+
54
+ const toggleSection = (section: string) => {
55
+ const newExpanded = new Set(expandedSections);
56
+ if (newExpanded.has(section)) {
57
+ newExpanded.delete(section);
58
+ } else {
59
+ newExpanded.add(section);
60
+ }
61
+ setExpandedSections(newExpanded);
62
+ };
63
+
64
+ return {
65
+ isTaskModalOpen,
66
+ isSprintModalOpen,
67
+ selectedTaskId,
68
+ expandedSections,
69
+ setIsTaskModalOpen,
70
+ setIsSprintModalOpen,
71
+ setSelectedTaskId,
72
+ toggleSection,
73
+ };
74
+ }
@@ -0,0 +1,22 @@
1
+ export * from "./backlog";
2
+ export * from "./task-panel";
3
+ export * from "./useAuthenticatedUser";
4
+ export * from "./useAuthenticatedUserWithOrg";
5
+ export * from "./useAuthLayoutLogic";
6
+ export * from "./useBoard";
7
+ export * from "./useDashboardLayout";
8
+ export * from "./useDocs";
9
+ export * from "./useDocsSidebarState";
10
+ export * from "./useFormState";
11
+ export * from "./useGlobalKeydowns";
12
+ export * from "./useInviteForm";
13
+ export * from "./useLoginForm";
14
+ export * from "./useMutationWithToast";
15
+ export * from "./useOrganizationQuery";
16
+ export * from "./useRegisterForm";
17
+ export * from "./useSprintsQuery";
18
+ export * from "./useTaskDescription";
19
+ export * from "./useTasksQuery";
20
+ export * from "./useTeamManagement";
21
+ export * from "./useWorkspaceCreateForm";
22
+ export * from "./useWorkspaceId";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Task Panel Hooks - Composite Export
3
+ *
4
+ * For backward compatibility, export all task panel hooks.
5
+ * Prefer using individual hooks for better tree-shaking and clarity.
6
+ */
7
+
8
+ export * from "./useTaskActions";
9
+ export * from "./useTaskComputedValues";
10
+ export * from "./useTaskData";
11
+ // Composite hook for components not yet refactored
12
+ export { useTaskPanelComposite as useTaskPanel } from "./useTaskPanelComposite";
13
+ export * from "./useTaskUIState";
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Task Panel Actions Hook
3
+ *
4
+ * Manages task operations (save, delete, approve, reject, etc.).
5
+ */
6
+
7
+ "use client";
8
+
9
+ import { type AcceptanceItem, TaskStatus } from "@locusai/shared";
10
+ import { notifications } from "@/services/notifications";
11
+
12
+ export interface TaskActionHandlers {
13
+ handleTitleSave: (editTitle: string, currentTitle?: string) => Promise<void>;
14
+ handleDescSave: (editDesc: string, currentDesc?: string) => Promise<void>;
15
+ handleAddChecklistItem: (
16
+ newItem: string,
17
+ checklist: AcceptanceItem[]
18
+ ) => Promise<void>;
19
+ handleToggleChecklistItem: (
20
+ itemId: string,
21
+ checklist: AcceptanceItem[]
22
+ ) => Promise<void>;
23
+ handleRemoveChecklistItem: (
24
+ itemId: string,
25
+ checklist: AcceptanceItem[]
26
+ ) => Promise<void>;
27
+ handleLinkDoc: (docId: string, currentDocIds: string[]) => Promise<void>;
28
+ handleUnlinkDoc: (docId: string, currentDocIds: string[]) => Promise<void>;
29
+ handleReject: (reason: string, onSuccess?: () => void) => Promise<void>;
30
+ handleApprove: (onSuccess?: () => void) => Promise<void>;
31
+ }
32
+
33
+ interface UseTaskActionsProps {
34
+ onUpdateTask: (
35
+ updates: Partial<{
36
+ title?: string;
37
+ description?: string;
38
+ acceptanceChecklist?: AcceptanceItem[];
39
+ status?: TaskStatus;
40
+ docIds?: string[];
41
+ }>
42
+ ) => Promise<void>;
43
+ onAddComment?: (text: string) => Promise<void>;
44
+ }
45
+
46
+ /**
47
+ * Manage task operations
48
+ */
49
+ export function useTaskActions({
50
+ onUpdateTask,
51
+ onAddComment,
52
+ }: UseTaskActionsProps): TaskActionHandlers {
53
+ const handleTitleSave = async (editTitle: string, currentTitle?: string) => {
54
+ if (editTitle.trim() && editTitle !== currentTitle) {
55
+ try {
56
+ await onUpdateTask({ title: editTitle.trim() });
57
+ } catch (error) {
58
+ notifications.error(
59
+ error instanceof Error ? error.message : "Failed to update title"
60
+ );
61
+ }
62
+ }
63
+ };
64
+
65
+ const handleDescSave = async (editDesc: string, currentDesc?: string) => {
66
+ if (editDesc !== currentDesc) {
67
+ try {
68
+ await onUpdateTask({ description: editDesc });
69
+ } catch (error) {
70
+ notifications.error(
71
+ error instanceof Error
72
+ ? error.message
73
+ : "Failed to update description"
74
+ );
75
+ }
76
+ }
77
+ };
78
+
79
+ const handleAddChecklistItem = async (
80
+ newItem: string,
81
+ checklist: AcceptanceItem[]
82
+ ) => {
83
+ if (!newItem.trim()) return;
84
+ try {
85
+ const item: AcceptanceItem = {
86
+ id: crypto.randomUUID(),
87
+ text: newItem.trim(),
88
+ done: false,
89
+ };
90
+ await onUpdateTask({
91
+ acceptanceChecklist: [...checklist, item],
92
+ });
93
+ } catch (error) {
94
+ notifications.error(
95
+ error instanceof Error ? error.message : "Failed to add checklist item"
96
+ );
97
+ }
98
+ };
99
+
100
+ const handleToggleChecklistItem = async (
101
+ itemId: string,
102
+ checklist: AcceptanceItem[]
103
+ ) => {
104
+ try {
105
+ const updated = checklist.map((item) =>
106
+ item.id === itemId ? { ...item, done: !item.done } : item
107
+ );
108
+ await onUpdateTask({ acceptanceChecklist: updated });
109
+ } catch (error) {
110
+ notifications.error(
111
+ error instanceof Error
112
+ ? error.message
113
+ : "Failed to toggle checklist item"
114
+ );
115
+ }
116
+ };
117
+
118
+ const handleRemoveChecklistItem = async (
119
+ itemId: string,
120
+ checklist: AcceptanceItem[]
121
+ ) => {
122
+ try {
123
+ const updated = checklist.filter((item) => item.id !== itemId);
124
+ await onUpdateTask({ acceptanceChecklist: updated });
125
+ } catch (error) {
126
+ notifications.error(
127
+ error instanceof Error
128
+ ? error.message
129
+ : "Failed to remove checklist item"
130
+ );
131
+ }
132
+ };
133
+
134
+ const handleLinkDoc = async (docId: string, currentDocIds: string[]) => {
135
+ if (currentDocIds.includes(docId)) return;
136
+ try {
137
+ await onUpdateTask({
138
+ docIds: [...currentDocIds, docId],
139
+ });
140
+ notifications.success("Document linked");
141
+ } catch (error) {
142
+ notifications.error(
143
+ error instanceof Error ? error.message : "Failed to link document"
144
+ );
145
+ }
146
+ };
147
+
148
+ const handleUnlinkDoc = async (docId: string, currentDocIds: string[]) => {
149
+ try {
150
+ await onUpdateTask({
151
+ docIds: currentDocIds.filter((id) => id !== docId),
152
+ });
153
+ notifications.success("Document unlinked");
154
+ } catch (error) {
155
+ notifications.error(
156
+ error instanceof Error ? error.message : "Failed to unlink document"
157
+ );
158
+ }
159
+ };
160
+
161
+ const handleReject = async (reason: string, onSuccess?: () => void) => {
162
+ if (!reason.trim()) return;
163
+ try {
164
+ await onUpdateTask({ status: TaskStatus.IN_PROGRESS });
165
+ if (onAddComment) {
166
+ await onAddComment(`❌ **Rejected**: ${reason}`);
167
+ }
168
+ notifications.success("Task rejected");
169
+ onSuccess?.();
170
+ } catch (error) {
171
+ notifications.error(
172
+ error instanceof Error ? error.message : "Failed to reject task"
173
+ );
174
+ }
175
+ };
176
+
177
+ const handleApprove = async (onSuccess?: () => void) => {
178
+ try {
179
+ await onUpdateTask({ status: TaskStatus.DONE });
180
+ notifications.success("Task approved");
181
+ onSuccess?.();
182
+ } catch (error) {
183
+ notifications.error(
184
+ error instanceof Error ? error.message : "Failed to approve task"
185
+ );
186
+ }
187
+ };
188
+
189
+ return {
190
+ handleTitleSave,
191
+ handleDescSave,
192
+ handleAddChecklistItem,
193
+ handleToggleChecklistItem,
194
+ handleRemoveChecklistItem,
195
+ handleLinkDoc,
196
+ handleUnlinkDoc,
197
+ handleReject,
198
+ handleApprove,
199
+ };
200
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Task Panel Computed Values Hook
3
+ *
4
+ * Manages computed values (checklist progress).
5
+ */
6
+
7
+ "use client";
8
+
9
+ import { type Task } from "@locusai/shared";
10
+
11
+ export interface TaskComputedValues {
12
+ checklistProgress: number;
13
+ }
14
+
15
+ /**
16
+ * Compute task-related values
17
+ */
18
+ export function useTaskComputedValues(task?: Task): TaskComputedValues {
19
+ const checklistProgress = task?.acceptanceChecklist?.length
20
+ ? Math.round(
21
+ (task.acceptanceChecklist.filter((i) => i.done).length /
22
+ task.acceptanceChecklist.length) *
23
+ 100
24
+ )
25
+ : 0;
26
+
27
+ return {
28
+ checklistProgress,
29
+ };
30
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Task Panel Data Hook
3
+ *
4
+ * Manages task fetching and basic mutations.
5
+ */
6
+
7
+ "use client";
8
+
9
+ import { type Task } from "@locusai/shared";
10
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
11
+ import { useWorkspaceId } from "@/hooks/useWorkspaceId";
12
+ import { locusClient } from "@/lib/api-client";
13
+ import { queryKeys } from "@/lib/query-keys";
14
+
15
+ interface UseTaskDataProps {
16
+ taskId: string;
17
+ onUpdated?: () => void;
18
+ }
19
+
20
+ export interface TaskDataState {
21
+ task: Task | undefined;
22
+ isLoading: boolean;
23
+ error: Error | null;
24
+ }
25
+
26
+ export interface TaskDataActions {
27
+ refetchTask: () => Promise<void>;
28
+ updateTask: (updates: Partial<Task> & { docIds?: string[] }) => Promise<void>;
29
+ deleteTask: () => Promise<void>;
30
+ }
31
+
32
+ /**
33
+ * Manage task data fetching and updates
34
+ */
35
+ export function useTaskData({
36
+ taskId,
37
+ onUpdated,
38
+ }: UseTaskDataProps): TaskDataState & TaskDataActions {
39
+ const workspaceId = useWorkspaceId();
40
+ const queryClient = useQueryClient();
41
+
42
+ const {
43
+ data: task,
44
+ isLoading,
45
+ error,
46
+ refetch,
47
+ } = useQuery({
48
+ queryKey: queryKeys.tasks.detail(taskId),
49
+ queryFn: () => locusClient.tasks.getById(taskId, workspaceId),
50
+ enabled: !!workspaceId,
51
+ });
52
+
53
+ const refetchTask = async () => {
54
+ await refetch();
55
+ };
56
+
57
+ const updateTask = async (updates: Partial<Task> & { docIds?: string[] }) => {
58
+ await locusClient.tasks.update(taskId, workspaceId, updates);
59
+ queryClient.invalidateQueries({
60
+ queryKey: queryKeys.tasks.detail(taskId),
61
+ });
62
+ onUpdated?.();
63
+ };
64
+
65
+ const deleteTask = async () => {
66
+ await locusClient.tasks.delete(taskId, workspaceId);
67
+ queryClient.invalidateQueries({ queryKey: queryKeys.tasks.all() });
68
+ };
69
+
70
+ return {
71
+ task,
72
+ isLoading,
73
+ error: error as Error | null,
74
+ refetchTask,
75
+ updateTask,
76
+ deleteTask,
77
+ };
78
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Task Panel Composite Hook
3
+ *
4
+ * Combines all task panel hooks for backward compatibility.
5
+ * Prefer using individual hooks for new code.
6
+ */
7
+
8
+ "use client";
9
+
10
+ import { useQueryClient } from "@tanstack/react-query";
11
+ import { useAuth } from "@/context/AuthContext";
12
+ import { useWorkspaceId } from "@/hooks/useWorkspaceId";
13
+ import { locusClient } from "@/lib/api-client";
14
+ import { queryKeys } from "@/lib/query-keys";
15
+ import { useTaskActions } from "./useTaskActions";
16
+ import { useTaskComputedValues } from "./useTaskComputedValues";
17
+ import { useTaskData } from "./useTaskData";
18
+ import { useTaskUIState } from "./useTaskUIState";
19
+
20
+ interface UseTaskPanelProps {
21
+ taskId: string;
22
+ onUpdated: () => void;
23
+ onDeleted: () => void;
24
+ onClose: () => void;
25
+ }
26
+
27
+ /**
28
+ * All-in-one task panel hook (kept for backward compatibility)
29
+ *
30
+ * @deprecated Prefer using individual hooks:
31
+ * - useTaskData for task fetching
32
+ * - useTaskUIState for UI state
33
+ * - useTaskActions for operations
34
+ * - useTaskComputedValues for derived values
35
+ */
36
+ export function useTaskPanelComposite({
37
+ taskId,
38
+ onUpdated,
39
+ onDeleted,
40
+ onClose,
41
+ }: UseTaskPanelProps) {
42
+ const { user } = useAuth();
43
+ const workspaceId = useWorkspaceId();
44
+ const queryClient = useQueryClient();
45
+
46
+ const { task, isLoading, updateTask, deleteTask, refetchTask } = useTaskData({
47
+ taskId,
48
+ onUpdated,
49
+ });
50
+
51
+ const ui = useTaskUIState(task);
52
+ const actions = useTaskActions({
53
+ onUpdateTask: updateTask,
54
+ onAddComment: async (text) => {
55
+ await locusClient.tasks.addComment(taskId, workspaceId, {
56
+ author: user?.name || "Anonymous",
57
+ text,
58
+ });
59
+ queryClient.invalidateQueries({
60
+ queryKey: queryKeys.tasks.detail(taskId),
61
+ });
62
+ },
63
+ });
64
+
65
+ const computed = useTaskComputedValues(task);
66
+
67
+ const handleDelete = async () => {
68
+ if (
69
+ !confirm(
70
+ "Are you sure you want to delete this task? This action cannot be undone."
71
+ )
72
+ ) {
73
+ return;
74
+ }
75
+ try {
76
+ await deleteTask();
77
+ onDeleted();
78
+ onClose();
79
+ } catch (error) {
80
+ console.error("Failed to delete task:", error);
81
+ }
82
+ };
83
+
84
+ const handleAddComment = async () => {
85
+ if (!ui.newComment.trim()) return;
86
+ try {
87
+ await locusClient.tasks.addComment(taskId, workspaceId, {
88
+ author: user?.name || "Anonymous",
89
+ text: ui.newComment,
90
+ });
91
+ queryClient.invalidateQueries({
92
+ queryKey: queryKeys.tasks.detail(taskId),
93
+ });
94
+ ui.resetCommentInput();
95
+ } catch (error) {
96
+ console.error("Failed to add comment:", error);
97
+ }
98
+ };
99
+
100
+ return {
101
+ task,
102
+ isLoading,
103
+ // UI State
104
+ isEditingTitle: ui.isEditingTitle,
105
+ setIsEditingTitle: ui.setIsEditingTitle,
106
+ editTitle: ui.editTitle,
107
+ setEditTitle: ui.setEditTitle,
108
+ editDesc: ui.editDesc,
109
+ setEditDesc: ui.setEditDesc,
110
+ newComment: ui.newComment,
111
+ setNewComment: ui.setNewComment,
112
+ newChecklistItem: ui.newChecklistItem,
113
+ setNewChecklistItem: ui.setNewChecklistItem,
114
+ descMode: ui.descMode,
115
+ setDescMode: ui.setDescMode,
116
+ showRejectModal: ui.showRejectModal,
117
+ setShowRejectModal: ui.setShowRejectModal,
118
+ rejectReason: ui.rejectReason,
119
+ setRejectReason: ui.setRejectReason,
120
+ // Computed
121
+ checklistProgress: computed.checklistProgress,
122
+ // Actions
123
+ handleUpdateTask: updateTask,
124
+ handleLinkDoc: async (docId: string) => {
125
+ const currentDocIds = task?.docs?.map((d) => d.id) || [];
126
+ await actions.handleLinkDoc(docId, currentDocIds);
127
+ },
128
+ handleUnlinkDoc: async (docId: string) => {
129
+ const currentDocIds = task?.docs?.map((d) => d.id) || [];
130
+ await actions.handleUnlinkDoc(docId, currentDocIds);
131
+ },
132
+ handleDelete,
133
+ handleTitleSave: () => actions.handleTitleSave(ui.editTitle, task?.title),
134
+ handleDescSave: () =>
135
+ actions.handleDescSave(ui.editDesc, task?.description),
136
+ handleAddChecklistItem: () =>
137
+ actions.handleAddChecklistItem(
138
+ ui.newChecklistItem,
139
+ task?.acceptanceChecklist || []
140
+ ),
141
+ handleToggleChecklistItem: (itemId: string) =>
142
+ actions.handleToggleChecklistItem(
143
+ itemId,
144
+ task?.acceptanceChecklist || []
145
+ ),
146
+ handleRemoveChecklistItem: (itemId: string) =>
147
+ actions.handleRemoveChecklistItem(
148
+ itemId,
149
+ task?.acceptanceChecklist || []
150
+ ),
151
+ handleAddComment,
152
+ handleReject: (onSuccess?: () => void) =>
153
+ actions.handleReject(ui.rejectReason, () => {
154
+ ui.setShowRejectModal(false);
155
+ ui.setRejectReason("");
156
+ onSuccess?.();
157
+ }),
158
+ handleApprove: (onSuccess?: () => void) => actions.handleApprove(onSuccess),
159
+ refresh: refetchTask,
160
+ };
161
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Task Panel UI State Hook
3
+ *
4
+ * Manages UI state (editing, modals, descriptions mode).
5
+ */
6
+
7
+ "use client";
8
+
9
+ import { type Task } from "@locusai/shared";
10
+ import { useEffect, useState } from "react";
11
+
12
+ export interface TaskUIState {
13
+ isEditingTitle: boolean;
14
+ editTitle: string;
15
+ editDesc: string;
16
+ newComment: string;
17
+ newChecklistItem: string;
18
+ descMode: "edit" | "preview";
19
+ showRejectModal: boolean;
20
+ rejectReason: string;
21
+ }
22
+
23
+ export interface TaskUIActions {
24
+ setIsEditingTitle: (value: boolean) => void;
25
+ setEditTitle: (value: string) => void;
26
+ setEditDesc: (value: string) => void;
27
+ setNewComment: (value: string) => void;
28
+ setNewChecklistItem: (value: string) => void;
29
+ setDescMode: (value: "edit" | "preview") => void;
30
+ setShowRejectModal: (value: boolean) => void;
31
+ setRejectReason: (value: string) => void;
32
+ resetCommentInput: () => void;
33
+ resetChecklistInput: () => void;
34
+ }
35
+
36
+ /**
37
+ * Manage task panel UI state
38
+ */
39
+ export function useTaskUIState(task?: Task): TaskUIState & TaskUIActions {
40
+ const [isEditingTitle, setIsEditingTitle] = useState(false);
41
+ const [editTitle, setEditTitle] = useState("");
42
+ const [editDesc, setEditDesc] = useState("");
43
+ const [newComment, setNewComment] = useState("");
44
+ const [newChecklistItem, setNewChecklistItem] = useState("");
45
+ const [descMode, setDescMode] = useState<"edit" | "preview">("preview");
46
+ const [showRejectModal, setShowRejectModal] = useState(false);
47
+ const [rejectReason, setRejectReason] = useState("");
48
+
49
+ // Sync with task data
50
+ useEffect(() => {
51
+ if (task) {
52
+ setEditTitle(task.title);
53
+ setEditDesc(task.description || "");
54
+ }
55
+ }, [task]);
56
+
57
+ const resetCommentInput = () => setNewComment("");
58
+ const resetChecklistInput = () => setNewChecklistItem("");
59
+
60
+ return {
61
+ isEditingTitle,
62
+ editTitle,
63
+ editDesc,
64
+ newComment,
65
+ newChecklistItem,
66
+ descMode,
67
+ showRejectModal,
68
+ rejectReason,
69
+ setIsEditingTitle,
70
+ setEditTitle,
71
+ setEditDesc,
72
+ setNewComment,
73
+ setNewChecklistItem,
74
+ setDescMode,
75
+ setShowRejectModal,
76
+ setRejectReason,
77
+ resetCommentInput,
78
+ resetChecklistInput,
79
+ };
80
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Hook for auth layout routing and protection logic
3
+ *
4
+ * Handles redirecting authenticated users away from auth pages
5
+ * (except onboarding pages which may be needed during account setup)
6
+ */
7
+ "use client";
8
+
9
+ import { usePathname, useRouter } from "next/navigation";
10
+ import { useEffect } from "react";
11
+ import { useAuth } from "@/context/AuthContext";
12
+ import { isCloudMode } from "@/utils/env.utils";
13
+
14
+ const AUTH_SKIP_ROUTES = ["/onboarding"];
15
+
16
+ /**
17
+ * Determines if a pathname should skip auth redirect
18
+ * @param pathname - Current pathname
19
+ * @returns true if auth redirect should be skipped
20
+ */
21
+ function shouldSkipAuthRedirect(pathname: string): boolean {
22
+ return AUTH_SKIP_ROUTES.some((route) => pathname.startsWith(route));
23
+ }
24
+
25
+ export function useAuthLayoutLogic() {
26
+ const { isAuthenticated, isLoading } = useAuth();
27
+ const router = useRouter();
28
+ const pathname = usePathname();
29
+
30
+ useEffect(() => {
31
+ // Skip redirect if still loading or not in cloud mode
32
+ if (isLoading || !isCloudMode()) {
33
+ return;
34
+ }
35
+
36
+ // Redirect authenticated users away from auth pages
37
+ if (isAuthenticated && !shouldSkipAuthRedirect(pathname)) {
38
+ router.push("/");
39
+ }
40
+ }, [isLoading, isAuthenticated, router, pathname]);
41
+
42
+ return { isLoading };
43
+ }