@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,142 @@
1
+ "use client";
2
+
3
+ import { type Task } from "@locusai/shared";
4
+ import { motion } from "framer-motion";
5
+ import { CheckCircle, X } from "lucide-react";
6
+ import { SecondaryText, SectionLabel } from "@/components/typography";
7
+ import { Button, Checkbox, EmptyState, Input } from "@/components/ui";
8
+ import { cn } from "@/lib/utils";
9
+
10
+ interface TaskChecklistProps {
11
+ task: Task;
12
+ isLoading?: boolean;
13
+ checklistProgress: number;
14
+ newChecklistItem: string;
15
+ setNewChecklistItem: (val: string) => void;
16
+ handleAddChecklistItem: () => void;
17
+ handleToggleChecklistItem: (id: string) => void;
18
+ handleRemoveChecklistItem: (id: string) => void;
19
+ }
20
+
21
+ export function TaskChecklist({
22
+ task,
23
+ isLoading = false,
24
+ checklistProgress,
25
+ newChecklistItem,
26
+ setNewChecklistItem,
27
+ handleAddChecklistItem,
28
+ handleToggleChecklistItem,
29
+ handleRemoveChecklistItem,
30
+ }: TaskChecklistProps) {
31
+ return (
32
+ <div className="px-8 pb-8">
33
+ <div className="flex items-center justify-between mb-6">
34
+ <div className="flex items-center gap-3">
35
+ <div className="h-8 w-8 rounded-xl bg-sky-500/10 flex items-center justify-center text-sky-500">
36
+ <CheckCircle size={16} />
37
+ </div>
38
+ <SectionLabel as="h4">Definition of Done</SectionLabel>
39
+ </div>
40
+ <div className="flex items-center gap-6">
41
+ <div className="flex flex-col items-end">
42
+ <SecondaryText size="xs" className="mb-1.5">
43
+ Calibration
44
+ </SecondaryText>
45
+ <span className="text-sm font-mono font-black text-sky-500">
46
+ {checklistProgress}%
47
+ </span>
48
+ </div>
49
+ <div className="w-48 h-2 bg-secondary/40 rounded-full overflow-hidden border border-border/30 shadow-inner">
50
+ <motion.div
51
+ initial={{ width: 0 }}
52
+ animate={{ width: `${checklistProgress}%` }}
53
+ transition={{ duration: 1, ease: "easeOut" }}
54
+ className="h-full bg-sky-500 shadow-[0_0_15px_rgba(14,165,233,0.4)]"
55
+ />
56
+ </div>
57
+ </div>
58
+ </div>
59
+
60
+ <div className="grid gap-3 mb-6">
61
+ {task.acceptanceChecklist.length === 0 && (
62
+ <EmptyState
63
+ variant="compact"
64
+ title="Zero Quality Gates"
65
+ description="Deployment requires standard validation criteria."
66
+ className="bg-secondary/5 border-dashed border-2 border-border/40 py-8"
67
+ action={
68
+ <Button
69
+ variant="ghost"
70
+ size="sm"
71
+ onClick={() =>
72
+ setNewChecklistItem("Initialize unit validation")
73
+ }
74
+ className="text-[10px] font-black uppercase tracking-widest hover:text-sky-500"
75
+ >
76
+ Suggest Criteria
77
+ </Button>
78
+ }
79
+ />
80
+ )}
81
+ {task.acceptanceChecklist.map((item) => (
82
+ <motion.div
83
+ layout
84
+ key={item.id}
85
+ className={cn(
86
+ "group flex items-center gap-4 p-4 bg-card/30 border border-border/40 rounded-2xl hover:border-sky-500/50 hover:bg-card/50 transition-all duration-300 shadow-sm",
87
+ isLoading && "opacity-60 pointer-events-none"
88
+ )}
89
+ >
90
+ <Checkbox
91
+ checked={item.done}
92
+ onChange={() => !isLoading && handleToggleChecklistItem(item.id)}
93
+ disabled={isLoading}
94
+ className="scale-110"
95
+ />
96
+ <span
97
+ className={cn(
98
+ "flex-1 text-sm font-bold transition-all duration-300",
99
+ item.done
100
+ ? "line-through text-muted-foreground/30 scale-[0.98] translate-x-1"
101
+ : "text-foreground/90"
102
+ )}
103
+ >
104
+ {item.text}
105
+ </span>
106
+ <Button
107
+ size="icon"
108
+ variant="ghost"
109
+ disabled={isLoading}
110
+ className="opacity-0 group-hover:opacity-100 h-8 w-8 text-muted-foreground/40 hover:text-destructive hover:bg-destructive/10 transition-all rounded-lg"
111
+ onClick={() => !isLoading && handleRemoveChecklistItem(item.id)}
112
+ >
113
+ <X size={14} />
114
+ </Button>
115
+ </motion.div>
116
+ ))}
117
+ </div>
118
+
119
+ <div className="flex gap-4 p-2.5 bg-secondary/5 rounded-2xl border border-border/40 focus-within:border-primary/40 transition-all shadow-inner">
120
+ <Input
121
+ value={newChecklistItem}
122
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
123
+ !isLoading && setNewChecklistItem(e.target.value)
124
+ }
125
+ disabled={isLoading}
126
+ placeholder="Add validation gate..."
127
+ className="h-10 bg-transparent border-none focus:ring-0 text-sm font-bold placeholder:font-black placeholder:uppercase placeholder:text-[10px] placeholder:tracking-widest"
128
+ onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
129
+ if (e.key === "Enter" && !isLoading) handleAddChecklistItem();
130
+ }}
131
+ />
132
+ <Button
133
+ onClick={handleAddChecklistItem}
134
+ disabled={!newChecklistItem.trim() || isLoading}
135
+ className="px-6 h-10 bg-foreground text-background font-black text-[10px] uppercase tracking-widest hover:scale-105 active:scale-95 transition-all rounded-xl"
136
+ >
137
+ Append
138
+ </Button>
139
+ </div>
140
+ </div>
141
+ );
142
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Task Description Component
3
+ *
4
+ * Displays and allows editing of task title and description.
5
+ * Supports markdown editing and preview modes.
6
+ * Integrates with useTaskPanel for optimistic updates.
7
+ *
8
+ * @example
9
+ * <TaskDescription task={task} onUpdate={handleUpdate} isLoading={isLoading} />
10
+ */
11
+
12
+ "use client";
13
+
14
+ import { type Task } from "@locusai/shared";
15
+ import { Edit, FileText, Loader2 } from "lucide-react";
16
+ import { SectionLabel } from "@/components/typography";
17
+ import { Input, Textarea } from "@/components/ui";
18
+ import { useTaskDescription } from "@/hooks/useTaskDescription";
19
+ import { cn } from "@/lib/utils";
20
+
21
+ interface TaskDescriptionProps {
22
+ /** Task to display */
23
+ task: Task;
24
+ /** Whether a task mutation is loading */
25
+ isLoading?: boolean;
26
+ /** Callback when task is updated */
27
+ onUpdate: (updates: Partial<Task>) => void;
28
+ }
29
+
30
+ /**
31
+ * Task Description Component
32
+ *
33
+ * Features:
34
+ * - Editable title with inline editing
35
+ * - Description with markdown support
36
+ * - Preview/Edit mode toggle
37
+ * - Auto-save on blur
38
+ * - Loading states during mutations
39
+ *
40
+ * @component
41
+ */
42
+ export function TaskDescription({
43
+ task,
44
+ isLoading = false,
45
+ onUpdate,
46
+ }: TaskDescriptionProps) {
47
+ const {
48
+ isEditingTitle,
49
+ editTitle,
50
+ setIsEditingTitle,
51
+ setEditTitle,
52
+ handleTitleSave,
53
+ editDesc,
54
+ setEditDesc,
55
+ handleDescSave,
56
+ descMode,
57
+ setDescMode,
58
+ } = useTaskDescription({ task, onUpdate });
59
+
60
+ return (
61
+ <div className="p-8 overflow-y-auto scrollbar-thin">
62
+ {/* Title Section */}
63
+ <div className="mb-8">
64
+ {isEditingTitle ? (
65
+ <div className="relative">
66
+ <Input
67
+ value={editTitle}
68
+ onChange={(e) => setEditTitle(e.target.value)}
69
+ onBlur={handleTitleSave}
70
+ disabled={isLoading}
71
+ onKeyDown={(e) => {
72
+ if (e.key === "Enter" && !isLoading) handleTitleSave();
73
+ if (e.key === "Escape") {
74
+ setEditTitle(task.title);
75
+ setIsEditingTitle(false);
76
+ }
77
+ }}
78
+ className="text-3xl h-16 font-black tracking-tight bg-secondary/20 border-primary/20 rounded-2xl px-6"
79
+ autoFocus
80
+ aria-label="Edit task title"
81
+ />
82
+ {isLoading && (
83
+ <Loader2
84
+ size={20}
85
+ className="absolute right-4 top-1/2 -translate-y-1/2 animate-spin text-primary"
86
+ aria-hidden="true"
87
+ />
88
+ )}
89
+ </div>
90
+ ) : (
91
+ <h2
92
+ className={cn(
93
+ "text-3xl font-black tracking-tight transition-all cursor-pointer leading-tight group flex items-start",
94
+ isLoading ? "opacity-50" : "hover:text-primary"
95
+ )}
96
+ onClick={() => !isLoading && setIsEditingTitle(true)}
97
+ onKeyDown={(e) => {
98
+ if ((e.key === "Enter" || e.key === " ") && !isLoading) {
99
+ setIsEditingTitle(true);
100
+ }
101
+ }}
102
+ aria-label="Task title - click to edit"
103
+ >
104
+ {task.title}
105
+ {!isLoading && (
106
+ <Edit
107
+ size={18}
108
+ className="ml-4 mt-1.5 opacity-0 group-hover:opacity-40 transition-opacity"
109
+ aria-hidden="true"
110
+ />
111
+ )}
112
+ </h2>
113
+ )}
114
+ </div>
115
+
116
+ {/* Description Section */}
117
+ <div className="mb-10">
118
+ <div className="flex items-center justify-between mb-5">
119
+ <div className="flex items-center gap-3">
120
+ <div className="h-8 w-8 rounded-xl bg-primary/10 flex items-center justify-center text-primary group-hover:bg-primary group-hover:text-primary-foreground transition-all">
121
+ <FileText size={16} aria-hidden="true" />
122
+ </div>
123
+ <SectionLabel as="h4">Technical Documentation</SectionLabel>
124
+ </div>
125
+ <div className="flex bg-secondary/40 p-1 rounded-xl border border-border/20 shadow-inner">
126
+ <button
127
+ className={cn(
128
+ "px-4 py-1.5 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all",
129
+ descMode === "preview"
130
+ ? "bg-background shadow-md text-primary scale-105"
131
+ : "text-muted-foreground hover:text-foreground",
132
+ isLoading && "opacity-50 cursor-not-allowed"
133
+ )}
134
+ onClick={() => !isLoading && setDescMode("preview")}
135
+ disabled={isLoading}
136
+ aria-pressed={descMode === "preview"}
137
+ >
138
+ Visual
139
+ </button>
140
+ <button
141
+ className={cn(
142
+ "px-4 py-1.5 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all",
143
+ descMode === "edit"
144
+ ? "bg-background shadow-md text-primary scale-105"
145
+ : "text-muted-foreground hover:text-foreground",
146
+ isLoading && "opacity-50 cursor-not-allowed"
147
+ )}
148
+ onClick={() => !isLoading && setDescMode("edit")}
149
+ disabled={isLoading}
150
+ aria-pressed={descMode === "edit"}
151
+ >
152
+ Markdown
153
+ </button>
154
+ </div>
155
+ </div>
156
+
157
+ {descMode === "edit" ? (
158
+ <div
159
+ className={cn(
160
+ "group border border-border/40 rounded-2xl overflow-hidden focus-within:ring-2 focus-within:ring-primary/20 transition-all bg-secondary/5 shadow-inner",
161
+ isLoading && "opacity-60"
162
+ )}
163
+ >
164
+ <Textarea
165
+ value={editDesc}
166
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
167
+ !isLoading && setEditDesc(e.target.value)
168
+ }
169
+ disabled={isLoading}
170
+ placeholder="Define implementation architecture, requirements, and scope..."
171
+ rows={12}
172
+ className="border-none focus:ring-0 text-base leading-relaxed p-8 bg-transparent scrollbar-thin"
173
+ onBlur={handleDescSave}
174
+ aria-label="Task description (markdown)"
175
+ />
176
+ </div>
177
+ ) : (
178
+ <div
179
+ className={cn(
180
+ "prose prose-invert max-w-none bg-secondary/10 p-10 rounded-3xl border border-border/40 shadow-[inset_0_2px_10px_rgba(0,0,0,0.1)] relative group",
181
+ isLoading && "opacity-60 pointer-events-none"
182
+ )}
183
+ >
184
+ {task.description ? (
185
+ <p className="text-foreground/80 leading-relaxed whitespace-pre-wrap font-medium">
186
+ {task.description}
187
+ </p>
188
+ ) : (
189
+ <div className="flex flex-col items-center justify-center py-10 opacity-30 select-none">
190
+ <FileText size={32} className="mb-4" aria-hidden="true" />
191
+ <span className="text-xs font-black uppercase tracking-[0.2em]">
192
+ Waiting for Specs
193
+ </span>
194
+ </div>
195
+ )}
196
+ </div>
197
+ )}
198
+ </div>
199
+ </div>
200
+ );
201
+ }
@@ -0,0 +1,137 @@
1
+ "use client";
2
+
3
+ import { type Task } from "@locusai/shared";
4
+ import { FileText, Link, Plus, Trash2, X } from "lucide-react";
5
+ import { useState } from "react";
6
+ import { useDocsQuery } from "@/hooks/useDocsQuery";
7
+ import { cn } from "@/lib/utils";
8
+ import {
9
+ EmptyStateText,
10
+ MetadataText,
11
+ SecondaryText,
12
+ SectionLabel,
13
+ } from "../typography";
14
+ import { Button } from "../ui";
15
+
16
+ interface TaskDocsProps {
17
+ /** Task to manage documents for */
18
+ task: Task;
19
+ /** Called when linking a document */
20
+ onLinkDoc: (docId: string) => void;
21
+ /** Called when unlinking a document */
22
+ onUnlinkDoc: (docId: string) => void;
23
+ }
24
+
25
+ /**
26
+ * Task Docs Component
27
+ *
28
+ * Displays and manages linked documentation for a task.
29
+ * Allows linking/unlinking documents from the knowledge base.
30
+ * Uses standardized typography components for consistent text styling.
31
+ *
32
+ * @component
33
+ */
34
+ export function TaskDocs({ task, onLinkDoc, onUnlinkDoc }: TaskDocsProps) {
35
+ const [isLinking, setIsLinking] = useState(false);
36
+ const { data: allDocs = [] } = useDocsQuery();
37
+
38
+ const linkedDocs =
39
+ (task as Task & { docs: { id: string; title: string }[] }).docs || [];
40
+ const linkedDocIds = linkedDocs.map((d: { id: string }) => d.id);
41
+ const availableDocs = allDocs.filter((d) => !linkedDocIds.includes(d.id));
42
+
43
+ return (
44
+ <div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
45
+ <div className="flex items-center justify-between px-1">
46
+ <SectionLabel as="h3">Knowledge base</SectionLabel>
47
+ <Button
48
+ variant="ghost"
49
+ size="sm"
50
+ onClick={() => setIsLinking(!isLinking)}
51
+ className={cn(
52
+ "h-7 px-2 text-[9px] font-black uppercase tracking-widest gap-1.5 transition-all rounded-lg",
53
+ isLinking
54
+ ? "bg-primary/20 text-primary"
55
+ : "hover:bg-primary/10 hover:text-primary opacity-60 hover:opacity-100"
56
+ )}
57
+ >
58
+ {isLinking ? <X size={12} /> : <Plus size={12} />}
59
+ {isLinking ? "Cancel" : "Link Doc"}
60
+ </Button>
61
+ </div>
62
+
63
+ {isLinking && (
64
+ <div className="p-3 bg-primary/5 border border-primary/20 rounded-2xl animate-in zoom-in-95 duration-200">
65
+ <SectionLabel className="mb-2 px-1 text-primary">
66
+ Available Nodes
67
+ </SectionLabel>
68
+ {availableDocs.length > 0 ? (
69
+ <div className="max-h-[200px] overflow-y-auto pr-1 space-y-1 scrollbar-thin">
70
+ {availableDocs.map((doc) => (
71
+ <button
72
+ key={doc.id}
73
+ onClick={() => {
74
+ onLinkDoc(doc.id);
75
+ setIsLinking(false);
76
+ }}
77
+ className="flex items-center gap-3 w-full p-2.5 text-xs font-bold rounded-xl bg-card border border-border/40 hover:border-primary/40 hover:bg-primary/5 transition-all text-left group"
78
+ >
79
+ <FileText
80
+ size={14}
81
+ className="text-muted-foreground group-hover:text-primary transition-colors"
82
+ />
83
+ <span className="flex-1 truncate uppercase tracking-tight opacity-80 group-hover:opacity-100 transition-opacity">
84
+ {doc.title}
85
+ </span>
86
+ <Link
87
+ size={12}
88
+ className="opacity-0 group-hover:opacity-40"
89
+ />
90
+ </button>
91
+ ))}
92
+ </div>
93
+ ) : (
94
+ <div className="py-6 text-center">
95
+ <SecondaryText size="xs" className="italic">
96
+ No Unlinked Nodes
97
+ </SecondaryText>
98
+ </div>
99
+ )}
100
+ </div>
101
+ )}
102
+
103
+ <div className="space-y-2 px-1">
104
+ {linkedDocs.length > 0
105
+ ? linkedDocs.map((doc: { id: string; title: string }) => (
106
+ <div
107
+ key={doc.id}
108
+ className="flex items-center gap-3 p-3 rounded-2xl bg-secondary/20 border border-border/20 hover:border-border/60 transition-all group"
109
+ >
110
+ <div className="w-8 h-8 flex items-center justify-center rounded-xl bg-primary/10 text-primary border border-primary/20">
111
+ <FileText size={16} />
112
+ </div>
113
+ <div className="flex-1 min-w-0">
114
+ <div className="text-[11px] font-black uppercase tracking-wider text-foreground/90 truncate">
115
+ {doc.title}
116
+ </div>
117
+ <MetadataText size="xs">
118
+ Document ID: {doc.id.split("-")[0]}
119
+ </MetadataText>
120
+ </div>
121
+ <button
122
+ onClick={() => onUnlinkDoc(doc.id)}
123
+ className="p-1.5 text-muted-foreground/40 hover:text-destructive transition-colors opacity-0 group-hover:opacity-100"
124
+ >
125
+ <Trash2 size={14} />
126
+ </button>
127
+ </div>
128
+ ))
129
+ : !isLinking && (
130
+ <EmptyStateText icon={<Link size={24} />}>
131
+ No Linked Documents
132
+ </EmptyStateText>
133
+ )}
134
+ </div>
135
+ </div>
136
+ );
137
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Task Header Component
3
+ *
4
+ * Displays task metadata and action buttons in the panel header.
5
+ * Shows task ID, status, priority, and contextual actions.
6
+ *
7
+ * @example
8
+ * <TaskHeader
9
+ * task={task}
10
+ * onClose={handleClose}
11
+ * onDelete={handleDelete}
12
+ * onApprove={handleApprove}
13
+ * onReject={handleReject}
14
+ * />
15
+ */
16
+
17
+ "use client";
18
+
19
+ import { type Task, TaskPriority, TaskStatus } from "@locusai/shared";
20
+ import { CheckCircle, ChevronRight, Trash2 } from "lucide-react";
21
+ import { MetadataText } from "@/components/typography";
22
+ import { Button, PriorityBadge, StatusBadge } from "@/components/ui";
23
+
24
+ interface TaskHeaderProps {
25
+ /** Task to display in header */
26
+ task: Task;
27
+ /** Whether a task mutation is loading */
28
+ isLoading?: boolean;
29
+ /** Whether task deletion is in progress */
30
+ isDeleting?: boolean;
31
+ /** Callback when closing panel */
32
+ onClose: () => void;
33
+ /** Callback to delete task */
34
+ onDelete: () => void;
35
+ /** Callback to approve task (if in verification) */
36
+ onApprove: () => void;
37
+ /** Callback to reject task (if in verification) */
38
+ onReject: () => void;
39
+ }
40
+
41
+ /**
42
+ * Task Header Component
43
+ *
44
+ * Features:
45
+ * - Task ID reference
46
+ * - Status and priority badges
47
+ * - Approve/reject buttons (if in verification status)
48
+ * - Delete button
49
+ * - Close button
50
+ *
51
+ * @component
52
+ */
53
+ export function TaskHeader({
54
+ task,
55
+ isLoading = false,
56
+ isDeleting = false,
57
+ onClose,
58
+ onDelete,
59
+ onApprove,
60
+ onReject,
61
+ }: TaskHeaderProps) {
62
+ const canApproveReject = task.status === TaskStatus.VERIFICATION;
63
+
64
+ return (
65
+ <header className="flex items-center gap-6 px-10 border-b border-border bg-card/50 backdrop-blur-md h-[84px] shrink-0">
66
+ <button
67
+ className="p-2.5 rounded-xl text-muted-foreground hover:bg-secondary hover:text-foreground hover:scale-105 transition-all duration-200 border border-transparent hover:border-border"
68
+ onClick={onClose}
69
+ aria-label="Close task panel"
70
+ >
71
+ <ChevronRight size={20} aria-hidden="true" />
72
+ </button>
73
+
74
+ <div className="flex-1 min-w-0">
75
+ <MetadataText size="sm" className="mb-1.5 block">
76
+ Reference: #{task.id}
77
+ </MetadataText>
78
+ <div className="flex gap-3">
79
+ <StatusBadge status={task.status} />
80
+ <PriorityBadge priority={task.priority || TaskPriority.MEDIUM} />
81
+ </div>
82
+ </div>
83
+
84
+ <div className="flex items-center gap-2">
85
+ {canApproveReject && (
86
+ <>
87
+ <Button
88
+ variant="danger"
89
+ size="sm"
90
+ onClick={onReject}
91
+ disabled={isLoading}
92
+ className="h-10 px-5 rounded-xl font-black uppercase tracking-widest text-[10px]"
93
+ aria-label="Reject task"
94
+ >
95
+ Reject
96
+ </Button>
97
+ <Button
98
+ size="sm"
99
+ variant="success"
100
+ onClick={onApprove}
101
+ disabled={isLoading}
102
+ className="h-10 px-5 rounded-xl font-black uppercase tracking-widest text-[10px]"
103
+ aria-label="Approve task"
104
+ >
105
+ <CheckCircle size={16} className="mr-2" aria-hidden="true" />
106
+ Approve
107
+ </Button>
108
+ <div className="w-px h-6 bg-border mx-2" aria-hidden="true" />
109
+ </>
110
+ )}
111
+ <Button
112
+ size="icon"
113
+ variant="danger"
114
+ onClick={onDelete}
115
+ disabled={isDeleting}
116
+ title="Delete"
117
+ className="h-10 w-10 hover:scale-105 active:scale-95 transition-transform rounded-xl"
118
+ aria-label="Delete task"
119
+ >
120
+ <Trash2 size={18} aria-hidden="true" />
121
+ </Button>
122
+ </div>
123
+ </header>
124
+ );
125
+ }