Skip to content
This repository has been archived by the owner on Jul 10, 2024. It is now read-only.

Add program web page #4845

Merged
merged 3 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
GITHUB_API_URL=https://api.github.com

CODINASION_GITHUB_TOKEN=insert_your_token_here

PROGRAM_DATA_URL=https://raw.githubusercontent.com/codinasion/codinasion/program-data
7 changes: 7 additions & 0 deletions apps/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,29 @@
"lint": "eslint . --max-warnings 0"
},
"dependencies": {
"@tailwindcss/typography": "^0.5.10",
"moment": "^2.29.4",
"next": "14.0.3",
"next-themes": "^0.2.1",
"react": "^18",
"react-dom": "^18",
"react-icons": "^4.12.0",
"react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.5.0",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.0",
"sharp": "^0.33.0"
},
"devDependencies": {
"@codinasion/eslint-config": "workspace:*",
"@codinasion/language-data": "workspace:*",
"@codinasion/typescript-config": "workspace:*",
"@next/eslint-plugin-next": "^14.0.2",
"@types/eslint": "^8.44.7",
"@types/node": "^20.10.0",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.10",
"autoprefixer": "^10.0.1",
"eslint": "^8.54.0",
"eslint-config-next": "14.0.3",
Expand Down
84 changes: 84 additions & 0 deletions apps/website/src/app/program/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { Metadata } from "next";
import ProgramDetailComponent from "@/components/Program/program-detail-component";
import { SiteMetadata, GetProgramList, GetProgramData } from "@/data";
import type { ProgramDataType, ProgramListType } from "@/types";

////////////////////////////////////////////////////////////////////////////////
// https://beta.nextjs.org/docs/api-reference/metadata#generatemetadata-function
// Generate metadata for this page
export async function generateMetadata({
params,
}: {
params: { slug: string };
}): Promise<Metadata> {
const slug = params.slug;

const programData: ProgramDataType = await GetProgramData(slug);

const title = programData.title;
const description = programData.description;

return {
title,
description,

keywords: ["codinasion", "program", "open source"].concat(
programData.languages.map((language) => language),
),

openGraph: {
title,
description,
url: `${SiteMetadata.site_url}/program/${slug}`,
siteName: SiteMetadata.title,
images: [
// TODO: Add og:image for program language page
],
locale: "en_US",
type: "website",
},
twitter: {
card: "summary_large_image",
title,
description,
siteId: SiteMetadata.twitter_userid,
creator: `@${SiteMetadata.twitter_username}`,
creatorId: SiteMetadata.twitter_userid,
images: [
// TODO: Add twitter:image for program language page
],
},
};
}
// End of metadata generation
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
// https://beta.nextjs.org/docs/data-fetching/generating-static-params
// Make this page statically generated, with dynamic params
export const dynamicParams = true;
export async function generateStaticParams() {
const ProgramData = await GetProgramList();

return ProgramData.slice(0, 1).map((program: ProgramListType) => ({
slug: program.slug,
}));
}
// End of static generation
//////////////////////////////////////////////////////////////////////

export default async function ProgramDetailPage({
params,
}: {
params: { slug: string };
}): Promise<JSX.Element> {
const slug = params.slug;

const programData: ProgramDataType = await GetProgramData(slug);

return (
<>
<ProgramDetailComponent programData={programData} />
</>
);
}
1 change: 1 addition & 0 deletions apps/website/src/app/program/default.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const PROGRAM_LIST_PER_PAGE = 15;
122 changes: 122 additions & 0 deletions apps/website/src/app/program/languages/[language]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import type { Metadata } from "next";
import PageTitle from "@/components/PageTitle";
import ProgramComponent from "@/components/Program/program-component";
import {
SiteMetadata,
GetProgramLanguageProgramList,
GetProgramLanguageList,
} from "@/data";
import { PROGRAM_LIST_PER_PAGE } from "../../default";

////////////////////////////////////////////////////////////////////////////////
// https://beta.nextjs.org/docs/api-reference/metadata#generatemetadata-function
// Generate metadata for this page
export async function generateMetadata({
params,
}: {
params: { language: string };
}): Promise<Metadata> {
const language = params.language;

const title = `${language
.replace(/-sharp/g, "#")
.replace(/-plus/g, "+")
.replace(/\w\S*/g, (w) =>
w.replace(/^\w/, (c) => c.toUpperCase()),
)} | Program`;
const description =
"An open-source codebase for sharing programming solutions.";

return {
title,
description,

keywords: ["codinasion", "program", "open source", language],

openGraph: {
title,
description,
url: `${SiteMetadata.site_url}/program/languages/${language}`,
siteName: SiteMetadata.title,
images: [
// TODO: Add og:image for program language page
],
locale: "en_US",
type: "website",
},
twitter: {
card: "summary_large_image",
title,
description,
siteId: SiteMetadata.twitter_userid,
creator: `@${SiteMetadata.twitter_username}`,
creatorId: SiteMetadata.twitter_userid,
images: [
// TODO: Add twitter:image for program language page
],
},
};
}
// End of metadata generation
////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
// https://beta.nextjs.org/docs/data-fetching/generating-static-params
// Make this page statically generated, with dynamic params
export const dynamicParams = true;
export async function generateStaticParams() {
const ProgramLanguageData = await GetProgramLanguageList();

return ProgramLanguageData.slice(0, 1).map((language: string) => ({
language: language,
}));
}
// End of static generation
//////////////////////////////////////////////////////////////////////

export default async function ProgramPage({
params,
}: {
params: { language: string };
}): Promise<JSX.Element> {
const language = params.language;

/////////////////////////////////////////////////////////////////////////////
// https://beta.nextjs.org/docs/data-fetching/fetching#parallel-data-fetching
// Initiate both requests in parallel
const ProgramDataLoad = GetProgramLanguageProgramList(language);
const ProgramLanguageDataLoad = GetProgramLanguageList();

// Wait for the promises to resolve
const [ProgramData, ProgramLanguageData] = await Promise.all([
ProgramDataLoad,
ProgramLanguageDataLoad,
]);
/////////////////////////////////////////////////////////////////////////////

const pageNumber = 1;
const displayProgramList = ProgramData.slice(
PROGRAM_LIST_PER_PAGE * (pageNumber - 1),
PROGRAM_LIST_PER_PAGE * pageNumber,
);
const pagination = {
currentPage: pageNumber,
totalPages: Math.ceil(ProgramData.length / PROGRAM_LIST_PER_PAGE),
};

return (
<>
<PageTitle
title="Program"
description="An open-source codebase for sharing programming solutions."
/>

<ProgramComponent
programLanguageListData={ProgramLanguageData}
displayProgramList={displayProgramList}
pagination={pagination}
basePath={`/program/languages/${language}`}
/>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import PageTitle from "@/components/PageTitle";
import ProgramComponent from "@/components/Program/program-component";
import { GetProgramLanguageProgramList, GetProgramLanguageList } from "@/data";
import { PROGRAM_LIST_PER_PAGE } from "../../../../default";

// https://beta.nextjs.org/docs/data-fetching/generating-static-params
// Make this page statically generated, with dynamic params
export const dynamicParams = true;
export async function generateStaticParams(): Promise<
{ language: string; page: string }[]
> {
const languageList = await GetProgramLanguageList();
const paths = languageList.slice(0, 1).flatMap((language) => {
return Array.from({ length: 1 }, (_, i) => ({
language: language,
page: (i + 2).toString(), // start from 1 instead of 0
})).slice(0, 1);
});
return paths;
}
// End of static generation

export default async function ProgramPage({
params,
}: {
params: { language: string; page: string };
}): Promise<JSX.Element> {
const language = params.language;

/////////////////////////////////////////////////////////////////////////////
// https://beta.nextjs.org/docs/data-fetching/fetching#parallel-data-fetching
// Initiate both requests in parallel
const ProgramDataLoad = GetProgramLanguageProgramList(language);
const ProgramLanguageDataLoad = GetProgramLanguageList();

// Wait for the promises to resolve
const [ProgramData, ProgramLanguageData] = await Promise.all([
ProgramDataLoad,
ProgramLanguageDataLoad,
]);
/////////////////////////////////////////////////////////////////////////////

const pageNumber = parseInt(params.page as string);
const displayProgramList = ProgramData.slice(
PROGRAM_LIST_PER_PAGE * (pageNumber - 1),
PROGRAM_LIST_PER_PAGE * pageNumber,
);
const pagination = {
currentPage: pageNumber,
totalPages: Math.ceil(ProgramData.length / PROGRAM_LIST_PER_PAGE),
};

return (
<>
<PageTitle
title="Program"
description="An open-source codebase for sharing programming solutions."
/>

<ProgramComponent
programLanguageListData={ProgramLanguageData}
displayProgramList={displayProgramList}
pagination={pagination}
basePath={`/program/languages/${language}`}
/>
</>
);
}
50 changes: 50 additions & 0 deletions apps/website/src/app/program/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import PageTitle from "@/components/PageTitle";
import ProgramComponent from "@/components/Program/program-component";
import { GetProgramList, GetProgramLanguageList } from "@/data";
import { PROGRAM_LIST_PER_PAGE } from "./default";

export const metadata = {
title: "Program",
description: "An open-source codebase for sharing programming solutions.",
};

export default async function ProgramPage(): Promise<JSX.Element> {
/////////////////////////////////////////////////////////////////////////////
// https://beta.nextjs.org/docs/data-fetching/fetching#parallel-data-fetching
// Initiate both requests in parallel
const ProgramDataLoad = GetProgramList();
const ProgramLanguageDataLoad = GetProgramLanguageList();

// Wait for the promises to resolve
const [ProgramData, ProgramLanguageData] = await Promise.all([
ProgramDataLoad,
ProgramLanguageDataLoad,
]);
/////////////////////////////////////////////////////////////////////////////

const pageNumber = 1;
const displayProgramList = ProgramData.slice(
PROGRAM_LIST_PER_PAGE * (pageNumber - 1),
PROGRAM_LIST_PER_PAGE * pageNumber,
);
const pagination = {
currentPage: pageNumber,
totalPages: Math.ceil(ProgramData.length / PROGRAM_LIST_PER_PAGE),
};

return (
<>
<PageTitle
title="Program"
description="An open-source codebase for sharing programming solutions."
/>

<ProgramComponent
programLanguageListData={ProgramLanguageData}
displayProgramList={displayProgramList}
pagination={pagination}
basePath="/program"
/>
</>
);
}
Loading
Loading