feat: add boards and tasks management endpoints

This commit is contained in:
Abhimanyu Saharan
2026-02-04 02:28:51 +05:30
parent 23faa0865b
commit 1abc8f68f3
170 changed files with 6860 additions and 10706 deletions

View File

@@ -0,0 +1,42 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
export function DashboardSidebar() {
const pathname = usePathname();
return (
<aside className="flex h-full flex-col gap-6 rounded-xl border-2 border-gray-200 bg-white p-6 shadow-lush">
<div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-gray-500">
Work
</p>
<nav className="space-y-1 text-sm">
<Link
href="/agents"
className={cn(
"block rounded-lg border border-transparent px-3 py-2 font-medium text-gray-700 hover:border-gray-200 hover:bg-gray-50",
pathname.startsWith("/agents") &&
"border-gray-200 bg-gray-50 text-gray-900"
)}
>
Agents
</Link>
<Link
href="/boards"
className={cn(
"block rounded-lg border border-transparent px-3 py-2 font-medium text-gray-700 hover:border-gray-200 hover:bg-gray-50",
pathname.startsWith("/boards") &&
"border-gray-200 bg-gray-50 text-gray-900"
)}
>
Boards
</Link>
</nav>
</div>
</aside>
);
}

View File

@@ -0,0 +1,88 @@
"use client";
import { SignInButton, SignedIn, SignedOut } from "@clerk/nextjs";
import { HeroCopy } from "@/components/molecules/HeroCopy";
import { Button } from "@/components/ui/button";
export function LandingHero() {
return (
<section className="grid w-full items-center gap-10 lg:grid-cols-[1.05fr_0.95fr]">
<div
className="space-y-8 animate-fade-in-up"
style={{ animationDelay: "0.05s" }}
>
<HeroCopy />
<div
className="flex flex-col gap-3 sm:flex-row sm:items-center animate-fade-in-up"
style={{ animationDelay: "0.12s" }}
>
<SignedOut>
<SignInButton
mode="modal"
afterSignInUrl="/boards"
afterSignUpUrl="/boards"
forceRedirectUrl="/boards"
signUpForceRedirectUrl="/boards"
>
<Button
size="lg"
className="w-full sm:w-auto border-2 border-gray-900 bg-gray-900 text-white hover:bg-gray-900/90"
>
Sign in to open mission control
</Button>
</SignInButton>
</SignedOut>
<SignedIn>
<div className="text-sm text-gray-600">
You&apos;re signed in. Open your boards when you&apos;re ready.
</div>
</SignedIn>
</div>
<p
className="text-xs uppercase tracking-[0.3em] text-gray-500 animate-fade-in-up"
style={{ animationDelay: "0.18s" }}
>
One login · clear ownership · faster decisions
</p>
</div>
<div
className="relative animate-fade-in-up"
style={{ animationDelay: "0.3s" }}
>
<div className="glass-panel rounded-2xl bg-white p-6">
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between text-xs font-semibold uppercase tracking-[0.3em] text-gray-500">
<span>Status</span>
<span className="rounded-full border border-gray-200 px-2 py-1 text-[10px]">
Live
</span>
</div>
<div className="space-y-2">
<p className="text-lg font-semibold text-gray-900">
Tasks claimed automatically
</p>
<p className="text-sm text-gray-600">
Agents pick the next task in queue, report progress, and ship
deliverables back to you.
</p>
</div>
<div className="grid grid-cols-2 gap-3">
{["Assignments", "In review", "Delivered", "Signals"].map(
(label) => (
<div
key={label}
className="rounded-xl border-2 border-gray-200 bg-white p-4 text-sm font-semibold text-gray-900 soft-shadow-sm"
>
{label}
</div>
)
)}
</div>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,99 @@
"use client";
import { useMemo } from "react";
import { TaskCard } from "@/components/molecules/TaskCard";
import { cn } from "@/lib/utils";
type Task = {
id: string;
title: string;
status: string;
due_at?: string | null;
};
type TaskBoardProps = {
tasks: Task[];
onCreateTask: () => void;
isCreateDisabled?: boolean;
};
const columns = [
{ title: "Inbox", status: "inbox" },
{ title: "Assigned", status: "assigned" },
{ title: "In Progress", status: "in_progress" },
{ title: "Testing", status: "testing" },
{ title: "Review", status: "review" },
{ title: "Done", status: "done" },
];
const formatDueDate = (value?: string | null) => {
if (!value) return undefined;
const date = new Date(value);
if (Number.isNaN(date.getTime())) return undefined;
return date.toLocaleDateString(undefined, {
month: "short",
day: "numeric",
});
};
export function TaskBoard({
tasks,
onCreateTask,
isCreateDisabled = false,
}: TaskBoardProps) {
const grouped = useMemo(() => {
const buckets: Record<string, Task[]> = {};
for (const column of columns) {
buckets[column.status] = [];
}
tasks.forEach((task) => {
const bucket = buckets[task.status] ?? buckets.inbox;
bucket.push(task);
});
return buckets;
}, [tasks]);
return (
<div className="grid grid-flow-col auto-cols-[minmax(260px,320px)] gap-6 overflow-x-auto pb-2">
{columns.map((column) => {
const columnTasks = grouped[column.status] ?? [];
return (
<div key={column.title} className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-gray-900">
{column.title}
</h3>
<span className="text-xs text-gray-500">
{columnTasks.length}
</span>
</div>
<div className="space-y-3">
{column.status === "inbox" ? (
<button
type="button"
onClick={onCreateTask}
disabled={isCreateDisabled}
className={cn(
"flex w-full items-center justify-center rounded-xl border-2 border-dashed border-gray-200 bg-gray-50 px-4 py-6 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500 transition hover:border-gray-300 hover:bg-white",
isCreateDisabled && "cursor-not-allowed opacity-60"
)}
>
New task
</button>
) : null}
{columnTasks.map((task) => (
<TaskCard
key={task.id}
title={task.title}
status={column.status}
due={formatDueDate(task.due_at)}
/>
))}
</div>
</div>
);
})}
</div>
);
}