Skip to content

Improve command palette navigation #3174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 25, 2025
165 changes: 132 additions & 33 deletions apps/web/src/components/command/add-task-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
const queryClient = useQueryClient();

// Get boards with lists
const { data: boardsData, isLoading: boardsLoading } = useQuery<{
const { data: boardsData, isLoading: boardsLoading, error: boardsError } = useQuery<{

Check warning on line 58 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L58

Added line #L58 was not covered by tests
boards: BoardWithLists[];
}>({
queryKey: ['boards-with-lists', wsId],
Expand All @@ -66,17 +66,19 @@
if (!response.ok) throw new Error('Failed to fetch boards');
return response.json();
},
retry: 2,
retryDelay: 1000,

Check warning on line 70 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L69-L70

Added lines #L69 - L70 were not covered by tests
});

const boards = boardsData?.boards;

// Get tasks for selected board/list
const { data: tasksData, isLoading: tasksLoading } = useQuery<{
const { data: tasksData, isLoading: tasksLoading, error: tasksError } = useQuery<{

Check warning on line 76 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L76

Added line #L76 was not covered by tests
tasks: Task[];
}>({
queryKey: ['tasks', wsId, selectedBoardId, selectedListId],
queryFn: async () => {
if (!selectedBoardId && !selectedListId) return [];
if (!selectedBoardId && !selectedListId) return { tasks: [] };

Check warning on line 81 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L81

Added line #L81 was not covered by tests

const params = new URLSearchParams();
if (selectedListId) params.append('listId', selectedListId);
Expand All @@ -89,6 +91,8 @@
return response.json();
},
enabled: !!(selectedBoardId || selectedListId),
retry: 2,
retryDelay: 1000,

Check warning on line 95 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L94-L95

Added lines #L94 - L95 were not covered by tests
});

const tasks = tasksData?.tasks;
Expand Down Expand Up @@ -189,6 +193,38 @@
);
}

if (boardsError) {
return (
<div className="flex flex-col items-center gap-4 p-8 text-center">
<div className="rounded-full bg-dynamic-red/10 p-3">
<AlertTriangle className="h-6 w-6 text-dynamic-red" />
</div>
<div>
<p className="font-semibold text-foreground">Failed to load boards</p>
<p className="text-sm text-muted-foreground">
{boardsError.message || 'Unable to fetch boards at the moment'}
</p>
</div>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => window.location.reload()}
>

Check warning on line 213 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L196-L213

Added lines #L196 - L213 were not covered by tests
Retry
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => setOpen(false)}
>

Check warning on line 220 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L215-L220

Added lines #L215 - L220 were not covered by tests
Close
</Button>
</div>
</div>

Check warning on line 224 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L222-L224

Added lines #L222 - L224 were not covered by tests
);
}

Check warning on line 226 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L226

Added line #L226 was not covered by tests

if (!boards || boards.length === 0) {
return (
<div className="flex flex-col items-center gap-4 p-8 text-center">
Expand All @@ -201,6 +237,17 @@
Create a board first to add tasks
</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => {
const wsId = window.location.pathname.split('/')[1];
window.location.href = `/${wsId}/tasks/boards`;
}}

Check warning on line 246 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L240-L246

Added lines #L240 - L246 were not covered by tests
>
<Plus className="h-4 w-4 mr-2" />

Check warning on line 248 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L248

Added line #L248 was not covered by tests
Create Board
</Button>

Check warning on line 250 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L250

Added line #L250 was not covered by tests
</div>
);
}
Expand Down Expand Up @@ -246,27 +293,39 @@
{selectedBoardId && (
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">List</label>
<Select
value={selectedListId}
onValueChange={(value) => {
setSelectedListId(value);
setShowTasks(true);
}}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a list..." />
</SelectTrigger>
<SelectContent>
{availableLists.map((list: any) => (
<SelectItem key={list.id} value={list.id}>
<div className="flex items-center gap-2">
<List className="h-4 w-4 text-muted-foreground" />
<span className="truncate">{list.name}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
{availableLists.length === 0 ? (
<div className="rounded-md border border-dynamic-orange/20 bg-dynamic-orange/5 p-3 text-center">
<div className="flex items-center justify-center gap-2 text-sm text-dynamic-orange">
<AlertTriangle className="h-4 w-4" />
<span>This board has no lists</span>
</div>
<p className="text-xs text-muted-foreground mt-1">

Check warning on line 302 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L296-L302

Added lines #L296 - L302 were not covered by tests
Create a list in the board first to add tasks
</p>
</div>

Check warning on line 305 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L304-L305

Added lines #L304 - L305 were not covered by tests
) : (
<Select
value={selectedListId}
onValueChange={(value) => {
setSelectedListId(value);
setShowTasks(true);
}}

Check warning on line 312 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L307-L312

Added lines #L307 - L312 were not covered by tests
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a list..." />
</SelectTrigger>
<SelectContent>
{availableLists.map((list: any) => (
<SelectItem key={list.id} value={list.id}>
<div className="flex items-center gap-2">
<List className="h-4 w-4 text-muted-foreground" />
<span className="truncate">{list.name}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>

Check warning on line 327 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L314-L327

Added lines #L314 - L327 were not covered by tests
)}
</div>
)}

Expand All @@ -280,12 +339,28 @@
{tasksLoading && (
<Loader className="h-3 w-3 animate-spin text-dynamic-blue" />
)}
{tasksError && (
<AlertTriangle className="h-3 w-3 text-dynamic-red" />

Check warning on line 343 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L342-L343

Added lines #L342 - L343 were not covered by tests
)}
</CardTitle>
</CardHeader>
<CardContent className="pt-0">
{tasksLoading ? (
<div className="flex items-center justify-center py-4">
<Loader className="h-4 w-4 animate-spin text-dynamic-blue" />
<div className="flex items-center gap-2">
<Loader className="h-4 w-4 animate-spin text-dynamic-blue" />
<span className="text-xs text-muted-foreground">Loading tasks...</span>
</div>
</div>
) : tasksError ? (
<div className="flex flex-col items-center gap-2 py-4 text-center">
<AlertTriangle className="h-4 w-4 text-dynamic-red" />
<div className="text-xs text-dynamic-red">

Check warning on line 358 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L350-L358

Added lines #L350 - L358 were not covered by tests
Failed to load tasks
</div>
<div className="text-xs text-muted-foreground">
{tasksError.message || 'Unable to fetch tasks'}
</div>

Check warning on line 363 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L360-L363

Added lines #L360 - L363 were not covered by tests
</div>
) : tasks && tasks.length > 0 ? (
<ScrollArea className="max-h-32">
Expand Down Expand Up @@ -330,6 +405,9 @@
<div className="text-xs text-muted-foreground">
No tasks in this list yet
</div>
<div className="text-xs text-muted-foreground/70 mt-1">

Check warning on line 408 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L408

Added line #L408 was not covered by tests
Perfect time to add the first one!
</div>

Check warning on line 410 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L410

Added line #L410 was not covered by tests
</div>
)}
</CardContent>
Expand All @@ -345,7 +423,8 @@
disabled={
!inputValue.trim() ||
!selectedListId ||
createTaskMutation.isPending
createTaskMutation.isPending ||
(selectedBoardId && availableLists.length === 0)

Check warning on line 427 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L426-L427

Added lines #L426 - L427 were not covered by tests
}
className="w-full"
>
Expand All @@ -363,13 +442,33 @@
</Button>
</div>

{/* Task name validation message */}
{!inputValue.trim() && (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<AlertTriangle className="h-3 w-3" />
<span>Enter a task name to continue</span>
</div>
)}
{/* Validation messages */}
<div className="space-y-1">
{!inputValue.trim() && (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<AlertTriangle className="h-3 w-3" />
<span>Enter a task name to continue</span>
</div>

Check warning on line 451 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L446-L451

Added lines #L446 - L451 were not covered by tests
)}
{inputValue.trim() && !selectedBoardId && (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<AlertTriangle className="h-3 w-3" />
<span>Please select a board</span>
</div>

Check warning on line 457 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L453-L457

Added lines #L453 - L457 were not covered by tests
)}
{inputValue.trim() && selectedBoardId && !selectedListId && availableLists.length > 0 && (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<AlertTriangle className="h-3 w-3" />
<span>Please select a list</span>
</div>

Check warning on line 463 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L459-L463

Added lines #L459 - L463 were not covered by tests
)}
{inputValue.trim() && selectedBoardId && availableLists.length === 0 && (
<div className="flex items-center gap-2 text-xs text-dynamic-orange">
<AlertTriangle className="h-3 w-3" />
<span>The selected board has no lists. Create a list first.</span>
</div>

Check warning on line 469 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L465-L469

Added lines #L465 - L469 were not covered by tests
)}
</div>

Check warning on line 471 in apps/web/src/components/command/add-task-form.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/add-task-form.tsx#L471

Added line #L471 was not covered by tests
</div>
);
}
114 changes: 112 additions & 2 deletions apps/web/src/components/command/board-navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,32 @@

import type { Board } from './types';
import { CommandGroup, CommandItem } from '@tuturuuu/ui/command';
import { ExternalLink, LayoutDashboard, MapPin, Tag } from '@tuturuuu/ui/icons';
import {

Check warning on line 5 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L5

Added line #L5 was not covered by tests
ExternalLink,
LayoutDashboard,
MapPin,
Tag,
Loader,
AlertTriangle,
RefreshCw,
Plus
} from '@tuturuuu/ui/icons';
import { Button } from '@tuturuuu/ui/button';

Check warning on line 15 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L15

Added line #L15 was not covered by tests
import * as React from 'react';

interface BoardNavigationProps {
boards: Board[];
inputValue: string;
isLoading?: boolean;
error?: Error | null;
onBoardSelect: (boardId: string) => void;
}

export function BoardNavigation({
boards,
inputValue,
isLoading,
error,

Check warning on line 30 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L29-L30

Added lines #L29 - L30 were not covered by tests
onBoardSelect,
}: BoardNavigationProps) {
const getBoardColor = (boardId: string) => {
Expand Down Expand Up @@ -41,7 +55,103 @@
.slice(0, 8); // Show up to 8 filtered results
}, [boards, inputValue]);

if (filteredBoards.length === 0) return null;
// Loading state
if (isLoading) {
return (
<CommandGroup heading="📋 Loading Boards...">
<div className="flex items-center justify-center p-6">
<div className="flex items-center gap-3">
<Loader className="h-5 w-5 animate-spin text-dynamic-blue" />
<span className="text-sm text-muted-foreground">

Check warning on line 65 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L59-L65

Added lines #L59 - L65 were not covered by tests
Fetching your boards...
</span>
</div>
</div>
</CommandGroup>

Check warning on line 70 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L67-L70

Added lines #L67 - L70 were not covered by tests
);
}

Check warning on line 72 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L72

Added line #L72 was not covered by tests

// Error state
if (error) {
return (
<CommandGroup heading="📋 Board Navigation">
<div className="flex flex-col items-center gap-3 p-6 text-center">
<div className="rounded-full bg-dynamic-red/10 p-3">
<AlertTriangle className="h-5 w-5 text-dynamic-red" />
</div>
<div className="space-y-1">
<p className="font-semibold text-foreground">Failed to load boards</p>
<p className="text-xs text-muted-foreground">
{error.message || 'Unable to fetch boards at the moment'}
</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => window.location.reload()}
className="gap-2"

Check warning on line 92 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L75-L92

Added lines #L75 - L92 were not covered by tests
>
<RefreshCw className="h-3 w-3" />

Check warning on line 94 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L94

Added line #L94 was not covered by tests
Retry
</Button>
</div>
</CommandGroup>

Check warning on line 98 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L96-L98

Added lines #L96 - L98 were not covered by tests
);
}

Check warning on line 100 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L100

Added line #L100 was not covered by tests

// No boards state
if (!boards || boards.length === 0) {
return (
<CommandGroup heading="📋 Board Navigation">
<div className="flex flex-col items-center gap-3 p-6 text-center">
<div className="rounded-full bg-dynamic-blue/10 p-3">
<LayoutDashboard className="h-5 w-5 text-dynamic-blue" />
</div>
<div className="space-y-1">
<p className="font-semibold text-foreground">No boards found</p>
<p className="text-xs text-muted-foreground">

Check warning on line 112 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L103-L112

Added lines #L103 - L112 were not covered by tests
Create your first board to get started with task management
</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => {

Check warning on line 119 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L114-L119

Added lines #L114 - L119 were not covered by tests
// Navigate to boards page to create new board
const wsId = window.location.pathname.split('/')[1];
window.location.href = `/${wsId}/tasks/boards`;
}}
className="gap-2"

Check warning on line 124 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L121-L124

Added lines #L121 - L124 were not covered by tests
>
<Plus className="h-3 w-3" />

Check warning on line 126 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L126

Added line #L126 was not covered by tests
Create Board
</Button>
</div>
</CommandGroup>

Check warning on line 130 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L128-L130

Added lines #L128 - L130 were not covered by tests
);
}

Check warning on line 132 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L132

Added line #L132 was not covered by tests

// No filtered results state
if (filteredBoards.length === 0) {
return (
<CommandGroup heading="📋 Board Navigation">
<div className="flex flex-col items-center gap-3 p-6 text-center">
<div className="rounded-full bg-dynamic-orange/10 p-3">
<LayoutDashboard className="h-5 w-5 text-dynamic-orange" />
</div>
<div className="space-y-1">
<p className="font-semibold text-foreground">No matching boards</p>
<p className="text-xs text-muted-foreground">

Check warning on line 144 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L135-L144

Added lines #L135 - L144 were not covered by tests
Try a different search term or browse all boards
</p>
</div>
<div className="text-xs text-muted-foreground">
Available boards: {boards.length}
</div>
</div>
</CommandGroup>

Check warning on line 152 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L146-L152

Added lines #L146 - L152 were not covered by tests
);
}

Check warning on line 154 in apps/web/src/components/command/board-navigation.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/components/command/board-navigation.tsx#L154

Added line #L154 was not covered by tests

return (
<CommandGroup heading="📋 Quick Board Navigation">
Expand Down
Loading
Loading