@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,124 @@
1
+ /**
2
+ * Error State Component
3
+ *
4
+ * Unified error display with recovery options.
5
+ * Use to display errors in a consistent, user-friendly way.
6
+ */
7
+
8
+ "use client";
9
+
10
+ import { AlertTriangle, RotateCcw } from "lucide-react";
11
+ import { Button } from "@/components/ui";
12
+ import { cn } from "@/lib/utils";
13
+
14
+ /**
15
+ * Error state component props
16
+ *
17
+ * @property variant - Display variant (page, section, or inline)
18
+ * @property title - Error title text
19
+ * @property message - Error description
20
+ * @property onRetry - Callback for retry button
21
+ */
22
+ interface ErrorStateProps {
23
+ /** Display variant */
24
+ variant?: "page" | "section" | "inline";
25
+ /** Error title */
26
+ title?: string;
27
+ /** Error description message */
28
+ message?: string;
29
+ /** Callback when user clicks retry */
30
+ onRetry?: () => void;
31
+ /** Additional CSS classes */
32
+ className?: string;
33
+ }
34
+
35
+ /**
36
+ * Error state component
37
+ *
38
+ * Displays error messages with optional retry functionality.
39
+ * Three variants for different contexts.
40
+ *
41
+ * @example
42
+ * // Page-level error
43
+ * <ErrorState
44
+ * title="Failed to load data"
45
+ * message="Please try again"
46
+ * onRetry={handleRetry}
47
+ * />
48
+ *
49
+ * @example
50
+ * // Inline error (e.g., in a form)
51
+ * <ErrorState
52
+ * variant="inline"
53
+ * message="Invalid email address"
54
+ * />
55
+ *
56
+ * @example
57
+ * // Section error (e.g., in a card)
58
+ * <ErrorState
59
+ * variant="section"
60
+ * title="Failed to load"
61
+ * onRetry={refetch}
62
+ * />
63
+ */
64
+ export function ErrorState({
65
+ variant = "page",
66
+ title = "Something went wrong",
67
+ message = "An error occurred. Please try again.",
68
+ onRetry,
69
+ className,
70
+ }: ErrorStateProps) {
71
+ const variantClasses = {
72
+ page: "flex flex-col items-center justify-center min-h-[400px] w-full gap-4 py-12",
73
+ section:
74
+ "flex flex-col items-center justify-center py-12 w-full gap-3 px-4",
75
+ inline:
76
+ "flex items-center gap-2 p-3 bg-destructive/10 rounded-lg border border-destructive/20",
77
+ };
78
+
79
+ const iconSize = {
80
+ page: 48,
81
+ section: 32,
82
+ inline: 16,
83
+ } as const;
84
+
85
+ return (
86
+ <div
87
+ className={cn(variantClasses[variant], className)}
88
+ role="alert"
89
+ aria-live="polite"
90
+ aria-atomic="true"
91
+ >
92
+ <div className="flex flex-col items-center gap-2">
93
+ <AlertTriangle
94
+ size={iconSize[variant]}
95
+ className="text-destructive/70"
96
+ aria-hidden="true"
97
+ />
98
+ {variant !== "inline" && (
99
+ <>
100
+ <h3 className="text-lg font-semibold text-destructive">{title}</h3>
101
+ <p className="text-sm text-muted-foreground text-center max-w-sm">
102
+ {message}
103
+ </p>
104
+ </>
105
+ )}
106
+ {variant === "inline" && (
107
+ <p className="text-sm text-destructive font-medium">{message}</p>
108
+ )}
109
+ </div>
110
+ {onRetry && (
111
+ <Button
112
+ variant="outline"
113
+ size={variant === "inline" ? "sm" : "md"}
114
+ onClick={onRetry}
115
+ className="gap-2"
116
+ aria-label="Retry the failed action"
117
+ >
118
+ <RotateCcw size={16} aria-hidden="true" />
119
+ Try Again
120
+ </Button>
121
+ )}
122
+ </div>
123
+ );
124
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Loading State Component
3
+ *
4
+ * Unified loading state display with consistent sizing.
5
+ * Use for data fetching, form submission, and async operations.
6
+ */
7
+
8
+ "use client";
9
+
10
+ import { Spinner } from "@/components/ui";
11
+ import { cn } from "@/lib/utils";
12
+
13
+ /**
14
+ * Loading state component props
15
+ *
16
+ * @property variant - Display variant (page, section, or inline)
17
+ * @property message - Optional loading message
18
+ */
19
+ interface LoadingStateProps {
20
+ /** Display variant */
21
+ variant?: "page" | "section" | "inline";
22
+ /** Optional loading message */
23
+ message?: string;
24
+ /** Additional CSS classes */
25
+ className?: string;
26
+ }
27
+
28
+ /**
29
+ * Loading state component
30
+ *
31
+ * Displays loading indicator with optional message.
32
+ * Three variants for different contexts.
33
+ *
34
+ * @example
35
+ * // Page-level loading
36
+ * <LoadingState
37
+ * variant="page"
38
+ * message="Loading data..."
39
+ * />
40
+ *
41
+ * @example
42
+ * // Inline loading (e.g., in a button)
43
+ * <LoadingState
44
+ * variant="inline"
45
+ * />
46
+ *
47
+ * @example
48
+ * // Section loading (e.g., in a card)
49
+ * <LoadingState
50
+ * variant="section"
51
+ * message="Fetching results..."
52
+ * />
53
+ */
54
+ export function LoadingState({
55
+ variant = "page",
56
+ message,
57
+ className,
58
+ }: LoadingStateProps) {
59
+ const variantClasses = {
60
+ page: "flex flex-col items-center justify-center min-h-[400px] w-full gap-4",
61
+ section: "flex flex-col items-center justify-center py-12 w-full gap-3",
62
+ inline: "flex items-center gap-2",
63
+ };
64
+
65
+ const spinnerSize = {
66
+ page: "lg" as const,
67
+ section: "md" as const,
68
+ inline: "sm" as const,
69
+ };
70
+
71
+ return (
72
+ <output
73
+ className={cn(variantClasses[variant], className)}
74
+ aria-live="polite"
75
+ aria-label={message || "Loading"}
76
+ >
77
+ <Spinner size={spinnerSize[variant]} />
78
+ {message && (
79
+ <p className="text-muted-foreground text-sm font-medium">{message}</p>
80
+ )}
81
+ </output>
82
+ );
83
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Common Components
3
+ *
4
+ * Reusable components for standardized UI patterns across the application.
5
+ *
6
+ * ## Error State
7
+ * Use `ErrorState` to display error messages with optional retry functionality.
8
+ * Supports three variants: page, section, and inline.
9
+ *
10
+ * ```tsx
11
+ * <ErrorState
12
+ * title="Failed to load"
13
+ * message="Please try again"
14
+ * onRetry={handleRetry}
15
+ * />
16
+ * ```
17
+ *
18
+ * ## Loading State
19
+ * Use `LoadingState` to display loading indicators while data is being fetched.
20
+ * Supports three variants: page, section, and inline.
21
+ *
22
+ * ```tsx
23
+ * <LoadingState
24
+ * variant="section"
25
+ * message="Loading data..."
26
+ * />
27
+ * ```
28
+ *
29
+ * ## Usage Pattern
30
+ * Use these components for:
31
+ * - Data fetching states
32
+ * - Form submission feedback
33
+ * - API error handling
34
+ * - Async operation feedback
35
+ *
36
+ * Keep error and loading states visible and accessible.
37
+ */
38
+
39
+ export * from "./ErrorState";
40
+ export * from "./LoadingState";
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Activity Feed Component
3
+ *
4
+ * Displays recent workspace activity and events.
5
+ * Shows recent actions performed by team members.
6
+ *
7
+ * @example
8
+ * <ActivityFeed activity={workspaceEvents} />
9
+ */
10
+
11
+ "use client";
12
+
13
+ import { type Event as WorkspaceEvent } from "@locusai/shared";
14
+ import { Activity } from "lucide-react";
15
+ import { useRouter } from "next/navigation";
16
+ import { Button } from "@/components/ui";
17
+ import { ActivityItem } from "./ActivityItem";
18
+
19
+ interface ActivityFeedProps {
20
+ /** Array of workspace events to display */
21
+ activity: WorkspaceEvent[];
22
+ }
23
+
24
+ /**
25
+ * Activity Feed Component
26
+ *
27
+ * Features:
28
+ * - Displays recent workspace events
29
+ * - Shows activity items with timestamps
30
+ * - Empty state when no activity
31
+ * - View all button for navigation
32
+ *
33
+ * @component
34
+ */
35
+ export function ActivityFeed({ activity }: ActivityFeedProps) {
36
+ const router = useRouter();
37
+
38
+ return (
39
+ <div className="bg-card border border-border/50 rounded-2xl p-6 shadow-sm h-full">
40
+ <div className="flex items-center justify-between mb-6">
41
+ <h3 className="font-semibold text-lg flex items-center gap-2">
42
+ <Activity size={20} className="text-primary" />
43
+ Recent Activity
44
+ </h3>
45
+ <Button
46
+ variant="ghost"
47
+ size="sm"
48
+ className="text-xs"
49
+ onClick={() => router.push("/activity")}
50
+ >
51
+ View All
52
+ </Button>
53
+ </div>
54
+ <div className="space-y-4">
55
+ {activity.length > 0 ? (
56
+ activity
57
+ .slice(0, 10)
58
+ .map((event) => <ActivityItem key={event.id} event={event} />)
59
+ ) : (
60
+ <div className="py-12 flex flex-col items-center justify-center text-center space-y-3">
61
+ <div className="p-3 bg-secondary/30 rounded-full">
62
+ <Activity size={24} className="text-muted-foreground/50" />
63
+ </div>
64
+ <div className="max-w-[200px]">
65
+ <p className="text-sm font-medium text-foreground">
66
+ No activity yet
67
+ </p>
68
+ <p className="text-xs text-muted-foreground">
69
+ When you or your team take actions, they will appear here.
70
+ </p>
71
+ </div>
72
+ </div>
73
+ )}
74
+ </div>
75
+ </div>
76
+ );
77
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Activity Item Component
3
+ *
4
+ * Displays a single activity event from the workspace.
5
+ * Shows event type, details, and relative timestamp.
6
+ */
7
+
8
+ "use client";
9
+
10
+ import {
11
+ type EventPayload,
12
+ type Event as WorkspaceEvent,
13
+ } from "@locusai/shared";
14
+ import { formatDistanceToNow } from "date-fns";
15
+ import {
16
+ Activity,
17
+ CheckCircle2,
18
+ Clock,
19
+ MessageSquare,
20
+ Plus,
21
+ Rocket,
22
+ ShieldCheck,
23
+ UserPlus,
24
+ } from "lucide-react";
25
+ import { useAuth } from "@/context/AuthContext";
26
+
27
+ interface ActivityItemProps {
28
+ /** Workspace event to display */
29
+ event: WorkspaceEvent;
30
+ }
31
+
32
+ const formatStatus = (status: string) => {
33
+ return status
34
+ .toLowerCase()
35
+ .split("_")
36
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
37
+ .join(" ");
38
+ };
39
+
40
+ export function ActivityItem({ event }: ActivityItemProps) {
41
+ const { user } = useAuth();
42
+ const { type, payload } = event;
43
+
44
+ const getEventConfig = () => {
45
+ switch (type) {
46
+ case "TASK_CREATED": {
47
+ const p = payload as Extract<
48
+ EventPayload,
49
+ { type: "TASK_CREATED" }
50
+ >["payload"];
51
+ return {
52
+ icon: Plus,
53
+ color: "text-emerald-500",
54
+ action: "created task",
55
+ target: p.title,
56
+ };
57
+ }
58
+ case "STATUS_CHANGED": {
59
+ const p = payload as Extract<
60
+ EventPayload,
61
+ { type: "STATUS_CHANGED" }
62
+ >["payload"];
63
+ return {
64
+ icon: Activity,
65
+ color: "text-primary",
66
+ action: `moved task to ${formatStatus(p.newStatus)}`,
67
+ target: p.title,
68
+ };
69
+ }
70
+ case "COMMENT_ADDED": {
71
+ const p = payload as Extract<
72
+ EventPayload,
73
+ { type: "COMMENT_ADDED" }
74
+ >["payload"];
75
+ return {
76
+ icon: MessageSquare,
77
+ color: "text-blue-500",
78
+ action: "commented on",
79
+ target: p.title,
80
+ };
81
+ }
82
+ case "WORKSPACE_CREATED": {
83
+ const p = payload as Extract<
84
+ EventPayload,
85
+ { type: "WORKSPACE_CREATED" }
86
+ >["payload"];
87
+ return {
88
+ icon: Rocket,
89
+ color: "text-purple-500",
90
+ action: "created the workspace",
91
+ target: p.name,
92
+ };
93
+ }
94
+ case "MEMBER_INVITED": {
95
+ const p = payload as Extract<
96
+ EventPayload,
97
+ { type: "MEMBER_INVITED" }
98
+ >["payload"];
99
+ return {
100
+ icon: UserPlus,
101
+ color: "text-amber-500",
102
+ action: `invited ${p.email} to the team`,
103
+ target: "",
104
+ };
105
+ }
106
+ case "SPRINT_CREATED": {
107
+ const p = payload as Extract<
108
+ EventPayload,
109
+ { type: "SPRINT_CREATED" }
110
+ >["payload"];
111
+ return {
112
+ icon: Clock,
113
+ color: "text-primary",
114
+ action: "created sprint",
115
+ target: p.name,
116
+ };
117
+ }
118
+ case "SPRINT_STATUS_CHANGED": {
119
+ const p = payload as Extract<
120
+ EventPayload,
121
+ { type: "SPRINT_STATUS_CHANGED" }
122
+ >["payload"];
123
+ return {
124
+ icon: Rocket,
125
+ color: "text-orange-500",
126
+ action: `moved sprint to ${formatStatus(p.newStatus)}`,
127
+ target: p.name,
128
+ };
129
+ }
130
+ case "TASK_DELETED": {
131
+ const p = payload as Extract<
132
+ EventPayload,
133
+ { type: "TASK_DELETED" }
134
+ >["payload"];
135
+ return {
136
+ icon: Activity,
137
+ color: "text-red-500",
138
+ action: "deleted task",
139
+ target: p.title,
140
+ };
141
+ }
142
+ case "CI_RAN": {
143
+ const p = payload as Extract<
144
+ EventPayload,
145
+ { type: "CI_RAN" }
146
+ >["payload"];
147
+ return {
148
+ icon: ShieldCheck,
149
+ color: p.ok ? "text-emerald-500" : "text-rose-500",
150
+ action: `ran verification: ${p.ok ? "Passed" : "Failed"}`,
151
+ target: p.preset,
152
+ };
153
+ }
154
+ case "CHECKLIST_INITIALIZED": {
155
+ const p = payload as Extract<
156
+ EventPayload,
157
+ { type: "CHECKLIST_INITIALIZED" }
158
+ >["payload"];
159
+ return {
160
+ icon: CheckCircle2,
161
+ color: "text-blue-500",
162
+ action: `initialized ${p.itemCount} checklist items`,
163
+ target: "",
164
+ };
165
+ }
166
+ default:
167
+ return {
168
+ icon: Activity,
169
+ color: "text-muted-foreground",
170
+ action: type.toLowerCase().replace(/_/g, " "),
171
+ target: "",
172
+ };
173
+ }
174
+ };
175
+
176
+ const config = getEventConfig();
177
+ const Icon = config.icon;
178
+
179
+ const parseDate = (date: string | number | Date | null | undefined) => {
180
+ if (!date) return new Date();
181
+ return new Date(date);
182
+ };
183
+
184
+ const eventDate = parseDate(event.createdAt);
185
+
186
+ return (
187
+ <div className="flex items-start gap-4 p-3 hover:bg-secondary/20 rounded-xl transition-colors group">
188
+ <div className={`p-2 rounded-lg bg-secondary/50 ${config.color}`}>
189
+ <Icon size={16} />
190
+ </div>
191
+ <div className="flex-1 min-w-0">
192
+ <p className="text-sm text-foreground leading-relaxed">
193
+ <span className="font-semibold text-foreground/90">
194
+ {event.userId === user?.id ? "You" : "Team member"}
195
+ </span>{" "}
196
+ <span className="text-muted-foreground">{config.action}</span>{" "}
197
+ {config.target && (
198
+ <span className="font-medium text-primary">"{config.target}"</span>
199
+ )}
200
+ </p>
201
+ <p className="text-[11px] text-muted-foreground mt-0.5">
202
+ {formatDistanceToNow(eventDate, { addSuffix: true })}
203
+ </p>
204
+ </div>
205
+ </div>
206
+ );
207
+ }
@@ -0,0 +1,50 @@
1
+ "use client";
2
+
3
+ import { Clock, Plus, Users } from "lucide-react";
4
+ import { useRouter } from "next/navigation";
5
+ import { Button } from "@/components/ui";
6
+
7
+ export function QuickActions() {
8
+ const router = useRouter();
9
+
10
+ return (
11
+ <div className="bg-card border border-border/50 rounded-2xl p-6 shadow-sm">
12
+ <h3 className="font-semibold text-lg mb-4 flex items-center gap-2">
13
+ <Plus size={20} className="text-primary" />
14
+ Quick Actions
15
+ </h3>
16
+ <div className="grid grid-cols-1 gap-3">
17
+ <Button
18
+ variant="outline"
19
+ className="justify-start gap-3 h-12 rounded-xl group"
20
+ onClick={() => router.push("/backlog?createTask=true")}
21
+ >
22
+ <div className="p-1.5 bg-primary/10 rounded-lg group-hover:bg-primary group-hover:text-primary-foreground transition-colors">
23
+ <Plus size={16} />
24
+ </div>
25
+ New Task
26
+ </Button>
27
+ <Button
28
+ variant="outline"
29
+ className="justify-start gap-3 h-12 rounded-xl group"
30
+ onClick={() => router.push("/backlog?createSprint=true")}
31
+ >
32
+ <div className="p-1.5 bg-amber-500/10 text-amber-500 rounded-lg group-hover:bg-amber-500 group-hover:text-white transition-colors">
33
+ <Clock size={16} />
34
+ </div>
35
+ Start Sprint
36
+ </Button>
37
+ <Button
38
+ variant="outline"
39
+ className="justify-start gap-3 h-12 rounded-xl group"
40
+ onClick={() => router.push("/settings/team")}
41
+ >
42
+ <div className="p-1.5 bg-purple-500/10 text-purple-500 rounded-lg group-hover:bg-purple-500 group-hover:text-white transition-colors">
43
+ <Users size={16} />
44
+ </div>
45
+ Invite Team
46
+ </Button>
47
+ </div>
48
+ </div>
49
+ );
50
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Stat Card Component
3
+ *
4
+ * Displays a statistic with icon, value, title, and trend.
5
+ * Used on dashboard to show key metrics.
6
+ *
7
+ * @example
8
+ * <StatCard
9
+ * title="Active Tasks"
10
+ * value={24}
11
+ * icon={CheckCircle}
12
+ * trend="+5% this week"
13
+ * color="success"
14
+ * />
15
+ */
16
+
17
+ "use client";
18
+
19
+ import { type LucideIcon } from "lucide-react";
20
+ import { Badge } from "@/components/ui";
21
+
22
+ export interface StatCardProps {
23
+ /** Display title */
24
+ title: string;
25
+ /** Statistic value */
26
+ value: number | string;
27
+ /** Icon component to display */
28
+ icon: LucideIcon;
29
+ /** Trend or additional info text */
30
+ trend: string;
31
+ /** Color theme */
32
+ color: "primary" | "warning" | "success" | "purple";
33
+ }
34
+
35
+ const colors = {
36
+ primary: "text-primary bg-primary/10",
37
+ warning: "text-amber-500 bg-amber-500/10",
38
+ success: "text-emerald-500 bg-emerald-500/10",
39
+ purple: "text-purple-500 bg-purple-500/10",
40
+ };
41
+
42
+ /**
43
+ * Stat Card Component
44
+ *
45
+ * Features:
46
+ * - Icon display with color theming
47
+ * - Value and title display
48
+ * - Trend badge
49
+ * - Hover effect for interactivity
50
+ *
51
+ * @component
52
+ */
53
+ export function StatCard({
54
+ title,
55
+ value,
56
+ icon: Icon,
57
+ trend,
58
+ color,
59
+ }: StatCardProps) {
60
+ return (
61
+ <div className="bg-card border border-border/50 rounded-2xl p-6 shadow-sm hover:shadow-md transition-shadow">
62
+ <div className="flex items-center justify-between mb-4">
63
+ <div className={`p-2 rounded-xl ${colors[color]}`}>
64
+ <Icon size={20} />
65
+ </div>
66
+ <Badge
67
+ variant="outline"
68
+ className="text-[10px] font-medium border-border/50"
69
+ >
70
+ {trend}
71
+ </Badge>
72
+ </div>
73
+ <div>
74
+ <div className="text-3xl font-bold text-foreground">{value}</div>
75
+ <div className="text-sm text-muted-foreground mt-1">{title}</div>
76
+ </div>
77
+ </div>
78
+ );
79
+ }