@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.
- package/CHANGELOG.md +26 -0
- package/next.config.js +15 -2
- package/package.json +26 -3
- package/src/app/(auth)/invite/page.tsx +109 -0
- package/src/app/(auth)/layout.tsx +19 -0
- package/src/app/(auth)/login/page.tsx +65 -0
- package/src/app/(auth)/onboarding/workspace/page.tsx +46 -0
- package/src/app/(auth)/register/page.tsx +165 -0
- package/src/app/(dashboard)/activity/page.tsx +7 -0
- package/src/app/(dashboard)/backlog/page.tsx +195 -0
- package/src/app/(dashboard)/board/page.tsx +141 -0
- package/src/app/(dashboard)/layout.tsx +32 -0
- package/src/app/(dashboard)/page.tsx +14 -0
- package/src/app/(dashboard)/settings/page.tsx +161 -0
- package/src/app/(dashboard)/settings/team/page.tsx +75 -0
- package/src/app/globals.css +259 -0
- package/src/app/layout.tsx +10 -20
- package/src/app/providers.tsx +26 -3
- package/src/components/AuthLayoutUI.tsx +53 -0
- package/src/components/BoardFilter.tsx +75 -74
- package/src/components/CreateModal/CreateModal.tsx +142 -0
- package/src/components/CreateModal/index.ts +1 -0
- package/src/components/Editor.tsx +279 -0
- package/src/components/Header.tsx +99 -12
- package/src/components/PageLayout.tsx +69 -0
- package/src/components/PropertyItem.tsx +55 -9
- package/src/components/Sidebar.tsx +280 -36
- package/src/components/SprintCreateModal.tsx +84 -0
- package/src/components/TaskCard.tsx +196 -78
- package/src/components/TaskCreateModal.tsx +181 -178
- package/src/components/TaskPanel.tsx +140 -692
- package/src/components/WorkspaceCreateModal.tsx +97 -0
- package/src/components/WorkspaceProtected.tsx +91 -0
- package/src/components/auth/InviteSteps.tsx +220 -0
- package/src/components/auth/LoginSteps.tsx +86 -0
- package/src/components/auth/RegisterSteps.tsx +371 -0
- package/src/components/auth/index.ts +3 -0
- package/src/components/backlog/BacklogList.tsx +92 -0
- package/src/components/backlog/BacklogSection.tsx +137 -0
- package/src/components/backlog/CompletedSprintItem.tsx +95 -0
- package/src/components/backlog/CompletedSprintsSection.tsx +77 -0
- package/src/components/backlog/SprintSection.tsx +155 -0
- package/src/components/backlog/constants.ts +26 -0
- package/src/components/board/BoardColumn.tsx +97 -0
- package/src/components/board/BoardContent.tsx +84 -0
- package/src/components/board/BoardEmptyState.tsx +82 -0
- package/src/components/board/BoardHeader.tsx +47 -0
- package/src/components/board/SprintMindmap.tsx +65 -0
- package/src/components/board/constants.ts +40 -0
- package/src/components/board/index.ts +5 -0
- package/src/components/common/ErrorState.tsx +124 -0
- package/src/components/common/LoadingState.tsx +83 -0
- package/src/components/common/index.ts +40 -0
- package/src/components/dashboard/ActivityFeed.tsx +77 -0
- package/src/components/dashboard/ActivityItem.tsx +207 -0
- package/src/components/dashboard/QuickActions.tsx +50 -0
- package/src/components/dashboard/StatCard.tsx +79 -0
- package/src/components/dnd/index.tsx +51 -0
- package/src/components/docs/DocsEditorArea.tsx +87 -0
- package/src/components/docs/DocsHeaderActions.tsx +121 -0
- package/src/components/docs/DocsSidebar.tsx +351 -0
- package/src/components/index.ts +7 -0
- package/src/components/onboarding/StepProgress.tsx +21 -0
- package/src/components/onboarding/index.ts +1 -0
- package/src/components/settings/ApiKeyConfirmationModal.tsx +81 -0
- package/src/components/settings/ApiKeyCreatedModal.tsx +96 -0
- package/src/components/settings/ApiKeysList.tsx +143 -0
- package/src/components/settings/ApiKeysSettings.tsx +144 -0
- package/src/components/settings/InviteMemberModal.tsx +106 -0
- package/src/components/settings/ProjectSetupGuide.tsx +147 -0
- package/src/components/settings/SettingItem.tsx +32 -0
- package/src/components/settings/SettingSection.tsx +50 -0
- package/src/components/settings/TeamInvitationsList.tsx +90 -0
- package/src/components/settings/TeamMembersList.tsx +95 -0
- package/src/components/settings/index.ts +8 -0
- package/src/components/task-panel/TaskActivity.tsx +127 -0
- package/src/components/task-panel/TaskChecklist.tsx +142 -0
- package/src/components/task-panel/TaskDescription.tsx +201 -0
- package/src/components/task-panel/TaskDocs.tsx +137 -0
- package/src/components/task-panel/TaskHeader.tsx +125 -0
- package/src/components/task-panel/TaskProperties.tsx +111 -0
- package/src/components/task-panel/index.ts +12 -0
- package/src/components/typography/EmptyStateText.tsx +59 -0
- package/src/components/typography/MetadataText.tsx +65 -0
- package/src/components/typography/SecondaryText.tsx +60 -0
- package/src/components/typography/SectionLabel.tsx +60 -0
- package/src/components/typography/index.ts +14 -0
- package/src/components/typography-scales.tsx +218 -0
- package/src/components/ui/Avatar.tsx +123 -0
- package/src/components/ui/Badge.tsx +69 -2
- package/src/components/ui/Button.tsx +71 -30
- package/src/components/ui/Checkbox.tsx +34 -0
- package/src/components/ui/Dropdown.tsx +67 -1
- package/src/components/ui/EmptyState.tsx +129 -0
- package/src/components/ui/Input.tsx +53 -6
- package/src/components/ui/Modal.tsx +45 -12
- package/src/components/ui/OtpInput.tsx +148 -0
- package/src/components/ui/Skeleton.tsx +36 -0
- package/src/components/ui/Spinner.tsx +112 -0
- package/src/components/ui/Textarea.tsx +28 -3
- package/src/components/ui/Toast.tsx +99 -0
- package/src/components/ui/Toggle.tsx +63 -0
- package/src/components/ui/constants.ts +108 -0
- package/src/components/ui/index.ts +7 -0
- package/src/context/AuthContext.tsx +140 -0
- package/src/context/index.ts +1 -0
- package/src/hooks/backlog/index.ts +13 -0
- package/src/hooks/backlog/useBacklogActions.ts +144 -0
- package/src/hooks/backlog/useBacklogComposite.ts +73 -0
- package/src/hooks/backlog/useBacklogData.ts +74 -0
- package/src/hooks/backlog/useBacklogDragDrop.ts +118 -0
- package/src/hooks/backlog/useBacklogUI.ts +74 -0
- package/src/hooks/index.ts +22 -0
- package/src/hooks/task-panel/index.ts +13 -0
- package/src/hooks/task-panel/useTaskActions.ts +200 -0
- package/src/hooks/task-panel/useTaskComputedValues.ts +30 -0
- package/src/hooks/task-panel/useTaskData.ts +78 -0
- package/src/hooks/task-panel/useTaskPanelComposite.ts +161 -0
- package/src/hooks/task-panel/useTaskUIState.ts +80 -0
- package/src/hooks/useAuthLayoutLogic.ts +43 -0
- package/src/hooks/useAuthenticatedUser.ts +36 -0
- package/src/hooks/useAuthenticatedUserWithOrg.ts +41 -0
- package/src/hooks/useBacklog.ts +303 -0
- package/src/hooks/useBoard.ts +230 -0
- package/src/hooks/useDashboardLayout.ts +49 -0
- package/src/hooks/useDocs.ts +279 -0
- package/src/hooks/useDocsQuery.ts +99 -0
- package/src/hooks/useDocsSidebarState.ts +104 -0
- package/src/hooks/useFormState.ts +40 -0
- package/src/hooks/useGlobalKeydowns.ts +52 -0
- package/src/hooks/useInviteForm.ts +122 -0
- package/src/hooks/useLoginForm.ts +84 -0
- package/src/hooks/useMutationWithToast.ts +56 -0
- package/src/hooks/useOrganizationQuery.ts +55 -0
- package/src/hooks/useRegisterForm.ts +216 -0
- package/src/hooks/useSprintsQuery.ts +38 -0
- package/src/hooks/useTaskDescription.ts +102 -0
- package/src/hooks/useTaskPanel.ts +341 -0
- package/src/hooks/useTasksQuery.ts +39 -0
- package/src/hooks/useTeamManagement.ts +92 -0
- package/src/hooks/useWorkspaceCreateForm.ts +70 -0
- package/src/hooks/useWorkspaceId.ts +29 -0
- package/src/lib/api-client.ts +40 -23
- package/src/lib/config.ts +25 -0
- package/src/lib/constants.ts +83 -0
- package/src/lib/options.ts +96 -0
- package/src/lib/query-keys.ts +91 -0
- package/src/lib/typography.ts +103 -0
- package/src/lib/utils.ts +4 -0
- package/src/lib/validation.ts +192 -0
- package/src/services/index.ts +7 -3
- package/src/services/notifications.ts +80 -0
- package/src/utils/env.utils.ts +15 -0
- package/src/views/ActivityView.tsx +123 -0
- package/src/views/Dashboard.tsx +126 -0
- package/src/views/Docs.tsx +98 -612
- package/tsconfig.tsbuildinfo +1 -1
- package/.next/BUILD_ID +0 -1
- package/.next/app-build-manifest.json +0 -55
- package/.next/app-path-routes-manifest.json +0 -8
- package/.next/build-manifest.json +0 -33
- package/.next/cache/.previewinfo +0 -1
- package/.next/cache/.rscinfo +0 -1
- package/.next/cache/.tsbuildinfo +0 -1
- package/.next/cache/config.json +0 -7
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/diagnostics/build-diagnostics.json +0 -6
- package/.next/diagnostics/framework.json +0 -1
- package/.next/export-detail.json +0 -5
- package/.next/export-marker.json +0 -6
- package/.next/images-manifest.json +0 -57
- package/.next/next-minimal-server.js.nft.json +0 -1
- package/.next/next-server.js.nft.json +0 -1
- package/.next/package.json +0 -1
- package/.next/prerender-manifest.json +0 -137
- package/.next/react-loadable-manifest.json +0 -32
- package/.next/required-server-files.json +0 -324
- package/.next/routes-manifest.json +0 -77
- package/.next/server/app/_not-found/page.js +0 -2
- package/.next/server/app/_not-found/page.js.nft.json +0 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
- package/.next/server/app/_not-found.html +0 -1
- package/.next/server/app/_not-found.meta +0 -8
- package/.next/server/app/_not-found.rsc +0 -21
- package/.next/server/app/backlog/page.js +0 -2
- package/.next/server/app/backlog/page.js.nft.json +0 -1
- package/.next/server/app/backlog/page_client-reference-manifest.js +0 -1
- package/.next/server/app/backlog.html +0 -1
- package/.next/server/app/backlog.meta +0 -7
- package/.next/server/app/backlog.rsc +0 -25
- package/.next/server/app/docs/page.js +0 -97
- package/.next/server/app/docs/page.js.nft.json +0 -1
- package/.next/server/app/docs/page_client-reference-manifest.js +0 -1
- package/.next/server/app/docs.html +0 -1
- package/.next/server/app/docs.meta +0 -7
- package/.next/server/app/docs.rsc +0 -26
- package/.next/server/app/favicon.ico/route.js +0 -1
- package/.next/server/app/favicon.ico/route.js.nft.json +0 -1
- package/.next/server/app/favicon.ico.body +0 -0
- package/.next/server/app/favicon.ico.meta +0 -1
- package/.next/server/app/index.html +0 -1
- package/.next/server/app/index.meta +0 -7
- package/.next/server/app/index.rsc +0 -25
- package/.next/server/app/page.js +0 -2
- package/.next/server/app/page.js.nft.json +0 -1
- package/.next/server/app/page_client-reference-manifest.js +0 -1
- package/.next/server/app/settings/page.js +0 -2
- package/.next/server/app/settings/page.js.nft.json +0 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +0 -1
- package/.next/server/app/settings.html +0 -1
- package/.next/server/app/settings.meta +0 -7
- package/.next/server/app/settings.rsc +0 -25
- package/.next/server/app-paths-manifest.json +0 -8
- package/.next/server/chunks/496.js +0 -6
- package/.next/server/chunks/585.js +0 -1
- package/.next/server/chunks/665.js +0 -1
- package/.next/server/chunks/699.js +0 -22
- package/.next/server/chunks/852.js +0 -7
- package/.next/server/functions-config-manifest.json +0 -4
- package/.next/server/interception-route-rewrite-manifest.js +0 -1
- package/.next/server/middleware-build-manifest.js +0 -1
- package/.next/server/middleware-manifest.json +0 -6
- package/.next/server/middleware-react-loadable-manifest.js +0 -1
- package/.next/server/next-font-manifest.js +0 -1
- package/.next/server/next-font-manifest.json +0 -1
- package/.next/server/pages/404.html +0 -1
- package/.next/server/pages/500.html +0 -1
- package/.next/server/pages/_app.js +0 -1
- package/.next/server/pages/_app.js.nft.json +0 -1
- package/.next/server/pages/_document.js +0 -1
- package/.next/server/pages/_document.js.nft.json +0 -1
- package/.next/server/pages/_error.js +0 -19
- package/.next/server/pages/_error.js.nft.json +0 -1
- package/.next/server/pages-manifest.json +0 -6
- package/.next/server/server-reference-manifest.js +0 -1
- package/.next/server/server-reference-manifest.json +0 -1
- package/.next/server/webpack-runtime.js +0 -1
- package/.next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
- package/.next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
- package/.next/static/chunks/138.b98511c56423f8bb.js +0 -1
- package/.next/static/chunks/146-34259952c594a3b0.js +0 -1
- package/.next/static/chunks/337-d3bb75304d130513.js +0 -1
- package/.next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
- package/.next/static/chunks/487-1808785ba665f784.js +0 -1
- package/.next/static/chunks/544.a9569941cc886e9d.js +0 -1
- package/.next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
- package/.next/static/chunks/902-d6926825a9fe8784.js +0 -1
- package/.next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
- package/.next/static/chunks/996.e0a334e6ae90900e.js +0 -1
- package/.next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
- package/.next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
- package/.next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
- package/.next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
- package/.next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
- package/.next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
- package/.next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
- package/.next/static/chunks/main-843ab130fc1be309.js +0 -1
- package/.next/static/chunks/main-app-123e879c5a937a00.js +0 -1
- package/.next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
- package/.next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
- package/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/.next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
- package/.next/static/css/13e8617b72f9d3aa.css +0 -1
- package/.next/static/css/8aea088cdc4338f0.css +0 -1
- package/.next/static/css/b301ab0424111664.css +0 -1
- package/.next/static/media/24c15609eaa28576-s.woff2 +0 -0
- package/.next/static/media/2c07349e02a7b712-s.woff2 +0 -0
- package/.next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
- package/.next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
- package/.next/static/media/4f77bef990aad698-s.woff2 +0 -0
- package/.next/static/media/627d916fd739a539-s.woff2 +0 -0
- package/.next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
- package/.next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
- package/.next/static/media/84602850c8fd81c3-s.woff2 +0 -0
- package/.next/trace +0 -46
- package/.next/types/app/backlog/page.ts +0 -84
- package/.next/types/app/docs/page.ts +0 -84
- package/.next/types/app/layout.ts +0 -84
- package/.next/types/app/page.ts +0 -84
- package/.next/types/app/settings/page.ts +0 -84
- package/.next/types/cache-life.d.ts +0 -141
- package/.next/types/package.json +0 -1
- package/next-env.d.ts +0 -5
- package/out/404.html +0 -1
- package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
- package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
- package/out/_next/static/chunks/138.b98511c56423f8bb.js +0 -1
- package/out/_next/static/chunks/146-34259952c594a3b0.js +0 -1
- package/out/_next/static/chunks/337-d3bb75304d130513.js +0 -1
- package/out/_next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
- package/out/_next/static/chunks/487-1808785ba665f784.js +0 -1
- package/out/_next/static/chunks/544.a9569941cc886e9d.js +0 -1
- package/out/_next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
- package/out/_next/static/chunks/902-d6926825a9fe8784.js +0 -1
- package/out/_next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
- package/out/_next/static/chunks/996.e0a334e6ae90900e.js +0 -1
- package/out/_next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
- package/out/_next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
- package/out/_next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
- package/out/_next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
- package/out/_next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
- package/out/_next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
- package/out/_next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
- package/out/_next/static/chunks/main-843ab130fc1be309.js +0 -1
- package/out/_next/static/chunks/main-app-123e879c5a937a00.js +0 -1
- package/out/_next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
- package/out/_next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
- package/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/out/_next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
- package/out/_next/static/css/13e8617b72f9d3aa.css +0 -1
- package/out/_next/static/css/8aea088cdc4338f0.css +0 -1
- package/out/_next/static/css/b301ab0424111664.css +0 -1
- package/out/_next/static/media/24c15609eaa28576-s.woff2 +0 -0
- package/out/_next/static/media/2c07349e02a7b712-s.woff2 +0 -0
- package/out/_next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
- package/out/_next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
- package/out/_next/static/media/4f77bef990aad698-s.woff2 +0 -0
- package/out/_next/static/media/627d916fd739a539-s.woff2 +0 -0
- package/out/_next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
- package/out/_next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
- package/out/_next/static/media/84602850c8fd81c3-s.woff2 +0 -0
- package/out/backlog.html +0 -1
- package/out/backlog.txt +0 -25
- package/out/docs.html +0 -1
- package/out/docs.txt +0 -26
- package/out/favicon.ico +0 -0
- package/out/index.html +0 -1
- package/out/index.txt +0 -25
- package/out/logo.png +0 -0
- package/out/settings.html +0 -1
- package/out/settings.txt +0 -25
- package/src/app/backlog/page.tsx +0 -19
- package/src/app/page.tsx +0 -16
- package/src/app/settings/page.tsx +0 -194
- package/src/hooks/useTasks.ts +0 -119
- package/src/services/doc.service.ts +0 -27
- package/src/services/sprint.service.ts +0 -24
- package/src/services/task.service.ts +0 -75
- package/src/views/Backlog.tsx +0 -691
- package/src/views/Board.tsx +0 -306
- /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
|
+
}
|