Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
839 changes: 839 additions & 0 deletions apps/dashboard/src/components/inbox/inbox-page.tsx

Large diffs are not rendered by default.

34 changes: 30 additions & 4 deletions apps/dashboard/src/components/issues/detail/issue-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,34 @@ const routeApi = getRouteApi("/_protected/$owner/$repo/issues/$issueId");
export function IssueDetailPage() {
const { user } = routeApi.useRouteContext();
const { owner, repo, issueId } = routeApi.useParams();
const issueNumber = Number(issueId);
const scope = useMemo(() => ({ userId: user.id }), [user.id]);

return (
<IssueDetailContent
owner={owner}
repo={repo}
issueNumber={Number(issueId)}
userId={user.id}
registerTab
/>
);
}

export type IssueDetailContentProps = {
owner: string;
repo: string;
issueNumber: number;
userId: string;
registerTab?: boolean;
};

export function IssueDetailContent({
owner,
repo,
issueNumber,
userId,
registerTab = false,
}: IssueDetailContentProps) {
const scope = useMemo(() => ({ userId }), [userId]);
const input = useMemo(
() => ({ owner, repo, issueNumber }),
[owner, repo, issueNumber],
Expand Down Expand Up @@ -63,12 +89,12 @@ export function IssueDetailPage() {
const eventPagination = pageQuery.data?.eventPagination;

useRegisterTab(
issue
registerTab && issue
? {
type: "issue",
title: issue.title,
number: issue.number,
url: `/${owner}/${repo}/issues/${issueId}`,
url: `/${owner}/${repo}/issues/${issueNumber}`,
repo: `${owner}/${repo}`,
iconColor: getIssueStateConfig(issue).color,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
GitPullRequestIcon,
HomeIcon,
InboxIcon,
IssuesIcon,
MoonIcon,
ReviewsIcon,
Expand Down Expand Up @@ -69,6 +70,7 @@ export function DashboardMobileNav({

const navItems: MobileNavItem[] = [
{ to: "/", label: "Overview", icon: HomeIcon },
{ to: "/inbox", label: "Inbox", icon: InboxIcon },
{
to: "/pulls",
label: "Pulls",
Expand Down
27 changes: 23 additions & 4 deletions apps/dashboard/src/components/layouts/dashboard-topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ExternalLinkIcon,
GitPullRequestIcon,
HomeIcon,
InboxIcon,
IssuesIcon,
LogOutIcon,
MoreHorizontalIcon,
Expand All @@ -28,7 +29,10 @@ import { Link, useRouter } from "@tanstack/react-router";
import { useEffect, useMemo, useRef, useState } from "react";
import { DashboardTabs } from "#/components/layouts/dashboard-tabs";
import { signOutToLogin } from "#/lib/auth-actions";
import { githubViewerQueryOptions } from "#/lib/github.query";
import {
githubNotificationsQueryOptions,
githubViewerQueryOptions,
} from "#/lib/github.query";
import { useGlobalShortcuts } from "#/lib/shortcuts";
import { type Tab, useTabs } from "#/lib/tab-store";
import { useHasMounted } from "#/lib/use-has-mounted";
Expand All @@ -53,9 +57,16 @@ type NavItem = {
label: string;
icon: typeof HomeIcon;
count?: number;
dot?: boolean;
};

const primaryNavRoutes = ["/", "/pulls", "/issues", "/reviews"] as const;
const primaryNavRoutes = [
"/",
"/inbox",
"/pulls",
"/issues",
"/reviews",
] as const;
const MAX_TAB_SHORTCUTS = 9;

export function DashboardTopbar({
Expand All @@ -71,6 +82,11 @@ export function DashboardTopbar({
enabled: hasMounted,
});
const viewerLogin = viewerQuery.data?.login;
const notificationsQuery = useQuery({
...githubNotificationsQueryOptions({ userId: user.id }, { all: false }),
enabled: hasMounted,
});
const hasUnread = (notificationsQuery.data?.notifications?.length ?? 0) > 0;
// Store router in a ref — only used imperatively (navigate, preload),
// never read during render, so we avoid subscribing to state changes.
const router = useRouter();
Expand All @@ -88,6 +104,7 @@ export function DashboardTopbar({
const navItems = useMemo<NavItem[]>(
() => [
{ to: "/", label: "Overview", icon: HomeIcon },
{ to: "/inbox", label: "Inbox", icon: InboxIcon, dot: hasUnread },
{
to: "/pulls",
label: "Pull Requests",
Expand All @@ -107,7 +124,7 @@ export function DashboardTopbar({
count: counts.reviews,
},
],
[counts.pulls, counts.issues, counts.reviews],
[counts.pulls, counts.issues, counts.reviews, hasUnread],
);

useEffect(() => {
Expand Down Expand Up @@ -284,7 +301,9 @@ export function DashboardTopbar({
>
<span className="flex items-center gap-2">
<span>{item.label}</span>
{typeof item.count === "number" ? (
{item.dot ? (
<span className="size-1.5 rounded-full bg-blue-500 translate-y-px" />
) : typeof item.count === "number" ? (
<span
data-slot="tab-count"
className="tabular-nums text-muted-foreground"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ function PrCard({
</span>
)}
</div>
{pr.headRef && pr.baseRef && (
{pr.headRef && pr.baseRef && pr.state !== "closed" && (
<div className="mt-auto flex min-w-0 items-center gap-1 text-[11px] text-muted-foreground">
<span className="min-w-0 shrink truncate rounded bg-surface-2 px-1.5 py-0.5 font-mono text-[11px] font-[550]">
{pr.headRef}
Expand Down
28 changes: 17 additions & 11 deletions apps/dashboard/src/components/pulls/detail/pull-detail-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,23 @@ export function PullDetailHeader({
<span className="shrink-0 font-medium text-foreground">
{pr.author.login}
</span>
<span className="shrink-0">wants to merge into</span>
<CopyBadge value={pr.baseRefName} />
<span className="shrink-0">from</span>
<CopyBadge
value={
pr.headRepoOwner && pr.headRepoOwner !== owner
? `${pr.headRepoOwner}:${pr.headRefName}`
: pr.headRefName
}
canTruncate
/>
{(pr.isMerged || pr.state !== "closed") && (
<>
<span className="shrink-0">
{pr.isMerged ? "merged into" : "wants to merge into"}
</span>
<CopyBadge value={pr.baseRefName} />
<span className="shrink-0">from</span>
<CopyBadge
value={
pr.headRepoOwner && pr.headRepoOwner !== owner
? `${pr.headRepoOwner}:${pr.headRefName}`
: pr.headRefName
}
canTruncate
/>
</>
)}
</>
)}
</div>
Expand Down
36 changes: 31 additions & 5 deletions apps/dashboard/src/components/pulls/detail/pull-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,34 @@ const routeApi = getRouteApi("/_protected/$owner/$repo/pull/$pullId");
export function PullDetailPage() {
const { user } = routeApi.useRouteContext();
const { owner, repo, pullId } = routeApi.useParams();
const pullNumber = Number(pullId);
const scope = useMemo(() => ({ userId: user.id }), [user.id]);

return (
<PullDetailContent
owner={owner}
repo={repo}
pullNumber={Number(pullId)}
userId={user.id}
registerTab
/>
);
}

export type PullDetailContentProps = {
owner: string;
repo: string;
pullNumber: number;
userId: string;
registerTab?: boolean;
};

export function PullDetailContent({
owner,
repo,
pullNumber,
userId,
registerTab = false,
}: PullDetailContentProps) {
const scope = useMemo(() => ({ userId }), [userId]);
const input = useMemo(
() => ({ owner, repo, pullNumber }),
[owner, repo, pullNumber],
Expand Down Expand Up @@ -92,12 +118,12 @@ export function PullDetailPage() {
const viewer = viewerQuery.data ?? null;

useRegisterTab(
pr
registerTab && pr
? {
type: "pull",
title: pr.title,
number: pr.number,
url: `/${owner}/${repo}/pull/${pullId}`,
url: `/${owner}/${repo}/pull/${pullNumber}`,
repo: `${owner}/${repo}`,
iconColor: getPrStateConfig(pr).color,
}
Expand All @@ -114,7 +140,7 @@ export function PullDetailPage() {
<PullDetailHeader
owner={owner}
repo={repo}
pullId={pullId}
pullId={String(pullNumber)}
pr={pr}
viewerLogin={viewer?.login}
/>
Expand Down
Loading
Loading