diff --git a/apps/dashboard/src/components/layouts/dashboard-mobile-nav.tsx b/apps/dashboard/src/components/layouts/dashboard-mobile-nav.tsx
index 6009653..58114c3 100644
--- a/apps/dashboard/src/components/layouts/dashboard-mobile-nav.tsx
+++ b/apps/dashboard/src/components/layouts/dashboard-mobile-nav.tsx
@@ -7,6 +7,7 @@ import {
ReviewsIcon,
SunIcon,
SystemIcon,
+ UserCircleIcon,
} from "@diffkit/icons";
import { Avatar, AvatarFallback } from "@diffkit/ui/components/avatar";
import {
@@ -20,10 +21,13 @@ import {
DropdownMenuTrigger,
} from "@diffkit/ui/components/dropdown-menu";
import { cn } from "@diffkit/ui/lib/utils";
+import { useQuery } from "@tanstack/react-query";
import { Link } from "@tanstack/react-router";
import { useTheme } from "next-themes";
import { useState } from "react";
import { signOutToLogin } from "#/lib/auth-actions";
+import { githubViewerQueryOptions } from "#/lib/github.query";
+import { useHasMounted } from "#/lib/use-has-mounted";
const themeOptions = [
{ value: "light", icon: SunIcon, label: "Light" },
@@ -40,6 +44,7 @@ interface MobileNavItem {
interface DashboardMobileNavProps {
user: {
+ id: string;
name?: string | null;
email: string;
image?: string | null;
@@ -59,6 +64,12 @@ export function DashboardMobileNav({
}: DashboardMobileNavProps) {
const { theme, setTheme } = useTheme();
const [avatarLoadFailed, setAvatarLoadFailed] = useState(false);
+ const hasMounted = useHasMounted();
+ const viewerQuery = useQuery({
+ ...githubViewerQueryOptions({ userId: user.id }),
+ enabled: hasMounted,
+ });
+ const viewerLogin = viewerQuery.data?.login;
const displayName = user.name ?? user.email;
const initials = displayName
@@ -161,9 +172,12 @@ export function DashboardMobileNav({
-
- Profile
-
+
+
+
+ Profile
+
+
diff --git a/apps/dashboard/src/components/layouts/dashboard-tabs.tsx b/apps/dashboard/src/components/layouts/dashboard-tabs.tsx
index 7bb1f20..8c86360 100644
--- a/apps/dashboard/src/components/layouts/dashboard-tabs.tsx
+++ b/apps/dashboard/src/components/layouts/dashboard-tabs.tsx
@@ -91,7 +91,10 @@ interface DashboardTabsProps {
export function DashboardTabs({ tabsReady, routerRef }: DashboardTabsProps) {
const openTabs = useTabs();
const pathname = useRouterState({ select: (s) => s.location.pathname });
- const contextTabRef = useRef<{ tab: Tab; index: number } | null>(null);
+ const [contextTab, setContextTab] = useState<{
+ tab: Tab;
+ index: number;
+ } | null>(null);
const {
scrollRef,
canScrollLeft,
@@ -119,34 +122,34 @@ export function DashboardTabs({ tabsReady, routerRef }: DashboardTabsProps) {
const handleCloseTab = useCallback(
(id: string, tabUrl: string) => {
const isActive = pathname === tabUrl;
+ const index = openTabs.findIndex((tab) => tab.id === id);
+ const nextTab =
+ index === -1 ? undefined : (openTabs[index + 1] ?? openTabs[index - 1]);
removeTab(id);
if (isActive) {
- void routerRef.current.navigate({ to: "/" });
+ void routerRef.current.navigate({ to: nextTab?.url ?? "/" });
}
},
- [pathname, routerRef],
+ [openTabs, pathname, routerRef],
);
const handleContextClose = useCallback(() => {
- const ctx = contextTabRef.current;
- if (!ctx) return;
- handleCloseTab(ctx.tab.id, ctx.tab.url);
- }, [handleCloseTab]);
+ if (!contextTab) return;
+ handleCloseTab(contextTab.tab.id, contextTab.tab.url);
+ }, [contextTab, handleCloseTab]);
const handleContextCloseOthers = useCallback(() => {
- const ctx = contextTabRef.current;
- if (!ctx) return;
- if (pathname !== ctx.tab.url) {
- void routerRef.current.navigate({ to: ctx.tab.url });
+ if (!contextTab) return;
+ if (pathname !== contextTab.tab.url) {
+ void routerRef.current.navigate({ to: contextTab.tab.url });
}
- removeOtherTabs(ctx.tab.id);
- }, [pathname, routerRef]);
+ removeOtherTabs(contextTab.tab.id);
+ }, [contextTab, pathname, routerRef]);
const handleContextCloseRight = useCallback(() => {
- const ctx = contextTabRef.current;
- if (!ctx) return;
- removeTabsToRight(ctx.tab.id);
- }, []);
+ if (!contextTab) return;
+ removeTabsToRight(contextTab.tab.id);
+ }, [contextTab]);
if (openTabs.length === 0) return null;
@@ -193,7 +196,7 @@ export function DashboardTabs({ tabsReady, routerRef }: DashboardTabsProps) {
icon={Icon}
onClose={handleCloseTab}
onContextMenu={() => {
- contextTabRef.current = { tab, index };
+ setContextTab({ tab, index });
}}
routerRef={routerRef}
/>
@@ -216,7 +219,7 @@ export function DashboardTabs({ tabsReady, routerRef }: DashboardTabsProps) {
Close tabs to the right
diff --git a/apps/dashboard/src/components/layouts/dashboard-topbar.tsx b/apps/dashboard/src/components/layouts/dashboard-topbar.tsx
index 1baa9b7..73a3747 100644
--- a/apps/dashboard/src/components/layouts/dashboard-topbar.tsx
+++ b/apps/dashboard/src/components/layouts/dashboard-topbar.tsx
@@ -34,7 +34,7 @@ import {
githubViewerQueryOptions,
} from "#/lib/github.query";
import { useGlobalShortcuts } from "#/lib/shortcuts";
-import { type Tab, useTabs } from "#/lib/tab-store";
+import { removeTab, type Tab, useTabs } from "#/lib/tab-store";
import { useHasMounted } from "#/lib/use-has-mounted";
interface DashboardTopbarProps {
@@ -154,6 +154,13 @@ export function DashboardTopbar({
void routerRef.current.navigate({ to: tab.url });
}
+ function getNeighborTab(tab: Tab | undefined) {
+ if (!tab) return undefined;
+ const index = openTabs.findIndex((item) => item.id === tab.id);
+ if (index === -1) return undefined;
+ return openTabs[index + 1] ?? openTabs[index - 1];
+ }
+
useGlobalShortcuts([
...Array.from(
{ length: Math.min(openTabs.length, MAX_TAB_SHORTCUTS) },
@@ -165,6 +172,28 @@ export function DashboardTopbar({
},
}),
),
+ {
+ shortcut: [{ key: "g" }, { key: "u" }],
+ enabled: tabsReady && Boolean(viewerLogin),
+ onTrigger: () => {
+ void routerRef.current.navigate({
+ to: "/$owner",
+ params: { owner: viewerLogin ?? "" },
+ });
+ },
+ },
+ {
+ shortcut: { key: "w", shift: true },
+ enabled: tabsReady && openTabs.length > 0,
+ onTrigger: () => {
+ const currentPath = routerRef.current.state.location.pathname;
+ const currentTab = openTabs.find((tab) => tab.url === currentPath);
+ if (!currentTab) return;
+ const nextTab = getNeighborTab(currentTab);
+ removeTab(currentTab.id);
+ void routerRef.current.navigate({ to: nextTab?.url ?? "/" });
+ },
+ },
{
shortcut: { key: "ArrowLeft", shift: true },
enabled: tabsReady && openTabs.length > 1,
@@ -252,7 +281,7 @@ export function DashboardTopbar({
Profile
-
+
diff --git a/apps/dashboard/src/lib/command-palette/use-command-items.ts b/apps/dashboard/src/lib/command-palette/use-command-items.ts
index 70789c3..8fff257 100644
--- a/apps/dashboard/src/lib/command-palette/use-command-items.ts
+++ b/apps/dashboard/src/lib/command-palette/use-command-items.ts
@@ -5,6 +5,7 @@ import {
GitPullRequestDraftIcon,
GitPullRequestIcon,
IssuesIcon,
+ UserCircleIcon,
} from "@diffkit/icons";
import { type QueryClient, useQueryClient } from "@tanstack/react-query";
import { getRouteApi } from "@tanstack/react-router";
@@ -70,9 +71,27 @@ export function useCommandItems(): CommandItem[] {
const issues = queryClient.getQueryData(
githubQueryKeys.issues.mine(scope),
);
+ const viewer = queryClient.getQueryData<{ login: string } | null>(
+ githubQueryKeys.viewer(scope),
+ );
const dynamicItems: CommandItem[] = [];
+ if (viewer) {
+ dynamicItems.push({
+ id: "nav:profile",
+ label: "Go to Profile",
+ group: "Pages",
+ icon: UserCircleIcon,
+ keywords: ["user", "me", viewer.login],
+ shortcut: ["G", "U"],
+ action: {
+ type: "navigate",
+ to: `/${viewer.login}`,
+ },
+ });
+ }
+
if (repos) {
for (const repo of repos) {
dynamicItems.push({
diff --git a/apps/dashboard/src/routes/_protected/settings/shortcuts.tsx b/apps/dashboard/src/routes/_protected/settings/shortcuts.tsx
index b81f040..286de52 100644
--- a/apps/dashboard/src/routes/_protected/settings/shortcuts.tsx
+++ b/apps/dashboard/src/routes/_protected/settings/shortcuts.tsx
@@ -200,6 +200,7 @@ const shortcutGroups: ShortcutGroup[] = [
title: "Navigation",
shortcuts: [
{ keys: ["G", "H"], description: "Go to Overview" },
+ { keys: ["G", "U"], description: "Go to Profile" },
{ keys: ["G", "P"], description: "Go to Pull Requests" },
{ keys: ["G", "I"], description: "Go to Issues" },
{ keys: ["G", "R"], description: "Go to Reviews" },
@@ -210,6 +211,7 @@ const shortcutGroups: ShortcutGroup[] = [
title: "Tabs",
shortcuts: [
{ keys: ["Shift", "1\u20139"], description: "Switch to tab by position" },
+ { keys: ["Shift", "W"], description: "Close current tab" },
{ keys: ["Shift", "\u2190"], description: "Previous tab" },
{ keys: ["Shift", "\u2192"], description: "Next tab" },
],