@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,97 @@
1
+ /**
2
+ * Workspace Create Modal Component
3
+ *
4
+ * Modal dialog for creating new workspaces.
5
+ * Requires authenticated user with organization.
6
+ * Integrates with useMutationWithToast for error handling.
7
+ *
8
+ * @example
9
+ * <WorkspaceCreateModal
10
+ * isOpen={isOpen}
11
+ * onClose={handleClose}
12
+ * onSuccess={handleSuccess}
13
+ * />
14
+ */
15
+
16
+ "use client";
17
+
18
+ import { useState } from "react";
19
+ import { toast } from "sonner";
20
+ import { CreateModal } from "@/components/CreateModal";
21
+ import { Input } from "@/components/ui";
22
+ import { useAuthenticatedUser } from "@/hooks";
23
+ import { useMutationWithToast } from "@/hooks/useMutationWithToast";
24
+ import { locusClient } from "@/lib/api-client";
25
+ import { queryKeys } from "@/lib/query-keys";
26
+
27
+ interface WorkspaceCreateModalProps {
28
+ /** Whether modal is open */
29
+ isOpen: boolean;
30
+ /** Called to close modal */
31
+ onClose: () => void;
32
+ /** Called after successful workspace creation */
33
+ onSuccess: () => void;
34
+ }
35
+
36
+ export function WorkspaceCreateModal({
37
+ isOpen,
38
+ onClose,
39
+ onSuccess,
40
+ }: WorkspaceCreateModalProps) {
41
+ const [name, setName] = useState("");
42
+ const user = useAuthenticatedUser();
43
+ const orgId = user.orgId;
44
+
45
+ const createWorkspace = useMutationWithToast({
46
+ mutationFn: (data: { name: string }) => {
47
+ if (!orgId) {
48
+ throw new Error("Organization ID is required");
49
+ }
50
+ return locusClient.workspaces.create({ orgId, name: data.name });
51
+ },
52
+ successMessage: "Workspace created successfully",
53
+ invalidateKeys: [queryKeys.workspaces.all()],
54
+ onSuccess: () => {
55
+ setName("");
56
+ onSuccess();
57
+ onClose();
58
+ },
59
+ });
60
+
61
+ const handleSubmit = (e: React.FormEvent) => {
62
+ e.preventDefault();
63
+ if (!orgId) {
64
+ toast.error("You must be in an organization to create a workspace");
65
+ return;
66
+ }
67
+ createWorkspace.mutate({ name });
68
+ };
69
+
70
+ return (
71
+ <CreateModal
72
+ isOpen={isOpen}
73
+ title="Create New Workspace"
74
+ size="sm"
75
+ fields={[
76
+ {
77
+ name: "name",
78
+ label: "Workspace Name",
79
+ component: (
80
+ <Input
81
+ placeholder="Engineering, Marketing, etc."
82
+ value={name}
83
+ onChange={(e) => setName(e.target.value)}
84
+ autoFocus
85
+ />
86
+ ),
87
+ required: true,
88
+ },
89
+ ]}
90
+ onSubmit={handleSubmit}
91
+ onClose={onClose}
92
+ submitText="Create Workspace"
93
+ isPending={createWorkspace.isPending}
94
+ submitDisabled={!name.trim()}
95
+ />
96
+ );
97
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Workspace Protected Component
3
+ *
4
+ * Wrapper that ensures all children have valid workspaceId.
5
+ * Redirects to workspace creation if user has no workspace.
6
+ * Handles authentication, organization, and workspace validation.
7
+ *
8
+ * Features:
9
+ * - Automatic workspace validation
10
+ * - Redirect to onboarding if no organization
11
+ * - Redirect to workspace creation if needed
12
+ * - Retry logic for race conditions
13
+ * - Loading state handling
14
+ *
15
+ * @example
16
+ * <WorkspaceProtected>
17
+ * <Dashboard />
18
+ * </WorkspaceProtected>
19
+ */
20
+
21
+ "use client";
22
+
23
+ import { useRouter } from "next/navigation";
24
+ import { useEffect, useRef } from "react";
25
+ import { LoadingPage } from "@/components/ui";
26
+ import { useAuth } from "@/context/AuthContext";
27
+
28
+ interface WorkspaceProtectedProps {
29
+ /** Components to render when workspace is valid */
30
+ children: React.ReactNode;
31
+ }
32
+
33
+ /**
34
+ * Workspace Protected Wrapper
35
+ *
36
+ * Ensures that all children have a valid workspaceId.
37
+ * Redirects to workspace creation if user has no workspace.
38
+ *
39
+ * Usage: Wrap components that require workspaceId
40
+ * - All children can safely use useWorkspaceId() without fallbacks
41
+ * - Early exits handle missing workspace automatically
42
+ *
43
+ * @component
44
+ */
45
+ export function WorkspaceProtected({ children }: WorkspaceProtectedProps) {
46
+ const { user, isLoading, refreshUser } = useAuth();
47
+ const router = useRouter();
48
+ const refreshAttemptRef = useRef(0);
49
+ const MAX_REFRESH_ATTEMPTS = 3;
50
+
51
+ useEffect(() => {
52
+ if (isLoading) return;
53
+
54
+ // If user has NO organization, redirect to onboarding
55
+ if (user && !user.orgId) {
56
+ router.push("/onboarding/workspace");
57
+ return;
58
+ }
59
+
60
+ // If user has NO workspace, try refreshing first (might have just been created)
61
+ if (user && !user.workspaceId) {
62
+ if (refreshAttemptRef.current < MAX_REFRESH_ATTEMPTS) {
63
+ refreshAttemptRef.current++;
64
+ // Wait a moment then refresh user data
65
+ const timer = setTimeout(() => {
66
+ refreshUser();
67
+ }, 500);
68
+ return () => clearTimeout(timer);
69
+ }
70
+ // After max attempts, redirect to onboarding
71
+ router.push("/onboarding/workspace");
72
+ }
73
+ }, [user, isLoading, router, refreshUser]);
74
+
75
+ if (isLoading) {
76
+ return null;
77
+ }
78
+
79
+ // If user is not authenticated, let parent layout handle redirect
80
+ if (!user) {
81
+ return null;
82
+ }
83
+
84
+ // If user has no workspace, we're redirecting (show loading state)
85
+ if (!user.workspaceId) {
86
+ return <LoadingPage />;
87
+ }
88
+
89
+ // User is authenticated and has workspace - safe to render children
90
+ return <>{children}</>;
91
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Invitation acceptance page components
3
+ * Separate UI components for invite states
4
+ */
5
+
6
+ import { LogIn, UserPlus, XCircle } from "lucide-react";
7
+ import { SecondaryText } from "@/components/typography";
8
+ import { Button, Input, Spinner } from "@/components/ui";
9
+
10
+ /**
11
+ * Loading state for invitation verification
12
+ */
13
+ export function InviteLoadingState() {
14
+ return (
15
+ <div className="flex flex-col items-center justify-center min-h-[400px]">
16
+ <Spinner size="lg" className="mb-4" />
17
+ <p className="text-muted-foreground animate-pulse text-sm">
18
+ Verifying invitation...
19
+ </p>
20
+ </div>
21
+ );
22
+ }
23
+
24
+ interface InviteErrorStateProps {
25
+ error: string;
26
+ onGoHome: () => void;
27
+ }
28
+
29
+ /**
30
+ * Error state for invitation errors
31
+ */
32
+ export function InviteErrorState({ error, onGoHome }: InviteErrorStateProps) {
33
+ return (
34
+ <div className="flex flex-col items-center justify-center p-8 text-center min-h-[400px]">
35
+ <div className="w-12 h-12 rounded-full bg-destructive/10 text-destructive flex items-center justify-center mb-4">
36
+ <XCircle size={24} />
37
+ </div>
38
+ <h1 className="text-xl font-bold mb-2">Invitation Error</h1>
39
+ <p className="text-sm text-muted-foreground mb-6 max-w-xs">{error}</p>
40
+ <Button onClick={onGoHome} variant="secondary" size="sm">
41
+ Go to Homepage
42
+ </Button>
43
+ </div>
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Header section for invitation page
49
+ */
50
+ export function InviteHeader() {
51
+ return (
52
+ <div className="text-center mb-8">
53
+ <div className="w-16 h-16 rounded-2xl bg-primary/10 text-primary flex items-center justify-center mx-auto mb-4 shadow-inner">
54
+ <UserPlus size={32} />
55
+ </div>
56
+ <h1 className="text-2xl font-bold tracking-tight mb-1 text-foreground">
57
+ Join Locus
58
+ </h1>
59
+ <p className="text-xs text-muted-foreground px-4">
60
+ You've been invited to collab on Locus AI.
61
+ </p>
62
+ </div>
63
+ );
64
+ }
65
+
66
+ interface InviteEmailDisplayProps {
67
+ email: string;
68
+ }
69
+
70
+ /**
71
+ * Email display section
72
+ */
73
+ export function InviteEmailDisplay({ email }: InviteEmailDisplayProps) {
74
+ return (
75
+ <div className="space-y-1.5">
76
+ <SecondaryText as="label" size="xs" className="px-1">
77
+ Email Address
78
+ </SecondaryText>
79
+ <div className="h-11 flex items-center px-4 bg-secondary/30 border border-secondary/50 rounded-xl text-foreground font-medium text-sm overflow-hidden text-ellipsis whitespace-nowrap">
80
+ {email}
81
+ </div>
82
+ </div>
83
+ );
84
+ }
85
+
86
+ interface InviteNewAccountFormProps {
87
+ name: string;
88
+ loading: boolean;
89
+ onNameChange: (name: string) => void;
90
+ onSubmit: (e: React.FormEvent) => void;
91
+ }
92
+
93
+ /**
94
+ * Form for new user accepting invitation
95
+ */
96
+ export function InviteNewAccountForm({
97
+ name,
98
+ loading,
99
+ onNameChange,
100
+ onSubmit,
101
+ }: InviteNewAccountFormProps) {
102
+ return (
103
+ <form onSubmit={onSubmit} className="space-y-5">
104
+ <div className="space-y-1.5">
105
+ <SecondaryText as="label" size="xs" className="px-1">
106
+ Full Name
107
+ </SecondaryText>
108
+ <Input
109
+ placeholder="e.g. Alex Rivera"
110
+ value={name}
111
+ onChange={(e) => onNameChange(e.target.value)}
112
+ className="h-11 text-sm rounded-xl bg-background/50 border-secondary focus:border-primary"
113
+ required
114
+ autoFocus
115
+ />
116
+ </div>
117
+
118
+ <Button
119
+ type="submit"
120
+ className="w-full h-12 text-sm font-bold rounded-xl shadow-lg shadow-primary/10 hover:shadow-primary/20 transition-all"
121
+ disabled={loading}
122
+ >
123
+ {loading ? <Spinner size="sm" /> : "Create Account & Join"}
124
+ </Button>
125
+ </form>
126
+ );
127
+ }
128
+
129
+ interface InviteExistingUserFormProps {
130
+ loading: boolean;
131
+ onSubmit: (e: React.FormEvent) => void;
132
+ }
133
+
134
+ /**
135
+ * Form for existing user accepting invitation
136
+ */
137
+ export function InviteExistingUserForm({
138
+ loading,
139
+ onSubmit,
140
+ }: InviteExistingUserFormProps) {
141
+ return (
142
+ <form onSubmit={onSubmit} className="space-y-5">
143
+ <Button
144
+ type="submit"
145
+ className="w-full h-12 text-sm font-bold rounded-xl shadow-lg shadow-primary/10 hover:shadow-primary/20 transition-all"
146
+ disabled={loading}
147
+ >
148
+ {loading ? <Spinner size="sm" /> : "Join Now"}
149
+ </Button>
150
+ </form>
151
+ );
152
+ }
153
+
154
+ interface InviteUserExistsMessageProps {
155
+ onSignIn: () => void;
156
+ }
157
+
158
+ /**
159
+ * Message when email already exists
160
+ */
161
+ export function InviteUserExistsMessage({
162
+ onSignIn,
163
+ }: InviteUserExistsMessageProps) {
164
+ return (
165
+ <div className="space-y-4 text-center">
166
+ <div className="p-3 bg-primary/5 border border-primary/10 rounded-xl">
167
+ <p className="text-xs leading-relaxed text-muted-foreground">
168
+ An account with this email already exists. Sign in to accept.
169
+ </p>
170
+ </div>
171
+ <Button
172
+ onClick={onSignIn}
173
+ className="w-full h-11 text-sm font-bold rounded-xl transition-all"
174
+ variant="primary"
175
+ >
176
+ <LogIn size={18} className="mr-2" />
177
+ Sign in to Continue
178
+ </Button>
179
+ </div>
180
+ );
181
+ }
182
+
183
+ interface InviteLoggedInMessageProps {
184
+ userName: string;
185
+ }
186
+
187
+ /**
188
+ * Message when user is already logged in as the invited user
189
+ */
190
+ export function InviteLoggedInMessage({
191
+ userName,
192
+ }: InviteLoggedInMessageProps) {
193
+ return (
194
+ <div className="p-3 bg-emerald-500/5 border border-emerald-500/10 rounded-xl flex items-center gap-2">
195
+ <div className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
196
+ <p className="text-[11px] font-medium text-emerald-600/80">
197
+ Logged in as <span className="text-emerald-600">{userName}</span>
198
+ </p>
199
+ </div>
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Footer with terms and privacy
205
+ */
206
+ export function InviteFooter() {
207
+ return (
208
+ <p className="text-center text-[10px] text-muted-foreground/50 mt-8 max-w-[200px] mx-auto leading-normal">
209
+ By joining, you agree to our{" "}
210
+ <span className="underline cursor-pointer hover:text-foreground">
211
+ Terms
212
+ </span>{" "}
213
+ and{" "}
214
+ <span className="underline cursor-pointer hover:text-foreground">
215
+ Privacy Policy
216
+ </span>
217
+ .
218
+ </p>
219
+ );
220
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Login page step components
3
+ * Separate UI components for each step of the login flow
4
+ */
5
+
6
+ import { Button } from "@/components/ui/Button";
7
+ import { Input } from "@/components/ui/Input";
8
+ import { OtpInput } from "@/components/ui/OtpInput";
9
+
10
+ interface LoginEmailStepProps {
11
+ email: string;
12
+ loading: boolean;
13
+ onEmailChange: (email: string) => void;
14
+ onSubmit: (e: React.FormEvent) => void;
15
+ }
16
+
17
+ /**
18
+ * Email entry step for login
19
+ */
20
+ export function LoginEmailStep({
21
+ email,
22
+ loading,
23
+ onEmailChange,
24
+ onSubmit,
25
+ }: LoginEmailStepProps) {
26
+ return (
27
+ <form onSubmit={onSubmit} className="space-y-4">
28
+ <div className="space-y-2">
29
+ <Input
30
+ type="email"
31
+ placeholder="name@example.com"
32
+ value={email}
33
+ onChange={(e) => onEmailChange(e.target.value)}
34
+ required
35
+ disabled={loading}
36
+ autoFocus
37
+ />
38
+ </div>
39
+ <Button type="submit" className="w-full" disabled={loading}>
40
+ {loading ? "Sending code..." : "Continue with Email"}
41
+ </Button>
42
+ </form>
43
+ );
44
+ }
45
+
46
+ interface LoginOtpStepProps {
47
+ email: string;
48
+ otp: string;
49
+ loading: boolean;
50
+ onOtpChange: (otp: string) => void;
51
+ onSubmit: (e: React.FormEvent) => void;
52
+ onBack: () => void;
53
+ }
54
+
55
+ /**
56
+ * OTP verification step for login
57
+ */
58
+ export function LoginOtpStep({
59
+ otp,
60
+ loading,
61
+ onOtpChange,
62
+ onSubmit,
63
+ onBack,
64
+ }: LoginOtpStepProps) {
65
+ return (
66
+ <form onSubmit={onSubmit} className="space-y-6">
67
+ <OtpInput value={otp} onChange={onOtpChange} disabled={loading} />
68
+ <Button
69
+ type="submit"
70
+ className="w-full h-11"
71
+ disabled={loading || otp.length < 6}
72
+ >
73
+ {loading ? "Verifying..." : "Verify Code"}
74
+ </Button>
75
+ <div className="text-center">
76
+ <button
77
+ type="button"
78
+ onClick={onBack}
79
+ className="text-sm text-muted-foreground hover:text-primary"
80
+ >
81
+ Change email
82
+ </button>
83
+ </div>
84
+ </form>
85
+ );
86
+ }