Skip to content

Commit 2e191f8

Browse files
committed
fix
1 parent 02eee03 commit 2e191f8

File tree

2 files changed

+248
-4
lines changed

2 files changed

+248
-4
lines changed

packages/react-doctor/src/scan.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,10 @@ const printSummary = (
279279
summaryLinePartsPlain.push(warningText);
280280
summaryLineParts.push(highlighter.warn(warningText));
281281
}
282-
const fileCountText = totalSourceFileCount > 0
283-
? `across ${affectedFileCount}/${totalSourceFileCount} files`
284-
: `across ${affectedFileCount} file${affectedFileCount === 1 ? "" : "s"}`;
282+
const fileCountText =
283+
totalSourceFileCount > 0
284+
? `across ${affectedFileCount}/${totalSourceFileCount} files`
285+
: `across ${affectedFileCount} file${affectedFileCount === 1 ? "" : "s"}`;
285286
const elapsedTimeText = `in ${elapsed}`;
286287

287288
summaryLinePartsPlain.push(fileCountText);
@@ -442,5 +443,11 @@ export const scan = async (directory: string, options: ScanOptions): Promise<voi
442443

443444
printDiagnostics(diagnostics, options.verbose);
444445

445-
printSummary(diagnostics, elapsedMilliseconds, scoreResult, projectInfo.projectName, projectInfo.sourceFileCount);
446+
printSummary(
447+
diagnostics,
448+
elapsedMilliseconds,
449+
scoreResult,
450+
projectInfo.projectName,
451+
projectInfo.sourceFileCount,
452+
);
446453
};
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import type { Metadata } from "next";
2+
import Link from "next/link";
3+
4+
const PERFECT_SCORE = 100;
5+
const SCORE_GOOD_THRESHOLD = 75;
6+
const SCORE_OK_THRESHOLD = 50;
7+
const SCORE_BAR_WIDTH = 20;
8+
const COMMAND = "npx -y react-doctor@latest .";
9+
10+
interface LeaderboardEntry {
11+
name: string;
12+
githubUrl: string;
13+
score: number;
14+
errorCount: number;
15+
warningCount: number;
16+
fileCount: number;
17+
shareUrl: string;
18+
}
19+
20+
const LEADERBOARD_ENTRIES: LeaderboardEntry[] = [
21+
{
22+
name: "tldraw",
23+
githubUrl: "https://github.com/tldraw/tldraw",
24+
score: 84,
25+
errorCount: 98,
26+
warningCount: 139,
27+
fileCount: 40,
28+
shareUrl: "/share?p=tldraw&s=84&e=98&w=139&f=40",
29+
},
30+
{
31+
name: "excalidraw",
32+
githubUrl: "https://github.com/excalidraw/excalidraw",
33+
score: 84,
34+
errorCount: 2,
35+
warningCount: 196,
36+
fileCount: 80,
37+
shareUrl: "/share?p=%40excalidraw%2Fexcalidraw&s=84&e=2&w=196&f=80",
38+
},
39+
{
40+
name: "twenty",
41+
githubUrl: "https://github.com/twentyhq/twenty",
42+
score: 78,
43+
errorCount: 99,
44+
warningCount: 293,
45+
fileCount: 268,
46+
shareUrl: "/share?p=twenty-front&s=78&e=99&w=293&f=268",
47+
},
48+
{
49+
name: "plane",
50+
githubUrl: "https://github.com/makeplane/plane",
51+
score: 78,
52+
errorCount: 7,
53+
warningCount: 525,
54+
fileCount: 292,
55+
shareUrl: "/share?p=web&s=78&e=7&w=525&f=292",
56+
},
57+
{
58+
name: "formbricks",
59+
githubUrl: "https://github.com/formbricks/formbricks",
60+
score: 75,
61+
errorCount: 15,
62+
warningCount: 389,
63+
fileCount: 242,
64+
shareUrl: "/share?p=%40formbricks%2Fweb&s=75&e=15&w=389&f=242",
65+
},
66+
{
67+
name: "posthog",
68+
githubUrl: "https://github.com/PostHog/posthog",
69+
score: 72,
70+
errorCount: 82,
71+
warningCount: 1177,
72+
fileCount: 585,
73+
shareUrl: "/share?p=%40posthog%2Ffrontend&s=72&e=82&w=1177&f=585",
74+
},
75+
{
76+
name: "supabase",
77+
githubUrl: "https://github.com/supabase/supabase",
78+
score: 69,
79+
errorCount: 74,
80+
warningCount: 1087,
81+
fileCount: 566,
82+
shareUrl: "/share?p=studio&s=69&e=74&w=1087&f=566",
83+
},
84+
{
85+
name: "onlook",
86+
githubUrl: "https://github.com/onlook-dev/onlook",
87+
score: 69,
88+
errorCount: 64,
89+
warningCount: 418,
90+
fileCount: 178,
91+
shareUrl: "/share?p=%40onlook%2Fweb-client&s=69&e=64&w=418&f=178",
92+
},
93+
{
94+
name: "payload",
95+
githubUrl: "https://github.com/payloadcms/payload",
96+
score: 68,
97+
errorCount: 139,
98+
warningCount: 408,
99+
fileCount: 298,
100+
shareUrl: "/share?p=%40payloadcms%2Fui&s=68&e=139&w=408&f=298",
101+
},
102+
{
103+
name: "sentry",
104+
githubUrl: "https://github.com/getsentry/sentry",
105+
score: 64,
106+
errorCount: 94,
107+
warningCount: 1345,
108+
fileCount: 818,
109+
shareUrl: "/share?p=sentry&s=64&e=94&w=1345&f=818",
110+
},
111+
{
112+
name: "cal.com",
113+
githubUrl: "https://github.com/calcom/cal.com",
114+
score: 63,
115+
errorCount: 31,
116+
warningCount: 558,
117+
fileCount: 311,
118+
shareUrl: "/share?p=%40calcom%2Fweb&s=63&e=31&w=558&f=311",
119+
},
120+
{
121+
name: "dub",
122+
githubUrl: "https://github.com/dubinc/dub",
123+
score: 62,
124+
errorCount: 52,
125+
warningCount: 966,
126+
fileCount: 457,
127+
shareUrl: "/share?p=web&s=62&e=52&w=966&f=457",
128+
},
129+
];
130+
131+
const getScoreColorClass = (score: number): string => {
132+
if (score >= SCORE_GOOD_THRESHOLD) return "text-green-400";
133+
if (score >= SCORE_OK_THRESHOLD) return "text-yellow-500";
134+
return "text-red-400";
135+
};
136+
137+
const getDoctorFace = (score: number): [string, string] => {
138+
if (score >= SCORE_GOOD_THRESHOLD) return ["◠ ◠", " ▽ "];
139+
if (score >= SCORE_OK_THRESHOLD) return ["• •", " ─ "];
140+
return ["x x", " ▽ "];
141+
};
142+
143+
const MAX_NAME_LENGTH = 12;
144+
145+
const ScoreBar = ({ score }: { score: number }) => {
146+
const filledCount = Math.round((score / PERFECT_SCORE) * SCORE_BAR_WIDTH);
147+
const emptyCount = SCORE_BAR_WIDTH - filledCount;
148+
const colorClass = getScoreColorClass(score);
149+
150+
return (
151+
<span className="text-xs sm:text-sm">
152+
<span className={colorClass}>{"█".repeat(filledCount)}</span>
153+
<span className="text-neutral-700">{"░".repeat(emptyCount)}</span>
154+
</span>
155+
);
156+
};
157+
158+
const LeaderboardRow = ({ entry, rank }: { entry: LeaderboardEntry; rank: number }) => {
159+
const colorClass = getScoreColorClass(entry.score);
160+
const paddedName = entry.name.padEnd(MAX_NAME_LENGTH);
161+
162+
return (
163+
<div className="group flex items-center gap-2 border-b border-white/5 py-2 transition-colors hover:bg-white/2 sm:gap-4 sm:py-2.5">
164+
<span className="w-6 shrink-0 text-right text-neutral-600 sm:w-8">{rank}</span>
165+
166+
<a
167+
href={entry.githubUrl}
168+
target="_blank"
169+
rel="noreferrer"
170+
className="shrink-0 text-white transition-colors hover:text-blue-400"
171+
>
172+
<span className="sm:hidden">
173+
{entry.name.length > 10 ? `${entry.name.slice(0, 9)}…` : entry.name}
174+
</span>
175+
<span className="hidden sm:inline">{paddedName}</span>
176+
</a>
177+
178+
<span className="hidden flex-1 sm:inline">
179+
<ScoreBar score={entry.score} />
180+
</span>
181+
182+
<Link href={entry.shareUrl} className="ml-auto shrink-0 transition-colors hover:underline">
183+
<span className={`${colorClass} font-medium`}>{entry.score}</span>
184+
<span className="text-neutral-600">/{PERFECT_SCORE}</span>
185+
</Link>
186+
</div>
187+
);
188+
};
189+
190+
export const metadata: Metadata = {
191+
title: "Leaderboard - React Doctor",
192+
description: "Scores for popular open-source React projects, diagnosed by React Doctor.",
193+
};
194+
195+
const LeaderboardPage = () => {
196+
const topScore = LEADERBOARD_ENTRIES[0]?.score ?? 0;
197+
const [eyes, mouth] = getDoctorFace(topScore);
198+
const topScoreColor = getScoreColorClass(topScore);
199+
200+
return (
201+
<div className="mx-auto min-h-screen w-full max-w-3xl bg-[#0a0a0a] p-6 pb-32 font-mono text-base leading-relaxed text-neutral-300 sm:p-8 sm:pb-40 sm:text-lg">
202+
<div className="mb-8">
203+
<Link
204+
href="/"
205+
className="inline-flex items-center gap-2 text-neutral-500 transition-colors hover:text-neutral-300"
206+
>
207+
<img src="/favicon.svg" alt="React Doctor" width={20} height={20} />
208+
<span>react-doctor</span>
209+
</Link>
210+
</div>
211+
212+
<div className="mb-2">
213+
<pre className={`${topScoreColor} leading-tight`}>
214+
{` ┌─────┐\n │ ${eyes} │\n │ ${mouth} │\n └─────┘`}
215+
</pre>
216+
</div>
217+
218+
<div className="mb-1 text-xl text-white">Leaderboard</div>
219+
<div className="mb-8 text-neutral-500">Scores for popular open-source React projects.</div>
220+
221+
<div className="mb-8">
222+
{LEADERBOARD_ENTRIES.map((entry, index) => (
223+
<LeaderboardRow key={entry.name} entry={entry} rank={index + 1} />
224+
))}
225+
</div>
226+
227+
<div className="min-h-[1.4em]" />
228+
229+
<div className="text-neutral-500">Run it on your codebase:</div>
230+
<div className="mt-2">
231+
<span className="border border-white/20 px-3 py-1.5 text-white">{COMMAND}</span>
232+
</div>
233+
</div>
234+
);
235+
};
236+
237+
export default LeaderboardPage;

0 commit comments

Comments
 (0)