From c20b11a6e46ea807dcfb9d55bd6c7978d92951be Mon Sep 17 00:00:00 2001
From: Adrian Polak <adrian.polak20@gmail.com>
Date: Mon, 12 Dec 2022 10:40:28 +0100
Subject: [PATCH] feat(app): add questions pagination (#403)

* Updates

* Refactor questions page

* Update page.tsx

* feat(app): add questions pagination

* feat(app): add dark mode to page item

* refactor(app): remove activeHref from props

* feat(app): add redirect to first category page

* feat(app): add current page technology

Co-authored-by: Michal Miszczyszyn <michal@mmiszy.pl>
---
 .../questions/[technology]/[page]/page.tsx    | 40 +++++++++++++++++++
 .../src/app/questions/[technology]/page.tsx   | 28 ++-----------
 apps/app/src/app/questions/layout.tsx         |  2 +-
 apps/app/src/app/questions/page.tsx           |  2 +-
 apps/app/src/components/ActiveLink.tsx        |  2 +-
 .../src/components/QuestionsPagination.tsx    | 26 ++++++++++++
 apps/app/src/lib/constants.ts                 |  1 +
 7 files changed, 73 insertions(+), 28 deletions(-)
 create mode 100644 apps/app/src/app/questions/[technology]/[page]/page.tsx
 create mode 100644 apps/app/src/components/QuestionsPagination.tsx
 create mode 100644 apps/app/src/lib/constants.ts

diff --git a/apps/app/src/app/questions/[technology]/[page]/page.tsx b/apps/app/src/app/questions/[technology]/[page]/page.tsx
new file mode 100644
index 00000000..0ed431a6
--- /dev/null
+++ b/apps/app/src/app/questions/[technology]/[page]/page.tsx
@@ -0,0 +1,40 @@
+import { redirect } from "next/navigation";
+import { QuestionItem } from "../../../../components/QuestionItem/QuestionItem";
+import { QuestionsPagination } from "../../../../components/QuestionsPagination";
+import { PAGE_SIZE } from "../../../../lib/constants";
+import { technologies } from "../../../../lib/technologies";
+import { getAllQuestions } from "../../../../services/questions.service";
+
+export default async function QuestionsPage({
+	params,
+}: {
+	params: { technology: string; page: string };
+}) {
+	const page = parseInt(params.page);
+
+	if (!technologies.includes(params.technology) || isNaN(page)) {
+		return redirect("/");
+	}
+
+	const { data } = await getAllQuestions({
+		category: params.technology,
+		limit: PAGE_SIZE,
+		offset: (page - 1) * PAGE_SIZE,
+	});
+
+	return (
+		<div className="flex flex-col gap-y-10">
+			{data.data.map(({ id, question, _levelId, acceptedAt, votesCount }) => (
+				<QuestionItem
+					key={id}
+					title={question}
+					level={_levelId}
+					creationDate={new Date(acceptedAt || "")}
+					votes={votesCount}
+					voted={id % 2 === 0}
+				/>
+			))}
+			<QuestionsPagination technology={params.technology} total={data.meta.total} />
+		</div>
+	);
+}
diff --git a/apps/app/src/app/questions/[technology]/page.tsx b/apps/app/src/app/questions/[technology]/page.tsx
index 67efac0a..1589989a 100644
--- a/apps/app/src/app/questions/[technology]/page.tsx
+++ b/apps/app/src/app/questions/[technology]/page.tsx
@@ -1,27 +1,5 @@
 import { redirect } from "next/navigation";
-import { QuestionItem } from "../../../components/QuestionItem/QuestionItem";
-import { technologies } from "../../../lib/technologies";
-import { getAllQuestions } from "../../../services/questions.service";
 
-export default async function QuestionsPage({ params }: { params: { technology: string } }) {
-	if (!technologies.includes(params.technology)) {
-		return redirect("/");
-	}
-
-	const { data } = await getAllQuestions({ category: params.technology, limit: 20 });
-
-	return (
-		<div className="flex flex-col gap-y-10">
-			{data.data.map(({ id, question, _levelId, acceptedAt, votesCount }) => (
-				<QuestionItem
-					key={id}
-					title={question}
-					level={_levelId}
-					creationDate={new Date(acceptedAt || "")}
-					votes={votesCount}
-					voted={id % 2 === 0}
-				/>
-			))}
-		</div>
-	);
-};
+export default function TechnologyPage({ params }: { params: { technology: string } }) {
+	return redirect(`/questions/${params.technology}/1`);
+}
diff --git a/apps/app/src/app/questions/layout.tsx b/apps/app/src/app/questions/layout.tsx
index 1abe46ac..6d1f9f47 100644
--- a/apps/app/src/app/questions/layout.tsx
+++ b/apps/app/src/app/questions/layout.tsx
@@ -7,7 +7,7 @@ const QuestionsPageLayout = ({ children }: { readonly children: ReactNode }) =>
 	<>
 		<Container className="flex">
 			<QuestionsSidebar />
-			<main className="grow py-8 pl-11">{children}</main>
+			<main className="grow py-4 pl-0 sm:py-8 sm:pl-11">{children}</main>
 		</Container>
 		<MobileActionButtons />
 	</>
diff --git a/apps/app/src/app/questions/page.tsx b/apps/app/src/app/questions/page.tsx
index 7f1cad0a..4fdc893e 100644
--- a/apps/app/src/app/questions/page.tsx
+++ b/apps/app/src/app/questions/page.tsx
@@ -1,5 +1,5 @@
 import { redirect } from "next/navigation";
 
 export default function QuestionsPage() {
-	return redirect("/questions/js");
+	return redirect("/questions/js/1");
 }
diff --git a/apps/app/src/components/ActiveLink.tsx b/apps/app/src/components/ActiveLink.tsx
index a356a7c6..2bface36 100644
--- a/apps/app/src/components/ActiveLink.tsx
+++ b/apps/app/src/components/ActiveLink.tsx
@@ -18,7 +18,7 @@ export const ActiveLink = ({
 	...rest
 }: ActiveLinkProps) => {
 	const pathname = usePathname();
-	const isActive = href === pathname;
+	const isActive = pathname?.startsWith(href.toString());
 
 	return (
 		<Link href={href} className={twMerge(className, isActive && activeClassName)} {...rest}>
diff --git a/apps/app/src/components/QuestionsPagination.tsx b/apps/app/src/components/QuestionsPagination.tsx
new file mode 100644
index 00000000..c3b2804f
--- /dev/null
+++ b/apps/app/src/components/QuestionsPagination.tsx
@@ -0,0 +1,26 @@
+import { PAGE_SIZE } from "../lib/constants";
+import { ActiveLink } from "./ActiveLink";
+
+type QuestionsPaginationProps = Readonly<{
+	technology: string;
+	total: number;
+}>;
+
+export const QuestionsPagination = ({ technology, total }: QuestionsPaginationProps) => {
+	const pages = Math.ceil(total / PAGE_SIZE);
+
+	return (
+		<div className="flex justify-center gap-x-3">
+			{Array.from({ length: pages }).map((_, i) => (
+				<ActiveLink
+					key={i}
+					href={`/questions/${technology}/${i + 1}`}
+					className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-full border-2 border-primary text-primary transition-colors duration-300 hover:bg-violet-100 dark:text-white dark:hover:bg-violet-800"
+					activeClassName="bg-primary text-white"
+				>
+					{i + 1}
+				</ActiveLink>
+			))}
+		</div>
+	);
+};
diff --git a/apps/app/src/lib/constants.ts b/apps/app/src/lib/constants.ts
new file mode 100644
index 00000000..bfab9067
--- /dev/null
+++ b/apps/app/src/lib/constants.ts
@@ -0,0 +1 @@
+export const PAGE_SIZE = 20;