Skip to content

Commit 97e2c07

Browse files
authored
fix(dashboard): use Link for navigation buttons to support Ctrl+Click (#494)
* fix(dashboard): use Link for navigation buttons to support Ctrl+Click Navigation action buttons in list pages used router.push via onClick, preventing native browser behaviors like Ctrl+Click / Cmd+Click to open in a new tab. Replaced with <Button asChild><Link href={...}> pattern across both Dashboard and Dashboard OSS. * fix(dashboard): avoid nested <a> tags in SkillList component When the card wrapper is a Link, the inner "View Files" button doesn't need to be a Link too — clicking the card already navigates. * fix(dashboard): convert messages page Disk and Task buttons to Link
1 parent 76c84d8 commit 97e2c07

12 files changed

Lines changed: 182 additions & 244 deletions

File tree

dashboard/app/project/[id]/agent-skills/agent-skills-page-client.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,13 @@ export function AgentSkillsPageClient({
197197
currentPage * PAGE_SIZE
198198
);
199199

200-
const handleSkillClick = (skill: SkillItem) => {
200+
const getSkillHref = (skill: SkillItem) => {
201201
const encodedSkillId = encodeId(skill.id);
202-
router.push(`/project/${encodedProjectId}/agent-skills/${encodedSkillId}`);
202+
return `/project/${encodedProjectId}/agent-skills/${encodedSkillId}`;
203+
};
204+
205+
const handleSkillClick = (skill: SkillItem) => {
206+
router.push(getSkillHref(skill));
203207
};
204208

205209
const handleSkillDelete = (skill: SkillItem) => {
@@ -445,6 +449,7 @@ export function AgentSkillsPageClient({
445449
<SkillList
446450
skills={paginatedSkills}
447451
onSkillClick={handleSkillClick}
452+
getSkillHref={getSkillHref}
448453
onSkillDelete={handleSkillDelete}
449454
className="overflow-auto flex-1"
450455
/>

dashboard/app/project/[id]/learning-spaces/[spaceId]/learning-space-detail-client.tsx

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useState, useEffect, useCallback } from "react";
44
import { useRouter } from "next/navigation";
5+
import Link from "next/link";
56
import { encodeId } from "@/lib/id-codec";
67
import { useTopNavStore } from "@/stores/top-nav";
78
import { Button } from "@/components/ui/button";
@@ -286,11 +287,13 @@ export function LearningSpaceDetailClient({
286287

287288
const returnTo = `/project/${encodedProjectId}/learning-spaces/${encodeId(spaceId)}`;
288289

289-
const navigateToAgentSkills = (skill: SkillItem) => {
290+
const getAgentSkillHref = (skill: SkillItem) => {
290291
const encodedSkillId = encodeId(skill.id);
291-
router.push(
292-
`/project/${encodedProjectId}/agent-skills/${encodedSkillId}?returnTo=${encodeURIComponent(returnTo)}`
293-
);
292+
return `/project/${encodedProjectId}/agent-skills/${encodedSkillId}?returnTo=${encodeURIComponent(returnTo)}`;
293+
};
294+
295+
const navigateToAgentSkills = (skill: SkillItem) => {
296+
router.push(getAgentSkillHref(skill));
294297
};
295298

296299
if (isLoading) {
@@ -445,6 +448,7 @@ export function LearningSpaceDetailClient({
445448
<SkillList
446449
skills={skills}
447450
onSkillClick={navigateToAgentSkills}
451+
getSkillHref={getAgentSkillHref}
448452
onSkillDelete={(skill) => setExcludeTarget(skill as AgentSkill)}
449453
emptyMessage="No skills associated. Add a skill to get started."
450454
deleteLabel="Remove"
@@ -490,20 +494,11 @@ export function LearningSpaceDetailClient({
490494
{new Date(session.created_at).toLocaleString()}
491495
</TableCell>
492496
<TableCell>
493-
<Button
494-
variant="secondary"
495-
size="sm"
496-
onClick={() => {
497-
const encodedSessionId = encodeId(
498-
session.session_id
499-
);
500-
router.push(
501-
`/project/${encodedProjectId}/session/${encodedSessionId}/messages?returnTo=${encodeURIComponent(returnTo)}`
502-
);
503-
}}
504-
>
505-
<ExternalLink className="h-3 w-3 mr-1" />
506-
View Session
497+
<Button variant="secondary" size="sm" asChild>
498+
<Link href={`/project/${encodedProjectId}/session/${encodeId(session.session_id)}/messages?returnTo=${encodeURIComponent(returnTo)}`}>
499+
<ExternalLink className="h-3 w-3 mr-1" />
500+
View Session
501+
</Link>
507502
</Button>
508503
</TableCell>
509504
</TableRow>

dashboard/app/project/[id]/learning-spaces/learning-spaces-page-client.tsx

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"use client";
22

33
import { useState, useEffect, useCallback, useMemo } from "react";
4-
import { useRouter, useSearchParams } from "next/navigation";
4+
import { useSearchParams } from "next/navigation";
5+
import Link from "next/link";
56
import { encodeId } from "@/lib/id-codec";
67
import { Button } from "@/components/ui/button";
78
import { Input } from "@/components/ui/input";
@@ -85,7 +86,6 @@ export function LearningSpacesPageClient({
8586
allOrganizations,
8687
projects,
8788
}: LearningSpacesPageClientProps) {
88-
const router = useRouter();
8989
const searchParams = useSearchParams();
9090
const { initialize, setHasSidebar } = useTopNavStore();
9191

@@ -463,13 +463,6 @@ export function LearningSpacesPageClient({
463463
{paginatedSpaces.map((space) => (
464464
<TableRow
465465
key={space.id}
466-
className="cursor-pointer"
467-
onClick={() => {
468-
const encodedSpaceId = encodeId(space.id);
469-
router.push(
470-
`/project/${encodedProjectId}/learning-spaces/${encodedSpaceId}`
471-
);
472-
}}
473466
>
474467
<TableCell className="font-mono text-sm">
475468
{space.id}
@@ -494,18 +487,10 @@ export function LearningSpacesPageClient({
494487
</TableCell>
495488
<TableCell>
496489
<div className="flex gap-2">
497-
<Button
498-
variant="secondary"
499-
size="sm"
500-
onClick={(e) => {
501-
e.stopPropagation();
502-
const encodedSpaceId = encodeId(space.id);
503-
router.push(
504-
`/project/${encodedProjectId}/learning-spaces/${encodedSpaceId}`
505-
);
506-
}}
507-
>
508-
Details
490+
<Button variant="secondary" size="sm" asChild>
491+
<Link href={`/project/${encodedProjectId}/learning-spaces/${encodeId(space.id)}`}>
492+
Details
493+
</Link>
509494
</Button>
510495
<Button
511496
variant="secondary"

dashboard/app/project/[id]/session/[sessionId]/messages/messages-page-client.tsx

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useState, useEffect, useMemo } from "react";
44
import { useRouter } from "next/navigation";
5+
import Link from "next/link";
56
import { encodeId } from "@/lib/id-codec";
67
import { useTopNavStore } from "@/stores/top-nav";
78
import Image from "next/image";
@@ -808,16 +809,11 @@ export function MessagesPageClient({
808809
</TableCell>
809810
<TableCell>
810811
{event.type === 'disk_event' && typeof event.data.disk_id === 'string' && (
811-
<Button
812-
variant="outline"
813-
size="sm"
814-
onClick={() => {
815-
const encodedProjectId = encodeId(project.id);
816-
router.push(`/project/${encodedProjectId}/disk?diskId=${event.data.disk_id}`);
817-
}}
818-
>
819-
<HardDrive className="h-3.5 w-3.5" />
820-
Disk
812+
<Button variant="outline" size="sm" asChild>
813+
<Link href={`/project/${encodeId(project.id)}/disk?diskId=${event.data.disk_id}`}>
814+
<HardDrive className="h-3.5 w-3.5" />
815+
Disk
816+
</Link>
821817
</Button>
822818
)}
823819
</TableCell>
@@ -855,29 +851,29 @@ export function MessagesPageClient({
855851
>
856852
View
857853
</Button>
858-
<Button
859-
variant="outline"
860-
size="sm"
861-
disabled={!message.task_id}
862-
onClick={() => {
863-
if (message.task_id) {
864-
const encodedProjectId = encodeId(project.id);
865-
const encodedSessionId = encodeId(sessionId);
866-
const messagesReturnTo = `/project/${encodedProjectId}/session/${encodedSessionId}/messages`;
867-
router.push(
868-
`/project/${encodedProjectId}/session/${encodedSessionId}/task?taskId=${message.task_id}&returnTo=${encodeURIComponent(messagesReturnTo)}`
869-
);
870-
}
871-
}}
872-
title={
873-
message.task_id
874-
? `View Task ${message.task_id.substring(0, 8)}...`
875-
: "No task associated"
876-
}
877-
>
878-
<ExternalLink className="h-3.5 w-3.5" />
879-
Task
880-
</Button>
854+
{message.task_id ? (
855+
<Button
856+
variant="outline"
857+
size="sm"
858+
asChild
859+
title={`View Task ${message.task_id.substring(0, 8)}...`}
860+
>
861+
<Link href={`/project/${encodeId(project.id)}/session/${encodeId(sessionId)}/task?taskId=${message.task_id}&returnTo=${encodeURIComponent(`/project/${encodeId(project.id)}/session/${encodeId(sessionId)}/messages`)}`}>
862+
<ExternalLink className="h-3.5 w-3.5" />
863+
Task
864+
</Link>
865+
</Button>
866+
) : (
867+
<Button
868+
variant="outline"
869+
size="sm"
870+
disabled
871+
title="No task associated"
872+
>
873+
<ExternalLink className="h-3.5 w-3.5" />
874+
Task
875+
</Button>
876+
)}
881877
</div>
882878
</TableCell>
883879
</TableRow>

dashboard/app/project/[id]/session/session-page-client.tsx

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"use client";
22

33
import { useState, useEffect, useCallback, useMemo } from "react";
4-
import { useRouter, useSearchParams } from "next/navigation";
4+
import { useSearchParams } from "next/navigation";
5+
import Link from "next/link";
56
import { encodeId } from "@/lib/id-codec";
67
import { useTopNavStore } from "@/stores/top-nav";
78
import { Button } from "@/components/ui/button";
@@ -86,7 +87,6 @@ export function SessionPageClient({
8687
allOrganizations,
8788
projects,
8889
}: SessionPageClientProps) {
89-
const router = useRouter();
9090
const searchParams = useSearchParams();
9191
const { initialize, setHasSidebar } = useTopNavStore();
9292

@@ -369,19 +369,7 @@ export function SessionPageClient({
369369
}
370370
};
371371

372-
const handleGoToMessages = (sessionId: string, e: React.MouseEvent) => {
373-
e.stopPropagation();
374-
const encodedProjectId = encodeId(project.id);
375-
const encodedSessionId = encodeId(sessionId);
376-
router.push(`/project/${encodedProjectId}/session/${encodedSessionId}/messages`);
377-
};
378-
379-
const handleGoToTasks = (sessionId: string, e: React.MouseEvent) => {
380-
e.stopPropagation();
381-
const encodedProjectId = encodeId(project.id);
382-
const encodedSessionId = encodeId(sessionId);
383-
router.push(`/project/${encodedProjectId}/session/${encodedSessionId}/task`);
384-
};
372+
const encodedProjectId = encodeId(project.id);
385373

386374
return (
387375
<div className="h-full bg-background p-6 flex flex-col overflow-hidden space-y-2">
@@ -493,19 +481,15 @@ export function SessionPageClient({
493481
</TableCell>
494482
<TableCell>
495483
<div className="flex gap-2">
496-
<Button
497-
variant="secondary"
498-
size="sm"
499-
onClick={(e) => handleGoToMessages(session.id, e)}
500-
>
501-
Messages
484+
<Button variant="secondary" size="sm" asChild>
485+
<Link href={`/project/${encodedProjectId}/session/${encodeId(session.id)}/messages`}>
486+
Messages
487+
</Link>
502488
</Button>
503-
<Button
504-
variant="secondary"
505-
size="sm"
506-
onClick={(e) => handleGoToTasks(session.id, e)}
507-
>
508-
Tasks
489+
<Button variant="secondary" size="sm" asChild>
490+
<Link href={`/project/${encodedProjectId}/session/${encodeId(session.id)}/task`}>
491+
Tasks
492+
</Link>
509493
</Button>
510494
<Button
511495
variant="secondary"

dashboard/app/project/[id]/user/user-page-client.tsx

Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useState, useEffect, useCallback } from "react";
4-
import { useRouter } from "next/navigation";
4+
import Link from "next/link";
55
import { encodeId } from "@/lib/id-codec";
66
import { useTopNavStore } from "@/stores/top-nav";
77
import { Button } from "@/components/ui/button";
@@ -49,7 +49,6 @@ export function UserPageClient({
4949
allOrganizations,
5050
projects,
5151
}: UserPageClientProps) {
52-
const router = useRouter();
5352
const { initialize, setHasSidebar } = useTopNavStore();
5453

5554
useEffect(() => {
@@ -182,23 +181,7 @@ export function UserPageClient({
182181
return count.toString();
183182
};
184183

185-
const handleGoToDisks = (userIdentifier: string, e: React.MouseEvent) => {
186-
e.stopPropagation();
187-
const encodedProjectId = encodeId(project.id);
188-
router.push(`/project/${encodedProjectId}/disk?user=${encodeURIComponent(userIdentifier)}`);
189-
};
190-
191-
const handleGoToSessions = (userIdentifier: string, e: React.MouseEvent) => {
192-
e.stopPropagation();
193-
const encodedProjectId = encodeId(project.id);
194-
router.push(`/project/${encodedProjectId}/session?user=${encodeURIComponent(userIdentifier)}`);
195-
};
196-
197-
const handleGoToAgentSkills = (userIdentifier: string, e: React.MouseEvent) => {
198-
e.stopPropagation();
199-
const encodedProjectId = encodeId(project.id);
200-
router.push(`/project/${encodedProjectId}/agent-skills?user=${encodeURIComponent(userIdentifier)}`);
201-
};
184+
const encodedProjectId = encodeId(project.id);
202185

203186
return (
204187
<div className="h-full bg-background p-6 flex flex-col overflow-hidden space-y-2">
@@ -302,30 +285,39 @@ export function UserPageClient({
302285
</TableCell>
303286
<TableCell>
304287
<div className="flex gap-2">
305-
<Button
306-
variant="secondary"
307-
size="sm"
308-
onClick={(e) => handleGoToDisks(user.identifier, e)}
309-
disabled={!user.counts?.disks_count}
310-
>
311-
Disks
312-
</Button>
313-
<Button
314-
variant="secondary"
315-
size="sm"
316-
onClick={(e) => handleGoToSessions(user.identifier, e)}
317-
disabled={!user.counts?.sessions_count}
318-
>
319-
Sessions
320-
</Button>
321-
<Button
322-
variant="secondary"
323-
size="sm"
324-
onClick={(e) => handleGoToAgentSkills(user.identifier, e)}
325-
disabled={!user.counts?.skills_count}
326-
>
327-
Agent Skills
328-
</Button>
288+
{user.counts?.disks_count ? (
289+
<Button variant="secondary" size="sm" asChild>
290+
<Link href={`/project/${encodedProjectId}/disk?user=${encodeURIComponent(user.identifier)}`}>
291+
Disks
292+
</Link>
293+
</Button>
294+
) : (
295+
<Button variant="secondary" size="sm" disabled>
296+
Disks
297+
</Button>
298+
)}
299+
{user.counts?.sessions_count ? (
300+
<Button variant="secondary" size="sm" asChild>
301+
<Link href={`/project/${encodedProjectId}/session?user=${encodeURIComponent(user.identifier)}`}>
302+
Sessions
303+
</Link>
304+
</Button>
305+
) : (
306+
<Button variant="secondary" size="sm" disabled>
307+
Sessions
308+
</Button>
309+
)}
310+
{user.counts?.skills_count ? (
311+
<Button variant="secondary" size="sm" asChild>
312+
<Link href={`/project/${encodedProjectId}/agent-skills?user=${encodeURIComponent(user.identifier)}`}>
313+
Agent Skills
314+
</Link>
315+
</Button>
316+
) : (
317+
<Button variant="secondary" size="sm" disabled>
318+
Agent Skills
319+
</Button>
320+
)}
329321
<Button
330322
variant="secondary"
331323
size="sm"

0 commit comments

Comments
 (0)