@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,108 @@
1
+ /**
2
+ * UI Component Constants
3
+ *
4
+ * Centralized constants for UI components including sizes, variants, and styling
5
+ * This ensures consistency across all UI components and makes maintenance easier.
6
+ */
7
+
8
+ /**
9
+ * Button component sizes and their corresponding Tailwind classes
10
+ */
11
+ export const BUTTON_SIZES = {
12
+ sm: "h-8 px-3 text-[10px] uppercase tracking-wider",
13
+ md: "h-10 px-5 text-sm",
14
+ lg: "h-12 px-8 text-base",
15
+ icon: "h-10 w-10 p-0",
16
+ } as const;
17
+
18
+ /**
19
+ * Button component variants and their styling
20
+ */
21
+ export const BUTTON_VARIANTS = {
22
+ primary:
23
+ "bg-primary text-primary-foreground shadow-sm hover:translate-y-[-1px] hover:shadow-md",
24
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
25
+ subtle: "bg-primary/10 text-primary hover:bg-primary/20",
26
+ outline: "border border-border bg-transparent hover:bg-secondary/50",
27
+ ghost: "hover:bg-secondary text-muted-foreground hover:text-foreground",
28
+ danger: "bg-red-500/10 text-red-500 hover:bg-red-500/20",
29
+ success: "bg-emerald-500/10 text-emerald-600 hover:bg-emerald-500/20",
30
+ emerald: "bg-emerald-500 text-white hover:bg-emerald-600",
31
+ amber: "bg-amber-500 text-black hover:bg-amber-600 font-bold",
32
+ "emerald-subtle":
33
+ "bg-emerald-500/10 text-emerald-600 hover:bg-emerald-500/20 border border-emerald-500/20",
34
+ "amber-subtle":
35
+ "bg-amber-500/10 text-amber-600 hover:bg-amber-500/20 border border-amber-500/20",
36
+ } as const;
37
+
38
+ /**
39
+ * Badge component sizes
40
+ */
41
+ export const BADGE_SIZES = {
42
+ sm: "px-2 py-1 text-xs",
43
+ md: "px-3 py-1.5 text-sm",
44
+ lg: "px-4 py-2 text-base",
45
+ } as const;
46
+
47
+ /**
48
+ * Badge component variants
49
+ */
50
+ export const BADGE_VARIANTS = {
51
+ default: "bg-primary/10 text-primary border border-primary/20",
52
+ success: "bg-emerald-500/10 text-emerald-600 border border-emerald-500/20",
53
+ warning: "bg-amber-500/10 text-amber-600 border border-amber-500/20",
54
+ danger: "bg-red-500/10 text-red-500 border border-red-500/20",
55
+ info: "bg-blue-500/10 text-blue-600 border border-blue-500/20",
56
+ } as const;
57
+
58
+ /**
59
+ * Modal sizes
60
+ */
61
+ export const MODAL_SIZES = {
62
+ sm: "w-[400px]",
63
+ md: "w-[520px]",
64
+ lg: "w-[680px]",
65
+ } as const;
66
+
67
+ /**
68
+ * Spinner sizes
69
+ */
70
+ export const SPINNER_SIZES = {
71
+ sm: "w-4 h-4",
72
+ md: "w-6 h-6",
73
+ lg: "w-8 h-8",
74
+ xl: "w-12 h-12",
75
+ } as const;
76
+
77
+ /**
78
+ * Z-index scale for proper layering
79
+ */
80
+ export const Z_INDEXES = {
81
+ base: "z-0",
82
+ dropdown: "z-100",
83
+ sticky: "z-500",
84
+ fixed: "z-900",
85
+ modalOverlay: "z-940",
86
+ modal: "z-950",
87
+ tooltip: "z-1000",
88
+ notification: "z-1100",
89
+ } as const;
90
+
91
+ /**
92
+ * Transition durations for consistent animations
93
+ */
94
+ export const TRANSITIONS = {
95
+ fast: "duration-100",
96
+ normal: "duration-200",
97
+ slow: "duration-300",
98
+ } as const;
99
+
100
+ /**
101
+ * Border radius constants
102
+ */
103
+ export const BORDER_RADIUS = {
104
+ sm: "rounded-md",
105
+ md: "rounded-lg",
106
+ lg: "rounded-xl",
107
+ full: "rounded-full",
108
+ } as const;
@@ -1,7 +1,14 @@
1
+ export * from "./Avatar";
1
2
  export * from "./Badge";
2
3
  export * from "./Button";
3
4
  export * from "./Checkbox";
4
5
  export * from "./Dropdown";
6
+ export * from "./EmptyState";
5
7
  export * from "./Input";
6
8
  export * from "./Modal";
9
+ export * from "./OtpInput";
10
+ export * from "./Skeleton";
11
+ export * from "./Spinner";
7
12
  export * from "./Textarea";
13
+ export * from "./Toast";
14
+ export * from "./Toggle";
@@ -0,0 +1,140 @@
1
+ "use client";
2
+
3
+ import { type User, type Workspace } from "@locusai/shared";
4
+ import { useRouter } from "next/navigation";
5
+ import {
6
+ createContext,
7
+ useCallback,
8
+ useContext,
9
+ useEffect,
10
+ useState,
11
+ } from "react";
12
+ import { locusClient, setClientToken } from "@/lib/api-client";
13
+
14
+ interface AuthContextType {
15
+ user: User | null;
16
+ workspaces: Workspace[];
17
+ isLoading: boolean;
18
+ isAuthenticated: boolean;
19
+ login: (token: string, user: User) => void;
20
+ logout: () => void;
21
+ refreshUser: () => Promise<void>;
22
+ switchWorkspace: (workspaceId: string) => void;
23
+ }
24
+
25
+ const AuthContext = createContext<AuthContextType | undefined>(undefined);
26
+
27
+ export function AuthProvider({ children }: { children: React.ReactNode }) {
28
+ const [user, setUser] = useState<User | null>(null);
29
+ const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
30
+ const [isLoading, setIsLoading] = useState(true);
31
+ const router = useRouter();
32
+
33
+ const switchWorkspace = useCallback((workspaceId: string) => {
34
+ setUser((prev) => (prev ? { ...prev, workspaceId } : null));
35
+ }, []);
36
+
37
+ const refreshUser = useCallback(async () => {
38
+ try {
39
+ // Check if token exists first - skip API call if not
40
+ const token =
41
+ typeof window !== "undefined"
42
+ ? localStorage.getItem("locus_token")
43
+ : null;
44
+
45
+ if (!token) {
46
+ // No token stored, immediately set loading to false
47
+ // Layout will redirect to login
48
+ setIsLoading(false);
49
+ return;
50
+ }
51
+
52
+ const userData = await locusClient.auth.getMe();
53
+ const workspacesData = await locusClient.workspaces.listAll();
54
+
55
+ let effectiveWorkspaceId = userData.workspaceId;
56
+
57
+ // Validation: Ensure current workspaceId is valid
58
+ if (workspacesData.length > 0) {
59
+ const isValid = workspacesData.some(
60
+ (w) => w.id === effectiveWorkspaceId
61
+ );
62
+ if (!isValid) {
63
+ effectiveWorkspaceId = workspacesData[0].id;
64
+ }
65
+ }
66
+
67
+ setUser({
68
+ ...userData,
69
+ workspaceId: effectiveWorkspaceId
70
+ ? String(effectiveWorkspaceId)
71
+ : undefined,
72
+ });
73
+ setWorkspaces(workspacesData);
74
+ } catch (error) {
75
+ console.error("Failed to fetch user:", error);
76
+ setClientToken(null);
77
+ setUser(null);
78
+ setWorkspaces([]);
79
+ } finally {
80
+ setIsLoading(false);
81
+ }
82
+ }, []);
83
+
84
+ useEffect(() => {
85
+ refreshUser();
86
+ }, [refreshUser]);
87
+
88
+ const login = (token: string, userData: User) => {
89
+ setClientToken(token);
90
+ setUser(userData);
91
+
92
+ // If user doesn't have a workspace, redirect to create one
93
+ if (!userData.workspaceId) {
94
+ router.push("/onboarding/workspace");
95
+ } else {
96
+ router.push("/");
97
+ }
98
+ };
99
+
100
+ const logout = () => {
101
+ setClientToken(null);
102
+ setUser(null);
103
+ router.push("/login");
104
+ };
105
+
106
+ return (
107
+ <AuthContext.Provider
108
+ value={{
109
+ user,
110
+ workspaces,
111
+ isLoading,
112
+ isAuthenticated: !!user,
113
+ login,
114
+ logout,
115
+ refreshUser,
116
+ switchWorkspace,
117
+ }}
118
+ >
119
+ {children}
120
+ </AuthContext.Provider>
121
+ );
122
+ }
123
+
124
+ export function useAuth() {
125
+ const context = useContext(AuthContext);
126
+ if (context === undefined) {
127
+ throw new Error("useAuth must be used within an AuthProvider");
128
+ }
129
+ return context;
130
+ }
131
+
132
+ export function useSafeAuth() {
133
+ const context = useContext(AuthContext);
134
+ if (context === undefined) {
135
+ throw new Error("useAuth must be used within an AuthProvider");
136
+ }
137
+ return context as AuthContextType & {
138
+ user: User;
139
+ };
140
+ }
@@ -0,0 +1 @@
1
+ export * from "./AuthContext";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Backlog Hooks - Composite Export
3
+ *
4
+ * For backward compatibility, export all backlog hooks.
5
+ * Prefer using individual hooks for better tree-shaking and clarity.
6
+ */
7
+
8
+ export * from "./useBacklogActions";
9
+ // Composite hook for components not yet refactored
10
+ export { useBacklogComposite as useBacklog } from "./useBacklogComposite";
11
+ export * from "./useBacklogData";
12
+ export * from "./useBacklogDragDrop";
13
+ export * from "./useBacklogUI";
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Backlog Actions Hook
3
+ *
4
+ * Handles API operations for sprints and tasks (create, start, complete, delete).
5
+ */
6
+
7
+ "use client";
8
+
9
+ import { type Sprint, SprintStatus } from "@locusai/shared";
10
+ import { useQueryClient } from "@tanstack/react-query";
11
+ import { useState } from "react";
12
+ import { useWorkspaceId } from "@/hooks/useWorkspaceId";
13
+ import { locusClient } from "@/lib/api-client";
14
+ import { queryKeys } from "@/lib/query-keys";
15
+ import { notifications } from "@/services/notifications";
16
+
17
+ export interface BacklogActionsState {
18
+ isSubmitting: boolean;
19
+ }
20
+
21
+ export interface BacklogActionsHandlers {
22
+ handleCreateSprint: (name: string) => Promise<void>;
23
+ handleStartSprint: (sprintId: string) => Promise<void>;
24
+ handleCompleteSprint: (sprintId: string) => Promise<void>;
25
+ handleDeleteTask: (taskId: string) => Promise<void>;
26
+ }
27
+
28
+ interface BacklogActionsOptions {
29
+ onSprintCreated?: () => void;
30
+ onSprintStarted?: () => void;
31
+ onSprintCompleted?: () => void;
32
+ onTaskDeleted?: () => void;
33
+ }
34
+
35
+ /**
36
+ * Manage backlog operations (sprint and task actions)
37
+ */
38
+ export function useBacklogActions(
39
+ options: BacklogActionsOptions = {}
40
+ ): BacklogActionsState & BacklogActionsHandlers {
41
+ const workspaceId = useWorkspaceId();
42
+ const queryClient = useQueryClient();
43
+ const [isSubmitting, setIsSubmitting] = useState(false);
44
+
45
+ const handleCreateSprint = async (name: string) => {
46
+ try {
47
+ setIsSubmitting(true);
48
+ await locusClient.sprints.create(workspaceId, { name });
49
+ notifications.created("Sprint");
50
+ options.onSprintCreated?.();
51
+ queryClient.invalidateQueries({
52
+ queryKey: queryKeys.sprints.list(workspaceId),
53
+ });
54
+ } catch (error) {
55
+ notifications.error(
56
+ error instanceof Error ? error.message : "Failed to create sprint"
57
+ );
58
+ } finally {
59
+ setIsSubmitting(false);
60
+ }
61
+ };
62
+
63
+ const handleStartSprint = async (sprintId: string) => {
64
+ try {
65
+ setIsSubmitting(true);
66
+ await locusClient.sprints.start(sprintId, workspaceId);
67
+ notifications.success("Sprint started");
68
+ options.onSprintStarted?.();
69
+ queryClient.invalidateQueries({
70
+ queryKey: queryKeys.sprints.list(workspaceId),
71
+ });
72
+ } catch (error) {
73
+ notifications.error(
74
+ error instanceof Error ? error.message : "Failed to start sprint"
75
+ );
76
+ } finally {
77
+ setIsSubmitting(false);
78
+ }
79
+ };
80
+
81
+ const handleCompleteSprint = async (sprintId: string) => {
82
+ try {
83
+ setIsSubmitting(true);
84
+
85
+ // Optimistic update
86
+ const sprintKey = queryKeys.sprints.list(workspaceId);
87
+ const previousSprints = queryClient.getQueryData<Sprint[]>(sprintKey);
88
+
89
+ if (previousSprints) {
90
+ queryClient.setQueryData<Sprint[]>(
91
+ sprintKey,
92
+ previousSprints.map((s) =>
93
+ s.id === sprintId
94
+ ? {
95
+ ...s,
96
+ status: SprintStatus.COMPLETED,
97
+ endDate: Date.now(),
98
+ }
99
+ : s
100
+ )
101
+ );
102
+ }
103
+
104
+ await locusClient.sprints.complete(sprintId, workspaceId);
105
+ notifications.success("Sprint completed");
106
+ options.onSprintCompleted?.();
107
+
108
+ // Full sync
109
+ queryClient.invalidateQueries({ queryKey: sprintKey });
110
+ queryClient.invalidateQueries({
111
+ queryKey: queryKeys.tasks.list(workspaceId),
112
+ });
113
+ } catch (error) {
114
+ notifications.error(
115
+ error instanceof Error ? error.message : "Failed to complete sprint"
116
+ );
117
+ } finally {
118
+ setIsSubmitting(false);
119
+ }
120
+ };
121
+
122
+ const handleDeleteTask = async (taskId: string) => {
123
+ try {
124
+ await locusClient.tasks.delete(taskId, workspaceId);
125
+ notifications.deleted();
126
+ options.onTaskDeleted?.();
127
+ queryClient.invalidateQueries({
128
+ queryKey: queryKeys.tasks.list(workspaceId),
129
+ });
130
+ } catch (error) {
131
+ notifications.error(
132
+ error instanceof Error ? error.message : "Failed to delete task"
133
+ );
134
+ }
135
+ };
136
+
137
+ return {
138
+ isSubmitting,
139
+ handleCreateSprint,
140
+ handleStartSprint,
141
+ handleCompleteSprint,
142
+ handleDeleteTask,
143
+ };
144
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Backlog Composite Hook
3
+ *
4
+ * Combines all backlog hooks for backward compatibility.
5
+ * Prefer using individual hooks for new code.
6
+ */
7
+
8
+ "use client";
9
+
10
+ import { useBacklogActions } from "./useBacklogActions";
11
+ import { useBacklogData } from "./useBacklogData";
12
+ import { useBacklogDragDrop } from "./useBacklogDragDrop";
13
+ import { useBacklogUI } from "./useBacklogUI";
14
+
15
+ /**
16
+ * All-in-one backlog hook (kept for backward compatibility)
17
+ *
18
+ * @deprecated Prefer using individual hooks:
19
+ * - useBacklogUI for UI state
20
+ * - useBacklogData for data
21
+ * - useBacklogDragDrop for drag operations
22
+ * - useBacklogActions for API calls
23
+ */
24
+ export function useBacklogComposite() {
25
+ const ui = useBacklogUI();
26
+ const data = useBacklogData();
27
+ const dragDrop = useBacklogDragDrop(data.tasks);
28
+ const actions = useBacklogActions({
29
+ onSprintCreated: () => {
30
+ ui.setIsSprintModalOpen(false);
31
+ data.refetchSprints();
32
+ },
33
+ });
34
+
35
+ return {
36
+ // Data
37
+ tasks: data.tasks,
38
+ sprints: data.sprints,
39
+ backlogTasks: data.backlogTasks,
40
+ activeSprint: data.activeSprint,
41
+ plannedSprints: data.plannedSprints,
42
+ completedSprints: data.completedSprints,
43
+ getSprintTasks: data.getSprintTasks,
44
+ isLoading: data.isLoading,
45
+
46
+ // UI State
47
+ isTaskModalOpen: ui.isTaskModalOpen,
48
+ setIsTaskModalOpen: ui.setIsTaskModalOpen,
49
+ isSprintModalOpen: ui.isSprintModalOpen,
50
+ setIsSprintModalOpen: ui.setIsSprintModalOpen,
51
+ selectedTaskId: ui.selectedTaskId,
52
+ setSelectedTaskId: ui.setSelectedTaskId,
53
+ expandedSections: ui.expandedSections,
54
+ toggleSection: ui.toggleSection,
55
+
56
+ // Drag & Drop
57
+ activeTask: dragDrop.activeTask,
58
+ sensors: dragDrop.sensors,
59
+ handleDragStart: dragDrop.handleDragStart,
60
+ handleDragEnd: dragDrop.handleDragEnd,
61
+
62
+ // Actions
63
+ isSubmitting: actions.isSubmitting,
64
+ handleCreateSprint: actions.handleCreateSprint,
65
+ handleStartSprint: actions.handleStartSprint,
66
+ handleCompleteSprint: actions.handleCompleteSprint,
67
+ handleDeleteTask: actions.handleDeleteTask,
68
+
69
+ // Data actions
70
+ refetchTasks: data.refetchTasks,
71
+ refetchSprints: data.refetchSprints,
72
+ };
73
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Backlog Data & Grouping Hook
3
+ *
4
+ * Manages task and sprint data grouping and organization.
5
+ */
6
+
7
+ "use client";
8
+
9
+ import { type Sprint, SprintStatus, type Task } from "@locusai/shared";
10
+ import { useMemo } from "react";
11
+ import { useSprintsQuery, useTasksQuery } from "@/hooks";
12
+
13
+ export interface BacklogGroupedData {
14
+ tasks: Task[];
15
+ sprints: Sprint[];
16
+ backlogTasks: Task[];
17
+ activeSprint: Sprint | undefined;
18
+ plannedSprints: Sprint[];
19
+ completedSprints: Sprint[];
20
+ isLoading: boolean;
21
+ }
22
+
23
+ export interface BacklogDataActions {
24
+ getSprintTasks: (sprintId: string) => Task[];
25
+ refetchTasks: () => void;
26
+ refetchSprints: () => void;
27
+ }
28
+
29
+ /**
30
+ * Manage backlog data grouping and organization
31
+ */
32
+ export function useBacklogData(): BacklogGroupedData & BacklogDataActions {
33
+ const {
34
+ data: tasks = [],
35
+ isLoading: tasksLoading,
36
+ refetch: refetchTasks,
37
+ } = useTasksQuery();
38
+
39
+ const {
40
+ data: sprints = [],
41
+ isLoading: sprintsLoading,
42
+ refetch: refetchSprints,
43
+ } = useSprintsQuery();
44
+
45
+ const { backlogTasks, activeSprint, plannedSprints, completedSprints } =
46
+ useMemo(() => {
47
+ return {
48
+ backlogTasks: tasks.filter((t) => !t.sprintId),
49
+ activeSprint: sprints.find((s) => s.status === SprintStatus.ACTIVE),
50
+ plannedSprints: sprints.filter(
51
+ (s) => s.status === SprintStatus.PLANNED
52
+ ),
53
+ completedSprints: sprints.filter(
54
+ (s) => s.status === SprintStatus.COMPLETED
55
+ ),
56
+ };
57
+ }, [tasks, sprints]);
58
+
59
+ const getSprintTasks = (sprintId: string) =>
60
+ tasks.filter((t) => t.sprintId === sprintId);
61
+
62
+ return {
63
+ tasks,
64
+ sprints,
65
+ backlogTasks,
66
+ activeSprint,
67
+ plannedSprints,
68
+ completedSprints,
69
+ isLoading: tasksLoading || sprintsLoading,
70
+ getSprintTasks,
71
+ refetchTasks,
72
+ refetchSprints,
73
+ };
74
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Backlog Drag & Drop Hook
3
+ *
4
+ * Handles drag and drop logic for task moving between sprints/backlog.
5
+ */
6
+
7
+ "use client";
8
+
9
+ import {
10
+ type DragEndEvent,
11
+ type DragStartEvent,
12
+ PointerSensor,
13
+ useSensor,
14
+ useSensors,
15
+ } from "@dnd-kit/core";
16
+ import { type Task } from "@locusai/shared";
17
+ import { useQueryClient } from "@tanstack/react-query";
18
+ import { useState } from "react";
19
+ import { useWorkspaceId } from "@/hooks/useWorkspaceId";
20
+ import { locusClient } from "@/lib/api-client";
21
+ import { queryKeys } from "@/lib/query-keys";
22
+ import { notifications } from "@/services/notifications";
23
+
24
+ export interface BacklogDragDropActions {
25
+ activeTask: Task | null;
26
+ sensors: ReturnType<typeof useSensors>;
27
+ handleDragStart: (event: DragStartEvent) => void;
28
+ handleDragEnd: (event: DragEndEvent) => Promise<void>;
29
+ }
30
+
31
+ /**
32
+ * Manage drag and drop operations for backlog tasks
33
+ */
34
+ export function useBacklogDragDrop(tasks: Task[]): BacklogDragDropActions {
35
+ const workspaceId = useWorkspaceId();
36
+ const queryClient = useQueryClient();
37
+ const [activeTask, setActiveTask] = useState<Task | null>(null);
38
+
39
+ const sensors = useSensors(
40
+ useSensor(PointerSensor, {
41
+ activationConstraint: { distance: 8 },
42
+ })
43
+ );
44
+
45
+ const handleDragStart = (event: DragStartEvent) => {
46
+ const task = tasks.find((t) => t.id === event.active.id);
47
+ setActiveTask(task || null);
48
+ };
49
+
50
+ const handleDragEnd = async (event: DragEndEvent) => {
51
+ const { active, over } = event;
52
+ setActiveTask(null);
53
+
54
+ if (!over) return;
55
+
56
+ const taskId = active.id as string;
57
+ const overId = over.id as string;
58
+
59
+ // Find the actual target container (sprint ID or "backlog")
60
+ let targetContainerId = overId;
61
+ const overTask = tasks.find((t) => t.id === overId);
62
+ if (overTask) {
63
+ targetContainerId = overTask.sprintId
64
+ ? `sprint-${overTask.sprintId}`
65
+ : "backlog";
66
+ }
67
+
68
+ let newSprintId: string | null = null;
69
+ if (targetContainerId === "backlog") {
70
+ newSprintId = null;
71
+ } else if (targetContainerId.startsWith("sprint-")) {
72
+ newSprintId = targetContainerId.replace("sprint-", "");
73
+ } else {
74
+ return;
75
+ }
76
+
77
+ const task = tasks.find((t) => t.id === taskId);
78
+ if (!task || task.sprintId === newSprintId) return;
79
+
80
+ // Optimistic update
81
+ const queryKey = queryKeys.tasks.list(workspaceId);
82
+ await queryClient.cancelQueries({ queryKey });
83
+
84
+ const previousTasks = queryClient.getQueryData<Task[]>(queryKey);
85
+
86
+ if (previousTasks) {
87
+ queryClient.setQueryData<Task[]>(
88
+ queryKey,
89
+ previousTasks.map((t) =>
90
+ t.id === taskId ? { ...t, sprintId: newSprintId } : t
91
+ )
92
+ );
93
+ }
94
+
95
+ try {
96
+ await locusClient.tasks.update(taskId, workspaceId, {
97
+ sprintId: newSprintId,
98
+ });
99
+ // Invalidate to ensure sync
100
+ queryClient.invalidateQueries({ queryKey });
101
+ } catch (error) {
102
+ // Rollback
103
+ if (previousTasks) {
104
+ queryClient.setQueryData(queryKey, previousTasks);
105
+ }
106
+ notifications.error(
107
+ error instanceof Error ? error.message : "Failed to move task"
108
+ );
109
+ }
110
+ };
111
+
112
+ return {
113
+ activeTask,
114
+ sensors,
115
+ handleDragStart,
116
+ handleDragEnd,
117
+ };
118
+ }