Skip to content

Commit 6b95f35

Browse files
Merge pull request #697 from swathi2006/fix-ideas
fix: resolve broken "Know more" links by dynamically fetching ideas from Info repo
2 parents 201ac36 + 5b1064c commit 6b95f35

File tree

5 files changed

+189
-26
lines changed

5 files changed

+189
-26
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Container } from '@/components/shared/Container';
2+
3+
export default function NotFound() {
4+
return (
5+
<Container className="mt-32">
6+
<h1 className="text-center text-4xl font-bold font-mono">
7+
Idea not found
8+
</h1>
9+
<p className="text-center mt-4 font-mono">
10+
We couldn&apos;t find the GSOC idea you&apos;re looking for.
11+
</p>
12+
</Container>
13+
);
14+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { fetchIdeaContent, fetchIdeaSlugsFromGitHub, getCurrentYear } from '@/helper/fetchGitHubIdeas';
2+
import { IdeaLayout } from '@/components/ideas/IdeaLayout';
3+
import ReactMarkdown from 'react-markdown';
4+
import remarkGfm from 'remark-gfm-4';
5+
import rehypeRaw from 'rehype-raw';
6+
import rehypeSanitize from 'rehype-sanitize';
7+
import { notFound } from 'next/navigation';
8+
9+
// Generate all static routes
10+
export async function generateStaticParams() {
11+
const currentYear = getCurrentYear();
12+
const allParams = [];
13+
14+
try {
15+
for (let year = 2022; year <= currentYear; year++) {
16+
const yearStr = year.toString();
17+
18+
// GitHub idea slugs
19+
const githubSlugs = await fetchIdeaSlugsFromGitHub(yearStr);
20+
githubSlugs.forEach((slug) => {
21+
allParams.push({
22+
year: yearStr,
23+
slug: slug,
24+
});
25+
});
26+
}
27+
} catch (error) {
28+
console.error('Error generating static params:', error);
29+
}
30+
31+
// De-duplicate params
32+
const uniqueParams = Array.from(
33+
new Set(allParams.map((p) => JSON.stringify(p)))
34+
).map((s) => JSON.parse(s));
35+
36+
return uniqueParams;
37+
}
38+
39+
// ✅ Metadata (lightweight)
40+
export async function generateMetadata({ params }) {
41+
const { year, slug } = params;
42+
43+
return {
44+
title: `${slug} - GSOC ${year} Idea`,
45+
description: `Google Summer of Code ${year} Idea`,
46+
};
47+
}
48+
49+
// ✅ Page rendering
50+
export default async function IdeaPage({ params }) {
51+
const { year, slug } = params;
52+
53+
const idea = await fetchIdeaContent(slug, year);
54+
55+
if (!idea) {
56+
notFound();
57+
}
58+
59+
const meta = {
60+
title: idea.title,
61+
};
62+
63+
return (
64+
<IdeaLayout meta={meta}>
65+
<ReactMarkdown
66+
remarkPlugins={[remarkGfm]}
67+
rehypePlugins={[rehypeRaw, rehypeSanitize]}
68+
components={{
69+
a: (props) => (
70+
<a
71+
{...props}
72+
target="_blank"
73+
rel="noopener noreferrer"
74+
className="text-green-600 dark:text-yellow-400 underline"
75+
/>
76+
),
77+
}}
78+
>
79+
{idea.content}
80+
</ReactMarkdown>
81+
</IdeaLayout>
82+
);
83+
}

src/app/ideas/[year]/page.jsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,41 @@
11
import { getIdeas } from '@/lib/ideas'
2+
import { fetchIdeasFromGitHub, getCurrentYear } from '@/helper/fetchGitHubIdeas'
23
import { IdeasList } from '@/components/ideas/IdeasList'
34
import { Container } from '@/components/shared/Container'
45

56
export async function generateMetadata({ params }) {
7+
const { year } = params
68
return {
7-
title: `Idea List ${params.year}`,
8-
description: `Idea List for GSOC ${params.year}`,
9+
title: `Idea List ${year}`,
10+
description: `Idea List for GSOC ${year}`,
911
}
1012
}
1113

1214
export async function generateStaticParams() {
13-
// Define the years we support
14-
return [
15-
{ year: '2022' },
16-
{ year: '2023' },
17-
{ year: '2024' },
18-
{ year: '2025' },
19-
]
15+
const currentYear = getCurrentYear()
16+
const years = Array.from({ length: currentYear - 2022 + 1 }, (_, i) => (2022 + i).toString())
17+
return years.map(year => ({ year }))
2018
}
2119

2220
export default async function YearIdeasPage({ params }) {
2321
const { year } = params
24-
const articles = await getIdeas(year)
22+
const localArticles = await getIdeas(year)
23+
const githubArticles = await fetchIdeasFromGitHub(year)
24+
25+
// Merge and remove duplicates by slug
26+
const allArticles = [...localArticles]
27+
githubArticles.forEach(gi => {
28+
if (!allArticles.some(li => li.slug.toLowerCase() === gi.slug.toLowerCase())) {
29+
allArticles.push(gi)
30+
}
31+
})
32+
33+
const articles = allArticles.sort((a, b) => (a.title || '').localeCompare(b.title || ''))
2534

26-
if (!articles) {
35+
if (articles.length === 0) {
2736
return (
2837
<Container className="mt-32">
29-
<h1 className="text-center text-4xl font-bold">Ideas not found for {year}</h1>
38+
<h1 className="text-center text-4xl font-bold font-mono">Ideas not found for {year}</h1>
3039
</Container>
3140
)
3241
}

src/app/ideas/page.jsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,38 @@
11
import { IdeasDisplay } from '@/components/ideas/IdeasDisplay'
22
import { IdeasHeader } from '@/components/ideas/IdeasHeader'
33
import { fetchIdeasFromGitHub, getCurrentYear } from '@/helper/fetchGitHubIdeas'
4+
import { getIdeas } from '@/lib/ideas'
45

56
export const metadata = {
67
title: 'Idea List',
78
description: 'Idea List for GSOC',
89
}
910

11+
export const revalidate = 3600
12+
1013
export default async function IdeasPage() {
1114
const currentYear = getCurrentYear()
12-
const articles = await fetchIdeasFromGitHub(currentYear)
15+
const years = Array.from({ length: currentYear - 2022 + 1 }, (_, i) => currentYear - i)
1316

14-
const validIdeasData = [
15-
{
16-
year: currentYear.toString(),
17-
articles
17+
const ideasData = await Promise.all(years.map(async (year) => {
18+
const localArticles = await getIdeas(year.toString())
19+
const githubArticles = await fetchIdeasFromGitHub(year)
20+
21+
// Merge and remove duplicates by slug (case-insensitive)
22+
const articles = [...localArticles]
23+
githubArticles.forEach(gi => {
24+
if (!articles.some(li => li.slug.toLowerCase() === gi.slug.toLowerCase())) {
25+
articles.push(gi)
26+
}
27+
})
28+
29+
return {
30+
year: year.toString(),
31+
articles: articles.sort((a, b) => (a.title || '').localeCompare(b.title || ''))
1832
}
19-
]
33+
}))
34+
35+
const validIdeasData = ideasData.filter(data => data.articles.length > 0)
2036

2137
return (
2238
<IdeasHeader validIdeasData={validIdeasData} currentYear={currentYear}>

src/helper/fetchGitHubIdeas.js

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,12 @@ export async function fetchIdeasFromGitHub(year = getCurrentYear()) {
122122
headers: {
123123
'Accept': 'application/vnd.github.v3+json',
124124
},
125-
});
126-
125+
next: { revalidate: 3600 },
126+
})
127127
if (!response.ok) {
128-
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
128+
const error = new Error(`GitHub API error: ${response.status} ${response.statusText}`);
129+
error.status = response.status;
130+
throw error;
129131
}
130132

131133
const files = await response.json();
@@ -156,8 +158,51 @@ export async function fetchIdeasFromGitHub(year = getCurrentYear()) {
156158

157159
return ideas;
158160
} catch (error) {
161+
if (error.status === 404) {
162+
return [];
163+
}
159164
console.error('Error fetching ideas from GitHub:', error);
160-
return [];
165+
throw error;
166+
}
167+
}
168+
169+
/**
170+
* Fetch only idea slugs from GitHub for a specific year
171+
*/
172+
export async function fetchIdeaSlugsFromGitHub(year = getCurrentYear()) {
173+
try {
174+
const contentsUrl = `${GITHUB_API_BASE}/repos/${REPO_OWNER}/${REPO_NAME}/contents/${IDEAS_PATH}/${year}`;
175+
176+
const response = await fetch(contentsUrl, {
177+
headers: {
178+
'Accept': 'application/vnd.github.v3+json',
179+
},
180+
next: { revalidate: 3600 },
181+
});
182+
183+
if (!response.ok) {
184+
const error = new Error(`GitHub API error: ${response.status} ${response.statusText}`);
185+
error.status = response.status;
186+
throw error;
187+
}
188+
189+
const files = await response.json();
190+
191+
// Filter for .md files and return only slugs
192+
return files
193+
.filter(file =>
194+
file.type === 'file' &&
195+
file.name.endsWith('.md') &&
196+
file.name !== 'index.md' &&
197+
file.name !== 'Template.md'
198+
)
199+
.map(file => file.name.replace(/\.md$/, ''));
200+
} catch (error) {
201+
if (error.status === 404) {
202+
return [];
203+
}
204+
console.error(`Error fetching idea slugs from GitHub for ${year}:`, error);
205+
throw error;
161206
}
162207
}
163208

@@ -168,8 +213,6 @@ export async function fetchIdeaContent(slug, year = getCurrentYear()) {
168213
try {
169214
const fileUrl = `https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/main/${IDEAS_PATH}/${year}/${slug}.md`;
170215

171-
console.log(`Fetching idea content from: ${fileUrl}`);
172-
173216
const response = await fetch(fileUrl);
174217

175218
if (!response.ok) {
@@ -184,8 +227,6 @@ export async function fetchIdeaContent(slug, year = getCurrentYear()) {
184227
return null;
185228
}
186229

187-
console.log(`Successfully fetched ${slug}, content length: ${content.length}`);
188-
189230
return {
190231
slug,
191232
title: formatTitle(slug + '.md'),

0 commit comments

Comments
 (0)