@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,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registration page step components
|
|
3
|
+
* Separate UI components for each step of the registration flow
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Button, Input, OtpInput } from "@/components/ui";
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
|
|
9
|
+
interface RegisterEmailStepProps {
|
|
10
|
+
email: string;
|
|
11
|
+
loading: boolean;
|
|
12
|
+
onEmailChange: (email: string) => void;
|
|
13
|
+
onSubmit: (e: React.FormEvent) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Email entry step for registration
|
|
18
|
+
*/
|
|
19
|
+
export function RegisterEmailStep({
|
|
20
|
+
email,
|
|
21
|
+
loading,
|
|
22
|
+
onEmailChange,
|
|
23
|
+
onSubmit,
|
|
24
|
+
}: RegisterEmailStepProps) {
|
|
25
|
+
return (
|
|
26
|
+
<form onSubmit={onSubmit} className="space-y-4">
|
|
27
|
+
<div className="space-y-2">
|
|
28
|
+
<label className="text-sm font-medium">Work Email</label>
|
|
29
|
+
<Input
|
|
30
|
+
type="email"
|
|
31
|
+
placeholder="name@company.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 RegisterOtpStepProps {
|
|
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 registration
|
|
57
|
+
*/
|
|
58
|
+
export function RegisterOtpStep({
|
|
59
|
+
email,
|
|
60
|
+
otp,
|
|
61
|
+
loading,
|
|
62
|
+
onOtpChange,
|
|
63
|
+
onSubmit,
|
|
64
|
+
onBack,
|
|
65
|
+
}: RegisterOtpStepProps) {
|
|
66
|
+
return (
|
|
67
|
+
<form onSubmit={onSubmit} className="space-y-6">
|
|
68
|
+
<div className="space-y-4">
|
|
69
|
+
<OtpInput value={otp} onChange={onOtpChange} disabled={loading} />
|
|
70
|
+
<p className="text-xs text-center text-muted-foreground">
|
|
71
|
+
Sent to <span className="font-semibold text-foreground">{email}</span>
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
onClick={onBack}
|
|
75
|
+
className="ml-2 text-primary hover:underline transition-colors"
|
|
76
|
+
>
|
|
77
|
+
Change
|
|
78
|
+
</button>
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
<Button type="submit" className="w-full h-11" disabled={otp.length < 6}>
|
|
82
|
+
Verify Code
|
|
83
|
+
</Button>
|
|
84
|
+
</form>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface RegisterProfileStepProps {
|
|
89
|
+
name: string;
|
|
90
|
+
userRole: string;
|
|
91
|
+
onNameChange: (name: string) => void;
|
|
92
|
+
onRoleChange: (role: string) => void;
|
|
93
|
+
onSubmit: (e: React.FormEvent) => void;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const ROLE_OPTIONS = [
|
|
97
|
+
{ id: "developer", label: "Developer" },
|
|
98
|
+
{ id: "designer", label: "Designer" },
|
|
99
|
+
{ id: "product_manager", label: "Product" },
|
|
100
|
+
{ id: "other", label: "Other" },
|
|
101
|
+
] as const;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Profile step for registration
|
|
105
|
+
*/
|
|
106
|
+
export function RegisterProfileStep({
|
|
107
|
+
name,
|
|
108
|
+
userRole,
|
|
109
|
+
onNameChange,
|
|
110
|
+
onRoleChange,
|
|
111
|
+
onSubmit,
|
|
112
|
+
}: RegisterProfileStepProps) {
|
|
113
|
+
return (
|
|
114
|
+
<form onSubmit={onSubmit} className="space-y-6">
|
|
115
|
+
<div className="space-y-2">
|
|
116
|
+
<label className="text-sm font-semibold text-foreground/90">
|
|
117
|
+
What's your name?
|
|
118
|
+
</label>
|
|
119
|
+
<Input
|
|
120
|
+
type="text"
|
|
121
|
+
placeholder="Full Name"
|
|
122
|
+
value={name}
|
|
123
|
+
onChange={(e) => onNameChange(e.target.value)}
|
|
124
|
+
required
|
|
125
|
+
autoFocus
|
|
126
|
+
className="h-11 bg-secondary/20 border-border/40 focus:bg-background"
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
<div className="space-y-3">
|
|
130
|
+
<label className="text-sm font-semibold text-foreground/90">
|
|
131
|
+
What's your role?
|
|
132
|
+
</label>
|
|
133
|
+
<div className="grid grid-cols-2 gap-3">
|
|
134
|
+
{ROLE_OPTIONS.map((role) => (
|
|
135
|
+
<button
|
|
136
|
+
key={role.id}
|
|
137
|
+
type="button"
|
|
138
|
+
onClick={() => onRoleChange(role.id)}
|
|
139
|
+
className={cn(
|
|
140
|
+
"flex items-center justify-center p-3 rounded-xl border text-sm font-medium transition-all duration-200",
|
|
141
|
+
userRole === role.id
|
|
142
|
+
? "bg-primary/10 border-primary text-primary shadow-[0_0_15px_-5px_rgba(var(--primary),0.3)]"
|
|
143
|
+
: "bg-secondary/10 border-border/40 text-muted-foreground hover:border-border hover:bg-secondary/20"
|
|
144
|
+
)}
|
|
145
|
+
>
|
|
146
|
+
{role.label}
|
|
147
|
+
</button>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
<Button
|
|
152
|
+
type="submit"
|
|
153
|
+
className="w-full h-11 text-base font-bold shadow-lg shadow-primary/20"
|
|
154
|
+
disabled={!userRole || !name}
|
|
155
|
+
>
|
|
156
|
+
Continue
|
|
157
|
+
</Button>
|
|
158
|
+
</form>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface RegisterOrganizationStepProps {
|
|
163
|
+
companyName: string;
|
|
164
|
+
teamSize: string;
|
|
165
|
+
onCompanyNameChange: (name: string) => void;
|
|
166
|
+
onTeamSizeChange: (size: string) => void;
|
|
167
|
+
onSubmit: (e: React.FormEvent) => void;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const TEAM_SIZE_OPTIONS = [
|
|
171
|
+
{ id: "solo", label: "Solo" },
|
|
172
|
+
{ id: "2-10", label: "2-10" },
|
|
173
|
+
{ id: "11-50", label: "11-50" },
|
|
174
|
+
{ id: "51-200", label: "51-200" },
|
|
175
|
+
{ id: "200+", label: "200+" },
|
|
176
|
+
] as const;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Organization step for registration
|
|
180
|
+
*/
|
|
181
|
+
export function RegisterOrganizationStep({
|
|
182
|
+
companyName,
|
|
183
|
+
teamSize,
|
|
184
|
+
onCompanyNameChange,
|
|
185
|
+
onTeamSizeChange,
|
|
186
|
+
onSubmit,
|
|
187
|
+
}: RegisterOrganizationStepProps) {
|
|
188
|
+
return (
|
|
189
|
+
<form onSubmit={onSubmit} className="space-y-6">
|
|
190
|
+
<div className="space-y-2">
|
|
191
|
+
<label className="text-sm font-semibold text-foreground/90">
|
|
192
|
+
Company Name
|
|
193
|
+
</label>
|
|
194
|
+
<Input
|
|
195
|
+
type="text"
|
|
196
|
+
placeholder="Acme Inc."
|
|
197
|
+
value={companyName}
|
|
198
|
+
onChange={(e) => onCompanyNameChange(e.target.value)}
|
|
199
|
+
required
|
|
200
|
+
autoFocus
|
|
201
|
+
className="h-11 bg-secondary/20 border-border/40 focus:bg-background"
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
<div className="space-y-3">
|
|
205
|
+
<label className="text-sm font-semibold text-foreground/90">
|
|
206
|
+
Team Size
|
|
207
|
+
</label>
|
|
208
|
+
<div className="grid grid-cols-2 gap-3">
|
|
209
|
+
{TEAM_SIZE_OPTIONS.map((size) => (
|
|
210
|
+
<button
|
|
211
|
+
key={size.id}
|
|
212
|
+
type="button"
|
|
213
|
+
onClick={() => onTeamSizeChange(size.id)}
|
|
214
|
+
className={cn(
|
|
215
|
+
"flex items-center justify-center p-3 rounded-xl border text-sm font-medium transition-all duration-200",
|
|
216
|
+
teamSize === size.id
|
|
217
|
+
? "bg-primary/10 border-primary text-primary shadow-[0_0_15px_-5px_rgba(var(--primary),0.3)]"
|
|
218
|
+
: "bg-secondary/10 border-border/40 text-muted-foreground hover:border-border hover:bg-secondary/20"
|
|
219
|
+
)}
|
|
220
|
+
>
|
|
221
|
+
{size.label}
|
|
222
|
+
</button>
|
|
223
|
+
))}
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
<Button
|
|
227
|
+
type="submit"
|
|
228
|
+
className="w-full h-11 text-base font-bold shadow-lg shadow-primary/20"
|
|
229
|
+
disabled={!teamSize || !companyName}
|
|
230
|
+
>
|
|
231
|
+
Continue
|
|
232
|
+
</Button>
|
|
233
|
+
</form>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
interface RegisterWorkspaceStepProps {
|
|
238
|
+
workspaceName: string;
|
|
239
|
+
onWorkspaceNameChange: (name: string) => void;
|
|
240
|
+
onSubmit: (e: React.FormEvent) => void;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Workspace step for registration
|
|
245
|
+
*/
|
|
246
|
+
export function RegisterWorkspaceStep({
|
|
247
|
+
workspaceName,
|
|
248
|
+
onWorkspaceNameChange,
|
|
249
|
+
onSubmit,
|
|
250
|
+
}: RegisterWorkspaceStepProps) {
|
|
251
|
+
return (
|
|
252
|
+
<form onSubmit={onSubmit} className="space-y-6">
|
|
253
|
+
<div className="space-y-2">
|
|
254
|
+
<label className="text-sm font-semibold text-foreground/90">
|
|
255
|
+
Name your first workspace
|
|
256
|
+
</label>
|
|
257
|
+
<p className="text-xs text-muted-foreground/70">
|
|
258
|
+
Workspaces are where your team manages specific projects or
|
|
259
|
+
departments.
|
|
260
|
+
</p>
|
|
261
|
+
<Input
|
|
262
|
+
type="text"
|
|
263
|
+
placeholder="e.g. Engineering, Marketing"
|
|
264
|
+
value={workspaceName}
|
|
265
|
+
onChange={(e) => onWorkspaceNameChange(e.target.value)}
|
|
266
|
+
required
|
|
267
|
+
autoFocus
|
|
268
|
+
className="h-11 bg-secondary/20 border-border/40 focus:bg-background"
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
<Button
|
|
272
|
+
type="submit"
|
|
273
|
+
className="w-full h-11 text-base font-bold shadow-lg shadow-primary/20"
|
|
274
|
+
disabled={!workspaceName}
|
|
275
|
+
>
|
|
276
|
+
Continue
|
|
277
|
+
</Button>
|
|
278
|
+
</form>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
interface RegisterInviteStepProps {
|
|
283
|
+
invitedEmails: string[];
|
|
284
|
+
currentInviteEmail: string;
|
|
285
|
+
loading: boolean;
|
|
286
|
+
onCurrentEmailChange: (email: string) => void;
|
|
287
|
+
onAddInvite: () => void;
|
|
288
|
+
onRemoveInvite: (email: string) => void;
|
|
289
|
+
onSkip: () => void;
|
|
290
|
+
onSubmit: () => void;
|
|
291
|
+
onKeyDown?: (e: React.KeyboardEvent) => void;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Invite step for registration
|
|
296
|
+
*/
|
|
297
|
+
export function RegisterInviteStep({
|
|
298
|
+
invitedEmails,
|
|
299
|
+
currentInviteEmail,
|
|
300
|
+
loading,
|
|
301
|
+
onCurrentEmailChange,
|
|
302
|
+
onAddInvite,
|
|
303
|
+
onRemoveInvite,
|
|
304
|
+
onSkip,
|
|
305
|
+
onSubmit,
|
|
306
|
+
onKeyDown,
|
|
307
|
+
}: RegisterInviteStepProps) {
|
|
308
|
+
return (
|
|
309
|
+
<div className="space-y-6">
|
|
310
|
+
<div className="space-y-3">
|
|
311
|
+
<label className="text-sm font-semibold text-foreground/90">
|
|
312
|
+
Invite your team (Optional)
|
|
313
|
+
</label>
|
|
314
|
+
<div className="flex gap-2">
|
|
315
|
+
<Input
|
|
316
|
+
type="email"
|
|
317
|
+
placeholder="colleague@company.com"
|
|
318
|
+
value={currentInviteEmail}
|
|
319
|
+
onChange={(e) => onCurrentEmailChange(e.target.value)}
|
|
320
|
+
onKeyDown={onKeyDown}
|
|
321
|
+
className="h-11 bg-secondary/20 border-border/40 focus:bg-background"
|
|
322
|
+
/>
|
|
323
|
+
<Button
|
|
324
|
+
type="button"
|
|
325
|
+
onClick={onAddInvite}
|
|
326
|
+
variant="secondary"
|
|
327
|
+
className="h-11 px-6"
|
|
328
|
+
>
|
|
329
|
+
Add
|
|
330
|
+
</Button>
|
|
331
|
+
</div>
|
|
332
|
+
{invitedEmails.length > 0 && (
|
|
333
|
+
<div className="flex flex-wrap gap-2 mt-4">
|
|
334
|
+
{invitedEmails.map((email) => (
|
|
335
|
+
<div
|
|
336
|
+
key={email}
|
|
337
|
+
className="bg-primary/5 text-primary border border-primary/20 px-3 py-1.5 rounded-full text-xs font-medium flex items-center gap-2 animate-in zoom-in-95 duration-200"
|
|
338
|
+
>
|
|
339
|
+
{email}
|
|
340
|
+
<button
|
|
341
|
+
type="button"
|
|
342
|
+
onClick={() => onRemoveInvite(email)}
|
|
343
|
+
className="hover:text-primary/70 transition-colors"
|
|
344
|
+
>
|
|
345
|
+
×
|
|
346
|
+
</button>
|
|
347
|
+
</div>
|
|
348
|
+
))}
|
|
349
|
+
</div>
|
|
350
|
+
)}
|
|
351
|
+
</div>
|
|
352
|
+
<div className="pt-4 space-y-3">
|
|
353
|
+
<Button
|
|
354
|
+
onClick={onSubmit}
|
|
355
|
+
className="w-full h-11 text-base font-bold shadow-lg shadow-primary/20"
|
|
356
|
+
disabled={loading}
|
|
357
|
+
>
|
|
358
|
+
{loading ? "Creating your space..." : "Finish Setup"}
|
|
359
|
+
</Button>
|
|
360
|
+
<Button
|
|
361
|
+
variant="ghost"
|
|
362
|
+
onClick={onSkip}
|
|
363
|
+
className="w-full h-11 text-muted-foreground hover:text-foreground"
|
|
364
|
+
disabled={loading}
|
|
365
|
+
>
|
|
366
|
+
Skip for now
|
|
367
|
+
</Button>
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backlog List Component
|
|
3
|
+
*
|
|
4
|
+
* Displays unscheduled tasks in the backlog.
|
|
5
|
+
* Supports expand/collapse and drag-drop to sprints.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <BacklogList
|
|
9
|
+
* tasks={unscheduledTasks}
|
|
10
|
+
* isExpanded={true}
|
|
11
|
+
* onToggle={handleToggle}
|
|
12
|
+
* onTaskClick={handleSelectTask}
|
|
13
|
+
* onTaskDelete={handleDeleteTask}
|
|
14
|
+
* />
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
"use client";
|
|
18
|
+
|
|
19
|
+
import { type Task } from "@locusai/shared";
|
|
20
|
+
import { motion } from "framer-motion";
|
|
21
|
+
import { Inbox } from "lucide-react";
|
|
22
|
+
import { DraggableTask, DroppableSection } from "@/components/dnd";
|
|
23
|
+
import { TaskCard } from "@/components/TaskCard";
|
|
24
|
+
import { BacklogSection } from "./BacklogSection";
|
|
25
|
+
|
|
26
|
+
interface BacklogListProps {
|
|
27
|
+
/** Unscheduled tasks to display */
|
|
28
|
+
tasks: Task[];
|
|
29
|
+
/** Whether the backlog section is expanded */
|
|
30
|
+
isExpanded: boolean;
|
|
31
|
+
/** Called when expanding/collapsing */
|
|
32
|
+
onToggle: () => void;
|
|
33
|
+
/** Called when a task is selected */
|
|
34
|
+
onTaskClick: (taskId: string) => void;
|
|
35
|
+
/** Called when delete action is triggered */
|
|
36
|
+
onTaskDelete: (taskId: string) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function BacklogList({
|
|
40
|
+
tasks,
|
|
41
|
+
isExpanded,
|
|
42
|
+
onToggle,
|
|
43
|
+
onTaskClick,
|
|
44
|
+
onTaskDelete,
|
|
45
|
+
}: BacklogListProps) {
|
|
46
|
+
return (
|
|
47
|
+
<motion.div
|
|
48
|
+
layout="position"
|
|
49
|
+
initial={{ opacity: 0 }}
|
|
50
|
+
animate={{ opacity: 1 }}
|
|
51
|
+
transition={{ duration: 0.15, delay: 0.05 }}
|
|
52
|
+
>
|
|
53
|
+
<BacklogSection
|
|
54
|
+
id="backlog"
|
|
55
|
+
title="Backlog"
|
|
56
|
+
icon={<Inbox size={18} className="text-slate-400" />}
|
|
57
|
+
count={tasks.length}
|
|
58
|
+
isExpanded={isExpanded}
|
|
59
|
+
onToggle={onToggle}
|
|
60
|
+
accentColor="slate"
|
|
61
|
+
>
|
|
62
|
+
<DroppableSection id="backlog">
|
|
63
|
+
{tasks.length === 0 ? (
|
|
64
|
+
<div className="py-6 border border-dashed border-border/20 rounded-xl text-center text-muted-foreground/40 text-[11px] font-medium bg-secondary/5">
|
|
65
|
+
No tasks in backlog
|
|
66
|
+
</div>
|
|
67
|
+
) : (
|
|
68
|
+
<div className="space-y-1.5 pt-1">
|
|
69
|
+
{tasks.map((task) => (
|
|
70
|
+
<motion.div
|
|
71
|
+
key={task.id}
|
|
72
|
+
layout="position"
|
|
73
|
+
initial={{ opacity: 0 }}
|
|
74
|
+
animate={{ opacity: 1 }}
|
|
75
|
+
>
|
|
76
|
+
<DraggableTask task={task}>
|
|
77
|
+
<TaskCard
|
|
78
|
+
task={task}
|
|
79
|
+
variant="list"
|
|
80
|
+
onClick={() => onTaskClick(task.id)}
|
|
81
|
+
onDelete={onTaskDelete}
|
|
82
|
+
/>
|
|
83
|
+
</DraggableTask>
|
|
84
|
+
</motion.div>
|
|
85
|
+
))}
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</DroppableSection>
|
|
89
|
+
</BacklogSection>
|
|
90
|
+
</motion.div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backlog Section Component
|
|
3
|
+
*
|
|
4
|
+
* Reusable section component for backlog and sprint organization.
|
|
5
|
+
* Supports collapsible header with count badge and actions.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <BacklogSection
|
|
9
|
+
* id="sprint-1"
|
|
10
|
+
* title="Sprint 1"
|
|
11
|
+
* icon={<Flag size={18} />}
|
|
12
|
+
* count={12}
|
|
13
|
+
* isExpanded={true}
|
|
14
|
+
* onToggle={handleToggle}
|
|
15
|
+
* accentColor="primary"
|
|
16
|
+
* >
|
|
17
|
+
* { Section content }
|
|
18
|
+
* </BacklogSection>
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
"use client";
|
|
22
|
+
|
|
23
|
+
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
24
|
+
import { cn } from "@/lib/utils";
|
|
25
|
+
|
|
26
|
+
interface BacklogSectionProps {
|
|
27
|
+
/** Unique section identifier */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Display title */
|
|
30
|
+
title: string;
|
|
31
|
+
/** Icon to display */
|
|
32
|
+
icon: React.ReactNode;
|
|
33
|
+
/** Item count to display */
|
|
34
|
+
count: number;
|
|
35
|
+
/** Whether section is expanded */
|
|
36
|
+
isExpanded: boolean;
|
|
37
|
+
/** Called when toggling expand state */
|
|
38
|
+
onToggle: () => void;
|
|
39
|
+
/** Color theme for section */
|
|
40
|
+
accentColor: "slate" | "primary" | "amber" | "green" | "emerald";
|
|
41
|
+
/** Optional badge text */
|
|
42
|
+
badge?: string;
|
|
43
|
+
/** Optional action elements */
|
|
44
|
+
actions?: React.ReactNode;
|
|
45
|
+
/** Section content */
|
|
46
|
+
children: React.ReactNode;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function BacklogSection({
|
|
50
|
+
id: _id,
|
|
51
|
+
title,
|
|
52
|
+
icon,
|
|
53
|
+
count,
|
|
54
|
+
isExpanded,
|
|
55
|
+
onToggle,
|
|
56
|
+
accentColor,
|
|
57
|
+
badge,
|
|
58
|
+
actions,
|
|
59
|
+
children,
|
|
60
|
+
}: BacklogSectionProps) {
|
|
61
|
+
const colors = {
|
|
62
|
+
slate: "border-l-slate-400/60 bg-slate-50/50 dark:bg-slate-900/10",
|
|
63
|
+
primary: "border-l-primary bg-primary/[0.08] dark:bg-primary/[0.06]",
|
|
64
|
+
emerald:
|
|
65
|
+
"border-l-emerald-500 bg-emerald-500/[0.08] dark:bg-emerald-500/[0.06]",
|
|
66
|
+
amber: "border-l-amber-500 bg-amber-500/[0.08] dark:bg-amber-500/[0.06]",
|
|
67
|
+
green: "border-l-green-500 bg-green-500/[0.03] dark:bg-green-500/[0.02]",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const badgeColors = {
|
|
71
|
+
slate: "bg-muted text-muted-foreground",
|
|
72
|
+
primary: "bg-primary/20 text-primary dark:text-primary-foreground/90",
|
|
73
|
+
emerald: "bg-emerald-500/20 text-emerald-700 dark:text-emerald-400",
|
|
74
|
+
amber: "bg-amber-500/15 text-amber-700 dark:text-amber-400",
|
|
75
|
+
green: "bg-green-500/15 text-green-700 dark:text-green-400",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
className={cn(
|
|
81
|
+
"rounded-xl border border-border/40 overflow-hidden transition-all duration-200",
|
|
82
|
+
colors[accentColor],
|
|
83
|
+
"border-l-4",
|
|
84
|
+
isExpanded
|
|
85
|
+
? "shadow-sm ring-1 ring-border/10"
|
|
86
|
+
: "hover:bg-secondary/5 dark:hover:bg-white/2"
|
|
87
|
+
)}
|
|
88
|
+
>
|
|
89
|
+
{/* Header */}
|
|
90
|
+
<div
|
|
91
|
+
onClick={onToggle}
|
|
92
|
+
onKeyDown={(e) => e.key === "Enter" && onToggle()}
|
|
93
|
+
className="w-full flex items-center justify-between p-3.5 cursor-pointer select-none group"
|
|
94
|
+
>
|
|
95
|
+
<div className="flex items-center gap-3">
|
|
96
|
+
<div className="p-1 rounded-md hover:bg-black/5 dark:hover:bg-white/10 transition-colors">
|
|
97
|
+
{isExpanded ? (
|
|
98
|
+
<ChevronDown size={18} className="text-muted-foreground/70" />
|
|
99
|
+
) : (
|
|
100
|
+
<ChevronRight size={18} className="text-muted-foreground/70" />
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
<div className="opacity-70 group-hover:opacity-100 transition-opacity">
|
|
104
|
+
{icon}
|
|
105
|
+
</div>
|
|
106
|
+
<span className="font-semibold text-sm tracking-tight text-foreground/90">
|
|
107
|
+
{title}
|
|
108
|
+
</span>
|
|
109
|
+
<span className="text-[11px] text-muted-foreground/70 bg-background/50 border border-border/10 px-2 py-0.5 rounded-full font-mono">
|
|
110
|
+
{count}
|
|
111
|
+
</span>
|
|
112
|
+
{badge && (
|
|
113
|
+
<span
|
|
114
|
+
className={cn(
|
|
115
|
+
"text-[10px] uppercase tracking-wider px-2 py-0.5 rounded-full font-bold shadow-sm",
|
|
116
|
+
badgeColors[accentColor]
|
|
117
|
+
)}
|
|
118
|
+
>
|
|
119
|
+
{badge}
|
|
120
|
+
</span>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
{actions && (
|
|
124
|
+
<div
|
|
125
|
+
className="flex items-center"
|
|
126
|
+
onClick={(e) => e.stopPropagation()}
|
|
127
|
+
>
|
|
128
|
+
{actions}
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{/* Content */}
|
|
134
|
+
{isExpanded && <div className="px-3.5 pb-3.5 space-y-1">{children}</div>}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completed Sprint Item Component
|
|
3
|
+
*
|
|
4
|
+
* Displays a single completed sprint as a collapsible item.
|
|
5
|
+
* Shows completed tasks and allows viewing historical sprint details.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use client";
|
|
9
|
+
|
|
10
|
+
import { type Task } from "@locusai/shared";
|
|
11
|
+
import { AnimatePresence, motion } from "framer-motion";
|
|
12
|
+
import { CheckCircle, ChevronDown, ChevronRight } from "lucide-react";
|
|
13
|
+
import { TaskCard } from "@/components/TaskCard";
|
|
14
|
+
|
|
15
|
+
interface CompletedSprintItemProps {
|
|
16
|
+
/** Sprint name */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Total tasks in sprint */
|
|
19
|
+
taskCount: number;
|
|
20
|
+
/** Completed tasks */
|
|
21
|
+
tasks: Task[];
|
|
22
|
+
/** Whether expanded */
|
|
23
|
+
isExpanded: boolean;
|
|
24
|
+
/** Called when toggling expand */
|
|
25
|
+
onToggle: () => void;
|
|
26
|
+
/** Called when task is clicked */
|
|
27
|
+
onTaskClick: (taskId: string) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function CompletedSprintItem({
|
|
31
|
+
name,
|
|
32
|
+
taskCount,
|
|
33
|
+
tasks,
|
|
34
|
+
isExpanded,
|
|
35
|
+
onToggle,
|
|
36
|
+
onTaskClick,
|
|
37
|
+
}: CompletedSprintItemProps) {
|
|
38
|
+
return (
|
|
39
|
+
<div className="rounded-lg border border-border/40 overflow-hidden bg-card/20 group transition-colors hover:border-border/60">
|
|
40
|
+
<div
|
|
41
|
+
onClick={onToggle}
|
|
42
|
+
className="p-3 flex items-center justify-between cursor-pointer hover:bg-card/40 transition-colors"
|
|
43
|
+
>
|
|
44
|
+
<div className="flex items-center gap-3">
|
|
45
|
+
<div className="text-muted-foreground/60 group-hover:text-muted-foreground transition-colors">
|
|
46
|
+
{isExpanded ? (
|
|
47
|
+
<ChevronDown size={14} />
|
|
48
|
+
) : (
|
|
49
|
+
<ChevronRight size={14} />
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
<div className="flex items-center gap-2">
|
|
53
|
+
<CheckCircle size={15} className="text-green-500/60" />
|
|
54
|
+
<span className="font-medium text-sm text-foreground/80">
|
|
55
|
+
{name}
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<span className="text-[10px] font-medium text-muted-foreground/60 bg-secondary/30 px-3 py-0.5 rounded-full border border-border/20">
|
|
60
|
+
{taskCount} tasks
|
|
61
|
+
</span>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<AnimatePresence>
|
|
65
|
+
{isExpanded && (
|
|
66
|
+
<motion.div
|
|
67
|
+
initial={{ height: 0, opacity: 0 }}
|
|
68
|
+
animate={{ height: "auto", opacity: 1 }}
|
|
69
|
+
exit={{ height: 0, opacity: 0 }}
|
|
70
|
+
transition={{ duration: 0.2 }}
|
|
71
|
+
>
|
|
72
|
+
<div className="px-3 pb-3 space-y-1">
|
|
73
|
+
{tasks.length === 0 ? (
|
|
74
|
+
<div className="py-4 text-center text-[10px] text-muted-foreground/40 italic">
|
|
75
|
+
No tasks in this sprint
|
|
76
|
+
</div>
|
|
77
|
+
) : (
|
|
78
|
+
<div className="space-y-1">
|
|
79
|
+
{tasks.map((task) => (
|
|
80
|
+
<TaskCard
|
|
81
|
+
key={task.id}
|
|
82
|
+
task={task}
|
|
83
|
+
variant="list"
|
|
84
|
+
onClick={() => onTaskClick(task.id)}
|
|
85
|
+
/>
|
|
86
|
+
))}
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
</motion.div>
|
|
91
|
+
)}
|
|
92
|
+
</AnimatePresence>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|