Skip to content

Commit

Permalink
feat: add single Question page
Browse files Browse the repository at this point in the history
  • Loading branch information
HereEast committed Feb 16, 2025
1 parent 53bd0bb commit 7f12434
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 11 deletions.
24 changes: 21 additions & 3 deletions src/api-client/answers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,31 @@ export async function submitAnswers(formData: IFormDataProps[]) {
// GET ANSWERS BY PERSON SLUG
export async function getAnswersByPersonSlug(slug: string) {
try {
const answersResponse = await fetch(`${BASE_URL}/api/answers/${slug}`);
const response = await fetch(`${BASE_URL}/api/answers/person/${slug}`);

if (!answersResponse.ok) {
if (!response.ok) {
throw new Error("🔴 Data fetch failed");
}

const answers: IAnswer[] = await answersResponse.json();
const answers: IAnswer[] = await response.json();

return answers;
} catch (error) {
console.error("Error fetching data:", error);
return null;
}
}

// GET ANSWERS BY QUESTION SLUG
export async function getAnswersByQuestionSlug(slug: string) {
try {
const response = await fetch(`${BASE_URL}/api/answers/question/${slug}`);

if (!response.ok) {
throw new Error("🔴 Data fetch failed");
}

const answers: IAnswer[] = await response.json();

return answers;
} catch (error) {
Expand Down
File renamed without changes.
54 changes: 54 additions & 0 deletions src/app/api/answers/question/[slug]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { NextResponse } from "next/server";

import { connectDB } from "~/app/lib/connectDB";
import { IQuestion, Question } from "~/models/Question";
import { IPerson, Person } from "~/models/Person";
import { Answer, IAnswer } from "~/models/Answer";

interface ReqParams {
params: { slug: string };
}

// GET ANSWERS BY QUESTION SLUG
export async function GET(req: Request, { params }: ReqParams) {
const { slug } = params;

try {
await connectDB();

const question: IQuestion | null = await Question.findOne({
slug: slug,
});

if (!question) {
return NextResponse.json("🔴 Failed to fetch a question by slug.", {
status: 400,
});
}

// Remove later (make sure Questions are available before populate)
const people: IPerson[] = await Person.find({}).exec();

const answers: IAnswer[] = await Answer.find({
questionId: question._id,
})
.populate("personId")
.exec();

const activeAnswers = answers.filter((answer) => {
const person = answer.personId as IPerson;
return person.isActive === true;
});

return NextResponse.json(activeAnswers, { status: 200 });
} catch (err) {
console.log(err);

return NextResponse.json(
{ message: "🔴 Error fetching answers by personId." },
{
status: 500,
},
);
}
}
5 changes: 5 additions & 0 deletions src/app/questions/[slug]/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Loader } from "~/components/Loader";

export default function Loading() {
return <Loader />;
}
23 changes: 23 additions & 0 deletions src/app/questions/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { QuestionPage } from "~/components/layouts/QuestionPage";

import { getQuestions } from "~/api-client/questions";

interface QuestionPageProps {
params: {
slug: string;
};
}

export async function generateStaticParams() {
const questions = await getQuestions();

return (
questions?.map((question) => ({
slug: question.slug,
})) || []
);
}

export default async function Question({ params }: QuestionPageProps) {
return <QuestionPage slug={params.slug} />;
}
10 changes: 5 additions & 5 deletions src/components/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function Content({ data }: ContentProps) {
return (
<div key={index} className="space-y-5 rounded-3xl bg-white p-8">
<Question>{question.body}</Question>
<Answer item={item} view={question.answerView || "text"} />
<Answer answerData={item} view={question.answerView || "text"} />
</div>
);
})}
Expand All @@ -33,22 +33,22 @@ export function Content({ data }: ContentProps) {

// Answer
interface AnswersProps {
item: IAnswer;
answerData: IAnswer;
view?: AnswerViewType;
}

function Answer({ item, view }: AnswersProps) {
export function Answer({ answerData, view = "text" }: AnswersProps) {
return (
<>
{view === "text" && (
<div className="answer text-xl leading-tight sm:text-xl lg:text-2xl">
<ParsedParagraph>{item.answer}</ParsedParagraph>
<ParsedParagraph>{answerData.answer}</ParsedParagraph>
</div>
)}

{view === "links" && (
<div className="space-y-0.5">
{item.links?.map((link, index) => (
{answerData.links?.map((link, index) => (
<LinkItem link={link} key={index} />
))}
</div>
Expand Down
117 changes: 117 additions & 0 deletions src/components/QuestionsNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import Link from "next/link";
// import { useRouter } from "next/navigation";

import { IQuestion } from "~/models/Question";
import { BASE_URL } from "~/utils/constants";
import { cn } from "~/utils/handlers";

interface QuestionsNavProps {
currentIndex: number;
questions: IQuestion[];
}

export function QuestionsNavigation({
questions,
currentIndex,
}: QuestionsNavProps) {
// const router = useRouter();

const lastIndex = questions.length - 1;

const prevQuestion =
currentIndex === 0 ? questions[lastIndex] : questions[currentIndex - 1];
const nextQuestion =
currentIndex === lastIndex ? questions[0] : questions[currentIndex + 1];

// function handleClick(path: string) {
// router.push(`${path}`, { scroll: false });
// }

return (
<div className="grid grid-cols-2 justify-between gap-6">
<NavLink
url={`${BASE_URL}/questions/${prevQuestion.slug}`}
question={prevQuestion.body}
direction="prev"
/>

<NavLink
url={`${BASE_URL}/questions/${nextQuestion.slug}`}
question={nextQuestion.body}
direction="next"
/>

{/* <NavButton
onClick={() => handleClick(`/questions/${prevQuestion.slug}`)}
question={prevQuestion.body}
direction="prev"
/>
<NavButton
onClick={() => handleClick(`/questions/${nextQuestion.slug}`)}
question={nextQuestion.body}
direction="next"
/> */}
</div>
);
}

// Nav Link
// interface NavButtonProps {
// onClick: () => void;
// direction: "prev" | "next";
// question: string;
// }

// function NavButton({ onClick, direction, question }: NavButtonProps) {
// return (
// <button
// onClick={onClick}
// className="group/questions-nav rounded-xl border p-6"
// >
// <span
// className={cn(
// "flex flex-col gap-1",
// direction === "prev" ? "text-left" : "text-right",
// )}
// >
// <span className="text-xs uppercase text-stone-400">
// {direction === "prev" ? "Prev" : "Next"}
// </span>
// <span className="text-base leading-tight underline group-hover/questions-nav:no-underline group-hover/questions-nav:opacity-50">
// {question}
// </span>
// </span>
// </button>
// );
// }

interface NavLinkProps {
url: string;
direction: "prev" | "next";
question: string;
}

function NavLink({ url, direction, question }: NavLinkProps) {
return (
<Link
href={url}
className="group/questions-nav rounded-xl border p-6"
scroll={false}
>
<span
className={cn(
"flex flex-col gap-1",
direction === "next" && "text-right",
)}
>
<span className="text-xs uppercase text-stone-400">
{direction === "prev" ? "Prev" : "Next"}
</span>
<span className="text-base leading-tight underline group-hover/questions-nav:no-underline group-hover/questions-nav:opacity-50">
{question}
</span>
</span>
</Link>
);
}
103 changes: 103 additions & 0 deletions src/components/layouts/QuestionPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import Link from "next/link";
import { notFound } from "next/navigation";

import { PageContainer } from "./PageContainer";
import { PersonImage } from "../PersonImage";
import { Answer } from "../Content";
import { QuestionsNavigation } from "../QuestionsNavigation";

import { getAnswersByQuestionSlug } from "~/api-client/answers";
import { getQuestions } from "~/api-client/questions";
import { IPerson } from "~/models/Person";

import { BASE_URL } from "~/utils/constants";

interface QuestionPageProps {
slug: string;
}

export async function QuestionPage({ slug }: QuestionPageProps) {
const questions = await getQuestions();
const answers = await getAnswersByQuestionSlug(slug);

if (!answers || !questions) {
notFound();
}

// console.log(answers);

const currentIndex = questions?.findIndex((item) => item.slug === slug);

const question = questions?.[currentIndex];

return (
<PageContainer className="max-w-4xl">
{answers && question && (
<>
<div className="mb-4">
<Link
href={`${BASE_URL}/questions`}
className="underline hover:no-underline hover:opacity-50"
scroll={false}
>
All Questions
</Link>
</div>

<div className="mb-10">
<h2 className="text-4xl font-extrabold tracking-header">
{question.body}
</h2>
</div>

<ul className="mb-6 space-y-2">
{answers.map((answer, index) => {
const person = answer.personId as IPerson;

return (
<li
className="rounded-2xl bg-stone-100 p-8 text-xl"
key={index}
>
<div className="space-y-8">
<Answer answerData={answer} view={question.answerView} />
<PersonDetails person={person} />
</div>
</li>
);
})}
</ul>

<QuestionsNavigation
questions={questions}
currentIndex={currentIndex}
/>
</>
)}
</PageContainer>
);
}

// Person details
interface PersonDetailsProps {
person: IPerson;
}

function PersonDetails({ person }: PersonDetailsProps) {
return (
<div className="flex items-start gap-3 text-base leading-none">
<div className="size-10 shrink-0 overflow-hidden rounded-full">
<PersonImage person={person} />
</div>
<div className="flex flex-col gap-1.5">
<Link
href={`${BASE_URL}/people/${person.slug}`}
className="w-fit font-semibold tracking-header underline underline-offset-1 hover:no-underline hover:opacity-50"
>
{person.name}
</Link>
<span>{`${person.jobTitle} @ ${person.company.name}`}</span>
</div>
</div>
);
}
2 changes: 0 additions & 2 deletions src/components/layouts/QuestionsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { BASE_URL } from "~/utils/constants";
export async function QuestionsPage() {
const questions = await getQuestions();

console.log(questions);

if (!questions) {
notFound();
}
Expand Down
3 changes: 2 additions & 1 deletion src/models/Answer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import mongoose, { Schema, Document, model } from "mongoose";

import { IQuestion } from "./Question";
import { IPerson } from "./Person";

export interface IAnswer extends Document {
personId: mongoose.Types.ObjectId;
personId: mongoose.Types.ObjectId | IPerson;
questionId: mongoose.Types.ObjectId | IQuestion;
name: string;
question: string;
Expand Down

0 comments on commit 7f12434

Please sign in to comment.