Skip to content

Commit 38c1ce2

Browse files
committed
feat: Add New Redesign Of Issue Explorer
Signed-off-by: aceppaluni <aceppaluni@gmail.com>
1 parent 95338ce commit 38c1ce2

1 file changed

Lines changed: 294 additions & 57 deletions

File tree

src/app/issues/page.tsx

Lines changed: 294 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,83 @@ import { useState } from "react";
66
import { useIssues } from "@/hooks/useIssues";
77
import { GitHubIssue } from "@/issues/types";
88

9+
type Difficulty = {
10+
label: string;
11+
value: string;
12+
};
13+
14+
const difficultyAliases: Record<string, string[]> = {
15+
"good first issue": [
16+
"good first issue",
17+
"skill: good first issue",
18+
],
19+
beginner: [
20+
"beginner",
21+
"skill: beginner",
22+
],
23+
intermediate: [
24+
"intermediate",
25+
"skill: intermediate",
26+
],
27+
advanced: [
28+
"advanced",
29+
"skill: advanced",
30+
],
31+
};
32+
33+
function getDifficulty(issue: GitHubIssue): Difficulty | null {
34+
const issueWithLabels = issue as GitHubIssue & {
35+
labels?: Array<string | { name?: string }>;
36+
};
37+
38+
const labels = issueWithLabels.labels ?? [];
39+
40+
const labelNames = labels.map(label =>
41+
(typeof label === "string" ? label : label.name ?? "").toLowerCase(),
42+
);
43+
44+
for (const [difficulty, aliases] of Object.entries(difficultyAliases)) {
45+
const found = aliases.some(alias =>
46+
labelNames.includes(alias.toLowerCase()),
47+
);
48+
49+
if (found) {
50+
return {
51+
label:
52+
difficulty === "good first issue"
53+
? "Good First Issue"
54+
: difficulty.charAt(0).toUpperCase() + difficulty.slice(1),
55+
value: difficulty,
56+
};
57+
}
58+
}
59+
60+
return null;
61+
}
62+
63+
function getDifficultyClasses(difficulty: string) {
64+
switch (difficulty.toLowerCase()) {
65+
case "good first issue":
66+
return "border-[#A7E8C7] bg-[#F0FFF7] text-[#16804F]";
67+
68+
case "beginner":
69+
return "border-[#B7D6FF] bg-[#F2F7FF] text-[#2768C7]";
70+
71+
case "intermediate":
72+
return "border-[#F1D27A] bg-[#FFF9E8] text-[#B47700]";
73+
74+
case "advanced":
75+
return "border-[#F2B4B4] bg-[#FFF3F3] text-[#C62828]";
76+
77+
default:
78+
return "border-gray-light bg-white text-gray";
79+
}
80+
}
81+
82+
function getRepositoryName(repositoryUrl: string) {
83+
return repositoryUrl.split("/").pop() ?? "unknown";
84+
}
85+
986
export default function IssueExplorer() {
1087
const [difficulty, setDifficulty] = useState<string>("");
1188
const [sdk, setSdk] = useState<string>("");
@@ -21,63 +98,223 @@ export default function IssueExplorer() {
2198
} = useIssues(difficulty, sdk);
2299

23100
return (
24-
<Container>
25-
<div className="mb-10">
26-
<h1 className="text-2xl mb-2.5 sm:text-4xl sm:mb-5">Issue Explorer</h1>
27-
<RichText
28-
className="text-lg max-w-full md:max-w-[800px]"
29-
markdown="Browse open issues across the Hiero SDKs by difficulty. The four levels below are the rungs of the [Issue Progression Initiative](/blog/hiero-issue-progression-initiative)."
101+
<div className="min-h-screen bg-white font-serif text-charcoal">
102+
{/* Hero */}
103+
<section className="relative overflow-hidden bg-[#24000F]">
104+
{/* Background gradient */}
105+
<div
106+
className="absolute inset-0"
107+
aria-hidden="true"
108+
style={{
109+
background: `
110+
radial-gradient(circle at 70% 20%, rgba(217, 45, 106, 0.55), transparent 34%),
111+
radial-gradient(circle at 88% 55%, rgba(184, 26, 86, 0.85), transparent 45%),
112+
linear-gradient(110deg, #21000d 0%, #5d0b2c 38%, #b81a56 100%)
113+
`,
114+
}}
115+
/>
116+
117+
{/* Subtle grid */}
118+
<div
119+
className="absolute inset-0 opacity-20"
120+
aria-hidden="true"
121+
style={{
122+
backgroundImage: `
123+
linear-gradient(rgba(255,255,255,0.12) 1px, transparent 1px),
124+
linear-gradient(90deg, rgba(255,255,255,0.12) 1px, transparent 1px)
125+
`,
126+
backgroundSize: "54px 54px",
127+
}}
30128
/>
31-
</div>
32-
33-
<div className="flex gap-4 mb-6">
34-
<select
35-
value={difficulty}
36-
onChange={e => {
37-
setDifficulty(e.target.value);
38-
}}>
39-
<option value="">All Difficulties</option>
40-
<option value="good first issue">Good First Issue</option>
41-
<option value="beginner">Beginner</option>
42-
<option value="intermediate">Intermediate</option>
43-
<option value="advanced">Advanced</option>
44-
</select>
45-
46-
<select
47-
value={sdk}
48-
onChange={e => {
49-
setSdk(e.target.value);
50-
}}>
51-
<option value="">All Repos</option>
52-
<option value="python">Python</option>
53-
<option value="javascript">JavaScript</option>
54-
<option value="go">Go</option>
55-
<option value="rust">Rust</option>
56-
<option value="java">Java</option>
57-
<option value="cpp">C++</option>
58-
<option value="swift">Swift</option>
59-
</select>
60-
</div>
61-
62-
{loading && <p>Loading...</p>}
63-
{typeof error === "string" && <p>{error}</p>}
64-
{!loading && !error && issues.length === 0 && (
65-
<p>No issues found matching the selected filters.</p>
66-
)}
67-
68-
<div className="grid grid-cols-4 gap-6">
69-
{issues.map(issue => (
70-
<div key={issue.id}>
71-
<a href={issue.html_url} target="_blank" rel="noopener noreferrer">
72-
<RichText markdown={issue.title ?? ""} className="line-clamp-2" />
73-
</a>
74-
75-
<p className="text-sm opacity-70 mt-2">
76-
{issue.repository_url.split("/").pop() ?? "unknown"}
77-
</p>
129+
130+
{/* Decorative network nodes */}
131+
<div
132+
className="absolute inset-0 opacity-30"
133+
aria-hidden="true"
134+
>
135+
<span className="absolute left-[29%] top-[42%] h-3 w-3 rounded-full bg-white/70 shadow-[0_0_0_12px_rgba(255,255,255,0.08)]" />
136+
<span className="absolute left-[47%] top-[62%] h-4 w-4 rounded-full bg-white/60 shadow-[0_0_0_18px_rgba(255,255,255,0.07)]" />
137+
<span className="absolute left-[69%] top-[28%] h-3 w-3 rounded-full bg-white/60 shadow-[0_0_0_14px_rgba(255,255,255,0.07)]" />
138+
<span className="absolute right-[10%] top-[54%] h-4 w-4 rounded-full bg-white/60 shadow-[0_0_0_18px_rgba(255,255,255,0.07)]" />
139+
<span className="absolute right-[28%] bottom-[17%] h-3 w-3 rounded-full bg-white/60 shadow-[0_0_0_14px_rgba(255,255,255,0.07)]" />
140+
141+
<div className="absolute left-[29%] top-[43%] h-px w-[21%] rotate-[18deg] bg-white/30" />
142+
<div className="absolute left-[48%] top-[62%] h-px w-[24%] -rotate-[29deg] bg-white/30" />
143+
<div className="absolute left-[69%] top-[29%] h-px w-[19%] rotate-[32deg] bg-white/30" />
144+
</div>
145+
146+
<Container>
147+
<div className="relative z-10 flex min-h-[580px] items-center py-24 sm:min-h-[580px] lg:min-h-[580px]">
148+
<div className="max-w-[620px]">
149+
<div className="mb-7 flex items-center gap-3 text-xs font-semibold uppercase tracking-[0.22em] text-white/75">
150+
<span className="h-2 w-2 rounded-full bg-red-light" />
151+
Open Source · Hiero SDKs
152+
</div>
153+
154+
<h1 className="max-w-[520px] text-6xl font-medium leading-[0.92] tracking-[-0.055em] text-white sm:text-7xl lg:text-[7.5rem] lg:leading-[0.88]">
155+
Issue
156+
<br />
157+
Explorer
158+
</h1>
159+
160+
<div className="my-8 h-px w-24 bg-white" />
161+
162+
<p className="max-w-[520px] text-lg leading-7 tracking-[-0.02em] text-white/80 sm:text-xl">
163+
Find open issues across Hiero SDKs and start contributing
164+
</p>
165+
166+
<div className="mt-8 flex flex-wrap gap-3">
167+
<span className="rounded-full border border-white/20 bg-white/15 px-5 py-2.5 text-sm font-medium text-white backdrop-blur-sm">
168+
{issues.length} open issues
169+
</span>
170+
171+
<span className="rounded-full border border-white/20 bg-white/15 px-5 py-2.5 text-sm font-medium text-white backdrop-blur-sm">
172+
Ready to contribute
173+
</span>
174+
</div>
175+
</div>
78176
</div>
79-
))}
80-
</div>
81-
</Container>
177+
</Container>
178+
</section>
179+
180+
{/* Filters */}
181+
<section className="border-b border-gray-light bg-white">
182+
<Container>
183+
<div className="flex flex-col gap-4 py-4 sm:flex-row sm:items-center sm:justify-between">
184+
<div className="flex flex-wrap gap-3">
185+
{/* Difficulty */}
186+
<div className="relative">
187+
<select
188+
value={difficulty}
189+
onChange={e => setDifficulty(e.target.value)}
190+
className="h-10 min-w-[140px] appearance-none rounded-lg border border-gray-light bg-white px-4 pr-10 text-sm font-medium text-charcoal outline-none transition-colors hover:border-gray focus:border-red focus:ring-2 focus:ring-red-light/20"
191+
>
192+
<option value="">All Difficulties</option>
193+
<option value="good first issue">Good First Issue</option>
194+
<option value="beginner">Beginner</option>
195+
<option value="intermediate">Intermediate</option>
196+
<option value="advanced">Advanced</option>
197+
</select>
198+
199+
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs text-gray">
200+
201+
</span>
202+
</div>
203+
204+
{/* Repository */}
205+
<div className="relative">
206+
<select
207+
value={sdk}
208+
onChange={e => setSdk(e.target.value)}
209+
className="h-10 min-w-[110px] appearance-none rounded-lg border border-gray-light bg-white px-4 pr-10 text-sm font-medium text-charcoal outline-none transition-colors hover:border-gray focus:border-red focus:ring-2 focus:ring-red-light/20"
210+
>
211+
<option value="">All Repos</option>
212+
<option value="python">Python</option>
213+
<option value="javascript">JavaScript</option>
214+
<option value="go">Go</option>
215+
<option value="rust">Rust</option>
216+
<option value="java">Java</option>
217+
<option value="cpp">C++</option>
218+
<option value="swift">Swift</option>
219+
</select>
220+
221+
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs text-gray">
222+
223+
</span>
224+
</div>
225+
</div>
226+
227+
<div className="w-fit rounded-lg border border-gray-light bg-gray-light/40 px-4 py-2 text-sm font-medium text-charcoal">
228+
{issues.length} {issues.length === 1 ? "issue" : "issues"}
229+
</div>
230+
</div>
231+
</Container>
232+
</section>
233+
234+
{/* Issue list */}
235+
<main className="bg-[#FAFAF9]">
236+
<Container>
237+
<div className="py-9 sm:py-10">
238+
{loading && (
239+
<div className="py-20 text-center">
240+
<p className="text-lg text-gray">Loading issues...</p>
241+
</div>
242+
)}
243+
244+
{typeof error === "string" && (
245+
<div className="rounded-xl border border-red/20 bg-red/5 p-6 text-red">
246+
<p>{error}</p>
247+
</div>
248+
)}
249+
250+
{!loading && !error && issues.length === 0 && (
251+
<div className="py-20 text-center">
252+
<p className="text-lg text-gray">
253+
No issues found matching the selected filters.
254+
</p>
255+
</div>
256+
)}
257+
258+
{!loading && !error && issues.length > 0 && (
259+
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
260+
{issues.map(issue => {
261+
const issueDifficulty = getDifficulty(issue);
262+
const repository = getRepositoryName(issue.repository_url);
263+
console.log(issue.title, issue.labels);
264+
return (
265+
<a
266+
key={issue.id}
267+
href={issue.html_url}
268+
target="_blank"
269+
rel="noopener noreferrer"
270+
className="group flex min-h-[185px] flex-col rounded-xl border border-[#E8E8E6] bg-white p-5 transition-all duration-200 hover:-translate-y-0.5 hover:border-red/30 hover:shadow-[0_8px_30px_rgba(30,30,30,0.08)]"
271+
>
272+
{/* Repository */}
273+
<div>
274+
<span className="inline-flex rounded-md bg-red/5 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.04em] text-red">
275+
{repository}
276+
</span>
277+
</div>
278+
279+
{/* Title */}
280+
<div className="mt-4 flex-1">
281+
<RichText
282+
markdown={issue.title ?? ""}
283+
className="line-clamp-3 text-[15px] font-medium leading-5 tracking-[-0.02em] text-charcoal"
284+
/>
285+
</div>
286+
287+
{/* Bottom row */}
288+
<div className="mt-5 flex items-center justify-between border-t border-gray-light pt-4">
289+
<div>
290+
{issueDifficulty ? (
291+
<span
292+
className={`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-medium ${getDifficultyClasses(
293+
issueDifficulty.value,
294+
)}`}
295+
>
296+
<span className="h-1.5 w-1.5 rounded-full bg-current" />
297+
{issueDifficulty.label}
298+
</span>
299+
) : (
300+
<span className="text-xs text-gray">
301+
Open issue
302+
</span>
303+
)}
304+
</div>
305+
306+
<span className="text-lg text-gray transition-transform duration-200 group-hover:translate-x-1 group-hover:text-red">
307+
308+
</span>
309+
</div>
310+
</a>
311+
);
312+
})}
313+
</div>
314+
)}
315+
</div>
316+
</Container>
317+
</main>
318+
</div>
82319
);
83-
}
320+
}

0 commit comments

Comments
 (0)