Skip to content

Commit 8a0c75a

Browse files
authored
feat: adds indicator to question card if user has answered or not (#405)
2 parents 0536e95 + c423420 commit 8a0c75a

11 files changed

+231
-82
lines changed

src/components/app/questions/layout/carousel/question-carousel-card.tsx

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import { QuestionWithTags } from '@/types/Questions';
22
import Link from 'next/link';
33
import Chip from '@/components/ui/chip';
44
import { capitalise, getQuestionDifficultyColor } from '@/utils';
5-
//import { getUserAnswer } from '@/utils/data/answers/get-user-answer';
6-
//import { CheckCircle, ChevronRight, Circle } from 'lucide-react';
5+
import { CheckCircle, ChevronRight } from 'lucide-react';
76

7+
import { Circle } from 'lucide-react';
8+
import { Answer } from '@/types/Answers';
89
export default async function QuestionCarouselCard(opts: {
9-
questionData: QuestionWithTags;
10+
questionData: QuestionWithTags & { userAnswers: Answer[] };
1011
}) {
1112
const { questionData } = opts;
1213

13-
//const userAnswered = await getUserAnswer({ questionUid: questionData.uid });
14+
console.log(questionData);
1415

1516
return (
1617
<Link
@@ -21,29 +22,39 @@ export default async function QuestionCarouselCard(opts: {
2122
<h6 className="text-wrap text-start line-clamp-2">
2223
{questionData?.question}
2324
</h6>
24-
<div className="flex w-full justify-between items-center">
25-
{/*<div className="flex items-center gap-x-2">
26-
{userAnswered ? (
27-
<CheckCircle className="flex-shrink-0 size-5 text-green-500" />
28-
) : (
29-
<Circle className="flex-shrink-0 size-5 text-black-50" />
30-
)}
31-
<div className="text-sm font-medium">
32-
{userAnswered ? (
33-
<p>Answered</p>
25+
<div className="flex items-center gap-x-2">
26+
{questionData.userAnswers && questionData.userAnswers.length > 0 ? (
27+
<div>
28+
{questionData.userAnswers[0].correctAnswer ? (
29+
<CheckCircle className="flex-shrink-0 size-5 text-green-500" />
3430
) : (
35-
<div className="relative">
36-
<p className="group-hover:opacity-0 transition-opacity duration-300">
37-
Not Answered
38-
</p>
39-
<div className="absolute top-0 left-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap flex items-center gap-x-1">
40-
<p>Learn Now</p>
41-
<ChevronRight className="flex-shrink-0 size-4 text-white group-hover:translate-x-2 transition-transform duration-300" />
42-
</div>
43-
</div>
31+
<Circle className="flex-shrink-0 size-5 text-black-50" />
4432
)}
4533
</div>
46-
</div> */}
34+
) : (
35+
<Circle className="flex-shrink-0 size-5 text-black-50" />
36+
)}
37+
<div className="text-sm font-medium">
38+
{questionData.userAnswers && questionData.userAnswers.length > 0 ? (
39+
questionData.userAnswers[0].correctAnswer ? (
40+
<p>Correct</p>
41+
) : (
42+
<p>Incorrect</p>
43+
)
44+
) : (
45+
<div className="relative">
46+
<p className="group-hover:opacity-0 transition-opacity duration-300">
47+
Not Answered
48+
</p>
49+
<div className="absolute top-0 left-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap flex items-center gap-x-1">
50+
<p>Learn Now</p>
51+
<ChevronRight className="flex-shrink-0 size-4 text-white group-hover:translate-x-2 transition-transform duration-300" />
52+
</div>
53+
</div>
54+
)}
55+
</div>
56+
</div>
57+
<div className="flex w-full justify-between items-center">
4758
<Chip
4859
text={capitalise(questionData?.difficulty)}
4960
color={getQuestionDifficultyColor(questionData?.difficulty).bg}

src/components/app/questions/layout/carousel/question-carousel-list.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getQuestionsByTag } from '@/utils/data/questions/get-questions-by-tag';
22
import QuestionCarousel from './question-carousel';
3+
import { Answer } from '@/types/Answers';
34
import { QuestionWithTags } from '@/types/Questions';
45

56
export default async function QuestionsCarouselList() {
@@ -47,22 +48,30 @@ export default async function QuestionsCarouselList() {
4748
const questionsByTag = await Promise.all(
4849
questionsCarousels.map(async (carousel) => {
4950
const questions = await getQuestionsByTag(carousel.tag);
50-
return { ...carousel, questions };
51+
return {
52+
...carousel,
53+
questions: questions.flatMap((q) =>
54+
q.questions.map((question) => question.question)
55+
),
56+
userAnswers: questions.flatMap((q) =>
57+
q.questions.map((question) => question.question.userAnswers)
58+
),
59+
};
5160
})
5261
);
5362

5463
return (
5564
<div className="flex flex-col gap-y-16 md:gap-y-28 pt-10">
56-
{questionsByTag.map((carousel) => (
65+
{questionsByTag.map((carousel, index) => (
5766
<QuestionCarousel
58-
key={carousel.title}
67+
key={`carousel-${index}-${carousel.tag}-${carousel.title}`}
5968
heading={carousel.title}
6069
description={carousel.description}
6170
image={carousel.image}
6271
questions={
63-
(carousel.questions[0]?.questions.map(
64-
(q) => q.question
65-
) as unknown as QuestionWithTags[]) || []
72+
carousel.questions as unknown as QuestionWithTags[] & {
73+
userAnswers: Answer;
74+
}
6675
}
6776
tag={carousel.tag}
6877
/>

src/components/app/questions/layout/carousel/question-carousel.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { QuestionWithTags } from '@/types/Questions';
1010
import QuestionCarouselCard from './question-carousel-card';
1111
import { Button } from '@/components/ui/button';
1212
import { ChevronRight } from 'lucide-react';
13+
import { Answer } from '@/types/Answers';
1314

1415
/**
1516
* A carousel that will showcase a set of questions.
@@ -21,7 +22,9 @@ export default function QuestionCarousel(opts: {
2122
heading: string | React.ReactNode;
2223
description: string | React.ReactNode;
2324
image: string;
24-
questions: QuestionWithTags[];
25+
questions: QuestionWithTags[] & {
26+
userAnswers: Answer;
27+
};
2528
tag: string | string[];
2629
}) {
2730
const { heading, description, image, questions, tag } = opts;
@@ -72,7 +75,12 @@ export default function QuestionCarousel(opts: {
7275
<CarouselContent className="grid grid-flow-col auto-cols-[calc(100%-8px)] md:auto-cols-[calc(50%-8px)] lg:auto-cols-[calc(33.33%-8px)] gap-4">
7376
{questions.map((q) => (
7477
<CarouselItem key={q.uid} className="flex">
75-
<QuestionCarouselCard key={q.uid} questionData={q} />
78+
<QuestionCarouselCard
79+
key={q.uid}
80+
questionData={
81+
q as QuestionWithTags & { userAnswers: Answer[] }
82+
}
83+
/>
7684
</CarouselItem>
7785
))}
7886
</CarouselContent>

src/components/app/questions/layout/question-card.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { getQuestionStats } from '@/utils/data/questions/get-question-stats';
55
import Link from 'next/link';
66
import Chip from '@/components/ui/chip';
77
import { Suspense } from 'react';
8+
import { ChevronRight } from 'lucide-react';
9+
import { Circle } from 'lucide-react';
10+
import { CheckCircle } from 'lucide-react';
11+
import { Answer } from '@/types/Answers';
812

913
// separate async component for stats to avoid blocking render
1014
async function QuestionStats({
@@ -16,7 +20,7 @@ async function QuestionStats({
1620
}) {
1721
const stats = await getQuestionStats(identifier, value);
1822
return (
19-
<div className="text-start text-[10px]">
23+
<div className="text-start text-xs">
2024
<p className="font-ubuntu text-sm">
2125
Submissions:{' '}
2226
<span className="font-medium">{stats.totalSubmissions}</span>
@@ -26,7 +30,7 @@ async function QuestionStats({
2630
}
2731

2832
export default function QuestionCard(opts: {
29-
questionData: QuestionWithoutAnswers;
33+
questionData: QuestionWithoutAnswers & { userAnswers: Answer[] };
3034
showSubmissions?: boolean;
3135
numberOfTags?: number;
3236
showcaseTag?: string;
@@ -75,6 +79,38 @@ export default function QuestionCard(opts: {
7579
</Suspense>
7680
)}
7781
</div>
82+
<div className="flex items-center gap-x-2">
83+
{questionData.userAnswers && questionData.userAnswers.length > 0 ? (
84+
<div>
85+
{questionData.userAnswers[0].correctAnswer ? (
86+
<CheckCircle className="flex-shrink-0 size-5 text-green-500" />
87+
) : (
88+
<Circle className="flex-shrink-0 size-5 text-black-50" />
89+
)}
90+
</div>
91+
) : (
92+
<Circle className="flex-shrink-0 size-5 text-black-50" />
93+
)}
94+
<div className="text-sm font-medium">
95+
{questionData.userAnswers && questionData.userAnswers.length > 0 ? (
96+
questionData.userAnswers[0].correctAnswer ? (
97+
<p>Correct</p>
98+
) : (
99+
<p>Incorrect</p>
100+
)
101+
) : (
102+
<div className="relative">
103+
<p className="group-hover:opacity-0 transition-opacity duration-300">
104+
Not Answered
105+
</p>
106+
<div className="absolute top-0 left-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap flex items-center gap-x-1">
107+
<p>Learn Now</p>
108+
<ChevronRight className="flex-shrink-0 size-4 text-white group-hover:translate-x-2 transition-transform duration-300" />
109+
</div>
110+
</div>
111+
)}
112+
</div>
113+
</div>
78114
<div className="mt-5 w-full flex justify-between items-end z-10 relative">
79115
{!customQuestion && (
80116
<div className="flex gap-4 items-end">

src/components/app/questions/layout/questions-list.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { listQuestions } from '@/utils/data/questions/list';
66
import { FilterParams } from '@/utils/search-params';
77
import { Button } from '@/components/ui/button';
88
import type { UserRecord } from '@/types/User';
9+
import { QuestionWithoutAnswers } from '@/types/Questions';
10+
import { Answer } from '@/types/Answers';
911

1012
const ITEMS_PER_PAGE = 15;
1113

@@ -68,7 +70,7 @@ export default async function QuestionsList({
6870
{data.questions.map((q) => (
6971
<QuestionCard
7072
key={q.uid}
71-
questionData={q}
73+
questionData={q as QuestionWithoutAnswers & { userAnswers: Answer[] }}
7274
showSubmissions={showSubmissions}
7375
identifier={customQuestions ? 'uid' : 'slug'}
7476
customQuestion={customQuestions}

src/components/app/questions/resources/question-stats-tab.tsx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,25 @@ export default function QuestionStatsTab(opts: {
1313
<h3 className="font-inter font-light text-base md:text-xl">
1414
Stats for this question
1515
</h3>
16-
<div className="flex flex-col md:flex-row items-start md:items-center gap-2 text-sm text-gray-400">
16+
<div className="flex flex-col items-start gap-2 text-sm text-gray-400">
1717
<div className="flex items-center gap-2">
18-
<div className="flex items-center gap-0.5">
18+
<div className="flex items-center gap-1">
1919
<User className="size-4" />
20-
<p>Total submissions:</p>
20+
<p className="text-lg">Total submissions:</p>
2121
</div>
22-
<p className="font-semibold">{totalSubmissions?.totalSubmissions}</p>
22+
<p className="font-semibold text-lg">
23+
{totalSubmissions?.totalSubmissions}
24+
</p>
25+
</div>
26+
<div className="flex items-center gap-2">
27+
<div className="flex items-center gap-1">
28+
<Check className="size-4" />
29+
<p className="text-lg">Success rate:</p>
30+
</div>
31+
<p className="font-semibold text-lg">
32+
{totalSubmissions?.percentageCorrect}%
33+
</p>
2334
</div>
24-
{totalSubmissions?.percentageCorrect &&
25-
totalSubmissions?.percentageCorrect > 0 && (
26-
<div className="flex items-center gap-0.5 md:ml-4">
27-
<Check className="size-4" />
28-
<p>Success rate:</p>
29-
<p className="font-semibold">
30-
{totalSubmissions?.percentageCorrect}%
31-
</p>
32-
</div>
33-
)}
3435
</div>
3536
</div>
3637
);

src/components/app/questions/single/layout/question-card.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import { Question } from '@/types/Questions';
2121
import { useQuestionSingle } from './question-single-context';
2222
import { Button } from '@/components/ui/button';
2323
import CodeDisplay from './code-snippet';
24+
import ExpandedCodeModal from './expanded-code-modal';
25+
import ChangeCodeTheme from './change-code-theme';
26+
import AiQuestionHelp from './ai-question-help';
2427

2528
export default function QuestionCard(opts: {
2629
// optional as this is not required to render the card
@@ -69,13 +72,25 @@ export default function QuestionCard(opts: {
6972
return (
7073
<div className="h-full bg-black-75 border border-black-50 rounded-xl flex flex-col overflow-hidden">
7174
<div className="p-2 lg:p-4 w-full flex flex-col gap-2 md:flex-row justify-between bg-black-25 md:items-center">
72-
<div className="w-fit">
73-
<Chip
74-
color={getQuestionDifficultyColor(question.difficulty).bg}
75-
text={capitalise(question.difficulty)}
76-
textColor={getQuestionDifficultyColor(question.difficulty).text}
77-
border={getQuestionDifficultyColor(question.difficulty).border}
78-
/>
75+
<div className="flex items-center gap-2 justify-between">
76+
<div className="w-fit">
77+
<Chip
78+
color={getQuestionDifficultyColor(question.difficulty).bg}
79+
text={capitalise(question.difficulty)}
80+
textColor={getQuestionDifficultyColor(question.difficulty).text}
81+
border={getQuestionDifficultyColor(question.difficulty).border}
82+
/>
83+
</div>
84+
<div className="flex lg:hidden text-sm w-full items-center justify-end bg-black-25 gap-x-3">
85+
{/** explain question ai button */}
86+
<AiQuestionHelp question={question} user={user} />
87+
{/** code theme selector */}
88+
<ChangeCodeTheme user={user} />
89+
{/** code snippet */}
90+
{question.codeSnippet && (
91+
<ExpandedCodeModal code={question.codeSnippet} />
92+
)}
93+
</div>
7994
</div>
8095
<div className="flex flex-wrap gap-2 justify-between items-center">
8196
<Button

src/components/app/questions/suggested-questions-table.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ export default async function QuestionSuggestedCard(opts: {
99
border?: boolean;
1010
textLimit?: number;
1111
customQuestions?: Question[];
12+
isCustomQuestion?: boolean;
1213
}) {
13-
const { border = true, textLimit = 35, customQuestions } = opts;
14+
const {
15+
border = true,
16+
textLimit = 35,
17+
customQuestions,
18+
isCustomQuestion,
19+
} = opts;
1420

1521
// if custom questions are provided, use them over the getSuggestions
1622
const questions = customQuestions ?? (await getSuggestions({ limit: 5 }));
@@ -42,7 +48,11 @@ export default async function QuestionSuggestedCard(opts: {
4248
? 'bg-[#000] hover:bg-black-100'
4349
: 'bg-black hover:bg-black-75'
4450
)}
45-
href={`/question/${question.slug}`}
51+
href={
52+
isCustomQuestion
53+
? `/question/custom/${question.uid}`
54+
: `/question/${question.slug}`
55+
}
4656
>
4757
<p className="text-sm font-satoshi line-clamp-1">
4858
{shortenText(question.question, textLimit)}

src/components/app/statistics/statistics-report-tabs.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export default async function StatisticsReportTabs(opts: {
142142
<QuestionSuggestedCard
143143
customQuestions={report.questions ?? []}
144144
textLimit={75}
145+
isCustomQuestion
145146
/>
146147
</div>
147148
</CardContent>

0 commit comments

Comments
 (0)