-
- {COLUMNS.map((column) => (
-
- ))}
+
+
+
+
+
+ {COLUMNS.map((column) => (
+
+ ))}
+
-
-
+
+
);
}
diff --git a/apps/web/components/kanban-card.tsx b/apps/web/components/kanban-card.tsx
index cf777e4b2f..4fe1ba04c0 100644
--- a/apps/web/components/kanban-card.tsx
+++ b/apps/web/components/kanban-card.tsx
@@ -9,6 +9,7 @@ export interface Task {
id: string;
title: string;
status: string;
+ description?: string;
}
interface KanbanCardProps {
@@ -30,13 +31,16 @@ export function KanbanCard({ task }: KanbanCardProps) {
style={style}
className={cn(
'cursor-grab active:cursor-grabbing mb-2 transition-opacity',
- isDragging && 'opacity-50'
+ isDragging && 'opacity-50 z-50'
)}
{...listeners}
{...attributes}
>
- {task.title}
+ {task.title}
+ {task.description && (
+ {task.description}
+ )}
);
diff --git a/apps/web/components/kanban-column.tsx b/apps/web/components/kanban-column.tsx
index c709bcfb9d..3bc5eb31bd 100644
--- a/apps/web/components/kanban-column.tsx
+++ b/apps/web/components/kanban-column.tsx
@@ -4,10 +4,10 @@ import { useDroppable } from '@dnd-kit/core';
import { KanbanCard, Task } from './kanban-card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
-import { Input } from '@/components/ui/input';
import { cn } from '@/lib/utils';
import { IconPlus } from '@tabler/icons-react';
-import { useState, KeyboardEvent } from 'react';
+import { useState } from 'react';
+import { TaskCreateDialog } from './task-create-dialog';
export interface Column {
id: string;
@@ -18,7 +18,7 @@ export interface Column {
interface KanbanColumnProps {
column: Column;
tasks: Task[];
- onAddTask: (columnId: string, title: string) => void;
+ onAddTask: (columnId: string, title: string, description?: string) => void;
}
export function KanbanColumn({ column, tasks, onAddTask }: KanbanColumnProps) {
@@ -26,28 +26,10 @@ export function KanbanColumn({ column, tasks, onAddTask }: KanbanColumnProps) {
id: column.id,
});
- const [isAdding, setIsAdding] = useState(false);
- const [newTaskTitle, setNewTaskTitle] = useState('');
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
- const handleAddClick = () => {
- setIsAdding(true);
- };
-
- const handleSubmit = () => {
- if (newTaskTitle.trim()) {
- onAddTask(column.id, newTaskTitle.trim());
- setNewTaskTitle('');
- setIsAdding(false);
- }
- };
-
- const handleKeyDown = (e: KeyboardEvent
) => {
- if (e.key === 'Enter') {
- handleSubmit();
- } else if (e.key === 'Escape') {
- setNewTaskTitle('');
- setIsAdding(false);
- }
+ const handleDialogSubmit = (title: string, description: string) => {
+ onAddTask(column.id, title, description);
};
return (
@@ -71,30 +53,21 @@ export function KanbanColumn({ column, tasks, onAddTask }: KanbanColumnProps) {
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
- onClick={handleAddClick}
+ onClick={() => setIsDialogOpen(true)}
>
- {/* Add Task Input */}
- {isAdding && (
-
- {tasks.length === 0 && !isAdding ? (
+ {tasks.length === 0 ? (
No tasks yet
) : (
tasks.map((task) =>
)
diff --git a/apps/web/components/task-create-dialog.tsx b/apps/web/components/task-create-dialog.tsx
new file mode 100644
index 0000000000..cdcead3033
--- /dev/null
+++ b/apps/web/components/task-create-dialog.tsx
@@ -0,0 +1,83 @@
+'use client';
+
+import { useState, FormEvent } from 'react';
+import {
+ AlertDialog,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogFooter,
+ AlertDialogCancel,
+} from '@/components/ui/alert-dialog';
+import { Field, FieldLabel } from '@/components/ui/field';
+import { Input } from '@/components/ui/input';
+import { Textarea } from '@/components/ui/textarea';
+import { Button } from '@/components/ui/button';
+
+interface TaskCreateDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onSubmit: (title: string, description: string) => void;
+}
+
+export function TaskCreateDialog({
+ open,
+ onOpenChange,
+ onSubmit,
+}: TaskCreateDialogProps) {
+ const [title, setTitle] = useState('');
+ const [description, setDescription] = useState('');
+
+ const handleSubmit = (e: FormEvent) => {
+ e.preventDefault();
+ if (title.trim()) {
+ onSubmit(title.trim(), description.trim());
+ setTitle('');
+ setDescription('');
+ onOpenChange(false);
+ }
+ };
+
+ const handleCancel = () => {
+ setTitle('');
+ setDescription('');
+ onOpenChange(false);
+ };
+
+ return (
+
+
+
+ Create Task
+
+
+
+
+ );
+}
diff --git a/apps/web/components/theme-provider.tsx b/apps/web/components/theme-provider.tsx
new file mode 100644
index 0000000000..acfd95c257
--- /dev/null
+++ b/apps/web/components/theme-provider.tsx
@@ -0,0 +1,16 @@
+'use client';
+
+import { ThemeProvider as NextThemesProvider } from 'next-themes';
+import { ReactNode } from 'react';
+
+export function ThemeProvider({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/web/components/theme-toggle.tsx b/apps/web/components/theme-toggle.tsx
new file mode 100644
index 0000000000..66b722e9ee
--- /dev/null
+++ b/apps/web/components/theme-toggle.tsx
@@ -0,0 +1,35 @@
+'use client';
+
+import { useTheme } from 'next-themes';
+import { IconMoon, IconSun } from '@tabler/icons-react';
+import { Button } from '@/components/ui/button';
+import { useEffect, useState } from 'react';
+
+export function ThemeToggle() {
+ const { theme, setTheme } = useTheme();
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ if (!mounted) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json
index 12103286d4..92e2520c7a 100644
--- a/apps/web/package-lock.json
+++ b/apps/web/package-lock.json
@@ -16,6 +16,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"next": "16.1.1",
+ "next-themes": "^0.4.6",
"radix-ui": "^1.4.3",
"react": "19.2.3",
"react-dom": "19.2.3",
@@ -8734,6 +8735,16 @@
}
}
},
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
diff --git a/apps/web/package.json b/apps/web/package.json
index eeba3d7bd6..b7aa496140 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -17,6 +17,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"next": "16.1.1",
+ "next-themes": "^0.4.6",
"radix-ui": "^1.4.3",
"react": "19.2.3",
"react-dom": "19.2.3",