From 193c50e0b61644f7b5f70f98f4d1034bd124c5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ai-chan-0411=20=28=E8=97=8D=29?= Date: Sun, 12 Apr 2026 05:59:26 +0900 Subject: [PATCH 1/2] feat: display friendly error messages on API failure (closes #34) - Distinguish between user-not-found (404), rate-limit (429), network errors (503), and generic failures (500) in API route - Return appropriate HTTP status codes for each error type - Replace plain error div with Alert component using AlertCircle icon - Pass error prop to CompareForm for inline error display - Provide actionable guidance in each error message Co-Authored-By: Claude Opus 4.6 --- app/api/compare/route.ts | 33 ++++++++++++++++++++++++++++----- app/page.tsx | 11 ++++++++--- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/app/api/compare/route.ts b/app/api/compare/route.ts index 809b290..95f3156 100644 --- a/app/api/compare/route.ts +++ b/app/api/compare/route.ts @@ -36,13 +36,36 @@ export async function GET(request: Request) { return NextResponse.json({ success: true, users: results }); } catch (error: any) { console.error("GitHub score error:", error); - const message = - error?.message === "User not found" - ? "GitHub user not found" - : "Failed to calculate score"; + + let message = "Something went wrong. Please try again later."; + let status = 500; + + const msg = error?.message ?? ""; + if (msg === "User not found") { + message = + "One or more GitHub users could not be found. Please check the usernames and try again."; + status = 404; + } else if ( + msg.includes("rate limit") || + msg.includes("API rate limit") || + error?.status === 403 + ) { + message = + "GitHub API rate limit exceeded. Please wait a few minutes and try again."; + status = 429; + } else if ( + msg.includes("ENOTFOUND") || + msg.includes("ECONNREFUSED") || + msg.includes("fetch failed") + ) { + message = + "Unable to reach GitHub. Please check your internet connection and try again."; + status = 503; + } + return NextResponse.json( { success: false, error: message }, - { status: 500 } + { status } ); } } diff --git a/app/page.tsx b/app/page.tsx index a1b5596..23c926b 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -5,6 +5,8 @@ import { CompareForm } from "../components/compare-form"; import { ResultDashboard } from "../components/result-dashboard"; import { DashboardSkeleton } from "../components/skeletons"; import { UserResult } from "@/types/user-result"; +import { AlertCircle } from "lucide-react"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; type ApiResponse = { success: boolean; @@ -84,13 +86,16 @@ export default function HomePage() { reset={reset} swapUsers={swapUsers} data={data} + error={error} /> {loading && skeleton} {error && ( -
- {error} -
+ + + Comparison Failed + {error} + )} {data && } {!loading && !error && !data && ( From 93ec1623488881997d49f89701361eaa5dca5f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ai-chan-0411=20=28=E8=97=8D=29?= Date: Sun, 12 Apr 2026 08:27:40 +0900 Subject: [PATCH 2/2] fix: show error only in compare-form, remove duplicate from result-dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove duplicate error Alert block from page.tsx (was shown twice) - Error is now displayed only via CompareForm's error prop - Improve invalid username detection in API route to handle GraphqlResponseError format from @octokit/graphql (catches 'Could not resolve to a User' messages) Signed-off-by: Ai-chan-0411 (藍) --- app/api/compare/route.ts | 18 +++++++++++++----- app/page.tsx | 11 +---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app/api/compare/route.ts b/app/api/compare/route.ts index 95f3156..ec81370 100644 --- a/app/api/compare/route.ts +++ b/app/api/compare/route.ts @@ -40,22 +40,30 @@ export async function GET(request: Request) { let message = "Something went wrong. Please try again later."; let status = 500; - const msg = error?.message ?? ""; - if (msg === "User not found") { + const msg = (error?.message ?? "").toLowerCase(); + const isNotFound = + msg === "user not found" || + msg.includes("could not resolve to a user") || + msg.includes("user not found") || + error?.errors?.some((e: any) => + e?.type === "NOT_FOUND" || e?.message?.toLowerCase().includes("user") + ); + + if (isNotFound) { message = "One or more GitHub users could not be found. Please check the usernames and try again."; status = 404; } else if ( msg.includes("rate limit") || - msg.includes("API rate limit") || + msg.includes("api rate limit") || error?.status === 403 ) { message = "GitHub API rate limit exceeded. Please wait a few minutes and try again."; status = 429; } else if ( - msg.includes("ENOTFOUND") || - msg.includes("ECONNREFUSED") || + msg.includes("enotfound") || + msg.includes("econnrefused") || msg.includes("fetch failed") ) { message = diff --git a/app/page.tsx b/app/page.tsx index 23c926b..b8d6f79 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -5,8 +5,6 @@ import { CompareForm } from "../components/compare-form"; import { ResultDashboard } from "../components/result-dashboard"; import { DashboardSkeleton } from "../components/skeletons"; import { UserResult } from "@/types/user-result"; -import { AlertCircle } from "lucide-react"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; type ApiResponse = { success: boolean; @@ -76,7 +74,7 @@ export default function HomePage() { DevImpact - +
@@ -90,13 +88,6 @@ export default function HomePage() { /> {loading && skeleton} - {error && ( - - - Comparison Failed - {error} - - )} {data && } {!loading && !error && !data && (