Skip to content

Commit 3f0f462

Browse files
committed
Enable multi-language repo search and selection
Updated the hero component to allow users to select multiple programming languages via checkboxes, both from main and 'other' languages. Modified the repo page logic to accept and process multiple languages, fetching and merging results from GitHub for each selected language. This enhances the search experience by supporting multi-language queries. (commit desc by copilot)
1 parent 24946ac commit 3f0f462

File tree

2 files changed

+101
-40
lines changed

2 files changed

+101
-40
lines changed

src/app/(public)/_components/hero.tsx

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
'use client';
22

33
import { useRouter } from 'next/navigation';
4+
import { useMemo, useState } from 'react';
45

56
import { Search } from 'lucide-react';
6-
import { LanguageButton } from './language-button';
77
import { Button } from './button';
8-
import Link from 'next/link';
98

109
import { sortByName } from '@/lib/utils';
1110
import languages from '@/assets/languages.json';
@@ -17,11 +16,34 @@ const { main: mainLanguages, others: otherLanguages } = languages;
1716
export function Hero() {
1817
const router = useRouter();
1918

19+
// Track selected languages as a string array
20+
const [selected, setSelected] = useState<string[]>([]);
21+
22+
const toggleLanguage = (language: string) => {
23+
setSelected(prev =>
24+
prev.includes(language) ? prev.filter(l => l !== language) : [...prev, language]
25+
);
26+
};
27+
28+
const sortedOthers = useMemo(() => [...otherLanguages].sort(sortByName), []);
29+
2030
function handleSearch(e: React.FormEvent) {
31+
e.preventDefault();
2132
const formData = new FormData(e.target as HTMLFormElement);
22-
const lang = formData.get('search') as string;
23-
if (lang.trim() === '') return;
24-
router.push(`/repos/${lang}`);
33+
let chosen = selected;
34+
35+
// Fallback: if no checkbox selected, use the single input value
36+
if (chosen.length === 0) {
37+
const typed = String(formData.get('search') || '').trim();
38+
if (typed) {
39+
chosen = [typed];
40+
}
41+
}
42+
43+
if (chosen.length === 0) return; // nothing to search
44+
45+
const csv = chosen.map(l => l.toLowerCase()).join(',');
46+
router.push(`/repos?l=${encodeURIComponent(csv)}`);
2547
}
2648

2749
return (
@@ -39,7 +61,7 @@ export function Hero() {
3961
<div className="relative flex w-full">
4062
<input
4163
type="text"
42-
placeholder="Search for your language"
64+
placeholder="Type a language (optional)"
4365
className="w-full max-w-xs bg-transparent rounded-tr-none rounded-br-none input input-bordered text-hacktoberfest-light border-hacktoberfest-light
4466
focus:border-hacktoberfest-light focus:!outline-none focus-visible:!outline-none placeholder:text-hacktoberfest-light text-sm sm:text-base"
4567
name="search"
@@ -54,33 +76,56 @@ export function Hero() {
5476
</div>
5577
</form>
5678
<p className="font-medium uppercase text-hacktoberfest-light text-sm sm:text-base">
57-
Or select the programming language you would like to find
79+
Or select the programming languages you would like to find
5880
repositories for.
5981
</p>
82+
6083
<div className="flex flex-wrap gap-4 sm:gap-6 items-center justify-center">
61-
{mainLanguages.map(language => (
62-
<LanguageButton key={language} language={language} />
63-
))}
84+
{mainLanguages.map(language => {
85+
const id = `lang-${language}`;
86+
const checked = selected.includes(language);
87+
return (
88+
<label key={language} htmlFor={id} className="flex items-center gap-2 cursor-pointer select-none">
89+
<input
90+
id={id}
91+
type="checkbox"
92+
className="checkbox checkbox-primary"
93+
checked={checked}
94+
onChange={() => toggleLanguage(language)}
95+
/>
96+
<span className="text-hacktoberfest-light text-sm sm:text-base">{language}</span>
97+
</label>
98+
);
99+
})}
64100
</div>
101+
65102
<div className="dropdown dropdown-top">
66103
<Button tabIndex={0} className="umami--click--otherlangs-button text-sm sm:text-base">
67104
Other languages
68105
</Button>
69106

70107
<ul
71108
tabIndex={0}
72-
className="h-64 p-2 overflow-y-auto shadow-lg menu dropdown-content bg-white/95 backdrop-blur-sm rounded-xl w-60 border border-gray-200/50 z-[9999]"
109+
className="h-64 p-2 overflow-y-auto shadow-lg menu dropdown-content bg-white/95 backdrop-blur-sm rounded-xl w-72 border border-gray-200/50 z-[9999]"
73110
>
74-
{otherLanguages.sort(sortByName).map(language => (
75-
<li key={language}>
76-
<Link
77-
href={`/repos/${language.toLowerCase()}`}
78-
className="text-gray-700 hover:text-white hover:bg-hacktoberfest-blue rounded-lg transition-colors duration-200 px-3 py-2 text-sm"
79-
>
80-
{language}
81-
</Link>
82-
</li>
83-
))}
111+
{sortedOthers.map(language => {
112+
const id = `other-${language}`;
113+
const checked = selected.includes(language);
114+
return (
115+
<li key={language} className="px-1">
116+
<label htmlFor={id} className="flex items-center gap-3 rounded-lg px-3 py-2 hover:bg-hacktoberfest-blue/80 hover:text-white cursor-pointer">
117+
<input
118+
id={id}
119+
type="checkbox"
120+
className="checkbox checkbox-primary"
121+
checked={checked}
122+
onChange={() => toggleLanguage(language)}
123+
/>
124+
<span className="text-sm text-gray-800">{language}</span>
125+
</label>
126+
</li>
127+
);
128+
})}
84129
</ul>
85130
</div>
86131
</div>

src/app/(public)/repos/[language]/page.tsx

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,7 @@ async function getRepos(
105105
? `stars:<${endStars}`
106106
: '';
107107

108-
const apiUrl = new URL('https://api.github.com/search/repositories');
109-
apiUrl.searchParams.set('page', page.toString());
110-
apiUrl.searchParams.set('per_page', '21');
111-
apiUrl.searchParams.set('sort', sort.toString());
112-
apiUrl.searchParams.set('order', order.toString());
113-
apiUrl.searchParams.set(
114-
'q',
115-
`topic:hacktoberfest language:${language} ${searchQuery} ${starsQuery}`
116-
);
117-
108+
const languages = language.split(',').map(l => l.trim()); // split multi-language
118109
const headers: HeadersInit = {
119110
Accept: 'application/vnd.github.mercy-preview+json'
120111
};
@@ -136,25 +127,50 @@ async function getRepos(
136127
headers.Authorization = `Bearer ${env.AUTH_GITHUB_TOKEN}`;
137128
}
138129

139-
const res = await fetch(apiUrl, { headers });
140-
if (!res.ok) return undefined;
141-
142-
const repos = (await res.json()) as RepoData;
143130
const reports = await getReportedRepos();
131+
let allRepos: RepoItem[] = [];
132+
133+
// fetch github repos for each language and merge
134+
for (const lang of languages) {
135+
const apiUrl = new URL('https://api.github.com/search/repositories');
136+
apiUrl.searchParams.set('page', page.toString());
137+
apiUrl.searchParams.set('per_page', '21');
138+
apiUrl.searchParams.set('sort', sort.toString());
139+
apiUrl.searchParams.set('order', order.toString());
140+
apiUrl.searchParams.set(
141+
'q',
142+
`topic:hacktoberfest language:${lang} ${searchQuery} ${starsQuery}`
143+
);
144+
145+
const res = await fetch(apiUrl, { headers });
146+
if (!res.ok) continue;
147+
148+
const reposData: RepoData = await res.json();
149+
const filteredItems = reposData.items.filter(
150+
(repo: RepoItem) => !repo.archived && !reports.find(r => r.repoId === repo.id)
151+
);
152+
153+
allRepos = allRepos.concat(filteredItems);
154+
}
144155

145-
repos.items = repos.items.filter((repo: RepoItem) => {
146-
return !repo.archived && !reports.find(report => report.repoId === repo.id);
147-
});
156+
if (allRepos.length < 1) return undefined;
148157

149-
if (!Array.isArray(repos.items) || repos.items?.length < 1) return undefined;
158+
// sort merged repos by updated date descending
159+
allRepos.sort(
160+
(a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
161+
);
150162

151163
return {
152164
page: +page.toString(),
153165
languageName: language,
154-
repos
166+
repos: {
167+
...{ total_count: allRepos.length, incomplete_results: false },
168+
items: allRepos
169+
}
155170
};
156171
}
157172

173+
158174
async function getReportedRepos() {
159175
const reports = await db
160176
.select()

0 commit comments

Comments
 (0)