@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
@@ -1,47 +1,49 @@
1
+ /**
2
+ * Task Create Modal Component
3
+ *
4
+ * Modal dialog for creating new tasks with full configuration.
5
+ * Includes title, description, priority, status, assignee, and sprint selection.
6
+ *
7
+ * @example
8
+ * <TaskCreateModal
9
+ * isOpen={isOpen}
10
+ * onClose={handleClose}
11
+ * onCreated={handleCreated}
12
+ * initialStatus={TaskStatus.BACKLOG}
13
+ * defaultSprintId={sprintId}
14
+ * />
15
+ */
16
+
1
17
  "use client";
2
18
 
3
19
  import { AssigneeRole, TaskPriority, TaskStatus } from "@locusai/shared";
4
20
  import { Plus, X } from "lucide-react";
5
21
  import { useState } from "react";
6
- import { Button, Dropdown, Input, Modal, Textarea } from "@/components/ui";
7
- import { taskService } from "@/services";
22
+ import { CreateModal } from "@/components/CreateModal";
23
+ import { Button, Dropdown, Input, Textarea } from "@/components/ui";
24
+ import { useWorkspaceId } from "@/hooks";
25
+ import { useMutationWithToast } from "@/hooks/useMutationWithToast";
26
+ import { locusClient } from "@/lib/api-client";
27
+ import {
28
+ getAssigneeOptions,
29
+ getPriorityOptions,
30
+ getStatusOptions,
31
+ } from "@/lib/options";
32
+ import { queryKeys } from "@/lib/query-keys";
8
33
 
9
34
  interface TaskCreateModalProps {
35
+ /** Whether modal is open */
10
36
  isOpen: boolean;
37
+ /** Called to close modal */
11
38
  onClose: () => void;
39
+ /** Called after successful creation */
12
40
  onCreated: () => void;
41
+ /** Initial task status */
13
42
  initialStatus?: TaskStatus;
14
- sprintId?: number | null;
15
- }
16
-
17
- const STATUS_OPTIONS = Object.values(TaskStatus).map((status) => ({
18
- value: status,
19
- label: status.replace(/_/g, " "),
20
- color: getStatusColor(status),
21
- }));
22
-
23
- const PRIORITY_OPTIONS = [
24
- { value: TaskPriority.LOW, label: "Low", color: "var(--text-muted)" },
25
- { value: TaskPriority.MEDIUM, label: "Medium", color: "#38bdf8" },
26
- { value: TaskPriority.HIGH, label: "High", color: "#f59e0b" },
27
- { value: TaskPriority.CRITICAL, label: "Critical", color: "#ef4444" },
28
- ];
29
-
30
- const ASSIGNEE_OPTIONS = Object.values(AssigneeRole).map((role) => ({
31
- value: role,
32
- label: role.charAt(0) + role.slice(1).toLowerCase(),
33
- }));
34
-
35
- function getStatusColor(status: TaskStatus): string {
36
- const colors: Record<TaskStatus, string> = {
37
- [TaskStatus.BACKLOG]: "#64748b",
38
- [TaskStatus.IN_PROGRESS]: "#f59e0b",
39
- [TaskStatus.REVIEW]: "#a855f7",
40
- [TaskStatus.VERIFICATION]: "#38bdf8",
41
- [TaskStatus.DONE]: "#10b981",
42
- [TaskStatus.BLOCKED]: "#ef4444",
43
- };
44
- return colors[status];
43
+ /** Sprint ID if creating from sprint */
44
+ sprintId?: string | null;
45
+ /** Default sprint ID to assign */
46
+ defaultSprintId?: string;
45
47
  }
46
48
 
47
49
  export function TaskCreateModal({
@@ -50,7 +52,9 @@ export function TaskCreateModal({
50
52
  onCreated,
51
53
  initialStatus = TaskStatus.BACKLOG,
52
54
  sprintId = null,
55
+ defaultSprintId = undefined,
53
56
  }: TaskCreateModalProps) {
57
+ const workspaceId = useWorkspaceId();
54
58
  const [title, setTitle] = useState("");
55
59
  const [description, setDescription] = useState("");
56
60
  const [status, setStatus] = useState<TaskStatus>(initialStatus);
@@ -58,7 +62,18 @@ export function TaskCreateModal({
58
62
  const [assigneeRole, setAssigneeRole] = useState<AssigneeRole | undefined>();
59
63
  const [labels, setLabels] = useState<string[]>([]);
60
64
  const [labelInput, setLabelInput] = useState("");
61
- const [isSubmitting, setIsSubmitting] = useState(false);
65
+
66
+ const createTaskMutation = useMutationWithToast({
67
+ mutationFn: (data: Parameters<typeof locusClient.tasks.create>[1]) =>
68
+ locusClient.tasks.create(workspaceId, data),
69
+ successMessage: "Task created successfully",
70
+ invalidateKeys: [queryKeys.tasks.all()],
71
+ onSuccess: () => {
72
+ resetForm();
73
+ onClose();
74
+ onCreated();
75
+ },
76
+ });
62
77
 
63
78
  const resetForm = () => {
64
79
  setTitle("");
@@ -70,11 +85,6 @@ export function TaskCreateModal({
70
85
  setLabelInput("");
71
86
  };
72
87
 
73
- const handleClose = () => {
74
- resetForm();
75
- onClose();
76
- };
77
-
78
88
  const handleAddLabel = () => {
79
89
  const trimmedLabel = labelInput.trim();
80
90
  if (trimmedLabel && !labels.includes(trimmedLabel)) {
@@ -91,153 +101,146 @@ export function TaskCreateModal({
91
101
  e.preventDefault();
92
102
  if (!title.trim()) return;
93
103
 
94
- setIsSubmitting(true);
95
- try {
96
- await taskService.create({
97
- title: title.trim(),
98
- description,
99
- status,
100
- priority,
101
- labels,
102
- assigneeRole,
103
- sprintId,
104
- });
104
+ createTaskMutation.mutate({
105
+ title: title.trim(),
106
+ description,
107
+ status,
108
+ priority,
109
+ labels,
110
+ assigneeRole,
111
+ sprintId: defaultSprintId || sprintId || undefined,
112
+ });
113
+ };
105
114
 
106
- handleClose();
107
- onCreated();
108
- } catch (err) {
109
- console.error("Failed to create task:", err);
110
- } finally {
111
- setIsSubmitting(false);
112
- }
115
+ const handleClose = () => {
116
+ resetForm();
117
+ onClose();
113
118
  };
114
119
 
120
+ const labelFieldComponent = (
121
+ <div className="space-y-4">
122
+ <div className="flex flex-wrap gap-2 min-h-[32px]">
123
+ {labels.length === 0 && (
124
+ <span className="text-xs text-muted-foreground/50 italic py-1">
125
+ No labels added...
126
+ </span>
127
+ )}
128
+ {labels.map((label) => (
129
+ <span
130
+ key={label}
131
+ className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-secondary text-secondary-foreground text-[11px] font-semibold border shadow-sm transition-all hover:bg-secondary/80 translate-y-0 hover:-translate-y-0.5"
132
+ >
133
+ {label}
134
+ <button
135
+ type="button"
136
+ onClick={() => handleRemoveLabel(label)}
137
+ className="text-muted-foreground hover:text-destructive transition-colors shrink-0"
138
+ >
139
+ <X size={12} />
140
+ </button>
141
+ </span>
142
+ ))}
143
+ </div>
144
+ <div className="flex gap-3">
145
+ <Input
146
+ value={labelInput}
147
+ onChange={(e) => setLabelInput(e.target.value)}
148
+ onKeyDown={(e) => {
149
+ if (e.key === "Enter") {
150
+ e.preventDefault();
151
+ handleAddLabel();
152
+ }
153
+ }}
154
+ placeholder="Add labels (e.g. Bug, Feature)..."
155
+ className="flex-1 h-10"
156
+ />
157
+ <Button
158
+ type="button"
159
+ onClick={handleAddLabel}
160
+ variant="secondary"
161
+ size="icon"
162
+ disabled={!labelInput.trim()}
163
+ className="h-10 w-10 shrink-0"
164
+ >
165
+ <Plus size={18} />
166
+ </Button>
167
+ </div>
168
+ </div>
169
+ );
170
+
171
+ const dropdownsFieldComponent = (
172
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
173
+ <Dropdown<TaskStatus>
174
+ label="Initial Status"
175
+ value={status}
176
+ onChange={setStatus}
177
+ options={getStatusOptions()}
178
+ />
179
+ <Dropdown<TaskPriority>
180
+ label="Priority Level"
181
+ value={priority}
182
+ onChange={setPriority}
183
+ options={getPriorityOptions()}
184
+ />
185
+ <Dropdown<AssigneeRole>
186
+ label="Primary Assignee"
187
+ value={assigneeRole}
188
+ onChange={setAssigneeRole}
189
+ options={getAssigneeOptions()}
190
+ placeholder="Unassigned"
191
+ />
192
+ </div>
193
+ );
194
+
115
195
  return (
116
- <Modal
196
+ <CreateModal
117
197
  isOpen={isOpen}
118
- onClose={handleClose}
119
198
  title="Create New Task"
120
199
  size="lg"
121
- >
122
- <form onSubmit={handleSubmit} className="space-y-8 py-2">
123
- <div className="space-y-3">
124
- <label className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground ml-1">
125
- Task Title <span className="text-destructive">*</span>
126
- </label>
127
- <Input
128
- value={title}
129
- onChange={(e) => setTitle(e.target.value)}
130
- placeholder="e.g. Implement authentication flow"
131
- autoFocus
132
- className="text-lg font-medium h-12"
133
- />
134
- </div>
135
-
136
- <div className="space-y-3">
137
- <label className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground ml-1">
138
- Description
139
- </label>
140
- <Textarea
141
- value={description}
142
- onChange={(e) => setDescription(e.target.value)}
143
- placeholder="Provide context for this task..."
144
- rows={5}
145
- className="resize-none"
146
- />
147
- </div>
148
-
149
- <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
150
- <Dropdown
151
- label="Initial Status"
152
- value={status}
153
- onChange={setStatus}
154
- options={STATUS_OPTIONS}
155
- />
156
- <Dropdown
157
- label="Priority Level"
158
- value={priority}
159
- onChange={setPriority}
160
- options={PRIORITY_OPTIONS}
161
- />
162
- <Dropdown
163
- label="Primary Assignee"
164
- value={assigneeRole}
165
- onChange={setAssigneeRole}
166
- options={ASSIGNEE_OPTIONS}
167
- placeholder="Unassigned"
168
- />
169
- </div>
170
-
171
- <div className="space-y-4">
172
- <label className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground ml-1">
173
- Task Labels
174
- </label>
175
- <div className="flex flex-wrap gap-2 min-h-[32px]">
176
- {labels.length === 0 && (
177
- <span className="text-xs text-muted-foreground/50 italic py-1">
178
- No labels added...
179
- </span>
180
- )}
181
- {labels.map((label) => (
182
- <span
183
- key={label}
184
- className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-secondary text-secondary-foreground text-[11px] font-semibold border shadow-sm transition-all hover:bg-secondary/80 translate-y-0 hover:-translate-y-0.5"
185
- >
186
- {label}
187
- <button
188
- type="button"
189
- onClick={() => handleRemoveLabel(label)}
190
- className="text-muted-foreground hover:text-destructive transition-colors shrink-0"
191
- >
192
- <X size={12} />
193
- </button>
194
- </span>
195
- ))}
196
- </div>
197
- <div className="flex gap-3">
200
+ fields={[
201
+ {
202
+ name: "title",
203
+ label: "Task Title",
204
+ component: (
198
205
  <Input
199
- value={labelInput}
200
- onChange={(e) => setLabelInput(e.target.value)}
201
- onKeyDown={(e) => {
202
- if (e.key === "Enter") {
203
- e.preventDefault();
204
- handleAddLabel();
205
- }
206
- }}
207
- placeholder="Add labels (e.g. Bug, Feature)..."
208
- className="flex-1 h-10"
206
+ value={title}
207
+ onChange={(e) => setTitle(e.target.value)}
208
+ placeholder="e.g. Implement authentication flow"
209
+ autoFocus
210
+ className="text-lg font-medium h-12"
209
211
  />
210
- <Button
211
- type="button"
212
- onClick={handleAddLabel}
213
- variant="secondary"
214
- size="icon"
215
- disabled={!labelInput.trim()}
216
- className="h-10 w-10 shrink-0"
217
- >
218
- <Plus size={18} />
219
- </Button>
220
- </div>
221
- </div>
222
-
223
- <div className="flex justify-end gap-3 pt-6 border-t mt-4">
224
- <Button
225
- type="button"
226
- onClick={handleClose}
227
- variant="ghost"
228
- className="px-6"
229
- >
230
- Discard
231
- </Button>
232
- <Button
233
- type="submit"
234
- disabled={!title.trim() || isSubmitting}
235
- className="px-8 shadow-lg shadow-primary/10"
236
- >
237
- {isSubmitting ? "Creating..." : "Create Task"}
238
- </Button>
239
- </div>
240
- </form>
241
- </Modal>
212
+ ),
213
+ required: true,
214
+ },
215
+ {
216
+ name: "description",
217
+ label: "Description",
218
+ component: (
219
+ <Textarea
220
+ value={description}
221
+ onChange={(e) => setDescription(e.target.value)}
222
+ placeholder="Provide context for this task..."
223
+ rows={5}
224
+ className="resize-none"
225
+ />
226
+ ),
227
+ },
228
+ {
229
+ name: "properties",
230
+ label: "Task Properties",
231
+ component: dropdownsFieldComponent,
232
+ },
233
+ {
234
+ name: "labels",
235
+ label: "Task Labels",
236
+ component: labelFieldComponent,
237
+ },
238
+ ]}
239
+ onSubmit={handleSubmit}
240
+ onClose={handleClose}
241
+ submitText="Create Task"
242
+ isPending={createTaskMutation.isPending}
243
+ submitDisabled={!title.trim()}
244
+ />
242
245
  );
243
246
  }