feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:
- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)
Made-with: Cursor
2026-03-26 23:39:16 -04:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { FolderKanban, ListChecks, Users } from "lucide-react";
|
|
|
|
|
import {
|
|
|
|
|
BaseBoxShapeUtil,
|
|
|
|
|
HTMLContainer,
|
|
|
|
|
T,
|
|
|
|
|
type TLBaseShape,
|
|
|
|
|
toDomPrecision,
|
|
|
|
|
} from "tldraw";
|
|
|
|
|
|
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
|
|
|
|
|
export type ProjectCardShape = TLBaseShape<
|
|
|
|
|
"project-card",
|
|
|
|
|
{
|
|
|
|
|
w: number;
|
|
|
|
|
h: number;
|
|
|
|
|
objectId: string;
|
|
|
|
|
title: string;
|
|
|
|
|
taskCount: number;
|
|
|
|
|
memberCount: number;
|
|
|
|
|
progress: number;
|
|
|
|
|
}
|
|
|
|
|
>;
|
|
|
|
|
|
2026-06-02 01:11:52 -04:00
|
|
|
declare module "@tldraw/tlschema" {
|
|
|
|
|
interface TLGlobalShapePropsMap {
|
|
|
|
|
"project-card": ProjectCardShape["props"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:
- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)
Made-with: Cursor
2026-03-26 23:39:16 -04:00
|
|
|
function clampProgress(n: number): number {
|
|
|
|
|
if (Number.isNaN(n)) return 0;
|
|
|
|
|
return Math.min(100, Math.max(0, n));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ProjectCardBody({ shape }: { shape: ProjectCardShape }) {
|
|
|
|
|
const { title, taskCount, memberCount, progress } = shape.props;
|
|
|
|
|
const pct = clampProgress(progress);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<HTMLContainer>
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex h-full w-full flex-col overflow-hidden rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] text-[hsl(var(--card-foreground))] shadow-md",
|
|
|
|
|
)}
|
|
|
|
|
style={{ fontSize: "11px", lineHeight: 1.35 }}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className="h-1 w-full shrink-0"
|
|
|
|
|
style={{
|
|
|
|
|
background: "linear-gradient(90deg, hsl(var(--primary)) 0%, hsl(var(--teal)) 100%)",
|
|
|
|
|
}}
|
|
|
|
|
aria-hidden
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<div className="flex min-h-0 flex-1 flex-col px-3 pb-2 pt-2.5">
|
|
|
|
|
<div className="flex min-w-0 items-start gap-2">
|
|
|
|
|
<FolderKanban
|
|
|
|
|
className="mt-0.5 size-[18px] shrink-0 text-[hsl(var(--primary))]"
|
|
|
|
|
strokeWidth={2}
|
|
|
|
|
aria-hidden
|
|
|
|
|
/>
|
|
|
|
|
<div className="min-w-0 flex-1 text-[14px] font-bold leading-tight tracking-tight">
|
|
|
|
|
{title || "Untitled project"}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mt-3 grid grid-cols-2 gap-2 text-[11px]">
|
|
|
|
|
<div className="flex items-center gap-1.5 rounded-lg bg-[hsl(var(--muted)/0.5)] px-2 py-1.5 text-[hsl(var(--muted-foreground))]">
|
|
|
|
|
<ListChecks className="size-3.5 shrink-0 text-[hsl(var(--primary))]" aria-hidden />
|
|
|
|
|
<span className="font-medium text-[hsl(var(--card-foreground))]">{taskCount}</span>
|
|
|
|
|
<span className="truncate">tasks</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-1.5 rounded-lg bg-[hsl(var(--muted)/0.5)] px-2 py-1.5 text-[hsl(var(--muted-foreground))]">
|
|
|
|
|
<Users className="size-3.5 shrink-0 text-[hsl(var(--teal))]" aria-hidden />
|
|
|
|
|
<span className="font-medium text-[hsl(var(--card-foreground))]">{memberCount}</span>
|
|
|
|
|
<span className="truncate">members</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mt-3">
|
|
|
|
|
<div className="mb-1 flex items-center justify-between text-[10px] text-[hsl(var(--muted-foreground))]">
|
|
|
|
|
<span>Progress</span>
|
|
|
|
|
<span className="font-semibold tabular-nums text-[hsl(var(--card-foreground))]">{pct}%</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
className="h-2 w-full overflow-hidden rounded-full bg-[hsl(var(--muted))]"
|
|
|
|
|
role="progressbar"
|
|
|
|
|
aria-valuenow={pct}
|
|
|
|
|
aria-valuemin={0}
|
|
|
|
|
aria-valuemax={100}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className="h-full rounded-full transition-[width]"
|
|
|
|
|
style={{
|
|
|
|
|
width: `${pct}%`,
|
|
|
|
|
background: "linear-gradient(90deg, hsl(var(--primary)) 0%, hsl(var(--teal)) 100%)",
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="border-t border-[hsl(var(--border)/0.65)] bg-[hsl(var(--muted)/0.35)] px-3 py-1 text-[9px] font-medium uppercase tracking-wider text-[hsl(var(--muted-foreground))]">
|
|
|
|
|
Project
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</HTMLContainer>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 01:11:52 -04:00
|
|
|
export class ProjectCardShapeUtil extends BaseBoxShapeUtil<ProjectCardShape> {
|
feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:
- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)
Made-with: Cursor
2026-03-26 23:39:16 -04:00
|
|
|
static override type = "project-card";
|
|
|
|
|
|
|
|
|
|
static override props = {
|
|
|
|
|
w: T.number,
|
|
|
|
|
h: T.number,
|
|
|
|
|
objectId: T.string,
|
|
|
|
|
title: T.string,
|
|
|
|
|
taskCount: T.number,
|
|
|
|
|
memberCount: T.number,
|
|
|
|
|
progress: T.number,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
override getDefaultProps(): ProjectCardShape["props"] {
|
|
|
|
|
return {
|
|
|
|
|
w: 260,
|
|
|
|
|
h: 180,
|
|
|
|
|
objectId: "",
|
|
|
|
|
title: "Project",
|
|
|
|
|
taskCount: 0,
|
|
|
|
|
memberCount: 0,
|
|
|
|
|
progress: 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override getAriaDescriptor(shape: ProjectCardShape) {
|
|
|
|
|
return shape.props.title;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override component(shape: ProjectCardShape) {
|
|
|
|
|
return <ProjectCardBody shape={shape} />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override indicator(shape: ProjectCardShape) {
|
|
|
|
|
return (
|
|
|
|
|
<rect
|
|
|
|
|
width={toDomPrecision(shape.props.w)}
|
|
|
|
|
height={toDomPrecision(shape.props.h)}
|
|
|
|
|
rx={10}
|
|
|
|
|
ry={10}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override useLegacyIndicator() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override getIndicatorPath(shape: ProjectCardShape) {
|
|
|
|
|
const p = new Path2D();
|
|
|
|
|
p.roundRect(0, 0, shape.props.w, shape.props.h, 10);
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
}
|