Skip to content
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
45563aa
feat: New IssueExplor PR
aceppaluni Apr 27, 2026
b6e30a6
fix: Fix Deployment
aceppaluni Apr 27, 2026
143279e
fix: Fix Codacy
aceppaluni Apr 27, 2026
8532a72
fix: Fix Format And Codacy
aceppaluni Apr 27, 2026
22da94a
fix: Fix Errors
aceppaluni Apr 27, 2026
5de0081
fix: Fix Codacy Errors
aceppaluni Apr 27, 2026
4e702a5
fix: Fix Codacy With Rule
aceppaluni Apr 28, 2026
2346729
fix: Fix Final Codacy Errors
aceppaluni Apr 28, 2026
cb8c6f0
fix: Fix Updated Errs
aceppaluni Apr 28, 2026
b2ccc0c
fix: Make New Changes
aceppaluni May 4, 2026
82287e4
fix: Make Codacy Changes
aceppaluni May 4, 2026
ab654d6
fix: Make New Codacy Changes
aceppaluni May 4, 2026
ddf57e9
fix: Make Additional Codacy Changes
aceppaluni May 4, 2026
f354bb9
fix: Fixing Codacy
aceppaluni May 4, 2026
3ac912b
fix: Fixing More Codacy
aceppaluni May 4, 2026
fcf53a9
fix: Implement Suggestions
aceppaluni May 5, 2026
33f3807
fix: Fix Some Codacy Issues
aceppaluni May 6, 2026
e91e4d9
fix: Fix Some More Codacy Issues
aceppaluni May 6, 2026
5d3f6ba
fix: Codacy
aceppaluni May 6, 2026
ad3dea2
fix: Fix Codacy Phase 1
aceppaluni May 7, 2026
1b0ebac
fix: Fix Codacy Phase 2
aceppaluni May 7, 2026
7e6edc7
fix: Fix Codacy Phase 3
aceppaluni May 7, 2026
5ffd5a1
fix: Fix Codacy Phase 4
aceppaluni May 7, 2026
4448acb
fix: Fix Codacy Phase 5
aceppaluni May 7, 2026
787d3a7
fix: Fix Codacy Phase 6
aceppaluni May 7, 2026
dbb9ae6
fix: Fix Codacy Phase 7
aceppaluni May 7, 2026
bde4b08
fix: Fix Codacy Phase 8
aceppaluni May 7, 2026
cf19ca1
fix: Fix Codacy Phase 9
aceppaluni May 7, 2026
07345b6
fix: Fix Codacy Phase 10
aceppaluni May 8, 2026
acfaa67
fix: Fix Codacy Phase 11
aceppaluni May 8, 2026
d1767ad
fix: Fix Codacy Phase 12
aceppaluni May 8, 2026
597ab48
fix: Fix Codacy Phase 13
aceppaluni May 8, 2026
ba53e00
fix: Fix Codacy Phase 14
aceppaluni May 11, 2026
c647ee5
fix: Fix Codacy Phase 15
aceppaluni May 11, 2026
d0ce6b8
fix: Fix Codacy Phase 16
aceppaluni May 11, 2026
a291588
fix: Fix Codacy Phase 17
aceppaluni May 11, 2026
725185d
fix: Fix Codacy Phase 18
aceppaluni May 11, 2026
803cc10
Merge branch 'main' into IssueTab
aceppaluni May 12, 2026
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
7 changes: 5 additions & 2 deletions netlify.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[build]
command = "pnpm build"
publish = "out"
publish = ".next"

[build.environment]
NODE_VERSION = "20"
NODE_VERSION = "20"

[[plugins]]
package = "@netlify/plugin-nextjs"
2 changes: 1 addition & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
output: "export",
//output: "export",
images: {
unoptimized: true,
},
Expand Down
27 changes: 27 additions & 0 deletions src/app/api/issues/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { searchIssues as searchIssues } from "src/lib/github/issues";

function getStatus(error: unknown): number {
if (typeof error === "object" && error !== null && "status" in error) {
const status = (error as { status?: unknown }).status;
if (typeof status === "number") return status;
}

return 502;
}

export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const q = searchParams.get("q") ?? "";

try {
const data = await searchIssues(q);

Check warning on line 17 in src/app/api/issues/route.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/app/api/issues/route.ts#L17

Unsafe assignment of an error typed value.

Check warning on line 17 in src/app/api/issues/route.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/app/api/issues/route.ts#L17

Unsafe call of an `error` type typed value.
return Response.json(data);
} catch (error) {
const status = getStatus(error);

const message =
error instanceof Error ? error.message : "Failed to fetch issues";

return Response.json({ items: [], error: message }, { status });
}
}
222 changes: 222 additions & 0 deletions src/app/issues/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
"use client";

Check warning on line 1 in src/app/issues/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/app/issues/page.tsx#L1

Method *global* has 59 lines of code (limit is 50)

import RichText from "@/components/RichText";
import Container from "@/components/Container";
import { useEffect, useState } from "react";

/* -----------------------------
Types
------------------------------ */
interface GitHubIssue {
id: number;
title: string;
html_url: string;
repository_url: string;
}

interface GitHubSearchResponse {
items: GitHubIssue[];
error?: string;
}

/* -----------------------------
Debounce hook
------------------------------ */
function useDebouncedValue<T>(value: T, delay = 400) {
const [debounced, setDebounced] = useState(value);

useEffect(() => {
const timer = setTimeout(() => {
setDebounced(value);
}, delay);

return () => {
clearTimeout(timer);
};
}, [value, delay]);

return debounced;
}

/* -----------------------------
Maps
------------------------------ */
const sdkMap: Record<string, string> = {
python: "repo:hiero-ledger/hiero-sdk-python",
javascript: "repo:hiero-ledger/hiero-sdk-js",
cpp: "repo:hiero-ledger/hiero-sdk-cpp",
java: "repo:hiero-ledger/hiero-sdk-java",
go: "repo:hiero-ledger/hiero-sdk-go",
rust: "repo:hiero-ledger/hiero-sdk-rust",
block_node: "repo:hiero-ledger/hiero-block-node",
mirror_node: "repo:hiero-ledger/hiero-mirror-node",
consensus_node: "repo:hiero-ledger/hiero-consensus-node",
hiero_docs: "repo:hiero-ledger/hiero-docs",
};

const difficultyMap: Record<string, string[]> = {
"good first issue": [
"good first issue",
"good-first-issue",
"starter",
"easy",
],
beginner: ["beginner", "easy", "starter"],
intermediate: ["intermediate"],
advanced: ["advanced"],
};

/* -----------------------------
Cache
------------------------------ */
const cache = new Map<string, GitHubSearchResponse>();

/* -----------------------------
Helpers
------------------------------ */
function matchesDifficulty(issue: GitHubIssue, difficulty: string) {
if (!difficulty) return true;

const text = issue.title.toLowerCase();
const keywords = difficultyMap[difficulty];

Check warning on line 81 in src/app/issues/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/app/issues/page.tsx#L81

Variable Assigned to Object Injection Sink

if (!keywords) return true;

Check warning on line 83 in src/app/issues/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/app/issues/page.tsx#L83

Unnecessary conditional, value is always falsy.

return keywords.some(k => text.includes(k));
}

/* -----------------------------
Component
------------------------------ */
export default function GoodFirstIssues() {
const [issues, setIssues] = useState<GitHubIssue[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

const [difficulty, setDifficulty] = useState("");
const [sdk, setSdk] = useState("");

const debouncedDifficulty = useDebouncedValue(difficulty);
const debouncedSdk = useDebouncedValue(sdk);

const getIssues = async (query: string, signal?: AbortSignal) => {
if (cache.has(query)) {
return cache.get(query)!;

Check warning on line 104 in src/app/issues/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/app/issues/page.tsx#L104

Forbidden non-null assertion.
}

const res = await fetch(`/api/issues?q=${encodeURIComponent(query)}`, {

Check failure on line 107 in src/app/issues/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/app/issues/page.tsx#L107

This application allows user-controlled URLs to be passed directly to HTTP client libraries.
signal,
});

const data = (await res.json()) as GitHubSearchResponse;

if (!res.ok) {
throw new Error(data.error ?? "Failed to fetch issues");
}

cache.set(query, data);
return data;
};

useEffect(() => {
const controller = new AbortController();

const fetchIssues = async () => {
setLoading(true);
setError(null);

try {
const base = "is:issue state:open";

const repos =
debouncedSdk && debouncedSdk in sdkMap
? [sdkMap[debouncedSdk]]

Check warning on line 133 in src/app/issues/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/app/issues/page.tsx#L133

Generic Object Injection Sink
: Object.values(sdkMap);

const results = await Promise.all(
repos.map(repo => {
const query = `${base} ${repo}`;
return getIssues(query, controller.signal);
}),
);

const merged = results.flatMap(r => r.items);

const unique = Array.from(new Map(merged.map(i => [i.id, i])).values());

const filtered = debouncedDifficulty
? unique.filter(i => matchesDifficulty(i, debouncedDifficulty))
: unique;

setIssues(filtered);
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") return;

setError(err instanceof Error ? err.message : "Unknown error occurred");
setIssues([]);
} finally {
setLoading(false);
}
};

void fetchIssues();

return () => {
controller.abort();
};
}, [debouncedDifficulty, debouncedSdk]);

return (
<Container>
{/* Filters */}
<div className="flex gap-4 mb-6">
<select
value={difficulty}
onChange={e => setDifficulty(e.target.value)}

Check warning on line 175 in src/app/issues/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/app/issues/page.tsx#L175

Returning a void expression from an arrow function shorthand is forbidden. Please add braces to the arrow function.
className="p-2 rounded border">
<option value="">All Difficulties</option>
<option value="good first issue">Good First Issue</option>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>

<select
value={sdk}
onChange={e => setSdk(e.target.value)}

Check warning on line 186 in src/app/issues/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/app/issues/page.tsx#L186

Returning a void expression from an arrow function shorthand is forbidden. Please add braces to the arrow function.
className="p-2 rounded border">
<option value="">All Repos</option>
<option value="python">Python</option>
<option value="javascript">JavaScript</option>
<option value="cpp">C++</option>
<option value="java">Java</option>
<option value="go">Go</option>
<option value="rust">Rust</option>
<option value="block_node">Block Node</option>
<option value="mirror_node">Mirror Node</option>
<option value="consensus_node">Consensus Node</option>
<option value="hiero_docs">Hiero Docs</option>
</select>
Comment thread
aceppaluni marked this conversation as resolved.
</div>

{/* Issues */}
{loading && <p>Loading issues...</p>}
{error && <p className="text-red-500">{error}</p>}

<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{issues.map(issue => (
<div
key={issue.id}
className="bg-gradient-to-br from-white-dark via-white to-white p-4 rounded-xl shadow-md">
<a href={issue.html_url} target="_blank" rel="noopener noreferrer">
<RichText markdown={issue.title} className="line-clamp-2" />
</a>
<p className="text-sm opacity-70 mt-2">
{issue.repository_url.split("/").pop()}
</p>
</div>
))}
</div>
</Container>
);
}
1 change: 1 addition & 0 deletions src/components/Menu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const menuItems: MenuItem[] = [
{ name: "Connect", href: "/#connect" },
{ name: "Blog", href: "/blog/" },
{ name: "TSC", href: "/tsc/" },
{ name: "Issue Explorer", href: "/issues/" },
{
name: "Calendar",
href: "https://zoom-lfx.platform.linuxfoundation.org/meetings/hiero?view=week",
Expand Down
48 changes: 48 additions & 0 deletions src/lib/github/issues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export interface GitHubIssue {
id: number;
title: string;
html_url: string;
repository_url: string;
}

export interface GitHubSearchResponse {
items: GitHubIssue[];
total_count: number;
}

export async function searchIssues(
query: string,
): Promise<GitHubSearchResponse> {
const headers: Record<string, string> = {
Accept: "application/vnd.github+json",
"User-Agent": "hiero-website",
};

if (process.env.GITHUB_TOKEN) {
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
}

const res = await fetch(
`https://api.github.com/search/issues?q=${encodeURIComponent(query)}`,
{ headers },
);

if (!res.ok) {
const body = await res.json().catch(() => ({}));
const message =
typeof body?.message === "string"
? body.message
: "GitHub request failed";
throw Object.assign(new Error(message), { status: res.status });
}

return (await res.json()) as GitHubSearchResponse;
}

export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const q = searchParams.get("q") || "";

const data = await searchIssues(q);
return Response.json(data);
}
Loading