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