feat: landing page and pnpm workspaces
This commit is contained in:
+10
@@ -71,3 +71,13 @@ apps/backend/bin/
|
||||
# Go binaries (built locally)
|
||||
apps/backend/kandev
|
||||
apps/backend/agentctl
|
||||
|
||||
# Next.js
|
||||
.next/
|
||||
out/
|
||||
node_modules/
|
||||
pnpm-lock.yaml
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
apps/landing/next-env.d.ts
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
# Directories
|
||||
BACKEND_DIR := apps/backend
|
||||
WEB_DIR := apps/web
|
||||
LANDING_DIR := apps/landing
|
||||
APPS_DIR := apps
|
||||
|
||||
# Tools
|
||||
NPM := npm
|
||||
PNPM := pnpm
|
||||
MAKE := make
|
||||
|
||||
# Default target
|
||||
@@ -23,17 +25,19 @@ help:
|
||||
@echo "Development Commands:"
|
||||
@echo " dev-backend Run backend in development mode (port 8080)"
|
||||
@echo " dev-web Run web app in development mode (port 3000)"
|
||||
@echo " dev-landing Run landing page in development mode (port 3001)"
|
||||
@echo " dev Note: Run dev-backend and dev-web in separate terminals"
|
||||
@echo ""
|
||||
@echo "Build Commands:"
|
||||
@echo " build Build both backend and web app"
|
||||
@echo " build Build backend, web app, and landing page"
|
||||
@echo " build-backend Build backend binary"
|
||||
@echo " build-web Build web app for production"
|
||||
@echo " build-landing Build landing page for production"
|
||||
@echo ""
|
||||
@echo "Installation:"
|
||||
@echo " install Install all dependencies (backend + web)"
|
||||
@echo " install Install all dependencies (backend + web + landing)"
|
||||
@echo " install-backend Install backend dependencies"
|
||||
@echo " install-web Install web dependencies"
|
||||
@echo " install-web Install web dependencies (uses pnpm workspace)"
|
||||
@echo ""
|
||||
@echo "Testing:"
|
||||
@echo " test Run all tests (backend + web)"
|
||||
@@ -71,7 +75,7 @@ dev:
|
||||
@echo "Backend: http://localhost:8080"
|
||||
@echo "Web app: http://localhost:3000"
|
||||
@echo ""
|
||||
@$(MAKE) -C $(BACKEND_DIR) run & cd $(WEB_DIR) && $(NPM) run dev
|
||||
@$(MAKE) -C $(BACKEND_DIR) run & cd $(APPS_DIR) && $(PNPM) --filter @kandev/web dev
|
||||
|
||||
.PHONY: dev-backend
|
||||
dev-backend:
|
||||
@@ -81,18 +85,24 @@ dev-backend:
|
||||
.PHONY: dev-web
|
||||
dev-web:
|
||||
@echo "Starting web app on http://localhost:3000"
|
||||
@cd $(WEB_DIR) && $(NPM) run dev
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web dev
|
||||
|
||||
.PHONY: dev-landing
|
||||
dev-landing:
|
||||
@echo "Starting landing page on http://localhost:3001"
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/landing dev
|
||||
|
||||
#
|
||||
# Build
|
||||
#
|
||||
|
||||
.PHONY: build
|
||||
build: build-backend build-web
|
||||
build: build-backend build-web build-landing
|
||||
@echo ""
|
||||
@echo "✓ Build complete!"
|
||||
@echo " Backend binary: $(BACKEND_DIR)/bin/kandev"
|
||||
@echo " Web app: $(WEB_DIR)/.next"
|
||||
@echo " Landing page: $(LANDING_DIR)/.next"
|
||||
|
||||
.PHONY: build-backend
|
||||
build-backend:
|
||||
@@ -102,7 +112,12 @@ build-backend:
|
||||
.PHONY: build-web
|
||||
build-web:
|
||||
@echo "Building web app..."
|
||||
@cd $(WEB_DIR) && $(NPM) run build
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web build
|
||||
|
||||
.PHONY: build-landing
|
||||
build-landing:
|
||||
@echo "Building landing page..."
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/landing build
|
||||
|
||||
#
|
||||
# Installation
|
||||
@@ -120,8 +135,8 @@ install-backend:
|
||||
|
||||
.PHONY: install-web
|
||||
install-web:
|
||||
@echo "Installing web dependencies..."
|
||||
@cd $(WEB_DIR) && $(NPM) install
|
||||
@echo "Installing web and landing dependencies..."
|
||||
@cd $(APPS_DIR) && $(PNPM) install
|
||||
|
||||
#
|
||||
# Testing
|
||||
@@ -140,7 +155,7 @@ test-backend:
|
||||
.PHONY: test-web
|
||||
test-web:
|
||||
@echo "Running web app tests..."
|
||||
@cd $(WEB_DIR) && $(NPM) test
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web test
|
||||
|
||||
#
|
||||
# Code Quality
|
||||
@@ -159,7 +174,7 @@ lint-backend:
|
||||
.PHONY: lint-web
|
||||
lint-web:
|
||||
@echo "Linting web app..."
|
||||
@cd $(WEB_DIR) && $(NPM) run lint
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web lint
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: fmt-backend fmt-web
|
||||
@@ -174,7 +189,7 @@ fmt-backend:
|
||||
.PHONY: fmt-web
|
||||
fmt-web:
|
||||
@echo "Formatting web code..."
|
||||
@cd $(WEB_DIR) && $(NPM) run lint -- --fix || true
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web lint -- --fix || true
|
||||
|
||||
#
|
||||
# Cleanup
|
||||
@@ -192,8 +207,9 @@ clean-backend:
|
||||
|
||||
.PHONY: clean-web
|
||||
clean-web:
|
||||
@echo "Cleaning web artifacts..."
|
||||
@rm -rf $(WEB_DIR)/.next $(WEB_DIR)/node_modules
|
||||
@echo "Cleaning web and landing artifacts..."
|
||||
@rm -rf $(WEB_DIR)/.next $(LANDING_DIR)/.next $(APPS_DIR)/node_modules
|
||||
@rm -rf $(APPS_DIR)/packages/*/node_modules
|
||||
|
||||
.PHONY: clean-db
|
||||
clean-db:
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
import { IconTarget, IconRocket, IconHeartHandshake } from '@tabler/icons-react';
|
||||
|
||||
const values = [
|
||||
{
|
||||
icon: IconTarget,
|
||||
title: 'Developer First',
|
||||
description: 'Built by developers, for developers. We understand your workflow because we live it every day.',
|
||||
},
|
||||
{
|
||||
icon: IconRocket,
|
||||
title: 'Ship Fast, Ship Safe',
|
||||
description: 'Speed without sacrificing security. AI-powered automation with human oversight and approval.',
|
||||
},
|
||||
{
|
||||
icon: IconHeartHandshake,
|
||||
title: 'Open & Transparent',
|
||||
description: 'Open source at heart. We believe in building in public and learning from the community.',
|
||||
},
|
||||
];
|
||||
|
||||
const timeline = [
|
||||
{
|
||||
year: '2026',
|
||||
title: 'The Beginning',
|
||||
description: 'kandev.ai is born from the need to combine visual task management with AI-assisted development.',
|
||||
},
|
||||
];
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<div className="container mx-auto py-24 max-w-6xl px-4">
|
||||
<div className="flex flex-col gap-4 mb-12">
|
||||
<h1 className="text-4xl font-bold">About kandev.ai</h1>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
We're building the future of development workflows.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="prose prose-slate dark:prose-invert max-w-none mb-12">
|
||||
<h2>Our Mission</h2>
|
||||
<p>
|
||||
kandev.ai exists to make software development more visual, more collaborative, and more efficient.
|
||||
We believe that combining Kanban methodology with AI-assisted coding creates a workflow that's
|
||||
greater than the sum of its parts.
|
||||
</p>
|
||||
<p>
|
||||
Traditional development tools force you to context switch between project management, coding,
|
||||
and deployment. We're building a unified experience where your Kanban board is the control center
|
||||
for AI agents working in isolated, secure environments.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-12">
|
||||
<h2 className="text-2xl font-bold mb-6">Our Values</h2>
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
{values.map((value) => {
|
||||
const Icon = value.icon;
|
||||
return (
|
||||
<Card key={value.title}>
|
||||
<CardHeader>
|
||||
<Icon className="h-8 w-8 text-primary mb-2" />
|
||||
<CardTitle>{value.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>{value.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-12">
|
||||
<h2 className="text-2xl font-bold mb-6">Our Journey</h2>
|
||||
<div className="space-y-6">
|
||||
{timeline.map((item) => (
|
||||
<Card key={item.year}>
|
||||
<CardHeader>
|
||||
<div className="text-sm text-muted-foreground mb-1">{item.year}</div>
|
||||
<CardTitle>{item.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground">{item.description}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="prose prose-slate dark:prose-invert max-w-none">
|
||||
<h2>Why Kanban + Development + AI?</h2>
|
||||
<p>
|
||||
Kanban boards provide visual clarity. Development tools provide execution. AI provides automation.
|
||||
Together, they create a workflow where you can see all your work, execute it in parallel, and
|
||||
automate the routine parts while maintaining full control.
|
||||
</p>
|
||||
<p>
|
||||
We're just getting started. As we grow, we'll continue to focus on making development more
|
||||
intuitive, more powerful, and more enjoyable.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
import Link from 'next/link';
|
||||
|
||||
const blogPosts = [
|
||||
{
|
||||
title: 'Introducing kandev.ai: AI-Powered Kanban for Developers',
|
||||
description: 'Learn how kandev.ai combines Kanban boards with AI agents, isolated worktrees, and Docker containers to accelerate your development workflow.',
|
||||
date: 'January 10, 2026',
|
||||
slug: 'introducing-kandev',
|
||||
author: 'Kandev Team',
|
||||
},
|
||||
{
|
||||
title: 'Running Multiple AI Agents in Parallel: Best Practices',
|
||||
description: 'Discover how to effectively manage multiple AI coding agents working simultaneously on different tasks using isolated environments and approval workflows.',
|
||||
date: 'January 5, 2026',
|
||||
slug: 'parallel-ai-agents',
|
||||
author: 'Kandev Team',
|
||||
},
|
||||
];
|
||||
|
||||
export default function BlogPage() {
|
||||
return (
|
||||
<div className="container mx-auto max-w-6xl py-24 px-4">
|
||||
<h1 className="text-4xl font-bold mb-4">Blog</h1>
|
||||
<p className="text-lg text-muted-foreground mb-12">
|
||||
Insights and updates from the kandev.ai team.
|
||||
</p>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{blogPosts.map((post) => (
|
||||
<Link key={post.slug} href={`/blog/${post.slug}`}>
|
||||
<Card className="h-full transition-transform hover:scale-[1.02] cursor-pointer">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-2">
|
||||
<span>{post.date}</span>
|
||||
<span>•</span>
|
||||
<span>{post.author}</span>
|
||||
</div>
|
||||
<CardTitle className="text-2xl">{post.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription className="text-base">{post.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { DocsSidebar } from '@/components/docs-sidebar';
|
||||
|
||||
export default function DocsLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex min-h-screen pt-16 justify-center">
|
||||
<DocsSidebar />
|
||||
<main className="flex-1 max-w-4xl py-8 px-6">{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
export default function DocsPage() {
|
||||
return (
|
||||
<div className="prose prose-slate dark:prose-invert max-w-none">
|
||||
<div className="not-prose mb-8">
|
||||
<h1 className="text-4xl font-bold mb-4">Getting Started with kandev.ai</h1>
|
||||
<p className="text-xl text-muted-foreground">
|
||||
kandev.ai combines Kanban task management with AI-powered development workflows. Manage tasks visually,
|
||||
execute them with AI agents in isolated environments, and ship faster with confidence.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="not-prose mb-8 p-6 bg-muted/50 rounded-lg border border-border">
|
||||
<h2 className="text-2xl font-bold mb-3">What is kandev.ai?</h2>
|
||||
<p className="text-muted-foreground">
|
||||
kandev.ai is a development tool that bridges project management and AI-assisted coding. Each task on your
|
||||
Kanban board can be executed by AI agents working in isolated git worktrees and Docker containers.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold mt-8 mb-4">Key Features</h2>
|
||||
<div className="grid gap-4 not-prose mb-8">
|
||||
<div className="p-4 border border-border rounded-lg">
|
||||
<h3 className="font-semibold mb-2">Visual Task Management</h3>
|
||||
<p className="text-sm text-muted-foreground">Organize development work with Kanban boards</p>
|
||||
</div>
|
||||
<div className="p-4 border border-border rounded-lg">
|
||||
<h3 className="font-semibold mb-2">Isolated Worktrees</h3>
|
||||
<p className="text-sm text-muted-foreground">Each task gets its own git worktree for parallel development</p>
|
||||
</div>
|
||||
<div className="p-4 border border-border rounded-lg">
|
||||
<h3 className="font-semibold mb-2">Local Docker Execution</h3>
|
||||
<p className="text-sm text-muted-foreground">AI agents run in secure Docker containers on your machine</p>
|
||||
</div>
|
||||
<div className="p-4 border border-border rounded-lg">
|
||||
<h3 className="font-semibold mb-2">Multiple AI Agents</h3>
|
||||
<p className="text-sm text-muted-foreground">Support for Claude Code, OpenAI Codex, Augment, and more</p>
|
||||
</div>
|
||||
<div className="p-4 border border-border rounded-lg">
|
||||
<h3 className="font-semibold mb-2">Approval Workflows</h3>
|
||||
<p className="text-sm text-muted-foreground">Review and approve agent changes before merging</p>
|
||||
</div>
|
||||
<div className="p-4 border border-border rounded-lg">
|
||||
<h3 className="font-semibold mb-2">Multiple Workspaces</h3>
|
||||
<p className="text-sm text-muted-foreground">Organize projects by client, team, or product</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold mt-8 mb-4">How It Works</h2>
|
||||
<div className="not-prose space-y-4 mb-8">
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">1</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-1">Create a Task</h3>
|
||||
<p className="text-sm text-muted-foreground">Add a task to your Kanban board with a description of what needs to be done</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">2</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-1">Configure Repository</h3>
|
||||
<p className="text-sm text-muted-foreground">Select a repository and base branch for the task</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">3</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-1">Choose AI Agent</h3>
|
||||
<p className="text-sm text-muted-foreground">Select which AI agent should work on the task</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">4</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-1">Isolated Environment</h3>
|
||||
<p className="text-sm text-muted-foreground">kandev creates an isolated git worktree and Docker container</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">5</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-1">AI Execution</h3>
|
||||
<p className="text-sm text-muted-foreground">The AI agent executes the task in the secure environment</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">6</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-1">Review & Approve</h3>
|
||||
<p className="text-sm text-muted-foreground">Review the changes and approve them for merging</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="not-prose mb-8 p-6 bg-primary/10 rounded-lg border border-primary/20">
|
||||
<h2 className="text-2xl font-bold mb-3">Why kandev.ai?</h2>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Traditional development workflows require constant context switching. kandev.ai eliminates this by:
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span className="text-sm">Visualizing all work in progress on a single board</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span className="text-sm">Enabling parallel development without branch conflicts</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span className="text-sm">Automating routine tasks with AI agents</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-primary">✓</span>
|
||||
<span className="text-sm">Maintaining security through isolated execution environments</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="not-prose mb-8 p-6 bg-accent/10 rounded-lg border border-accent/20">
|
||||
<h2 className="text-2xl font-bold mb-3">Security & Control</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Every operation performed by AI agents is logged and requires approval. Agents run in isolated Docker
|
||||
containers with restricted permissions, ensuring your codebase and system remain secure.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="not-prose mt-12 p-8 bg-gradient-to-br from-primary/10 to-accent/10 rounded-lg border border-border text-center">
|
||||
<h2 className="text-2xl font-bold mb-4">Ready to Get Started?</h2>
|
||||
<p className="text-muted-foreground mb-6">
|
||||
Check out our guides to install and configure kandev.ai
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center">
|
||||
<a
|
||||
href="/docs/installation"
|
||||
className="inline-flex items-center justify-center px-6 py-3 rounded-md bg-primary text-primary-foreground font-medium hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
Installation Guide
|
||||
</a>
|
||||
<a
|
||||
href="/docs/quick-start"
|
||||
className="inline-flex items-center justify-center px-6 py-3 rounded-md border border-border bg-background hover:bg-muted font-medium transition-colors"
|
||||
>
|
||||
Quick Start Tutorial
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
'use client';
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Error - kandev.ai</title>
|
||||
</head>
|
||||
<body style={{
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontFamily: 'system-ui, sans-serif',
|
||||
display: 'flex',
|
||||
minHeight: '100vh',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#ffffff'
|
||||
}}>
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '2rem'
|
||||
}}>
|
||||
<h1 style={{
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '1rem',
|
||||
color: '#000000'
|
||||
}}>
|
||||
Something went wrong!
|
||||
</h1>
|
||||
<p style={{
|
||||
marginBottom: '2rem',
|
||||
color: '#666666'
|
||||
}}>
|
||||
We're sorry, but something unexpected happened.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => reset()}
|
||||
style={{
|
||||
padding: '0.75rem 1.5rem',
|
||||
backgroundColor: '#6B46C1',
|
||||
color: '#ffffff',
|
||||
border: 'none',
|
||||
borderRadius: '0.5rem',
|
||||
fontSize: '1rem',
|
||||
fontWeight: '500',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
@import "@kandev/theme/fonts.css";
|
||||
|
||||
@source "../components/**/*.{js,ts,jsx,tsx}";
|
||||
@source "../../packages/ui/src/**/*.{js,ts,jsx,tsx}";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--radius-sm: var(--radius);
|
||||
--radius-md: var(--radius);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: var(--radius);
|
||||
--radius-2xl: var(--radius);
|
||||
--radius-3xl: var(--radius);
|
||||
--radius-4xl: var(--radius);
|
||||
}
|
||||
|
||||
:root {
|
||||
--font-geist-sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--font-sans: "Figtree", "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
|
||||
sans-serif;
|
||||
--font-geist-mono: "Geist Mono", ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas,
|
||||
"Liberation Mono", "Courier New", monospace;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.51 0.23 277);
|
||||
--primary-foreground: oklch(0.96 0.02 272);
|
||||
--secondary: oklch(0.967 0.001 286.375);
|
||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.68 0.18 276);
|
||||
--accent-foreground: oklch(0.98 0 0);
|
||||
--destructive: oklch(0.58 0.22 27);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.79 0.10 275);
|
||||
--chart-2: oklch(0.68 0.16 277);
|
||||
--chart-3: oklch(0.59 0.20 277);
|
||||
--chart-4: oklch(0.51 0.23 277);
|
||||
--chart-5: oklch(0.46 0.21 277);
|
||||
--radius: 0.375rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.51 0.23 277);
|
||||
--sidebar-primary-foreground: oklch(0.96 0.02 272);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.59 0.20 277);
|
||||
--primary-foreground: oklch(0.96 0.02 272);
|
||||
--secondary: oklch(0.274 0.006 286.033);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.62 0.18 276);
|
||||
--accent-foreground: oklch(0.98 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.79 0.10 275);
|
||||
--chart-2: oklch(0.68 0.16 277);
|
||||
--chart-3: oklch(0.59 0.20 277);
|
||||
--chart-4: oklch(0.51 0.23 277);
|
||||
--chart-5: oklch(0.46 0.21 277);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.68 0.16 277);
|
||||
--sidebar-primary-foreground: oklch(0.96 0.02 272);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
html {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.animate-pulse-slow {
|
||||
animation: pulse-slow 8s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-slow {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { Metadata } from 'next';
|
||||
import './globals.css';
|
||||
import { ThemeProvider } from '@/components/theme-provider';
|
||||
import { Navbar } from '@/components/navbar';
|
||||
import { Footer } from '@/components/footer';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'kandev.ai - AI-powered Kanban for Developers',
|
||||
description: 'Kanban meets AI development. Manage tasks with isolated worktrees, Docker containers, and AI agents.',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className="font-sans antialiased">
|
||||
<ThemeProvider>
|
||||
<Navbar />
|
||||
<main className="pt-14">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { HeroSection } from '@/components/hero-section';
|
||||
import { FeaturesSection } from '@/components/features-section';
|
||||
import { AgentsSection } from '@/components/agents-section';
|
||||
import { FutureFeaturesSection } from '@/components/future-features-section';
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main>
|
||||
<HeroSection />
|
||||
<FeaturesSection />
|
||||
<AgentsSection />
|
||||
<FutureFeaturesSection />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
|
||||
const agents = [
|
||||
{
|
||||
name: 'Claude Code',
|
||||
description: "Anthropic's official CLI for Claude. Powerful agent with excellent reasoning and code generation.",
|
||||
icon: '🤖',
|
||||
},
|
||||
{
|
||||
name: 'OpenAI Codex',
|
||||
description: 'GPT-4 powered coding assistant with strong performance across multiple languages.',
|
||||
icon: '🔮',
|
||||
},
|
||||
{
|
||||
name: 'Augment',
|
||||
description: 'AI coding assistant focused on code completion and refactoring workflows.',
|
||||
icon: '⚡',
|
||||
},
|
||||
{
|
||||
name: 'Opencode',
|
||||
description: 'Open-source AI coding agent with customizable models and integrations.',
|
||||
icon: '🌐',
|
||||
},
|
||||
];
|
||||
|
||||
export function AgentsSection() {
|
||||
return (
|
||||
<section className="w-full bg-muted/50 py-16 md:py-24">
|
||||
<div className="container mx-auto max-w-6xl px-4">
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
|
||||
Supported AI Agents
|
||||
</h2>
|
||||
<p className="max-w-2xl text-lg text-muted-foreground">
|
||||
Work with your favorite AI coding assistants. All agents run in isolated, secure environments.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-12 grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
{agents.map((agent) => (
|
||||
<Card key={agent.name} className="transition-transform hover:scale-105 cursor-pointer">
|
||||
<CardHeader>
|
||||
<div className="text-4xl mb-2">{agent.icon}</div>
|
||||
<CardTitle>{agent.name}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>{agent.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: 'Getting Started',
|
||||
items: [
|
||||
{ title: 'Introduction', href: '/docs' },
|
||||
{ title: 'Installation', href: '/docs/installation' },
|
||||
{ title: 'Quick Start', href: '/docs/quick-start' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Core Concepts',
|
||||
items: [
|
||||
{ title: 'Workspaces', href: '/docs/workspaces' },
|
||||
{ title: 'Boards', href: '/docs/boards' },
|
||||
{ title: 'Tasks', href: '/docs/tasks' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Features',
|
||||
items: [
|
||||
{ title: 'Git Worktrees', href: '/docs/worktrees' },
|
||||
{ title: 'AI Agents', href: '/docs/agents' },
|
||||
{ title: 'Docker Execution', href: '/docs/docker' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Integrations',
|
||||
items: [
|
||||
{ title: 'MCP Servers', href: '/docs/mcp' },
|
||||
{ title: 'GitHub', href: '/docs/github' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Security',
|
||||
items: [
|
||||
{ title: 'Approval Workflows', href: '/docs/approvals' },
|
||||
{ title: 'Audit Logs', href: '/docs/audit-logs' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Advanced',
|
||||
items: [
|
||||
{ title: 'Configuration', href: '/docs/configuration' },
|
||||
{ title: 'CLI Reference', href: '/docs/cli' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function DocsSidebar() {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<aside className="sticky top-14 h-[calc(100vh-3.5rem)] w-64 shrink-0 overflow-y-auto border-r border-border py-8 pr-6">
|
||||
<nav className="space-y-6">
|
||||
{sections.map((section) => (
|
||||
<div key={section.title}>
|
||||
<h4 className="mb-2 font-semibold text-sm">{section.title}</h4>
|
||||
<ul className="space-y-2">
|
||||
{section.items.map((item) => (
|
||||
<li key={item.href}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
'block text-sm transition-colors hover:text-foreground',
|
||||
pathname === item.href
|
||||
? 'font-medium text-foreground'
|
||||
: 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { IconGitBranch, IconCode, IconShieldCheck, IconPlugConnected, IconFolders, IconBrandDocker } from '@tabler/icons-react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: IconCode,
|
||||
title: 'Parallel Agent Execution',
|
||||
description:
|
||||
'Run multiple AI coding agents simultaneously on different tasks. Each agent works in isolated environments, accelerating development without conflicts.',
|
||||
},
|
||||
{
|
||||
icon: IconGitBranch,
|
||||
title: 'Isolated Worktrees',
|
||||
description:
|
||||
'Each task gets its own git worktree. Work on multiple features, bugs, and experiments in parallel without branch switching or stashing.',
|
||||
},
|
||||
{
|
||||
icon: IconShieldCheck,
|
||||
title: 'Built-in Code Review',
|
||||
description:
|
||||
'Automated code review and approval workflows. Review agent changes before merging, with full audit logs and rollback capabilities.',
|
||||
},
|
||||
{
|
||||
icon: IconPlugConnected,
|
||||
title: 'MCP Integration',
|
||||
description:
|
||||
'Connect with Model Context Protocol servers. Extend agent capabilities with custom tools, APIs, and integrations.',
|
||||
},
|
||||
{
|
||||
icon: IconFolders,
|
||||
title: 'Multiple Workspaces',
|
||||
description:
|
||||
'Organize projects by client, team, or product. Switch between workspaces seamlessly and keep your work isolated.',
|
||||
},
|
||||
{
|
||||
icon: IconBrandDocker,
|
||||
title: 'Local Docker',
|
||||
description:
|
||||
'AI agents run in local Docker containers for complete isolation. Secure execution with approval workflows and audit logs for every operation.',
|
||||
},
|
||||
];
|
||||
|
||||
export function FeaturesSection() {
|
||||
return (
|
||||
<section id="features" className="container mx-auto max-w-6xl py-16 md:py-24 px-4">
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
|
||||
Build for development velocity
|
||||
</h2>
|
||||
<p className="max-w-2xl text-lg text-muted-foreground">
|
||||
Combine the visual clarity of Kanban boards with the power of AI-assisted development.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-12 grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{features.map((feature) => {
|
||||
const Icon = feature.icon;
|
||||
return (
|
||||
<Card key={feature.title}>
|
||||
<CardHeader>
|
||||
<Icon className="h-8 w-8 text-primary mb-2" />
|
||||
<CardTitle>{feature.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>{feature.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import Link from 'next/link';
|
||||
import { IconBrandGithub, IconBrandTwitter, IconBrandDiscord } from '@tabler/icons-react';
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-border bg-background">
|
||||
<div className="container mx-auto max-w-6xl py-12 md:py-16 px-4">
|
||||
<div className="grid gap-8 md:grid-cols-3">
|
||||
<div>
|
||||
<h3 className="font-bold mb-4">kandev.ai</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
AI-powered Kanban for developers. Ship faster with isolated worktrees and secure Docker execution.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-4">Product</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li>
|
||||
<Link href="/#features" className="text-muted-foreground hover:text-foreground">
|
||||
Features
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/docs" className="text-muted-foreground hover:text-foreground">
|
||||
Documentation
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-4">Company</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li>
|
||||
<Link href="/about" className="text-muted-foreground hover:text-foreground">
|
||||
About
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/blog" className="text-muted-foreground hover:text-foreground">
|
||||
Blog
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 border-t border-border pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
© 2026 kandev.ai. All rights reserved. Built with respect for your privacy and GDPR compliance.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<a
|
||||
href="https://github.com/kdlbs/kandev"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconBrandGithub className="h-5 w-5" />
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/kandev_ai"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconBrandTwitter className="h-5 w-5" />
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.gg/kandev"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconBrandDiscord className="h-5 w-5" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { IconCloud, IconDeviceMobile, IconUsers } from '@tabler/icons-react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
|
||||
const futureFeatures = [
|
||||
{
|
||||
icon: IconCloud,
|
||||
title: 'Remote Executors',
|
||||
description: 'Run AI agents on cloud or remote machines. Scale your development with powerful remote compute.',
|
||||
status: 'Coming Soon',
|
||||
},
|
||||
{
|
||||
icon: IconDeviceMobile,
|
||||
title: 'Mobile App',
|
||||
description: 'Launch and monitor tasks from your mobile device. Stay connected to your development workflow.',
|
||||
status: 'In Development',
|
||||
},
|
||||
{
|
||||
icon: IconUsers,
|
||||
title: 'Team Collaboration',
|
||||
description: 'Share workspaces, assign tasks, and collaborate with your team in real-time.',
|
||||
status: 'Planned',
|
||||
},
|
||||
];
|
||||
|
||||
export function FutureFeaturesSection() {
|
||||
return (
|
||||
<section className="container mx-auto max-w-6xl py-16 md:py-24 px-4">
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
|
||||
Coming Soon
|
||||
</h2>
|
||||
<p className="max-w-2xl text-lg text-muted-foreground">
|
||||
We're constantly improving kandev.ai. Here's what's on our roadmap.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-12 grid gap-6 md:grid-cols-3">
|
||||
{futureFeatures.map((feature) => {
|
||||
const Icon = feature.icon;
|
||||
return (
|
||||
<Card key={feature.title} className="relative">
|
||||
<CardHeader>
|
||||
<Icon className="h-8 w-8 text-primary mb-2" />
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>{feature.title}</CardTitle>
|
||||
</div>
|
||||
<Badge variant="secondary" className="w-fit">
|
||||
{feature.status}
|
||||
</Badge>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>{feature.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
import { IconCheck, IconCopy } from '@tabler/icons-react';
|
||||
|
||||
export function HeroSection() {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copyCommand = () => {
|
||||
navigator.clipboard.writeText('npx kandev');
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden">
|
||||
{/* Animated gradient cloud */}
|
||||
<div className="absolute inset-0 -z-10 flex items-center justify-center">
|
||||
<div
|
||||
className="absolute w-[800px] h-[800px] rounded-full filter blur-3xl opacity-60 animate-pulse-slow"
|
||||
style={{
|
||||
background: 'radial-gradient(circle, rgba(133, 110, 232, 0.3) 0%, rgba(177, 140, 254, 0.15) 40%, transparent 70%)'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="container mx-auto max-w-6xl flex flex-col items-center gap-8 px-4 pt-32 pb-16 md:pt-40 md:pb-24">
|
||||
<Badge variant="secondary" className="cursor-pointer border border-border px-6 py-3">
|
||||
<span className="text-sm">kandev.ai – AI-powered Kanban for developers</span>
|
||||
</Badge>
|
||||
<h1 className="max-w-4xl text-center text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl lg:text-7xl">
|
||||
Kanban meets <span className="text-primary">AI development</span>
|
||||
</h1>
|
||||
<p className="max-w-2xl text-center text-lg text-muted-foreground md:text-xl">
|
||||
Manage development tasks with isolated git worktrees, secure Docker execution, and AI agents.
|
||||
Ship faster without breaking your workflow.
|
||||
</p>
|
||||
<div className="flex flex-col items-center gap-4 sm:flex-row">
|
||||
<Button size="lg" asChild className="cursor-pointer">
|
||||
<a href="#features">Get Started</a>
|
||||
</Button>
|
||||
<Button size="lg" variant="outline" asChild className="cursor-pointer">
|
||||
<a href="#demo">Watch Demo</a>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-8 w-full max-w-2xl">
|
||||
<div className="flex items-center justify-center gap-2 rounded-lg border border-border bg-card p-4">
|
||||
<code className="flex-1 text-sm font-mono text-foreground">npx kandev</code>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={copyCommand}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{copied ? (
|
||||
<IconCheck className="h-4 w-4 text-green-500" />
|
||||
) : (
|
||||
<IconCopy className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 w-full max-w-5xl">
|
||||
<div className="aspect-video rounded-lg border border-border bg-muted flex items-center justify-center">
|
||||
<p className="text-muted-foreground">Video placeholder</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { IconBrandGithub } from '@tabler/icons-react';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export function Navbar() {
|
||||
const pathname = usePathname();
|
||||
const stars = '2k'; // Dummy star count display
|
||||
|
||||
return (
|
||||
<header className="fixed top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container mx-auto flex h-14 max-w-6xl items-center justify-between px-4">
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<span className="font-bold">kandev.ai</span>
|
||||
</Link>
|
||||
|
||||
<nav className="flex items-center gap-6 text-sm">
|
||||
<Link
|
||||
href="/#product"
|
||||
className={cn(
|
||||
'text-sm font-medium transition-colors hover:text-foreground cursor-pointer',
|
||||
pathname === '/' ? 'text-foreground' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
Product
|
||||
</Link>
|
||||
<Link
|
||||
href="/docs"
|
||||
className={cn(
|
||||
'text-sm font-medium transition-colors hover:text-foreground cursor-pointer',
|
||||
pathname?.startsWith('/docs') ? 'text-foreground' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
Documentation
|
||||
</Link>
|
||||
<Link
|
||||
href="/blog"
|
||||
className={cn(
|
||||
'text-sm font-medium transition-colors hover:text-foreground cursor-pointer',
|
||||
pathname?.startsWith('/blog') ? 'text-foreground' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
Blog
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a
|
||||
href="https://github.com/kdlbs/kandev"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<IconBrandGithub className="h-4 w-4" />
|
||||
<span>Star</span>
|
||||
<span className="ml-1 rounded-full bg-muted px-2 py-0.5 text-xs font-medium">
|
||||
{stars}
|
||||
</span>
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<NextThemesProvider attribute="class" defaultTheme="dark" enableSystem={false}>
|
||||
{children}
|
||||
</NextThemesProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
transpilePackages: ["@kandev/ui", "@kandev/theme", "@kandev/types"],
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@kandev/landing",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3001",
|
||||
"build": "NODE_ENV=production next build",
|
||||
"start": "next start -p 3001",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kandev/theme": "workspace:*",
|
||||
"@kandev/ui": "workspace:*",
|
||||
"@tabler/icons-react": "^3.24.0",
|
||||
"clsx": "^2.1.1",
|
||||
"next": "16.1.1",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"shadcn": "^3.6.3",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.0.0",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.1",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"@kandev/ui": ["../packages/ui/src"],
|
||||
"@kandev/ui/*": ["../packages/ui/src/*"],
|
||||
"@kandev/theme": ["../packages/theme/src"],
|
||||
"@kandev/types": ["../packages/types/src"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "kandev-monorepo",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "pnpm -r run dev",
|
||||
"dev:web": "pnpm --filter @kandev/web dev",
|
||||
"dev:landing": "pnpm --filter @kandev/landing dev",
|
||||
"build": "pnpm -r run build",
|
||||
"build:web": "pnpm --filter @kandev/web build",
|
||||
"build:landing": "pnpm --filter @kandev/landing build",
|
||||
"lint": "pnpm -r run lint",
|
||||
"clean": "pnpm -r run clean && rm -rf node_modules"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@kandev/theme",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
"./globals.css": "./src/globals.css",
|
||||
"./fonts.css": "./src/fonts.css",
|
||||
"./postcss.config.mjs": "./src/postcss.config.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"shadcn": "^3.6.3",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.0.0",
|
||||
"tailwindcss": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
@font-face {
|
||||
font-family: 'Figtree';
|
||||
font-style: normal;
|
||||
font-weight: 300 900;
|
||||
font-display: swap;
|
||||
src: url('/fonts/figtree/figtree-300-normal.woff2') format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Figtree';
|
||||
font-style: normal;
|
||||
font-weight: 300 900;
|
||||
font-display: swap;
|
||||
src: url('/fonts/figtree/figtree-300-normal.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('/fonts/geist/geist-100-normal.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('/fonts/geist/geist-100-normal.woff2') format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('/fonts/geist/geist-100-normal.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist Mono';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('/fonts/geist-mono/geist-mono-100-normal.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist Mono';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('/fonts/geist-mono/geist-mono-100-normal.woff2') format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist Mono';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('/fonts/geist-mono/geist-mono-100-normal.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--radius-sm: var(--radius);
|
||||
--radius-md: var(--radius);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: var(--radius);
|
||||
--radius-2xl: var(--radius);
|
||||
--radius-3xl: var(--radius);
|
||||
--radius-4xl: var(--radius);
|
||||
}
|
||||
|
||||
:root {
|
||||
--font-geist-sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--font-sans: "Figtree", "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
|
||||
sans-serif;
|
||||
--font-geist-mono: "Geist Mono", ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas,
|
||||
"Liberation Mono", "Courier New", monospace;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.51 0.23 277);
|
||||
--primary-foreground: oklch(0.96 0.02 272);
|
||||
--secondary: oklch(0.967 0.001 286.375);
|
||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.68 0.18 276);
|
||||
--accent-foreground: oklch(0.98 0 0);
|
||||
--destructive: oklch(0.58 0.22 27);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.79 0.10 275);
|
||||
--chart-2: oklch(0.68 0.16 277);
|
||||
--chart-3: oklch(0.59 0.20 277);
|
||||
--chart-4: oklch(0.51 0.23 277);
|
||||
--chart-5: oklch(0.46 0.21 277);
|
||||
--radius: 0.375rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.51 0.23 277);
|
||||
--sidebar-primary-foreground: oklch(0.96 0.02 272);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.59 0.20 277);
|
||||
--primary-foreground: oklch(0.96 0.02 272);
|
||||
--secondary: oklch(0.274 0.006 286.033);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.62 0.18 276);
|
||||
--accent-foreground: oklch(0.98 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.79 0.10 275);
|
||||
--chart-2: oklch(0.68 0.16 277);
|
||||
--chart-3: oklch(0.59 0.20 277);
|
||||
--chart-4: oklch(0.51 0.23 277);
|
||||
--chart-5: oklch(0.46 0.21 277);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.68 0.16 277);
|
||||
--sidebar-primary-foreground: oklch(0.96 0.02 272);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.kanban-task-pulse {
|
||||
animation: kanban-task-pulse 5s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes kanban-task-pulse {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(79, 70, 229, 0.12);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.22);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@kandev/types",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*.ts"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
export type BackendMessageType =
|
||||
| 'kanban.update'
|
||||
| 'task.created'
|
||||
| 'task.updated'
|
||||
| 'task.deleted'
|
||||
| 'task.state_changed'
|
||||
| 'agent.updated'
|
||||
| 'terminal.output'
|
||||
| 'diff.update'
|
||||
| 'system.error'
|
||||
| 'workspace.created'
|
||||
| 'workspace.updated'
|
||||
| 'workspace.deleted'
|
||||
| 'board.created'
|
||||
| 'board.updated'
|
||||
| 'board.deleted'
|
||||
| 'column.created'
|
||||
| 'column.updated'
|
||||
| 'column.deleted';
|
||||
|
||||
export type BackendMessage<T extends BackendMessageType, P> = {
|
||||
id?: string;
|
||||
type: 'request' | 'response' | 'notification' | 'error';
|
||||
action: T;
|
||||
payload: P;
|
||||
timestamp?: string;
|
||||
};
|
||||
|
||||
export type KanbanUpdatePayload = {
|
||||
boardId: string;
|
||||
columns: Array<{ id: string; title: string; color?: string; position?: number }>;
|
||||
tasks: Array<{
|
||||
id: string;
|
||||
columnId: string;
|
||||
title: string;
|
||||
position?: number;
|
||||
description?: string;
|
||||
state?: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type TaskEventPayload = {
|
||||
task_id: string;
|
||||
board_id: string;
|
||||
column_id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
state?: string;
|
||||
priority?: number;
|
||||
position?: number;
|
||||
};
|
||||
|
||||
export type AgentUpdatePayload = {
|
||||
agentId: string;
|
||||
status: 'idle' | 'running' | 'error';
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export type TerminalOutputPayload = {
|
||||
terminalId: string;
|
||||
data: string;
|
||||
stream?: 'stdout' | 'stderr';
|
||||
};
|
||||
|
||||
export type DiffUpdatePayload = {
|
||||
taskId: string;
|
||||
files: Array<{
|
||||
path: string;
|
||||
status: 'A' | 'M' | 'D';
|
||||
plus: number;
|
||||
minus: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type SystemErrorPayload = {
|
||||
message: string;
|
||||
code?: string;
|
||||
};
|
||||
|
||||
export type WorkspacePayload = {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
owner_id?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type BoardPayload = {
|
||||
id: string;
|
||||
workspace_id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type ColumnPayload = {
|
||||
id: string;
|
||||
board_id: string;
|
||||
name: string;
|
||||
position: number;
|
||||
state: string;
|
||||
color: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type BackendMessageMap = {
|
||||
'kanban.update': BackendMessage<'kanban.update', KanbanUpdatePayload>;
|
||||
'task.created': BackendMessage<'task.created', TaskEventPayload>;
|
||||
'task.updated': BackendMessage<'task.updated', TaskEventPayload>;
|
||||
'task.deleted': BackendMessage<'task.deleted', TaskEventPayload>;
|
||||
'task.state_changed': BackendMessage<'task.state_changed', TaskEventPayload>;
|
||||
'agent.updated': BackendMessage<'agent.updated', AgentUpdatePayload>;
|
||||
'terminal.output': BackendMessage<'terminal.output', TerminalOutputPayload>;
|
||||
'diff.update': BackendMessage<'diff.update', DiffUpdatePayload>;
|
||||
'system.error': BackendMessage<'system.error', SystemErrorPayload>;
|
||||
'workspace.created': BackendMessage<'workspace.created', WorkspacePayload>;
|
||||
'workspace.updated': BackendMessage<'workspace.updated', WorkspacePayload>;
|
||||
'workspace.deleted': BackendMessage<'workspace.deleted', WorkspacePayload>;
|
||||
'board.created': BackendMessage<'board.created', BoardPayload>;
|
||||
'board.updated': BackendMessage<'board.updated', BoardPayload>;
|
||||
'board.deleted': BackendMessage<'board.deleted', BoardPayload>;
|
||||
'column.created': BackendMessage<'column.created', ColumnPayload>;
|
||||
'column.updated': BackendMessage<'column.updated', ColumnPayload>;
|
||||
'column.deleted': BackendMessage<'column.deleted', ColumnPayload>;
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
export type TaskState =
|
||||
| 'CREATED'
|
||||
| 'SCHEDULING'
|
||||
| 'TODO'
|
||||
| 'IN_PROGRESS'
|
||||
| 'REVIEW'
|
||||
| 'BLOCKED'
|
||||
| 'WAITING_FOR_INPUT'
|
||||
| 'COMPLETED'
|
||||
| 'FAILED'
|
||||
| 'CANCELLED';
|
||||
|
||||
export type Board = {
|
||||
id: string;
|
||||
workspace_id: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type Workspace = {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
owner_id: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type Column = {
|
||||
id: string;
|
||||
board_id: string;
|
||||
name: string;
|
||||
position: number;
|
||||
state: TaskState;
|
||||
color: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type Repository = {
|
||||
id: string;
|
||||
workspace_id: string;
|
||||
name: string;
|
||||
source_type: string;
|
||||
local_path: string;
|
||||
provider: string;
|
||||
provider_repo_id: string;
|
||||
provider_owner: string;
|
||||
provider_name: string;
|
||||
default_branch: string;
|
||||
setup_script: string;
|
||||
cleanup_script: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type RepositoryScript = {
|
||||
id: string;
|
||||
repository_id: string;
|
||||
name: string;
|
||||
command: string;
|
||||
position: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type Task = {
|
||||
id: string;
|
||||
workspace_id: string;
|
||||
board_id: string;
|
||||
column_id: string;
|
||||
position: number;
|
||||
title: string;
|
||||
description: string;
|
||||
state: TaskState;
|
||||
priority: number;
|
||||
agent_type?: string | null;
|
||||
repository_url?: string | null;
|
||||
branch?: string | null;
|
||||
assigned_agent_id?: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
};
|
||||
|
||||
export type BoardSnapshot = {
|
||||
board: Board;
|
||||
columns: Column[];
|
||||
tasks: Task[];
|
||||
};
|
||||
|
||||
export type ListBoardsResponse = {
|
||||
boards: Board[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type ListColumnsResponse = {
|
||||
columns: Column[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type ListRepositoriesResponse = {
|
||||
repositories: Repository[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type ListRepositoryScriptsResponse = {
|
||||
scripts: RepositoryScript[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type LocalRepository = {
|
||||
path: string;
|
||||
name: string;
|
||||
default_branch?: string;
|
||||
};
|
||||
|
||||
export type RepositoryDiscoveryResponse = {
|
||||
roots: string[];
|
||||
repositories: LocalRepository[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type RepositoryPathValidationResponse = {
|
||||
path: string;
|
||||
exists: boolean;
|
||||
is_git: boolean;
|
||||
allowed: boolean;
|
||||
default_branch?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export type RepositoryBranchesResponse = {
|
||||
branches: string[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type ListWorkspacesResponse = {
|
||||
workspaces: Workspace[];
|
||||
total: number;
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './backend';
|
||||
export * from './http';
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@kandev/ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
"./*": "./src/*.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-alert-dialog": "^1.1.2",
|
||||
"@radix-ui/react-collapsible": "^1.1.1",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-select": "^2.1.2",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slider": "^1.2.1",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.1",
|
||||
"@radix-ui/react-tabs": "^1.1.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.4",
|
||||
"@tabler/icons-react": "^3.24.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"react-resizable-panels": "^2.1.9",
|
||||
"tailwind-merge": "^2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
|
||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { cn } from "./lib/utils"
|
||||
import { Button } from "./button"
|
||||
|
||||
function AlertDialog({
|
||||
...props
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { Slot } from "radix-ui"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge",
|
||||
@@ -30,7 +30,7 @@ function Badge({
|
||||
...props
|
||||
}: React.ComponentProps<"span"> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot.Root : "span"
|
||||
const Comp = asChild ? Slot : "span"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "radix-ui"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
import { IconChevronRight, IconDots } from "@tabler/icons-react"
|
||||
|
||||
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
|
||||
@@ -45,7 +45,7 @@ function BreadcrumbLink({
|
||||
}: React.ComponentProps<"a"> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot.Root : "a"
|
||||
const Comp = asChild ? Slot : "a"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { Slot } from "radix-ui"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
|
||||
@@ -44,7 +44,7 @@ function Button({
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot.Root : "button"
|
||||
const Comp = asChild ? Slot : "button"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
function Card({
|
||||
className,
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { Collapsible as CollapsiblePrimitive } from "radix-ui"
|
||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
||||
|
||||
function Collapsible({
|
||||
...props
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Dialog as DialogPrimitive } from "radix-ui"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { cn } from "./lib/utils"
|
||||
import { Button } from "./button"
|
||||
import { IconX } from "@tabler/icons-react"
|
||||
|
||||
function Dialog({
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
import { IconCheck, IconChevronRight } from "@tabler/icons-react"
|
||||
|
||||
function DropdownMenu({
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
}
|
||||
mql.addEventListener("change", onChange)
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
return () => mql.removeEventListener("change", onChange)
|
||||
}, [])
|
||||
|
||||
return !!isMobile
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export * from './alert-dialog';
|
||||
export * from './badge';
|
||||
export * from './breadcrumb';
|
||||
export * from './button';
|
||||
export * from './card';
|
||||
export * from './collapsible';
|
||||
export * from './dialog';
|
||||
export * from './dropdown-menu';
|
||||
export * from './input';
|
||||
export * from './label';
|
||||
export * from './resizable';
|
||||
export * from './select';
|
||||
export * from './separator';
|
||||
export * from './sheet';
|
||||
export * from './sidebar';
|
||||
export * from './skeleton';
|
||||
export * from './slider';
|
||||
export * from './switch';
|
||||
export * from './tabs';
|
||||
export * from './textarea';
|
||||
export * from './tooltip';
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||
return (
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Label as LabelPrimitive } from "radix-ui"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
function Label({
|
||||
className,
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
import * as React from "react"
|
||||
import * as ResizablePrimitive from "react-resizable-panels"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
function ResizablePanelGroup({
|
||||
className,
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Select as SelectPrimitive } from "radix-ui"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
import { IconSelector, IconCheck, IconChevronUp, IconChevronDown } from "@tabler/icons-react"
|
||||
|
||||
function Select({
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Separator as SeparatorPrimitive } from "radix-ui"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
function Separator({
|
||||
className,
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Dialog as SheetPrimitive } from "radix-ui"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { cn } from "./lib/utils"
|
||||
import { Button } from "./button"
|
||||
import { IconX } from "@tabler/icons-react"
|
||||
|
||||
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||
@@ -2,26 +2,26 @@
|
||||
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { Slot } from "radix-ui"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { cn } from "./lib/utils"
|
||||
import { Button } from "./button"
|
||||
import { Input } from "./input"
|
||||
import { Separator } from "./separator"
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from "@/components/ui/sheet"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
} from "./sheet"
|
||||
import { Skeleton } from "./skeleton"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip"
|
||||
import { useIsMobile } from "@/hooks/use-mobile"
|
||||
} from "./tooltip"
|
||||
import { useIsMobile } from "./hooks/use-mobile"
|
||||
import { IconLayoutSidebar } from "@tabler/icons-react"
|
||||
|
||||
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
||||
@@ -398,7 +398,7 @@ function SidebarGroupLabel({
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot.Root : "div"
|
||||
const Comp = asChild ? Slot : "div"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
@@ -418,7 +418,7 @@ function SidebarGroupAction({
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot.Root : "button"
|
||||
const Comp = asChild ? Slot : "button"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
@@ -503,7 +503,7 @@ function SidebarMenuButton({
|
||||
isActive?: boolean
|
||||
tooltip?: string | React.ComponentProps<typeof TooltipContent>
|
||||
} & VariantProps<typeof sidebarMenuButtonVariants>) {
|
||||
const Comp = asChild ? Slot.Root : "button"
|
||||
const Comp = asChild ? Slot : "button"
|
||||
const { isMobile, state } = useSidebar()
|
||||
|
||||
const button = (
|
||||
@@ -549,7 +549,7 @@ function SidebarMenuAction({
|
||||
asChild?: boolean
|
||||
showOnHover?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot.Root : "button"
|
||||
const Comp = asChild ? Slot : "button"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
@@ -657,7 +657,7 @@ function SidebarMenuSubButton({
|
||||
size?: "sm" | "md"
|
||||
isActive?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot.Root : "a"
|
||||
const Comp = asChild ? Slot : "a"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
@@ -1,4 +1,4 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Slider as SliderPrimitive } from "radix-ui"
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
function Slider({
|
||||
className,
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Switch as SwitchPrimitive } from "radix-ui"
|
||||
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
function Switch({
|
||||
className,
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { Tabs as TabsPrimitive } from "radix-ui"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
function Tabs({
|
||||
className,
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||
return (
|
||||
@@ -3,7 +3,7 @@
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "./lib/utils"
|
||||
|
||||
function TooltipProvider({
|
||||
delayDuration = 0,
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
- 'web'
|
||||
- 'landing'
|
||||
- 'packages/*'
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
@import "./fonts.css";
|
||||
@import "@kandev/theme/fonts.css";
|
||||
|
||||
@source "../components/**/*.{js,ts,jsx,tsx}";
|
||||
@source "../../packages/ui/src/**/*.{js,ts,jsx,tsx}";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import { use, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { IconTrash } from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import { Label } from '@kandev/ui/label';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@kandev/ui/card';
|
||||
import { Separator } from '@kandev/ui/separator';
|
||||
import { Switch } from '@kandev/ui/switch';
|
||||
import { Slider } from '@kandev/ui/slider';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -17,9 +17,9 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
} from '@kandev/ui/dialog';
|
||||
import { SETTINGS_DATA } from '@/lib/settings/dummy-data';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kandev/ui/select';
|
||||
import type { AgentProfile, AgentType } from '@/lib/settings/types';
|
||||
|
||||
const MODEL_OPTIONS: Record<AgentType, { value: string; label: string }[]> = {
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { IconPlus } from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import { Label } from '@kandev/ui/label';
|
||||
import { Card, CardContent } from '@kandev/ui/card';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kandev/ui/select';
|
||||
import { Separator } from '@kandev/ui/separator';
|
||||
import { AgentCard } from '@/components/settings/agent-card';
|
||||
import { SETTINGS_DATA } from '@/lib/settings/dummy-data';
|
||||
import type { AgentProfile, AgentType } from '@/lib/settings/types';
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import { use, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { IconPlus, IconTrash } from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import { Label } from '@kandev/ui/label';
|
||||
import { Textarea } from '@kandev/ui/textarea';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@kandev/ui/card';
|
||||
import { Separator } from '@kandev/ui/separator';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kandev/ui/select';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
} from '@kandev/ui/dialog';
|
||||
import { generateUUID } from '@/lib/utils';
|
||||
import { SETTINGS_DATA } from '@/lib/settings/dummy-data';
|
||||
import type { Environment, EnvironmentType, BaseDocker, AgentType } from '@/lib/settings/types';
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { IconPlus } from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import { Label } from '@kandev/ui/label';
|
||||
import { Textarea } from '@kandev/ui/textarea';
|
||||
import { Card, CardContent } from '@kandev/ui/card';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kandev/ui/select';
|
||||
import { Separator } from '@kandev/ui/separator';
|
||||
import { EnvironmentCard } from '@/components/settings/environment-card';
|
||||
import { KeyValueInput } from '@/components/settings/key-value-input';
|
||||
import { SETTINGS_DATA } from '@/lib/settings/dummy-data';
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { useState, useSyncExternalStore } from 'react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { IconPalette, IconCode, IconBell, IconServer } from '@tabler/icons-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
import { Label } from '@kandev/ui/label';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kandev/ui/select';
|
||||
import { Separator } from '@kandev/ui/separator';
|
||||
import { SettingsSection } from '@/components/settings/settings-section';
|
||||
import { SETTINGS_DATA } from '@/lib/settings/dummy-data';
|
||||
import { getBackendConfig } from '@/lib/config';
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
IconBox,
|
||||
IconCircleDot,
|
||||
} from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Card, CardContent } from '@kandev/ui/card';
|
||||
import { Separator } from '@kandev/ui/separator';
|
||||
|
||||
type Integration = {
|
||||
id: string;
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
IconKey,
|
||||
IconShield,
|
||||
} from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
import { Separator } from '@kandev/ui/separator';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
|
||||
type Plugin = {
|
||||
id: string;
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { IconGitBranch, IconLoader2, IconPlus, IconTrash } from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import { Label } from '@kandev/ui/label';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
import { Separator } from '@kandev/ui/separator';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
} from '@kandev/ui/dialog';
|
||||
import { SettingsSection } from '@/components/settings/settings-section';
|
||||
import { RepositoryCard } from '@/components/settings/repository-card';
|
||||
import { BoardCard } from '@/components/settings/board-card';
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { IconFolder, IconPlus, IconChevronRight } from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Card, CardContent } from '@kandev/ui/card';
|
||||
import { Separator } from '@kandev/ui/separator';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import { Label } from '@kandev/ui/label';
|
||||
import { createWorkspaceAction } from '@/app/actions/workspaces';
|
||||
import type { Workspace } from '@/lib/types/http';
|
||||
import { useRequest } from '@/lib/http/use-request';
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import '@git-diff-view/react/styles/diff-view.css';
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@kandev/ui/resizable';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@kandev/ui/tabs';
|
||||
import { TooltipProvider } from '@kandev/ui/tooltip';
|
||||
import { Textarea } from '@kandev/ui/textarea';
|
||||
import { getLocalStorage, setLocalStorage } from '@/lib/local-storage';
|
||||
import { TaskChatPanel } from '@/components/task/task-chat-panel';
|
||||
import { TaskTopBar } from '@/components/task/task-top-bar';
|
||||
@@ -29,11 +29,13 @@ type TaskPageClientProps = {
|
||||
task: Task | null;
|
||||
};
|
||||
|
||||
const DEFAULT_HORIZONTAL_LAYOUT: [number, number] = [75, 25];
|
||||
|
||||
export default function TaskPage({ task: initialTask }: TaskPageClientProps) {
|
||||
const store = useAppStoreApi();
|
||||
const defaultHorizontalLayout: [number, number] = [75, 25];
|
||||
const [horizontalLayout, setHorizontalLayout] = useState<[number, number]>(() =>
|
||||
getLocalStorage('task-layout-horizontal', defaultHorizontalLayout)
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const [horizontalLayout, setHorizontalLayout] = useState<[number, number]>(
|
||||
getLocalStorage('task-layout-horizontal', DEFAULT_HORIZONTAL_LAYOUT)
|
||||
);
|
||||
const [leftTab, setLeftTab] = useState('chat');
|
||||
const [selectedDiffPath, setSelectedDiffPath] = useState<string | null>(null);
|
||||
@@ -52,6 +54,10 @@ export default function TaskPage({ task: initialTask }: TaskPageClientProps) {
|
||||
state.kanban.tasks.find((t) => t.id === task?.id)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
// Fetch task execution status from orchestrator on mount
|
||||
useEffect(() => {
|
||||
if (!task?.id) return;
|
||||
@@ -212,6 +218,10 @@ export default function TaskPage({ task: initialTask }: TaskPageClientProps) {
|
||||
branchesRequest.run(task.workspace_id, task.repository_url).catch(() => {});
|
||||
}, [branchesRequest.run, task?.repository_url, task?.workspace_id]);
|
||||
|
||||
if (!isMounted) {
|
||||
return <div className="h-screen w-full bg-background" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="h-screen w-full flex flex-col bg-background">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@kandev/ui/tooltip';
|
||||
import { IconCircleFilled } from '@tabler/icons-react';
|
||||
import { useAppStore } from '@/components/state-provider';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
|
||||
type CommitStatBadgeProps = {
|
||||
label: string;
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { KanbanColumn, Column } from './kanban-column';
|
||||
import { KanbanCardPreview, Task } from './kanban-card';
|
||||
import { ConnectionStatus } from './connection-status';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { IconPlus, IconSettings } from '@tabler/icons-react';
|
||||
import { TaskCreateDialog } from './task-create-dialog';
|
||||
import Link from 'next/link';
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
} from '@kandev/ui/select';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useAppStore, useAppStoreApi } from '@/components/state-provider';
|
||||
import { getWebSocketClient } from '@/lib/ws/connection';
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import { useDraggable } from '@dnd-kit/core';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { IconAlertTriangle, IconCircleCheck, IconCircleX, IconDots, IconLoader2 } from '@tabler/icons-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Card, CardContent } from '@kandev/ui/card';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
} from '@kandev/ui/dropdown-menu';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface Task {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
import { KanbanCard, Task } from './kanban-card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface Column {
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogDescription,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
} from '@kandev/ui/dialog';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { IconCheck, IconRocket } from '@tabler/icons-react';
|
||||
interface OnboardingDialogProps {
|
||||
open: boolean;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
import { IconRobot, IconChevronRight } from '@tabler/icons-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent } from '@kandev/ui/card';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
import type { AgentProfile, AgentType } from '@/lib/settings/types';
|
||||
|
||||
type AgentCardProps = {
|
||||
|
||||
@@ -17,10 +17,10 @@ import {
|
||||
IconTrash,
|
||||
IconPlus,
|
||||
} from '@tabler/icons-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent } from '@kandev/ui/card';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import { Label } from '@kandev/ui/label';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
} from '@kandev/ui/dialog';
|
||||
import type { Board, Column } from '@/lib/types/http';
|
||||
import { useRequest } from '@/lib/http/use-request';
|
||||
import { useToast } from '@/components/toast-provider';
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
import { IconServer, IconChevronRight } from '@tabler/icons-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent } from '@kandev/ui/card';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
import type { Environment } from '@/lib/settings/types';
|
||||
|
||||
type EnvironmentCardProps = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { IconPlus, IconX } from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import type { KeyValue } from '@/lib/settings/types';
|
||||
import { generateUUID } from '@/lib/utils';
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { IconGitBranch, IconPlus, IconTrash, IconX } from '@tabler/icons-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Card, CardContent } from '@kandev/ui/card';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import { Label } from '@kandev/ui/label';
|
||||
import { Textarea } from '@kandev/ui/textarea';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
} from '@kandev/ui/dialog';
|
||||
import { useRequest } from '@/lib/http/use-request';
|
||||
import { useToast } from '@/components/toast-provider';
|
||||
import { UnsavedChangesBadge, UnsavedSaveButton } from '@/components/settings/unsaved-indicator';
|
||||
|
||||
@@ -26,8 +26,8 @@ import {
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuAction,
|
||||
SidebarHeader,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
} from '@kandev/ui/sidebar';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@kandev/ui/collapsible';
|
||||
import { SETTINGS_DATA } from '@/lib/settings/dummy-data';
|
||||
import { useAppStore } from '@/components/state-provider';
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ import {
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
|
||||
} from '@kandev/ui/breadcrumb';
|
||||
import { SidebarInset, SidebarProvider } from '@kandev/ui/sidebar';
|
||||
import { SettingsAppSidebar } from '@/components/settings/settings-app-sidebar';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { TooltipProvider } from '@kandev/ui/tooltip';
|
||||
|
||||
export function SettingsLayoutClient({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Sheet, SheetContent } from '@/components/ui/sheet';
|
||||
import { Sheet, SheetContent } from '@kandev/ui/sheet';
|
||||
import { SettingsSidebar } from './settings-sidebar';
|
||||
|
||||
type SettingsMobileNavProps = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { IconAlertCircle } from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { RequestIndicator } from '@/components/request-indicator';
|
||||
|
||||
type UnsavedSaveButtonProps = {
|
||||
|
||||
@@ -8,19 +8,19 @@ import {
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Button } from '@/components/ui/button';
|
||||
} from '@kandev/ui/dialog';
|
||||
import { Label } from '@kandev/ui/label';
|
||||
import { Input } from '@kandev/ui/input';
|
||||
import { Textarea } from '@kandev/ui/textarea';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
} from '@kandev/ui/select';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@kandev/ui/tooltip';
|
||||
import type { Repository, Task } from '@/lib/types/http';
|
||||
import { getBackendConfig } from '@/lib/config';
|
||||
import { createTask, listRepositories, listRepositoryBranches, updateTask } from '@/lib/http/client';
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { memo, useMemo, useState } from 'react';
|
||||
import { memo, useEffect, useMemo, useState } from 'react';
|
||||
import { DiffModeEnum, DiffView } from '@git-diff-view/react';
|
||||
import { IconArrowBackUp, IconCopy, IconLayoutColumns, IconLayoutRows } from '@tabler/icons-react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@kandev/ui/tooltip';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { getLocalStorage, setLocalStorage } from '@/lib/local-storage';
|
||||
import { buildDiffData, CHANGED_FILES, DIFF_SAMPLES } from '@/components/task/task-data';
|
||||
@@ -16,16 +16,19 @@ type TaskChangesPanelProps = {
|
||||
onClearSelected: () => void;
|
||||
};
|
||||
|
||||
const DEFAULT_DIFF_MODE: 'unified' | 'split' = 'unified';
|
||||
|
||||
const TaskChangesPanel = memo(function TaskChangesPanel({
|
||||
selectedDiffPath,
|
||||
onClearSelected,
|
||||
}: TaskChangesPanelProps) {
|
||||
const defaultDiffMode: 'unified' | 'split' = 'unified';
|
||||
const [diffViewMode, setDiffViewMode] = useState<'unified' | 'split'>(() =>
|
||||
getLocalStorage('task-diff-view-mode', defaultDiffMode)
|
||||
);
|
||||
const [diffViewMode, setDiffViewMode] = useState<'unified' | 'split'>(DEFAULT_DIFF_MODE);
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
setDiffViewMode(getLocalStorage('task-diff-view-mode', DEFAULT_DIFF_MODE));
|
||||
}, []);
|
||||
|
||||
const diffTargets = useMemo(
|
||||
() => (selectedDiffPath ? [selectedDiffPath] : CHANGED_FILES.map((file) => file.path)),
|
||||
[selectedDiffPath]
|
||||
|
||||
@@ -20,16 +20,16 @@ import {
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Textarea } from '@kandev/ui/textarea';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
} from '@kandev/ui/dropdown-menu';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kandev/ui/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@kandev/ui/tooltip';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useAppStore } from '@/components/state-provider';
|
||||
import type { Comment } from '@/lib/types/http';
|
||||
|
||||
@@ -9,12 +9,12 @@ import {
|
||||
IconFile,
|
||||
IconFolder,
|
||||
} from '@tabler/icons-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible';
|
||||
} from '@kandev/ui/collapsible';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@@ -26,9 +26,9 @@ import {
|
||||
SidebarMenuSub,
|
||||
SidebarProvider,
|
||||
SidebarRail,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
} from '@kandev/ui/sidebar';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@kandev/ui/tabs';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@kandev/ui/tooltip';
|
||||
import { LineStat } from '@/components/diff-stat';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { CHANGED_FILES, FILE_TREE } from '@/components/task/task-data';
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
import { IconChevronDown, IconChevronUp, IconX } from '@tabler/icons-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@kandev/ui/resizable';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@kandev/ui/tabs';
|
||||
import { getLocalStorage, setLocalStorage } from '@/lib/local-storage';
|
||||
import { COMMANDS } from '@/components/task/task-data';
|
||||
|
||||
@@ -13,10 +13,11 @@ type TaskRightPanelProps = {
|
||||
topPanel: ReactNode;
|
||||
};
|
||||
|
||||
const DEFAULT_RIGHT_LAYOUT: [number, number] = [55, 45];
|
||||
|
||||
const TaskRightPanel = memo(function TaskRightPanel({ topPanel }: TaskRightPanelProps) {
|
||||
const defaultRightLayout: [number, number] = [55, 45];
|
||||
const [rightLayout, setRightLayout] = useState<[number, number]>(() =>
|
||||
getLocalStorage('task-layout-right', defaultRightLayout)
|
||||
getLocalStorage('task-layout-right', DEFAULT_RIGHT_LAYOUT)
|
||||
);
|
||||
const [activeTerminalId, setActiveTerminalId] = useState(1);
|
||||
const [terminalIds, setTerminalIds] = useState([1]);
|
||||
|
||||
@@ -20,11 +20,11 @@ import {
|
||||
IconPlayerPlay,
|
||||
IconPlayerStop,
|
||||
} from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { CommitStatBadge, LineStat } from '@/components/diff-stat';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@kandev/ui/dropdown-menu';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@kandev/ui/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@kandev/ui/tooltip';
|
||||
|
||||
type TaskTopBarProps = {
|
||||
taskTitle?: string;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useTheme } from 'next-themes';
|
||||
import { IconMoon, IconSun } from '@tabler/icons-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { useSyncExternalStore } from 'react';
|
||||
|
||||
export function ThemeToggle() {
|
||||
|
||||
@@ -2,7 +2,7 @@ export type AppConfig = {
|
||||
apiBaseUrl: string;
|
||||
};
|
||||
|
||||
const DEFAULT_API_BASE_URL = 'http://nova:8080';
|
||||
const DEFAULT_API_BASE_URL = 'http://localhost:8080';
|
||||
|
||||
export function getBackendConfig(): AppConfig {
|
||||
if (typeof window === 'undefined') {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user