diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index d693bc96f6..e767e64fef 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -15,8 +15,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "Kandev - AI Kanban",
+ description: "AI-powered kanban board for developers",
};
export default function RootLayout({
diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx
index 141152731d..fb9e7aa404 100644
--- a/apps/web/app/page.tsx
+++ b/apps/web/app/page.tsx
@@ -1,5 +1,5 @@
-import { ComponentExample } from "@/components/component-example";
+import { KanbanBoard } from '@/components/kanban-board';
export default function Page() {
-return ;
+ return ;
}
\ No newline at end of file
diff --git a/apps/web/components/component-example.tsx b/apps/web/components/component-example.tsx
deleted file mode 100644
index ed4dc3bbab..0000000000
--- a/apps/web/components/component-example.tsx
+++ /dev/null
@@ -1,495 +0,0 @@
-"use client"
-
-import * as React from "react"
-
-import {
- Example,
- ExampleWrapper,
-} from "@/components/example"
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogMedia,
- AlertDialogTitle,
- AlertDialogTrigger,
-} from "@/components/ui/alert-dialog"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import {
- Card,
- CardAction,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card"
-import {
- Combobox,
- ComboboxContent,
- ComboboxEmpty,
- ComboboxInput,
- ComboboxItem,
- ComboboxList,
-} from "@/components/ui/combobox"
-import {
- DropdownMenu,
- DropdownMenuCheckboxItem,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuPortal,
- DropdownMenuRadioGroup,
- DropdownMenuRadioItem,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuSub,
- DropdownMenuSubContent,
- DropdownMenuSubTrigger,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
-import { Input } from "@/components/ui/input"
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
-import { Textarea } from "@/components/ui/textarea"
-import { IconPlus, IconBluetooth, IconDotsVertical, IconFile, IconFolder, IconFolderOpen, IconFileCode, IconDots, IconFolderSearch, IconDeviceFloppy, IconDownload, IconEye, IconLayout, IconPalette, IconSun, IconMoon, IconDeviceDesktop, IconUser, IconCreditCard, IconSettings, IconKeyboard, IconLanguage, IconBell, IconMail, IconShield, IconHelpCircle, IconFileText, IconLogout } from "@tabler/icons-react"
-
-export function ComponentExample() {
- return (
-
-
-
-
- )
-}
-
-function CardExample() {
- return (
-
-
-
-
-
- Observability Plus is replacing Monitoring
-
- Switch to the improved way to explore your data, with natural
- language. Monitoring will no longer be available on the Pro plan in
- November, 2025
-
-
-
-
-
-
-
-
-
-
-
-
- Allow accessory to connect?
-
- Do you want to allow the USB accessory to connect to this
- device?
-
-
-
- Don't allow
- Allow
-
-
-
-
- Warning
-
-
-
-
- )
-}
-
-const frameworks = [
- "Next.js",
- "SvelteKit",
- "Nuxt.js",
- "Remix",
- "Astro",
-] as const
-
-function FormExample() {
- const [notifications, setNotifications] = React.useState({
- email: true,
- sms: false,
- push: true,
- })
- const [theme, setTheme] = React.useState("light")
-
- return (
-
-
-
- User Information
- Please fill in your details below
-
-
-
-
-
-
-
- File
-
-
- New File
- ⌘N
-
-
-
- New Folder
- ⇧⌘N
-
-
-
-
- Open Recent
-
-
-
-
- Recent Projects
-
-
- Project Alpha
-
-
-
- Project Beta
-
-
-
-
- More Projects
-
-
-
-
-
- Project Gamma
-
-
-
- Project Delta
-
-
-
-
-
-
-
-
-
- Browse...
-
-
-
-
-
-
-
-
- Save
- ⌘S
-
-
-
- Export
- ⇧⌘E
-
-
-
-
- View
-
- setNotifications({
- ...notifications,
- email: checked === true,
- })
- }
- >
-
- Show Sidebar
-
-
- setNotifications({
- ...notifications,
- sms: checked === true,
- })
- }
- >
-
- Show Status Bar
-
-
-
-
- Theme
-
-
-
-
- Appearance
-
-
-
- Light
-
-
-
- Dark
-
-
-
- System
-
-
-
-
-
-
-
-
-
- Account
-
-
- Profile
- ⇧⌘P
-
-
-
- Billing
-
-
-
-
- Settings
-
-
-
-
- Preferences
-
-
- Keyboard Shortcuts
-
-
-
- Language
-
-
-
-
- Notifications
-
-
-
-
-
- Notification Types
-
-
- setNotifications({
- ...notifications,
- push: checked === true,
- })
- }
- >
-
- Push Notifications
-
-
- setNotifications({
- ...notifications,
- email: checked === true,
- })
- }
- >
-
- Email Notifications
-
-
-
-
-
-
-
-
-
-
- Privacy & Security
-
-
-
-
-
-
-
-
-
-
- Help & Support
-
-
-
- Documentation
-
-
-
-
-
-
- Sign Out
- ⇧⌘Q
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/apps/web/components/example.tsx b/apps/web/components/example.tsx
deleted file mode 100644
index 1464fd2fac..0000000000
--- a/apps/web/components/example.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { cn } from "@/lib/utils"
-
-function ExampleWrapper({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function Example({
- title,
- children,
- className,
- containerClassName,
- ...props
-}: React.ComponentProps<"div"> & {
- title: string
- containerClassName?: string
-}) {
- return (
-
-
- {title}
-
-
- {children}
-
-
- )
-}
-
-export { ExampleWrapper, Example }
diff --git a/apps/web/components/kanban-board.tsx b/apps/web/components/kanban-board.tsx
new file mode 100644
index 0000000000..9c40fdccca
--- /dev/null
+++ b/apps/web/components/kanban-board.tsx
@@ -0,0 +1,84 @@
+'use client';
+
+import { useState } from 'react';
+import {
+ DndContext,
+ DragEndEvent,
+ PointerSensor,
+ useSensor,
+ useSensors,
+} from '@dnd-kit/core';
+import { KanbanColumn, Column } from './kanban-column';
+import { Task } from './kanban-card';
+
+type ColumnId = 'todo' | 'in-progress' | 'in-review' | 'done';
+
+const COLUMNS: Column[] = [
+ { id: 'todo', title: 'To Do', color: 'bg-neutral-400' },
+ { id: 'in-progress', title: 'In Progress', color: 'bg-blue-500' },
+ { id: 'in-review', title: 'In Review', color: 'bg-yellow-500' },
+ { id: 'done', title: 'Done', color: 'bg-green-500' },
+];
+
+const initialTasks: Task[] = [
+ { id: '1', title: 'Design database schema', status: 'todo' },
+ { id: '2', title: 'Setup agent container environment', status: 'in-progress' },
+ { id: '3', title: 'Implement WebSocket connection', status: 'todo' },
+];
+
+export function KanbanBoard() {
+ const [tasks, setTasks] = useState(initialTasks);
+
+ const sensors = useSensors(
+ useSensor(PointerSensor, {
+ activationConstraint: {
+ distance: 8,
+ },
+ })
+ );
+
+ const handleDragEnd = (event: DragEndEvent) => {
+ const { active, over } = event;
+
+ if (!over) return;
+
+ const taskId = active.id as string;
+ const newStatus = over.id as string;
+
+ setTasks((tasks) =>
+ tasks.map((task) =>
+ task.id === taskId ? { ...task, status: newStatus } : task
+ )
+ );
+ };
+
+ const handleAddTask = (columnId: string, title: string) => {
+ const newTask: Task = {
+ id: crypto.randomUUID(),
+ title,
+ status: columnId,
+ };
+ setTasks((tasks) => [...tasks, newTask]);
+ };
+
+ const getTasksForColumn = (columnId: string) => {
+ return tasks.filter((task) => task.status === columnId);
+ };
+
+ return (
+
+
+
+ {COLUMNS.map((column) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/web/components/kanban-card.tsx b/apps/web/components/kanban-card.tsx
new file mode 100644
index 0000000000..cf777e4b2f
--- /dev/null
+++ b/apps/web/components/kanban-card.tsx
@@ -0,0 +1,43 @@
+'use client';
+
+import { useDraggable } from '@dnd-kit/core';
+import { CSS } from '@dnd-kit/utilities';
+import { Card, CardContent } from '@/components/ui/card';
+import { cn } from '@/lib/utils';
+
+export interface Task {
+ id: string;
+ title: string;
+ status: string;
+}
+
+interface KanbanCardProps {
+ task: Task;
+}
+
+export function KanbanCard({ task }: KanbanCardProps) {
+ const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
+ id: task.id,
+ });
+
+ const style = {
+ transform: CSS.Translate.toString(transform),
+ };
+
+ return (
+
+
+ {task.title}
+
+
+ );
+}
diff --git a/apps/web/components/kanban-column.tsx b/apps/web/components/kanban-column.tsx
new file mode 100644
index 0000000000..c709bcfb9d
--- /dev/null
+++ b/apps/web/components/kanban-column.tsx
@@ -0,0 +1,105 @@
+'use client';
+
+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';
+
+export interface Column {
+ id: string;
+ title: string;
+ color: string;
+}
+
+interface KanbanColumnProps {
+ column: Column;
+ tasks: Task[];
+ onAddTask: (columnId: string, title: string) => void;
+}
+
+export function KanbanColumn({ column, tasks, onAddTask }: KanbanColumnProps) {
+ const { setNodeRef, isOver } = useDroppable({
+ id: column.id,
+ });
+
+ const [isAdding, setIsAdding] = useState(false);
+ const [newTaskTitle, setNewTaskTitle] = useState('');
+
+ 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);
+ }
+ };
+
+ return (
+
+ {/* Column Header */}
+
+
+
+
{column.title}
+
+ {tasks.length}
+
+
+
+
+
+ {/* Add Task Input */}
+ {isAdding && (
+
+ setNewTaskTitle(e.target.value)}
+ onKeyDown={handleKeyDown}
+ onBlur={handleSubmit}
+ className="text-sm"
+ />
+
+ )}
+
+ {/* Tasks */}
+
+ {tasks.length === 0 && !isAdding ? (
+
No tasks yet
+ ) : (
+ tasks.map((task) =>
)
+ )}
+
+
+ );
+}
diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json
index e898bb2f2e..12103286d4 100644
--- a/apps/web/package-lock.json
+++ b/apps/web/package-lock.json
@@ -9,6 +9,9 @@
"version": "0.1.0",
"dependencies": {
"@base-ui/react": "^1.0.0",
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
"@tabler/icons-react": "^3.36.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -534,6 +537,60 @@
}
}
},
+ "node_modules/@dnd-kit/accessibility": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
+ "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/core": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
+ "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@dnd-kit/accessibility": "^3.1.1",
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/sortable": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
+ "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@dnd-kit/core": "^6.3.0",
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/utilities": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
+ "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/@dotenvx/dotenvx": {
"version": "1.51.4",
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.51.4.tgz",
diff --git a/apps/web/package.json b/apps/web/package.json
index 1dd2b07c9f..eeba3d7bd6 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -10,6 +10,9 @@
},
"dependencies": {
"@base-ui/react": "^1.0.0",
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
"@tabler/icons-react": "^3.36.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
diff --git a/apps/web/public/file.svg b/apps/web/public/file.svg
deleted file mode 100644
index 004145cddf..0000000000
--- a/apps/web/public/file.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/web/public/globe.svg b/apps/web/public/globe.svg
deleted file mode 100644
index 567f17b0d7..0000000000
--- a/apps/web/public/globe.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/web/public/next.svg b/apps/web/public/next.svg
deleted file mode 100644
index 5174b28c56..0000000000
--- a/apps/web/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/web/public/vercel.svg b/apps/web/public/vercel.svg
deleted file mode 100644
index 7705396033..0000000000
--- a/apps/web/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/web/public/window.svg b/apps/web/public/window.svg
deleted file mode 100644
index b2b2a44f6e..0000000000
--- a/apps/web/public/window.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file