@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,17 +1,50 @@
1
+ /**
2
+ * Property Item Component
3
+ *
4
+ * Displays an editable task property with label and value.
5
+ * Supports text, date, and dropdown editing modes.
6
+ * Used in task panels for property editing.
7
+ *
8
+ * Features:
9
+ * - Inline editing
10
+ * - Multiple input types (text, date, dropdown)
11
+ * - Edit/save/cancel actions
12
+ * - Optional dropdown options
13
+ *
14
+ * @example
15
+ * <PropertyItem
16
+ * label="Priority"
17
+ * value="High"
18
+ * type="dropdown"
19
+ * options={["Low", "Medium", "High"]}
20
+ * onEdit={handleEdit}
21
+ * />
22
+ */
23
+
1
24
  "use client";
2
- import { Check, Edit2, X } from "lucide-react";
3
25
 
26
+ import { Check, Edit2, X } from "lucide-react";
4
27
  import { useState } from "react";
28
+ import { SecondaryText } from "./typography";
5
29
  import { Button } from "./ui/Button";
6
30
  import { Dropdown } from "./ui/Dropdown";
7
31
  import { Input } from "./ui/Input";
8
32
 
9
33
  export interface PropertyItemProps {
34
+ /** Property label */
10
35
  label: string;
36
+ /** Current property value */
11
37
  value: string | number;
38
+ /** Called when value is updated */
12
39
  onEdit: (newValue: string) => void;
40
+ /** Options for dropdown type */
13
41
  options?: string[];
42
+ /** Input type for editing */
14
43
  type?: "text" | "date" | "dropdown";
44
+ /** Whether the property is disabled */
45
+ disabled?: boolean;
46
+ /** Placeholder text for text input */
47
+ placeholder?: string;
15
48
  }
16
49
 
17
50
  export function PropertyItem({
@@ -20,6 +53,8 @@ export function PropertyItem({
20
53
  onEdit,
21
54
  options,
22
55
  type = "text",
56
+ disabled = false,
57
+ placeholder,
23
58
  }: PropertyItemProps) {
24
59
  const [isEditing, setIsEditing] = useState(false);
25
60
  const [editValue, setEditValue] = useState(String(value));
@@ -36,9 +71,9 @@ export function PropertyItem({
36
71
 
37
72
  return (
38
73
  <div className="flex flex-col gap-1.5 py-3 first:pt-0 last:pb-0">
39
- <span className="text-[10px] items-center font-bold uppercase tracking-widest text-muted-foreground">
74
+ <SecondaryText as="span" size="xs" className="items-center">
40
75
  {label}
41
- </span>
76
+ </SecondaryText>
42
77
  <div className="relative group min-h-[32px] flex items-center">
43
78
  {isEditing ? (
44
79
  <div className="flex items-center gap-2 w-full">
@@ -48,12 +83,15 @@ export function PropertyItem({
48
83
  value={editValue}
49
84
  options={options.map((o) => ({ value: o, label: o }))}
50
85
  onChange={(v) => setEditValue(v)}
86
+ disabled={disabled}
51
87
  />
52
88
  ) : (
53
89
  <Input
54
90
  type={type}
55
91
  value={editValue}
56
92
  onChange={(e) => setEditValue(e.target.value)}
93
+ disabled={disabled}
94
+ placeholder={placeholder}
57
95
  autoFocus
58
96
  className="h-8 py-1"
59
97
  />
@@ -65,6 +103,7 @@ export function PropertyItem({
65
103
  variant="ghost"
66
104
  className="h-8 w-8"
67
105
  onClick={handleSave}
106
+ disabled={disabled}
68
107
  >
69
108
  <Check size={14} />
70
109
  </Button>
@@ -73,6 +112,7 @@ export function PropertyItem({
73
112
  variant="ghost"
74
113
  className="h-8 w-8"
75
114
  onClick={handleCancel}
115
+ disabled={disabled}
76
116
  >
77
117
  <X size={14} />
78
118
  </Button>
@@ -80,16 +120,22 @@ export function PropertyItem({
80
120
  </div>
81
121
  ) : (
82
122
  <div
83
- className="flex items-center justify-between gap-2 w-full cursor-pointer hover:bg-secondary/50 px-2 -mx-2 py-1 rounded-md transition-colors"
84
- onClick={() => setIsEditing(true)}
123
+ className={`flex items-center justify-between gap-2 w-full px-2 -mx-2 py-1 rounded-md transition-colors ${
124
+ disabled
125
+ ? "opacity-50 cursor-not-allowed"
126
+ : "cursor-pointer hover:bg-secondary/50"
127
+ }`}
128
+ onClick={() => !disabled && setIsEditing(true)}
85
129
  >
86
130
  <span className="text-sm font-medium text-foreground truncate">
87
131
  {value || "None"}
88
132
  </span>
89
- <Edit2
90
- size={12}
91
- className="text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
92
- />
133
+ {!disabled && (
134
+ <Edit2
135
+ size={12}
136
+ className="text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
137
+ />
138
+ )}
93
139
  </div>
94
140
  )}
95
141
  </div>
@@ -1,61 +1,216 @@
1
+ /**
2
+ * Sidebar Component
3
+ *
4
+ * Main navigation sidebar with workspace switcher and user menu.
5
+ * Displays navigation links, workspace selection, and user profile.
6
+ * Features quick access to workspaces, settings, and user actions.
7
+ *
8
+ * Features:
9
+ * - Workspace switcher with quick access
10
+ * - Navigation links (Dashboard, Backlog, Docs)
11
+ * - User profile menu
12
+ * - Create workspace button
13
+ * - Keyboard shortcuts (Cmd+K for workspace switcher)
14
+ * - Logout functionality
15
+ * - Workspace-aware routing
16
+ *
17
+ * @example
18
+ * <Sidebar />
19
+ */
20
+
1
21
  "use client";
2
22
 
23
+ import { type Workspace } from "@locusai/shared";
24
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
3
25
  import {
26
+ Activity,
27
+ ChevronDown,
4
28
  ChevronRight,
5
29
  FileText,
30
+ FolderKanban,
6
31
  LayoutDashboard,
7
32
  List,
33
+ LogOut,
34
+ Plus,
8
35
  Settings,
36
+ User as UserIcon,
9
37
  } from "lucide-react";
10
38
  import Image from "next/image";
11
39
  import Link from "next/link";
12
- import { usePathname } from "next/navigation";
40
+ import { usePathname, useRouter } from "next/navigation";
41
+ import { useState } from "react";
42
+ import { Avatar } from "@/components/ui";
43
+ import { useAuth } from "@/context/AuthContext";
44
+ import { useGlobalKeydowns } from "@/hooks";
45
+ import { locusClient } from "@/lib/api-client";
46
+ import { queryKeys } from "@/lib/query-keys";
47
+ import { getTypographyClass } from "@/lib/typography";
13
48
  import { cn } from "@/lib/utils";
49
+ import { WorkspaceCreateModal } from "./WorkspaceCreateModal";
14
50
 
15
51
  export function Sidebar() {
16
52
  const pathname = usePathname();
53
+ const router = useRouter();
54
+ const queryClient = useQueryClient();
55
+ const { user, logout, switchWorkspace } = useAuth();
56
+ const [isWorkspaceOpen, setIsWorkspaceOpen] = useState(false);
57
+ const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
58
+ const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
59
+
60
+ const { data: workspaces = [] } = useQuery<Workspace[]>({
61
+ queryKey: queryKeys.workspaces.all(),
62
+ queryFn: () => locusClient.workspaces.listAll(),
63
+ enabled: !!user,
64
+ });
65
+
66
+ const currentWorkspace =
67
+ workspaces.find((w) => w.id === user?.workspaceId) || workspaces[0];
68
+
69
+ const invalidateWorkspaces = () => {
70
+ queryClient.invalidateQueries({ queryKey: queryKeys.workspaces.all() });
71
+ };
72
+
73
+ useGlobalKeydowns({
74
+ onOpenCreateTask: () => {
75
+ router.push("/backlog?createTask=true");
76
+ },
77
+ onOpenCreateSprint: () => {
78
+ router.push("/backlog?createSprint=true");
79
+ },
80
+ onCloseCreateTask: () => {
81
+ // Global escape could handle closing menus or global UI
82
+ setIsWorkspaceOpen(false);
83
+ setIsUserMenuOpen(false);
84
+ setIsCreateModalOpen(false);
85
+ },
86
+ });
17
87
 
18
- const menuItems = [
88
+ const mainMenuItems = [
19
89
  {
20
90
  href: "/",
21
- label: "Board",
91
+ label: "Dashboard",
22
92
  icon: LayoutDashboard,
23
- description: "Manage tasks",
93
+ description: "Overview",
24
94
  },
25
95
  {
26
- href: "/backlog", // Added Backlog item
96
+ href: "/board",
97
+ label: "Board",
98
+ icon: FolderKanban,
99
+ description: "Sprint board",
100
+ },
101
+ {
102
+ href: "/backlog",
27
103
  label: "Backlog",
28
104
  icon: List,
29
- description: "Manage backlog items",
105
+ description: "All tasks",
106
+ },
107
+ {
108
+ href: "/activity",
109
+ label: "Activity",
110
+ icon: Activity,
111
+ description: "Workspace history",
30
112
  },
31
113
  {
32
114
  href: "/docs",
33
115
  label: "Library",
34
116
  icon: FileText,
35
- description: "Documents",
117
+ description: "Documentation",
36
118
  },
37
119
  ];
38
120
 
39
121
  return (
40
- <aside className="w-[260px] flex flex-col border-r border-border/50 bg-card/50 backdrop-blur-sm h-full">
122
+ <aside className="w-[260px] flex flex-col border-r border-border/50 bg-card/30 backdrop-blur-xl h-full">
41
123
  {/* Logo */}
42
- <div className="flex items-center gap-3 p-5 border-b border-border/50">
124
+ <div className="flex items-center gap-3 p-4 border-b border-border/30">
43
125
  <Image
44
126
  src="/logo.png"
45
127
  alt="Locus"
46
128
  width={97.81}
47
129
  height={32}
48
- className="rounded-xl shadow-lg"
130
+ className="rounded-xl"
49
131
  />
50
132
  </div>
51
133
 
52
- {/* Navigation */}
53
- <div className="flex-1 p-4">
54
- <div className="text-[10px] uppercase font-bold tracking-widest text-muted-foreground/70 mb-3 px-3">
134
+ {/* Workspace Selector */}
135
+ <div className="p-3 border-b border-border/30">
136
+ <button
137
+ onClick={() => setIsWorkspaceOpen(!isWorkspaceOpen)}
138
+ className="w-full flex items-center gap-3 p-2.5 rounded-xl hover:bg-secondary/50 transition-all group"
139
+ >
140
+ <div className="flex items-center justify-center h-9 w-9 rounded-lg bg-linear-to-br from-primary/20 to-primary/5 border border-border/50 text-lg">
141
+ {"🚀"}
142
+ </div>
143
+ <div className="flex-1 text-left min-w-0">
144
+ <div className="text-sm font-semibold text-foreground truncate">
145
+ {currentWorkspace?.name || "Select Workspace"}
146
+ </div>
147
+ <div
148
+ className={cn(
149
+ getTypographyClass("label"),
150
+ "text-muted-foreground/70"
151
+ )}
152
+ >
153
+ Workspace
154
+ </div>
155
+ </div>
156
+ <ChevronDown
157
+ size={16}
158
+ className={cn(
159
+ "text-muted-foreground transition-transform duration-200",
160
+ isWorkspaceOpen && "rotate-180"
161
+ )}
162
+ />
163
+ </button>
164
+
165
+ {/* Workspace Dropdown */}
166
+ {isWorkspaceOpen && (
167
+ <div className="mt-2 p-2 bg-secondary/30 rounded-xl border border-border/30 animate-in fade-in slide-in-from-top-2 duration-200">
168
+ {workspaces.map((workspace) => (
169
+ <button
170
+ key={workspace.id}
171
+ className={cn(
172
+ "w-full flex items-center gap-3 p-2 rounded-lg transition-colors text-sm",
173
+ workspace.id === currentWorkspace?.id
174
+ ? "bg-primary/10 text-primary"
175
+ : "text-muted-foreground hover:bg-secondary/50 hover:text-foreground"
176
+ )}
177
+ onClick={() => {
178
+ switchWorkspace(workspace.id as string);
179
+ setIsWorkspaceOpen(false);
180
+ }}
181
+ >
182
+ <span className="text-base">{"🚀"}</span>
183
+ <span className="font-medium">{workspace.name}</span>
184
+ </button>
185
+ ))}
186
+ <div className="border-t border-border/30 mt-2 pt-2">
187
+ <button
188
+ onClick={() => {
189
+ setIsCreateModalOpen(true);
190
+ setIsWorkspaceOpen(false);
191
+ }}
192
+ className="w-full flex items-center gap-2 p-2 rounded-lg text-muted-foreground hover:bg-secondary/50 hover:text-foreground transition-colors text-sm"
193
+ >
194
+ <Plus size={16} />
195
+ <span>New Workspace</span>
196
+ </button>
197
+ </div>
198
+ </div>
199
+ )}
200
+ </div>
201
+
202
+ {/* Main Navigation */}
203
+ <div className="flex-1 p-3 overflow-y-auto">
204
+ <div
205
+ className={cn(
206
+ getTypographyClass("label"),
207
+ "text-muted-foreground/60 mb-2 px-2"
208
+ )}
209
+ >
55
210
  Navigation
56
211
  </div>
57
212
  <nav className="space-y-1">
58
- {menuItems.map((item) => {
213
+ {mainMenuItems.map((item) => {
59
214
  const Icon = item.icon;
60
215
  const isActive = pathname === item.href;
61
216
  return (
@@ -63,43 +218,132 @@ export function Sidebar() {
63
218
  key={item.href}
64
219
  href={item.href}
65
220
  className={cn(
66
- "group flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-xl transition-all",
221
+ "group flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-xl transition-all duration-200",
67
222
  isActive
68
- ? "bg-primary text-primary-foreground shadow-md shadow-primary/20"
69
- : "text-muted-foreground hover:bg-secondary hover:text-foreground"
223
+ ? "bg-primary text-primary-foreground shadow-lg shadow-primary/20"
224
+ : "text-muted-foreground hover:bg-secondary/50 hover:text-foreground"
70
225
  )}
71
226
  >
72
227
  <Icon
73
228
  size={18}
74
- className={
75
- isActive ? "" : "group-hover:scale-110 transition-transform"
76
- }
229
+ className={cn(
230
+ "shrink-0",
231
+ !isActive && "group-hover:scale-110 transition-transform"
232
+ )}
77
233
  />
78
- <div className="flex-1">
79
- <span className="block">{item.label}</span>
80
- </div>
234
+ <span className="flex-1">{item.label}</span>
81
235
  {isActive && <ChevronRight size={14} className="opacity-70" />}
82
236
  </Link>
83
237
  );
84
238
  })}
85
239
  </nav>
240
+
241
+ {/* Quick Actions */}
242
+ <div className="mt-6">
243
+ <div
244
+ className={cn(
245
+ getTypographyClass("label"),
246
+ "text-muted-foreground/60 mb-2 px-2"
247
+ )}
248
+ >
249
+ Quick Actions
250
+ </div>
251
+ <div className="space-y-1">
252
+ <button
253
+ className="w-full flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-xl text-muted-foreground hover:bg-secondary/50 hover:text-foreground transition-all"
254
+ onClick={() => router.push("/backlog?createTask=true")}
255
+ >
256
+ <Plus size={18} />
257
+ <span>New Task</span>
258
+ <kbd className="ml-auto text-[10px] px-1.5 py-0.5 rounded bg-secondary/80 border border-border/50 text-muted-foreground/70">
259
+ Alt N
260
+ </kbd>
261
+ </button>
262
+ <button
263
+ className="w-full flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-xl text-muted-foreground hover:bg-secondary/50 hover:text-foreground transition-all"
264
+ onClick={() => router.push("/backlog?createSprint=true")}
265
+ >
266
+ <FolderKanban size={18} />
267
+ <span>New Sprint</span>
268
+ <kbd className="ml-auto text-[10px] px-1.5 py-0.5 rounded bg-secondary/80 border border-border/50 text-muted-foreground/70">
269
+ Alt S
270
+ </kbd>
271
+ </button>
272
+ </div>
273
+ </div>
86
274
  </div>
87
275
 
88
- {/* Footer */}
89
- <div className="p-4 border-t border-border/50">
90
- <Link
91
- href="/settings"
92
- className={cn(
93
- "flex items-center gap-3 w-full px-3 py-2.5 text-sm font-medium rounded-xl transition-all",
94
- pathname === "/settings"
95
- ? "bg-secondary text-foreground"
96
- : "text-muted-foreground hover:bg-secondary/60 hover:text-foreground"
276
+ {/* Bottom Section */}
277
+ <div className="p-3 border-t border-border/30 space-y-1">
278
+ {/* User Profile */}
279
+ <div className="relative">
280
+ <button
281
+ onClick={() => setIsUserMenuOpen(!isUserMenuOpen)}
282
+ className="w-full flex items-center gap-3 p-2.5 rounded-xl hover:bg-secondary/50 transition-all group"
283
+ >
284
+ <Avatar
285
+ name={user?.name || "User"}
286
+ src={user?.avatarUrl}
287
+ size="md"
288
+ />
289
+ <div className="flex-1 text-left min-w-0">
290
+ <div className="text-sm font-semibold text-foreground truncate">
291
+ {user?.name}
292
+ </div>
293
+ <div className="text-[11px] text-muted-foreground truncate">
294
+ {user?.email}
295
+ </div>
296
+ </div>
297
+ <ChevronDown
298
+ size={14}
299
+ className={cn(
300
+ "text-muted-foreground transition-transform duration-200",
301
+ isUserMenuOpen && "rotate-180"
302
+ )}
303
+ />
304
+ </button>
305
+
306
+ {/* User Menu Dropdown */}
307
+ {isUserMenuOpen && (
308
+ <div className="absolute bottom-full left-0 right-0 mb-2 p-2 bg-popover rounded-xl border border-border shadow-xl animate-in fade-in slide-in-from-bottom-2 duration-200">
309
+ <Link
310
+ href="/settings/profile"
311
+ className="flex items-center gap-2 p-2 rounded-lg text-sm text-muted-foreground hover:bg-secondary/50 hover:text-foreground transition-colors"
312
+ onClick={() => setIsUserMenuOpen(false)}
313
+ >
314
+ <UserIcon size={16} />
315
+ <span>Profile</span>
316
+ </Link>
317
+ <Link
318
+ href="/settings"
319
+ className="flex items-center gap-2 p-2 rounded-lg text-sm text-muted-foreground hover:bg-secondary/50 hover:text-foreground transition-colors"
320
+ onClick={() => setIsUserMenuOpen(false)}
321
+ >
322
+ <Settings size={16} />
323
+ <span>Settings</span>
324
+ </Link>
325
+ <div className="border-t border-border/50 mt-1 pt-1">
326
+ <button
327
+ onClick={() => {
328
+ logout();
329
+ router.push("/login");
330
+ }}
331
+ className="w-full flex items-center gap-2 p-2 rounded-lg text-sm text-rose-400 hover:bg-rose-500/10 transition-colors"
332
+ >
333
+ <LogOut size={16} />
334
+ <span>Sign Out</span>
335
+ </button>
336
+ </div>
337
+ </div>
97
338
  )}
98
- >
99
- <Settings size={18} />
100
- <span>Settings</span>
101
- </Link>
339
+ </div>
102
340
  </div>
341
+
342
+ <WorkspaceCreateModal
343
+ isOpen={isCreateModalOpen}
344
+ onClose={() => setIsCreateModalOpen(false)}
345
+ onSuccess={invalidateWorkspaces}
346
+ />
103
347
  </aside>
104
348
  );
105
349
  }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Sprint Create Modal Component
3
+ *
4
+ * Modal dialog for creating new sprints.
5
+ * Simple form for entering sprint name.
6
+ *
7
+ * @example
8
+ * <SprintCreateModal
9
+ * isOpen={isOpen}
10
+ * onClose={handleClose}
11
+ * onCreated={handleCreated}
12
+ * isSubmitting={false}
13
+ * />
14
+ */
15
+
16
+ "use client";
17
+
18
+ import { Target } from "lucide-react";
19
+ import { useState } from "react";
20
+ import { CreateModal } from "@/components/CreateModal";
21
+ import { Input } from "@/components/ui";
22
+
23
+ interface SprintCreateModalProps {
24
+ /** Whether modal is open */
25
+ isOpen: boolean;
26
+ /** Called to close modal */
27
+ onClose: () => void;
28
+ /** Called with sprint name after creation */
29
+ onCreated: (name: string) => void;
30
+ /** Whether submission is in progress */
31
+ isSubmitting?: boolean;
32
+ }
33
+
34
+ export function SprintCreateModal({
35
+ isOpen,
36
+ onClose,
37
+ onCreated,
38
+ isSubmitting = false,
39
+ }: SprintCreateModalProps) {
40
+ const [name, setName] = useState("");
41
+
42
+ const handleSubmit = (e: React.FormEvent) => {
43
+ e.preventDefault();
44
+ if (!name.trim()) return;
45
+ onCreated(name.trim());
46
+ setName("");
47
+ };
48
+
49
+ const handleClose = () => {
50
+ setName("");
51
+ onClose();
52
+ };
53
+
54
+ return (
55
+ <CreateModal
56
+ isOpen={isOpen}
57
+ title="Create New Sprint"
58
+ size="sm"
59
+ fields={[
60
+ {
61
+ name: "name",
62
+ label: "Sprint Name",
63
+ component: (
64
+ <Input
65
+ value={name}
66
+ onChange={(e) => setName(e.target.value)}
67
+ placeholder="e.g. Sprint 24"
68
+ autoFocus
69
+ className="h-11"
70
+ />
71
+ ),
72
+ required: true,
73
+ help: "Give your sprint a descriptive name to identify it later.",
74
+ },
75
+ ]}
76
+ onSubmit={handleSubmit}
77
+ onClose={handleClose}
78
+ submitText="Create Sprint"
79
+ icon={<Target size={16} />}
80
+ isPending={isSubmitting}
81
+ submitDisabled={!name.trim()}
82
+ />
83
+ );
84
+ }