From 0e2734a4757722d753be398c9f7273b7fdfc1274 Mon Sep 17 00:00:00 2001 From: Andrej Date: Sat, 18 Jan 2025 12:13:47 +0100 Subject: [PATCH] feat: :fire: adding initial kanban board added the whole kanban view with mock data --- apps/web/package.json | 4 + .../src/components/common/sidebar/index.tsx | 6 +- .../sidebar/sections/workspaces/index.tsx | 21 +- .../kanban-board/column/column-dropzone.tsx | 36 ++++ .../kanban-board/column/column-header.tsx | 27 +++ .../components/kanban-board/column/index.tsx | 22 ++ .../web/src/components/kanban-board/index.tsx | 152 ++++++++++++++ .../src/components/kanban-board/task-card.tsx | 72 +++++++ apps/web/src/constants/descriptions.ts | 14 ++ .../src/constants/priorities-and-statuses.ts | 2 + apps/web/src/constants/task-titles.ts | 14 ++ apps/web/src/hooks/queries/use-get-me.ts | 2 + .../queries/workspace/use-get-workspace.ts | 2 + .../web/src/lib/workspace/generate-project.ts | 57 ++++++ .../lib/workspace/generate-random-due-date.ts | 8 + apps/web/src/lib/workspace/generate-task.ts | 30 +++ .../workspace/generate-tasks-for-column.ts | 20 ++ apps/web/src/query-client/index.ts | 9 +- apps/web/src/routes/dashboard/index.tsx | 5 +- apps/web/src/store/workspace.ts | 13 ++ apps/web/src/types/workspace/index.ts | 23 +++ pnpm-lock.yaml | 189 +++++++++++++----- 22 files changed, 668 insertions(+), 60 deletions(-) create mode 100644 apps/web/src/components/kanban-board/column/column-dropzone.tsx create mode 100644 apps/web/src/components/kanban-board/column/column-header.tsx create mode 100644 apps/web/src/components/kanban-board/column/index.tsx create mode 100644 apps/web/src/components/kanban-board/index.tsx create mode 100644 apps/web/src/components/kanban-board/task-card.tsx create mode 100644 apps/web/src/constants/descriptions.ts create mode 100644 apps/web/src/constants/priorities-and-statuses.ts create mode 100644 apps/web/src/constants/task-titles.ts create mode 100644 apps/web/src/lib/workspace/generate-project.ts create mode 100644 apps/web/src/lib/workspace/generate-random-due-date.ts create mode 100644 apps/web/src/lib/workspace/generate-task.ts create mode 100644 apps/web/src/lib/workspace/generate-tasks-for-column.ts create mode 100644 apps/web/src/store/workspace.ts create mode 100644 apps/web/src/types/workspace/index.ts diff --git a/apps/web/package.json b/apps/web/package.json index 4d17914..90f9ee6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -9,6 +9,9 @@ "preview": "vite preview" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@hookform/resolvers": "^3.10.0", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-dialog": "^1.1.4", @@ -20,6 +23,7 @@ "@tanstack/react-router": "^1.97.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^4.1.0", "framer-motion": "^11.18.0", "lucide-react": "^0.471.1", "react": "^19.0.0", diff --git a/apps/web/src/components/common/sidebar/index.tsx b/apps/web/src/components/common/sidebar/index.tsx index 5de2fc1..ee3f64b 100644 --- a/apps/web/src/components/common/sidebar/index.tsx +++ b/apps/web/src/components/common/sidebar/index.tsx @@ -5,11 +5,7 @@ import { SidebarHeader } from "./sidebar-header"; export function Sidebar() { return ( - +
diff --git a/apps/web/src/components/common/sidebar/sections/workspaces/index.tsx b/apps/web/src/components/common/sidebar/sections/workspaces/index.tsx index 8a2cf2b..3fc358d 100644 --- a/apps/web/src/components/common/sidebar/sections/workspaces/index.tsx +++ b/apps/web/src/components/common/sidebar/sections/workspaces/index.tsx @@ -1,16 +1,24 @@ import useGetWorkspaces from "@/hooks/queries/workspace/use-get-workspace"; +import useWorkspaceStore from "@/store/workspace"; +import { useEffect } from "react"; import AddWorkspace from "./add-workspace"; import WorkspaceItemButton from "./workspace-item-button"; -const selectedWorkspace = { - id: "1", -}; - function Workspaces() { const { data } = useGetWorkspaces(); const workspaces = data?.data; + const { workspace: selectedWorkspace, setWorkspace } = useWorkspaceStore( + (state) => state, + ); + + useEffect(() => { + if (data?.data) { + setWorkspace(data?.data[0]); + } + }, [data?.data, setWorkspace]); if (!workspaces || !workspaces?.length) { + // TODO: Add better empty screen return
You don't have any workspaces
; } @@ -24,11 +32,10 @@ function Workspaces() { console.log("Selected")} - isSelected={workspace.id === selectedWorkspace.id} + onSelectWorkspace={() => setWorkspace(workspace)} + isSelected={workspace.id === selectedWorkspace?.id} /> ))} -
diff --git a/apps/web/src/components/kanban-board/column/column-dropzone.tsx b/apps/web/src/components/kanban-board/column/column-dropzone.tsx new file mode 100644 index 0000000..add8f19 --- /dev/null +++ b/apps/web/src/components/kanban-board/column/column-dropzone.tsx @@ -0,0 +1,36 @@ +import type { Column } from "@/types/workspace"; +import { useDroppable } from "@dnd-kit/core"; +import { + SortableContext, + verticalListSortingStrategy, +} from "@dnd-kit/sortable"; +import TaskCard from "../task-card"; + +interface ColumnDropzoneProps { + column: Column; +} + +export function ColumnDropzone({ column }: ColumnDropzoneProps) { + const { setNodeRef } = useDroppable({ + id: column.id, + data: { + type: "column", + column, + }, + }); + + return ( +
+ +
+ {column.tasks.map((task) => ( + + ))} +
+
+
+ ); +} diff --git a/apps/web/src/components/kanban-board/column/column-header.tsx b/apps/web/src/components/kanban-board/column/column-header.tsx new file mode 100644 index 0000000..35e2d07 --- /dev/null +++ b/apps/web/src/components/kanban-board/column/column-header.tsx @@ -0,0 +1,27 @@ +import type { Column } from "@/types/workspace"; +import { MoreHorizontal } from "lucide-react"; + +interface ColumnHeaderProps { + column: Column; +} + +export function ColumnHeader({ column }: ColumnHeaderProps) { + return ( +
+
+

+ {column.name} +

+ + {column.tasks.length} + +
+ +
+ ); +} diff --git a/apps/web/src/components/kanban-board/column/index.tsx b/apps/web/src/components/kanban-board/column/index.tsx new file mode 100644 index 0000000..ef3f0c2 --- /dev/null +++ b/apps/web/src/components/kanban-board/column/index.tsx @@ -0,0 +1,22 @@ +import type { Column as ColumnType } from "@/types/workspace"; +import { ColumnDropzone } from "./column-dropzone"; +import { ColumnHeader } from "./column-header"; + +interface ColumnProps { + column: ColumnType; +} + +function Column({ column }: ColumnProps) { + return ( +
+
+ +
+
+ +
+
+ ); +} + +export default Column; diff --git a/apps/web/src/components/kanban-board/index.tsx b/apps/web/src/components/kanban-board/index.tsx new file mode 100644 index 0000000..76c08cf --- /dev/null +++ b/apps/web/src/components/kanban-board/index.tsx @@ -0,0 +1,152 @@ +import generateProject from "@/lib/workspace/generate-project"; +import { + DndContext, + type DragEndEvent, + DragOverlay, + type DragStartEvent, + type UniqueIdentifier, + closestCorners, +} from "@dnd-kit/core"; +import { useState } from "react"; +import Column from "./column"; +import TaskCard from "./task-card"; + +function KanbanBoard() { + const [project, setProject] = useState( + generateProject({ + projectId: "sample-1", + workspaceId: "workspace-1", + tasksPerColumn: 4, + }), + ); + const [activeId, setActiveId] = useState(null); + + const handleDragStart = (event: DragStartEvent) => { + setActiveId(event.active.id); + }; + + // TODO: Simplify this function + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + if (!over) return; + + const activeId = active.id.toString(); + const overId = over.id.toString(); + + const sourceColumn = project.columns.find((col) => + col.tasks.some((task) => task.id === activeId), + ); + + const destinationColumn = project.columns.find((col) => { + if (col.id === overId) return true; + return col.tasks.some((task) => task.id === overId); + }); + + if (!sourceColumn || !destinationColumn) return; + + setProject((currentProject) => { + const updatedColumns = [...currentProject.columns]; + + const sourceColumnIndex = updatedColumns.findIndex( + (col) => col.id === sourceColumn.id, + ); + const destinationColumnIndex = updatedColumns.findIndex( + (col) => col.id === destinationColumn.id, + ); + + const sourceTaskIndex = sourceColumn.tasks.findIndex( + (task) => task.id === activeId, + ); + const task = sourceColumn.tasks[sourceTaskIndex]; + + updatedColumns[sourceColumnIndex] = { + ...sourceColumn, + tasks: sourceColumn.tasks.filter((t) => t.id !== activeId), + }; + + if (sourceColumn.id === destinationColumn.id) { + const destinationIndex = destinationColumn.tasks.findIndex( + (task) => task.id === overId, + ); + const newTasks = [...destinationColumn.tasks]; + newTasks.splice(sourceTaskIndex, 1); + newTasks.splice(destinationIndex, 0, task); + + updatedColumns[destinationColumnIndex] = { + ...destinationColumn, + tasks: newTasks, + }; + } else { + const updatedTask = { ...task, status: destinationColumn.id }; + + if (overId === destinationColumn.id) { + updatedColumns[destinationColumnIndex] = { + ...destinationColumn, + tasks: [...destinationColumn.tasks, updatedTask], + }; + } else { + const destinationIndex = destinationColumn.tasks.findIndex( + (task) => task.id === overId, + ); + const newTasks = [...destinationColumn.tasks]; + newTasks.splice(destinationIndex, 0, updatedTask); + + updatedColumns[destinationColumnIndex] = { + ...destinationColumn, + tasks: newTasks, + }; + } + } + + return { + ...currentProject, + columns: updatedColumns, + }; + }); + + setActiveId(null); + }; + + const activeTask = activeId + ? project.columns + .flatMap((col) => col.tasks) + .find((task) => task.id === activeId) + : null; + + return ( + +
+
+
+

+ {project.name} +

+
+
+ +
+
+ {project.columns.map((column) => ( + + ))} +
+
+
+ + + {activeTask ? ( +
+ +
+ ) : null} +
+
+ ); +} + +export default KanbanBoard; diff --git a/apps/web/src/components/kanban-board/task-card.tsx b/apps/web/src/components/kanban-board/task-card.tsx new file mode 100644 index 0000000..6fd6a8f --- /dev/null +++ b/apps/web/src/components/kanban-board/task-card.tsx @@ -0,0 +1,72 @@ +import type { Task } from "@/types/workspace"; +import { useSortable } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import { format } from "date-fns"; +import { Calendar, Flag } from "lucide-react"; + +interface TaskCardProps { + task: Task; +} + +function TaskCard({ task }: TaskCardProps) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: task.id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + }; + + const priorityColors = { + low: "bg-blue-50 text-blue-700 dark:bg-blue-500/10 dark:text-blue-500", + medium: + "bg-yellow-50 text-yellow-700 dark:bg-yellow-500/10 dark:text-yellow-500", + high: "bg-orange-50 text-orange-700 dark:bg-orange-500/10 dark:text-orange-500", + urgent: "bg-red-50 text-red-700 dark:bg-red-500/10 dark:text-red-500", + }; + + return ( +
+

+ {task.title} +

+ +

+ {task.description} +

+ +
+
+ + + {task.priority} + +
+ + {task.dueDate && ( +
+ + {format(new Date(task.dueDate), "MMM d")} +
+ )} +
+
+ ); +} + +export default TaskCard; diff --git a/apps/web/src/constants/descriptions.ts b/apps/web/src/constants/descriptions.ts new file mode 100644 index 0000000..dfe7943 --- /dev/null +++ b/apps/web/src/constants/descriptions.ts @@ -0,0 +1,14 @@ +const descriptions = [ + "This needs to be completed ASAP for the next release", + "Several users reported this issue in production", + "Documentation is outdated and needs revision", + "Please review and provide feedback", + "Current implementation is not performant", + "Coverage is currently below threshold", + "Users are experiencing slow load times", + "Security vulnerabilities in old packages", + "System crashes when input is invalid", + "Users need better visualization of data", +]; + +export default descriptions; diff --git a/apps/web/src/constants/priorities-and-statuses.ts b/apps/web/src/constants/priorities-and-statuses.ts new file mode 100644 index 0000000..37f3184 --- /dev/null +++ b/apps/web/src/constants/priorities-and-statuses.ts @@ -0,0 +1,2 @@ +export const priorities = ["low", "medium", "high", "urgent"]; +export const statuses = ["todo", "in-progress", "in-review", "done"]; diff --git a/apps/web/src/constants/task-titles.ts b/apps/web/src/constants/task-titles.ts new file mode 100644 index 0000000..d446765 --- /dev/null +++ b/apps/web/src/constants/task-titles.ts @@ -0,0 +1,14 @@ +const taskTitles = [ + "Implement new feature", + "Fix bug in authentication", + "Update documentation", + "Review pull request", + "Refactor legacy code", + "Write unit tests", + "Optimize database queries", + "Update dependencies", + "Add error handling", + "Create user dashboard", +]; + +export default taskTitles; diff --git a/apps/web/src/hooks/queries/use-get-me.ts b/apps/web/src/hooks/queries/use-get-me.ts index e3d99d7..15d2ccf 100644 --- a/apps/web/src/hooks/queries/use-get-me.ts +++ b/apps/web/src/hooks/queries/use-get-me.ts @@ -6,6 +6,8 @@ function useGetMe() { queryKey: ["me"], queryFn: () => me(), retry: 0, + refetchOnMount: false, + refetchOnWindowFocus: false, }); } diff --git a/apps/web/src/hooks/queries/workspace/use-get-workspace.ts b/apps/web/src/hooks/queries/workspace/use-get-workspace.ts index 5414b9a..c7badf1 100644 --- a/apps/web/src/hooks/queries/workspace/use-get-workspace.ts +++ b/apps/web/src/hooks/queries/workspace/use-get-workspace.ts @@ -5,6 +5,8 @@ function useGetWorkspaces() { return useQuery({ queryFn: () => getWorkspaces(), queryKey: ["workspaces"], + refetchOnWindowFocus: false, + refetchOnMount: false, }); } diff --git a/apps/web/src/lib/workspace/generate-project.ts b/apps/web/src/lib/workspace/generate-project.ts new file mode 100644 index 0000000..a2c2e55 --- /dev/null +++ b/apps/web/src/lib/workspace/generate-project.ts @@ -0,0 +1,57 @@ +import generateTasksForColumn from "./generate-tasks-for-column"; + +function generateProject({ + projectId = "1", + workspaceId = "1", + tasksPerColumn = 3, +}) { + return { + id: projectId, + name: `Project ${projectId}`, + workspaceId, + columns: [ + { + id: "todo", + name: "To Do", + tasks: generateTasksForColumn( + tasksPerColumn, + "todo", + projectId, + workspaceId, + ), + }, + { + id: "in-progress", + name: "In Progress", + tasks: generateTasksForColumn( + Math.ceil(tasksPerColumn / 2), + "in-progress", + projectId, + workspaceId, + ), + }, + { + id: "in-review", + name: "In Review", + tasks: generateTasksForColumn( + Math.ceil(tasksPerColumn / 3), + "in-review", + projectId, + workspaceId, + ), + }, + { + id: "done", + name: "Done", + tasks: generateTasksForColumn( + tasksPerColumn, + "done", + projectId, + workspaceId, + ), + }, + ], + }; +} + +export default generateProject; diff --git a/apps/web/src/lib/workspace/generate-random-due-date.ts b/apps/web/src/lib/workspace/generate-random-due-date.ts new file mode 100644 index 0000000..45fb70e --- /dev/null +++ b/apps/web/src/lib/workspace/generate-random-due-date.ts @@ -0,0 +1,8 @@ +function generateRandomDueDate() { + const today = new Date(); + const futureDate = new Date(); + futureDate.setDate(today.getDate() + Math.floor(Math.random() * 30)); + return futureDate.toLocaleDateString(); +} + +export default generateRandomDueDate; diff --git a/apps/web/src/lib/workspace/generate-task.ts b/apps/web/src/lib/workspace/generate-task.ts new file mode 100644 index 0000000..4d4830d --- /dev/null +++ b/apps/web/src/lib/workspace/generate-task.ts @@ -0,0 +1,30 @@ +import descriptions from "@/constants/descriptions"; +import { priorities } from "@/constants/priorities-and-statuses"; +import taskTitles from "@/constants/task-titles"; +import generateRandomDueDate from "./generate-random-due-date"; + +function generateTask({ + projectId, + workspaceId, + status, +}: { + projectId: string; + workspaceId: string; + status: string; +}) { + const id = Math.random().toString(36).substr(2, 9); + + return { + id, + title: taskTitles[Math.floor(Math.random() * taskTitles.length)], + description: descriptions[Math.floor(Math.random() * descriptions.length)], + assigneeId: Math.floor(Math.random() * 5) + 1, // Random assignee 1-5 + priority: priorities[Math.floor(Math.random() * priorities.length)], + dueDate: Math.random() > 0.3 ? generateRandomDueDate() : null, // 70% chance of having a due date + status, + projectId, + workspaceId, + }; +} + +export default generateTask; diff --git a/apps/web/src/lib/workspace/generate-tasks-for-column.ts b/apps/web/src/lib/workspace/generate-tasks-for-column.ts new file mode 100644 index 0000000..ab08318 --- /dev/null +++ b/apps/web/src/lib/workspace/generate-tasks-for-column.ts @@ -0,0 +1,20 @@ +import generateTask from "./generate-task"; + +function generateTasksForColumn( + count: number, + columnId: string, + projectId: string, + workspaceId: string, +) { + return Array(count) + .fill(null) + .map(() => + generateTask({ + projectId, + workspaceId, + status: columnId, + }), + ); +} + +export default generateTasksForColumn; diff --git a/apps/web/src/query-client/index.ts b/apps/web/src/query-client/index.ts index db2b63d..211179e 100644 --- a/apps/web/src/query-client/index.ts +++ b/apps/web/src/query-client/index.ts @@ -1,5 +1,12 @@ import { QueryClient } from "@tanstack/react-query"; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + refetchOnMount: false, + }, + }, +}); export default queryClient; diff --git a/apps/web/src/routes/dashboard/index.tsx b/apps/web/src/routes/dashboard/index.tsx index c281045..0c212e3 100644 --- a/apps/web/src/routes/dashboard/index.tsx +++ b/apps/web/src/routes/dashboard/index.tsx @@ -1,4 +1,5 @@ import { Sidebar } from "@/components/common/sidebar"; +import KanbanBoard from "@/components/kanban-board"; import { redirect } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router"; @@ -17,7 +18,9 @@ function DashboardIndexRouteComponent() { return ( <> -
+
+ +
); } diff --git a/apps/web/src/store/workspace.ts b/apps/web/src/store/workspace.ts new file mode 100644 index 0000000..7c6759c --- /dev/null +++ b/apps/web/src/store/workspace.ts @@ -0,0 +1,13 @@ +import type { Workspace } from "@/types/workspace"; +import { create } from "zustand"; + +const useWorkspaceStore = create<{ + workspace: Workspace | null; + setWorkspace: (updatedWorkspace: Workspace) => void; +}>((set) => ({ + workspace: null, + setWorkspace: (updatedWorkspace: Workspace) => + set(() => ({ workspace: updatedWorkspace })), +})); + +export default useWorkspaceStore; diff --git a/apps/web/src/types/workspace/index.ts b/apps/web/src/types/workspace/index.ts new file mode 100644 index 0000000..0f35fbb --- /dev/null +++ b/apps/web/src/types/workspace/index.ts @@ -0,0 +1,23 @@ +export type Workspace = { + id: string; + name: string; + ownerId: string; +}; + +export type Column = { + id: string; + name: string; + tasks: Task[]; +}; + +export type Task = { + id: string; + title: string; + description: string; + assigneeId: number; + priority: string; + dueDate: string | null; + status: string; + projectId: string; + workspaceId: string; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8584cab..b4cf18b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,23 +71,32 @@ importers: version: 0.30.2 drizzle-orm: specifier: ^0.38.4 - version: 0.38.4(@types/react@19.0.7)(better-sqlite3@11.8.0)(bun-types@1.1.43)(react@19.0.0) + version: 0.38.4(@types/react@19.0.7)(better-sqlite3@11.8.0)(bun-types@1.1.45)(react@19.0.0) drizzle-typebox: specifier: ^0.2.1 - version: 0.2.1(@sinclair/typebox@0.34.13)(drizzle-orm@0.38.4(@types/react@19.0.7)(better-sqlite3@11.8.0)(bun-types@1.1.43)(react@19.0.0)) + version: 0.2.1(@sinclair/typebox@0.34.13)(drizzle-orm@0.38.4(@types/react@19.0.7)(better-sqlite3@11.8.0)(bun-types@1.1.45)(react@19.0.0)) elysia: specifier: 1.2.10 version: 1.2.10(@sinclair/typebox@0.34.13)(typescript@5.7.3) devDependencies: bun-types: specifier: latest - version: 1.1.43 + version: 1.1.45 apps/web: dependencies: + '@dnd-kit/core': + specifier: ^6.3.1 + version: 6.3.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@dnd-kit/sortable': + specifier: ^10.0.0 + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@19.0.0) '@hookform/resolvers': - specifier: ^3.9.1 - version: 3.9.1(react-hook-form@7.54.2(react@19.0.0)) + specifier: ^3.10.0 + version: 3.10.0(react-hook-form@7.54.2(react@19.0.0)) '@radix-ui/react-avatar': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -110,20 +119,23 @@ importers: specifier: ^5.64.1 version: 5.64.1(@tanstack/react-query@5.64.1(react@19.0.0))(react@19.0.0) '@tanstack/react-router': - specifier: ^1.94.1 - version: 1.94.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: ^1.97.1 + version: 1.97.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 clsx: specifier: ^2.1.1 version: 2.1.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 framer-motion: - specifier: ^11.15.0 - version: 11.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: ^11.18.0 + version: 11.18.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) lucide-react: - specifier: ^0.469.0 - version: 0.469.0(react@19.0.0) + specifier: ^0.471.1 + version: 0.471.2(react@19.0.0) react: specifier: ^19.0.0 version: 19.0.0 @@ -143,8 +155,8 @@ importers: specifier: 3.24.1 version: 3.24.1 zustand: - specifier: ^5.0.2 - version: 5.0.2(@types/react@19.0.7)(react@19.0.0)(use-sync-external-store@1.4.0(react@19.0.0)) + specifier: ^5.0.3 + version: 5.0.3(@types/react@19.0.7)(react@19.0.0)(use-sync-external-store@1.4.0(react@19.0.0)) devDependencies: '@kaneo/libs': specifier: workspace:* @@ -193,8 +205,8 @@ importers: specifier: workspace:* version: link:../typescript-config '@types/bun': - specifier: ^1.1.16 - version: 1.1.16 + specifier: ^1.1.17 + version: 1.1.17 '@types/react': specifier: ^19.0.7 version: 19.0.7 @@ -424,6 +436,28 @@ packages: resolution: {integrity: sha512-DSHae2obMSMkAtTBSOulg5X7/z+rGLxcXQIkg3OmWvY6wifojge5uVMydfhUvs7yQj+V7jNmRZ2Xzl8GJyqRgg==} engines: {node: '>=v18'} + '@dnd-kit/accessibility@3.1.1': + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/sortable@10.0.0': + resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} @@ -1034,8 +1068,8 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} - '@hookform/resolvers@3.9.1': - resolution: {integrity: sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==} + '@hookform/resolvers@3.10.0': + resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} peerDependencies: react-hook-form: ^7.0.0 @@ -1534,6 +1568,10 @@ packages: resolution: {integrity: sha512-riNhDGm+fAwxgZRJ0J/36IZis1UDHsDCNIxfEodbw6BgTWJr0ah+G20V4HT91uBXiCqYFvX3somlfTLhS5yHDA==} engines: {node: '>=12'} + '@tanstack/history@1.97.0': + resolution: {integrity: sha512-xFdpGJqwSLUJW5TYRNjRO5T41KjGkJeHWyhANZsNJ1KDm7uCVNkfLxNXeCI1XhIFbpzJmk13vo/mY0WJDe0A5g==} + engines: {node: '>=12'} + '@tanstack/query-core@5.64.1': resolution: {integrity: sha512-978Wx4Wl4UJZbmvU/rkaM9cQtXXrbhK0lsz/UZhYIbyKYA8E4LdomTwyh2GHZ4oU0BKKoDH4YlKk2VscCUgNmg==} @@ -1558,6 +1596,13 @@ packages: react: '>=18' react-dom: '>=18' + '@tanstack/react-router@1.97.1': + resolution: {integrity: sha512-loKN9SMjAiHV5FE5SQHSZMMSUgR36xSl2rsbHrbm0v0GEJgATLAI4KH2KExs4vmAyxJ0WkPx2qCBI6MtzjA0Lw==} + engines: {node: '>=12'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + '@tanstack/react-store@0.7.0': resolution: {integrity: sha512-S/Rq17HaGOk+tQHV/yrePMnG1xbsKZIl/VsNWnNXt4XW+tTY8dTlvpJH2ZQ3GRALsusG5K6Q3unAGJ2pd9W/Ng==} peerDependencies: @@ -1615,8 +1660,8 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@types/bun@1.1.16': - resolution: {integrity: sha512-E+ue6NMcn4FXC5bDRE1W/BXUVs01h5Mt02qH8/8HGCox9akuh8KNOFdwvaQS9TDgT2RmUyJYFRRqA60WtTnm2g==} + '@types/bun@1.1.17': + resolution: {integrity: sha512-zZt0Kao/8hAwNOXh4bmt8nKbMEd4QD8n7PeTGF+NZTVY5ouXhU/TX7jUj4He1p7mgY+WdplnU1B6MB1j17vdzg==} '@types/conventional-commits-parser@5.0.1': resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} @@ -1741,8 +1786,11 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bun-types@1.1.43: - resolution: {integrity: sha512-W0wCtVH+bwFp7p3Zgs03CqxEDmXxEvmmUM/FBKgWIv9T8gyeotvIjIbHzuDScc2DphhRNtr7hJLCR5PspYL5qw==} + bun-types@1.1.44: + resolution: {integrity: sha512-jtcekoZeSINgEcHSISzhR13w/cyE+Fankw2Cpl4c0fN3lRmKVAX0i9ay4FyK4lOxUK1HG4HkuIlrPvXKz4Y7sw==} + + bun-types@1.1.45: + resolution: {integrity: sha512-8NT3BYwkyO8nzTG1k+q86VEvucw7s5W1fjRIGs0Y6/XNbTZn+mHEU39LFnuDLj4UmGCMpWCQtXUhLd6cko49Ww==} callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -1844,6 +1892,9 @@ packages: resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} engines: {node: '>=12'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -2078,8 +2129,8 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - framer-motion@11.15.0: - resolution: {integrity: sha512-MLk8IvZntxOMg7lDBLw2qgTHHv664bYoYmnFTmE0Gm/FW67aOJk0WM3ctMcG+Xhcv+vh5uyyXwxvxhSeJzSe+w==} + framer-motion@11.18.1: + resolution: {integrity: sha512-EQa8c9lWVOm4zlz14MsBJWr8woq87HsNmsBnQNvcS0hs8uzw6HtGAxZyIU7EGTVpHD1C1n01ufxRyarXcNzpPg==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -2307,8 +2358,8 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@0.469.0: - resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==} + lucide-react@0.471.2: + resolution: {integrity: sha512-A8fDycQxGeaSOTaI7Bm4fg8LBXO7Qr9ORAX47bDRvugCsjLIliugQO0PkKFoeAD57LIQwlWKd3NIQ3J7hYp84g==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2345,11 +2396,11 @@ packages: mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - motion-dom@11.14.3: - resolution: {integrity: sha512-lW+D2wBy5vxLJi6aCP0xyxTxlTfiu+b+zcpVbGVFUxotwThqhdpPRSmX8xztAgtZMPMeU0WGVn/k1w4I+TbPqA==} + motion-dom@11.18.1: + resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==} - motion-utils@11.14.3: - resolution: {integrity: sha512-Xg+8xnqIJTpr0L/cidfTTBFkvRw26ZtGGuIhA94J9PQ2p4mEa06Xx7QVYZH0BP+EpMSaDlu+q0I0mmvwADPsaQ==} + motion-utils@11.18.1: + resolution: {integrity: sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2920,8 +2971,8 @@ packages: zod@3.24.1: resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} - zustand@5.0.2: - resolution: {integrity: sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==} + zustand@5.0.3: + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': '>=18.0.0' @@ -3202,6 +3253,31 @@ snapshots: '@types/conventional-commits-parser': 5.0.1 chalk: 5.4.1 + '@dnd-kit/accessibility@3.1.1(react@19.0.0)': + dependencies: + react: 19.0.0 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@19.0.0) + '@dnd-kit/utilities': 3.2.2(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tslib: 2.8.1 + + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@dnd-kit/utilities': 3.2.2(react@19.0.0) + react: 19.0.0 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@19.0.0)': + dependencies: + react: 19.0.0 + tslib: 2.8.1 + '@drizzle-team/brocli@0.10.2': {} '@elysiajs/cors@1.2.0(elysia@1.2.10(@sinclair/typebox@0.34.13)(typescript@5.7.3))': @@ -3532,7 +3608,7 @@ snapshots: '@floating-ui/utils@0.2.9': {} - '@hookform/resolvers@3.9.1(react-hook-form@7.54.2(react@19.0.0))': + '@hookform/resolvers@3.10.0(react-hook-form@7.54.2(react@19.0.0))': dependencies: react-hook-form: 7.54.2(react@19.0.0) @@ -3937,6 +4013,8 @@ snapshots: '@tanstack/history@1.90.0': {} + '@tanstack/history@1.97.0': {} + '@tanstack/query-core@5.64.1': {} '@tanstack/query-devtools@5.62.16': {} @@ -3962,6 +4040,16 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 + '@tanstack/react-router@1.97.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@tanstack/history': 1.97.0 + '@tanstack/react-store': 0.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + jsesc: 3.1.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + '@tanstack/react-store@0.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@tanstack/store': 0.7.0 @@ -4039,9 +4127,9 @@ snapshots: dependencies: '@babel/types': 7.26.3 - '@types/bun@1.1.16': + '@types/bun@1.1.17': dependencies: - bun-types: 1.1.43 + bun-types: 1.1.44 '@types/conventional-commits-parser@5.0.1': dependencies: @@ -4179,7 +4267,12 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - bun-types@1.1.43: + bun-types@1.1.44: + dependencies: + '@types/node': 20.12.14 + '@types/ws': 8.5.13 + + bun-types@1.1.45: dependencies: '@types/node': 20.12.14 '@types/ws': 8.5.13 @@ -4278,6 +4371,8 @@ snapshots: dargs@8.1.0: {} + date-fns@4.1.0: {} + debug@4.4.0: dependencies: ms: 2.1.3 @@ -4309,17 +4404,17 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.38.4(@types/react@19.0.7)(better-sqlite3@11.8.0)(bun-types@1.1.43)(react@19.0.0): + drizzle-orm@0.38.4(@types/react@19.0.7)(better-sqlite3@11.8.0)(bun-types@1.1.45)(react@19.0.0): optionalDependencies: '@types/react': 19.0.7 better-sqlite3: 11.8.0 - bun-types: 1.1.43 + bun-types: 1.1.45 react: 19.0.0 - drizzle-typebox@0.2.1(@sinclair/typebox@0.34.13)(drizzle-orm@0.38.4(@types/react@19.0.7)(better-sqlite3@11.8.0)(bun-types@1.1.43)(react@19.0.0)): + drizzle-typebox@0.2.1(@sinclair/typebox@0.34.13)(drizzle-orm@0.38.4(@types/react@19.0.7)(better-sqlite3@11.8.0)(bun-types@1.1.45)(react@19.0.0)): dependencies: '@sinclair/typebox': 0.34.13 - drizzle-orm: 0.38.4(@types/react@19.0.7)(better-sqlite3@11.8.0)(bun-types@1.1.43)(react@19.0.0) + drizzle-orm: 0.38.4(@types/react@19.0.7)(better-sqlite3@11.8.0)(bun-types@1.1.45)(react@19.0.0) eastasianwidth@0.2.0: {} @@ -4499,10 +4594,10 @@ snapshots: fraction.js@4.3.7: {} - framer-motion@11.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + framer-motion@11.18.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - motion-dom: 11.14.3 - motion-utils: 11.14.3 + motion-dom: 11.18.1 + motion-utils: 11.18.1 tslib: 2.8.1 optionalDependencies: react: 19.0.0 @@ -4671,7 +4766,7 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@0.469.0(react@19.0.0): + lucide-react@0.471.2(react@19.0.0): dependencies: react: 19.0.0 @@ -4698,9 +4793,11 @@ snapshots: mkdirp-classic@0.5.3: {} - motion-dom@11.14.3: {} + motion-dom@11.18.1: + dependencies: + motion-utils: 11.18.1 - motion-utils@11.14.3: {} + motion-utils@11.18.1: {} ms@2.1.3: {} @@ -5224,7 +5321,7 @@ snapshots: zod@3.24.1: {} - zustand@5.0.2(@types/react@19.0.7)(react@19.0.0)(use-sync-external-store@1.4.0(react@19.0.0)): + zustand@5.0.3(@types/react@19.0.7)(react@19.0.0)(use-sync-external-store@1.4.0(react@19.0.0)): optionalDependencies: '@types/react': 19.0.7 react: 19.0.0