Skip to content
Open
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
10 changes: 6 additions & 4 deletions src/app/api/metrics/prs/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NextRequest } from "next/server";
import { authOptions } from "@/lib/auth";
import { getAccountToken, getAllAccounts } from "@/lib/github-accounts";
import { GITHUB_API } from "@/lib/github";
import { GitHubAuthError, githubAuthErrorResponse } from "@/lib/github-fetch";
import {
isMetricsCacheBypassed,
METRICS_CACHE_TTL_SECONDS,
Expand Down Expand Up @@ -538,7 +539,7 @@ async function fetchReviewMetrics(token: string): Promise<ReviewMetrics> {
export async function GET(req: NextRequest) {
const session = await getServerSession(authOptions);
if (!session?.accessToken) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
return githubAuthErrorResponse();
}

const gitlabToken = typeof session.gitlabToken === "string" ? session.gitlabToken : undefined;
Expand Down Expand Up @@ -603,9 +604,10 @@ export async function GET(req: NextRequest) {
]);

return Response.json({ ...formatPRMetricsResponse(result, gitlab), reviews });
} catch {
// Catches errors from fetchCachedPRMetrics (GitHub Search API failures).
// Returns 502 so the client knows the data is unavailable, not just empty.
} catch (e: any) {
if (e instanceof GitHubAuthError || e?.status === 401) {
return githubAuthErrorResponse();
}
return Response.json({ error: "GitHub API error" }, { status: 502 });
}
}
Expand Down
40 changes: 39 additions & 1 deletion src/components/ContributionGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export default function ContributionGraph() {
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
const [minutesAgo, setMinutesAgo] = useState(0);
const [error, setError] = useState<string | null>(null);
const [sessionExpired, setSessionExpired] = useState(false);
const [commits, setCommits] = useState<CommitItem[]>([]);
const [usesTouchTooltip, setUsesTouchTooltip] = useState(false);
const [repo, setRepo] = useState<string>("all");
Expand Down Expand Up @@ -264,7 +265,26 @@ export default function ContributionGraph() {
// 2. Perform background sync / standard fetch
try {
const r = await fetch(url);
if (!r.ok) throw new Error("API error");
if (!r.ok) {
if (r.status === 401) {
const errData = await r.json().catch(() => ({}));
if (
errData.error === "token_expired" ||
errData.error === "session_expired" ||
errData.error === "Unauthorized"
) {
if (active) {
setSessionExpired(true);
setError("Your session expired — please sign in again");
setTimeout(() => {
window.location.href = "/api/auth/signin";
}, 3000);
}
return;
}
}
throw new Error("API error");
}
const res: ContributionResponse = await r.json();

if (!active) return;
Expand Down Expand Up @@ -488,6 +508,24 @@ export default function ContributionGraph() {
id="contribution-activity"
className="rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm transition-all duration-300 hover:shadow-md hover:-translate-y-1 fade-in-up"
>
{sessionExpired && (
<div className="mb-4 flex items-center justify-between rounded-lg border border-amber-500/30 bg-amber-500/10 p-4 text-sm text-amber-300 shadow-md">
<div className="flex items-center gap-2">
<span className="text-lg">🔒</span>
<div>
<p className="font-semibold">Your session expired — please sign in again</p>
<p className="text-xs opacity-90">Auto-redirecting to sign in page in 3 seconds…</p>
</div>
</div>
<a
href="/api/auth/signin"
className="rounded-md bg-amber-500 px-3 py-1.5 text-xs font-semibold text-black hover:opacity-90 transition-opacity"
>
Sign in now
</a>
</div>
)}

<div className="flex flex-wrap items-center justify-between mb-4 gap-2">
<div className="min-w-0">
<h2 className="text-sm md:text-base lg:text-lg font-semibold text-[var(--foreground)]">
Expand Down
6 changes: 5 additions & 1 deletion src/lib/github-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ function isSecondaryRateLimitBody(body: unknown): boolean {

async function buildGitHubError(
res: Response,
): Promise<GitHubRateLimitError | GitHubApiError> {
): Promise<GitHubRateLimitError | GitHubAuthError | GitHubApiError> {
if (res.status === 401) {
return new GitHubAuthError();
}

const { resetAt, retryAfter, remaining } = extractRateLimitInfo(res.headers);

// 429: always a rate limit
Expand Down
Loading