diff --git a/frontend/src/components/kanbanBoard/columnContainer.jsx b/frontend/src/components/kanbanBoard/columnContainer.jsx
new file mode 100644
index 0000000..03bc011
--- /dev/null
+++ b/frontend/src/components/kanbanBoard/columnContainer.jsx
@@ -0,0 +1,179 @@
+import { SortableContext, useSortable } from "@dnd-kit/sortable";
+import TrashIcon from "../icons/TrashIcon";
+import { Column, Id, Task } from "../types";
+import { CSS } from "@dnd-kit/utilities";
+import { useMemo, useState } from "react";
+import PlusIcon from "../icons/PlusIcon";
+import TaskCard from "./taskCard";
+
+function ColumnContainer({
+ column,
+ deleteColumn,
+ updateColumn,
+ createTask,
+ tasks,
+ deleteTask,
+ updateTask,
+}) {
+ const [editMode, setEditMode] = useState(false);
+
+ const tasksIds = useMemo(() => {
+ return tasks.map((task) => task.id);
+ }, [tasks]);
+
+ const {
+ setNodeRef,
+ attributes,
+ listeners,
+ transform,
+ transition,
+ isDragging,
+ } = useSortable({
+ id: column.id,
+ data: {
+ type: "Column",
+ column,
+ },
+ disabled: editMode,
+ });
+
+ const style = {
+ transition,
+ transform: CSS.Transform.toString(transform),
+ };
+
+ if (isDragging) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {/* Column title */}
+
{
+ setEditMode(true);
+ }}
+ className="
+ bg-mainBackgroundColor
+ text-md
+ h-[60px]
+ cursor-grab
+ rounded-md
+ rounded-b-none
+ p-3
+ font-bold
+ border-columnBackgroundColor
+ border-4
+ flex
+ items-center
+ justify-between
+ "
+ >
+
+
+ 0
+
+ {!editMode && column.title}
+ {editMode && (
+
updateColumn(column.id, e.target.value)}
+ autoFocus
+ onBlur={() => {
+ setEditMode(false);
+ }}
+ onKeyDown={(e) => {
+ if (e.key !== "Enter") return;
+ setEditMode(false);
+ }}
+ />
+ )}
+
+
+
+
+ {/* Column task container */}
+
+
+ {tasks.map((task) => (
+
+ ))}
+
+
+ {/* Column footer */}
+
+
+ );
+}
+
+export default ColumnContainer;
diff --git a/frontend/src/components/kanbanBoard/kanbanBoard.jsx b/frontend/src/components/kanbanBoard/kanbanBoard.jsx
new file mode 100644
index 0000000..a289652
--- /dev/null
+++ b/frontend/src/components/kanbanBoard/kanbanBoard.jsx
@@ -0,0 +1,342 @@
+import PlusIcon from "../icons/PlusIcon";
+import { useMemo, useState } from "react";
+import { Column, Id, Task } from "../types";
+import ColumnContainer from "./columnContainer";
+import {
+ DndContext,
+ DragEndEvent,
+ DragOverEvent,
+ DragOverlay,
+ DragStartEvent,
+ PointerSensor,
+ useSensor,
+ useSensors,
+} from "@dnd-kit/core";
+import { SortableContext, arrayMove } from "@dnd-kit/sortable";
+import { createPortal } from "react-dom";
+import TaskCard from "./taskCard";
+
+const defaultCols = [
+ {
+ id: "todo",
+ title: "Todo",
+ },
+ {
+ id: "doing",
+ title: "Work in progress",
+ },
+ {
+ id: "done",
+ title: "Done",
+ },
+];
+
+const defaultTasks = [
+ {
+ id: "1",
+ columnId: "todo",
+ content: "List admin APIs for dashboard",
+ },
+ {
+ id: "2",
+ columnId: "todo",
+ content:
+ "Develop user registration functionality with OTP delivered on SMS after email confirmation and phone number confirmation",
+ },
+ {
+ id: "3",
+ columnId: "doing",
+ content: "Conduct security testing",
+ },
+ {
+ id: "4",
+ columnId: "doing",
+ content: "Analyze competitors",
+ },
+ {
+ id: "5",
+ columnId: "done",
+ content: "Create UI kit documentation",
+ },
+ {
+ id: "6",
+ columnId: "done",
+ content: "Dev meeting",
+ },
+ {
+ id: "7",
+ columnId: "done",
+ content: "Deliver dashboard prototype",
+ },
+ {
+ id: "8",
+ columnId: "todo",
+ content: "Optimize application performance",
+ },
+ {
+ id: "9",
+ columnId: "todo",
+ content: "Implement data validation",
+ },
+ {
+ id: "10",
+ columnId: "todo",
+ content: "Design database schema",
+ },
+ {
+ id: "11",
+ columnId: "todo",
+ content: "Integrate SSL web certificates into workflow",
+ },
+ {
+ id: "12",
+ columnId: "doing",
+ content: "Implement error logging and monitoring",
+ },
+ {
+ id: "13",
+ columnId: "doing",
+ content: "Design and implement responsive UI",
+ },
+];
+
+function KanbanBoard() {
+ const [columns, setColumns] = useState(defaultCols);
+ const columnsId = useMemo(() => columns.map((col) => col.id), [columns]);
+
+ const [tasks, setTasks] = useState(defaultTasks);
+
+ const [activeColumn, setActiveColumn] = useState(null);
+
+ const [activeTask, setActiveTask] = useState(null);
+
+ const sensors = useSensors(
+ useSensor(PointerSensor, {
+ activationConstraint: {
+ distance: 10,
+ },
+ })
+ );
+
+ return (
+
+
+
+
+
+ {columns.map((col) => (
+ task.columnId === col.id)}
+ />
+ ))}
+
+
+
+
+
+ {createPortal(
+
+ {activeColumn && (
+ task.columnId === activeColumn.id
+ )}
+ />
+ )}
+ {activeTask && (
+
+ )}
+ ,
+ document.body
+ )}
+
+
+ );
+
+ function createTask(columnId) {
+ const newTask = {
+ id: generateId(),
+ columnId,
+ content: `Task ${tasks.length + 1}`,
+ };
+
+ setTasks([...tasks, newTask]);
+ }
+
+ function deleteTask(id) {
+ const newTasks = tasks.filter((task) => task.id !== id);
+ setTasks(newTasks);
+ }
+
+ function updateTask(id, content) {
+ const newTasks = tasks.map((task) => {
+ if (task.id !== id) return task;
+ return { ...task, content };
+ });
+
+ setTasks(newTasks);
+ }
+
+ function createNewColumn() {
+ const columnToAdd = {
+ id: generateId(),
+ title: `Column ${columns.length + 1}`,
+ };
+
+ setColumns([...columns, columnToAdd]);
+ }
+
+ function deleteColumn(id) {
+ const filteredColumns = columns.filter((col) => col.id !== id);
+ setColumns(filteredColumns);
+
+ const newTasks = tasks.filter((t) => t.columnId !== id);
+ setTasks(newTasks);
+ }
+
+ function updateColumn(id, title) {
+ const newColumns = columns.map((col) => {
+ if (col.id !== id) return col;
+ return { ...col, title };
+ });
+
+ setColumns(newColumns);
+ }
+
+ function onDragStart(event) {
+ if (event.active.data.current?.type === "Column") {
+ setActiveColumn(event.active.data.current.column);
+ return;
+ }
+
+ if (event.active.data.current?.type === "Task") {
+ setActiveTask(event.active.data.current.task);
+ return;
+ }
+ }
+
+ function onDragEnd(event) {
+ setActiveColumn(null);
+ setActiveTask(null);
+
+ const { active, over } = event;
+ if (!over) return;
+
+ const activeId = active.id;
+ const overId = over.id;
+
+ if (activeId === overId) return;
+
+ const isActiveAColumn = active.data.current?.type === "Column";
+ if (!isActiveAColumn) return;
+
+ setColumns((columns) => {
+ const activeColumnIndex = columns.findIndex((col) => col.id === activeId);
+
+ const overColumnIndex = columns.findIndex((col) => col.id === overId);
+
+ return arrayMove(columns, activeColumnIndex, overColumnIndex);
+ });
+ }
+
+ function onDragOver(event) {
+ const { active, over } = event;
+ if (!over) return;
+
+ const activeId = active.id;
+ const overId = over.id;
+
+ if (activeId === overId) return;
+
+ const isActiveATask = active.data.current?.type === "Task";
+ const isOverATask = over.data.current?.type === "Task";
+
+ if (!isActiveATask) return;
+
+ if (isActiveATask && isOverATask) {
+ setTasks((tasks) => {
+ const activeIndex = tasks.findIndex((t) => t.id === activeId);
+ const overIndex = tasks.findIndex((t) => t.id === overId);
+
+ if (tasks[activeIndex].columnId !== tasks[overIndex].columnId) {
+ tasks[activeIndex].columnId = tasks[overIndex].columnId;
+ return arrayMove(tasks, activeIndex, overIndex - 1);
+ }
+
+ return arrayMove(tasks, activeIndex, overIndex);
+ });
+ }
+
+ const isOverAColumn = over.data.current?.type === "Column";
+
+ if (isActiveATask && isOverAColumn) {
+ setTasks((tasks) => {
+ const activeIndex = tasks.findIndex((t) => t.id === activeId);
+
+ tasks[activeIndex].columnId = overId;
+ return arrayMove(tasks, activeIndex, activeIndex);
+ });
+ }
+ }
+
+ function generateId() {
+ return Math.floor(Math.random() * 10001);
+ }
+}
+
+export default KanbanBoard;
diff --git a/frontend/src/components/kanbanBoard/taskCard.jsx b/frontend/src/components/kanbanBoard/taskCard.jsx
new file mode 100644
index 0000000..226b319
--- /dev/null
+++ b/frontend/src/components/kanbanBoard/taskCard.jsx
@@ -0,0 +1,111 @@
+import { useState } from "react";
+import TrashIcon from "../icons/TrashIcon";
+import { useSortable } from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+
+function TaskCard({ task, deleteTask, updateTask }) {
+ const [mouseIsOver, setMouseIsOver] = useState(false);
+ const [editMode, setEditMode] = useState(true);
+
+ const {
+ setNodeRef,
+ attributes,
+ listeners,
+ transform,
+ transition,
+ isDragging,
+ } = useSortable({
+ id: task.id,
+ data: {
+ type: "Task",
+ task,
+ },
+ disabled: editMode,
+ });
+
+ const style = {
+ transition,
+ transform: CSS.Transform.toString(transform),
+ };
+
+ const toggleEditMode = () => {
+ setEditMode((prev) => !prev);
+ setMouseIsOver(false);
+ };
+
+ if (isDragging) {
+ return (
+
+ );
+ }
+
+ if (editMode) {
+ return (
+
+
+ );
+ }
+
+ return (
+ {
+ setMouseIsOver(true);
+ }}
+ onMouseLeave={() => {
+ setMouseIsOver(false);
+ }}
+ >
+
+ {task.content}
+
+
+ {mouseIsOver && (
+
+ )}
+
+ );
+}
+
+export default TaskCard;