Skip to content

Commit 818ff01

Browse files
Merge pull request #25 from cbvora/feature/update_url_to_current_q
Few issues/enhancement
2 parents 73804c7 + 4f119e9 commit 818ff01

File tree

13 files changed

+292
-95
lines changed

13 files changed

+292
-95
lines changed

app/exam/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Button } from "@azure-fundamentals/components/Button";
88
import QuizExamForm from "@azure-fundamentals/components/QuizExamForm";
99
import { Question } from "@azure-fundamentals/components/types";
1010
import ExamResult from "@azure-fundamentals/components/ExamResult";
11+
import LoadingIndicator from "@azure-fundamentals/components/LoadingIndicator";
1112

1213
const questionsQuery = gql`
1314
query RandomQuestions($range: Int!, $link: String) {
@@ -86,7 +87,7 @@ const Exam: NextPage<{ searchParams: { url: string; name: string } }> = ({
8687
setCurrentQuestion(data?.randomQuestions[0]);
8788
}, [data]);
8889

89-
if (loading) return <p>Loading</p>;
90+
if (loading) return <LoadingIndicator />;
9091
if (error) return <p>Oh no... {error.message}</p>;
9192

9293
const numberOfQuestions = data.randomQuestions.length || 0;

app/modes/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const Modes: NextPage<{ searchParams: { url: string; name: string } }> = ({
2323
}}
2424
heading="Practice mode"
2525
paragraph="Learn and familiarize yourself with the questions and answers without any time constraint."
26+
subparagraph="You can copy URL to comeback to the same question later."
2627
wrapperClassNames="from-[#0284C7] to-[#2DD48F]"
2728
headingClassNames="group-hover:from-[#0284C7] group-hover:to-[#2DD48F]"
2829
/>

app/page.tsx

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,50 @@
1+
"use client";
2+
3+
import { useState } from "react";
14
import type { NextPage } from "next";
25
import NameLink from "@azure-fundamentals/components/NameLink";
36
import exams from "@azure-fundamentals/lib/exams.json";
7+
import useDebounce from "@azure-fundamentals/hooks/useDebounce";
48

59
const Home: NextPage = () => {
10+
const [searchTerm, setSearchTerm] = useState("");
11+
const debouncedSearchTerm = useDebounce(searchTerm, 300); // 300ms debounce delay
12+
13+
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
14+
setSearchTerm(event.target.value);
15+
};
16+
17+
const filteredExams = exams.filter((exam) =>
18+
exam.name.toLowerCase().includes(debouncedSearchTerm.toLowerCase()),
19+
);
20+
621
return (
722
<div className="mx-auto mb-6 w-full lg:w-[70vw] 2xl:w-[45%] text-center">
8-
<h2 className="text-white text-5xl text-leading font-bold uppercase md:mt-14">
9-
Welcome!
10-
</h2>
11-
<p className="text-white text-lg mt-4 mb-14 px-5 leading-6">
12-
Select an exam from the list bellow.
23+
<h2 className="text-white text-5xl font-bold uppercase">Welcome!</h2>
24+
<p className="text-white text-lg mt-4 mb-6 px-5 leading-6">
25+
Select an exam from the list below.
1326
</p>
27+
<input
28+
type="text"
29+
value={searchTerm}
30+
onChange={handleSearchChange}
31+
placeholder="Search exams"
32+
className="mb-6 px-4 py-2 border border-gray-300 rounded-md w-3/4 lg:w-1/2"
33+
/>
1434
<div className="grid grid-cols-1 sm:grid-cols-2 gap-x-10 gap-y-5 mx-5 lg:mx-0">
15-
{exams.map((exam) => {
16-
return (
17-
<NameLink
18-
key={exam.name}
19-
href={{
20-
pathname: "/modes",
21-
query: { url: exam.url, name: exam.name },
22-
}}
23-
heading={exam.name}
24-
paragraph={exam.subtitle}
25-
wrapperClassNames="hover:bg-[#C7D2E2]"
26-
headingClassNames="group-hover:from-[#fff] group-hover:to-[#fff]"
27-
/>
28-
);
29-
})}
35+
{filteredExams.map((exam) => (
36+
<NameLink
37+
key={exam.name}
38+
href={{
39+
pathname: "/modes",
40+
query: { url: exam.url, name: exam.name },
41+
}}
42+
heading={exam.name}
43+
paragraph={exam.subtitle}
44+
wrapperClassNames="hover:bg-[#C7D2E2]"
45+
headingClassNames="group-hover:from-[#fff] group-hover:to-[#fff]"
46+
/>
47+
))}
3048
</div>
3149
</div>
3250
);

app/practice/page.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useState, useEffect, useCallback } from "react";
4+
import { useRouter, useSearchParams } from "next/navigation";
45
import { gql, useQuery } from "@apollo/client";
56
import type { NextPage } from "next";
67
import QuizForm from "@azure-fundamentals/components/QuizForm";
@@ -29,11 +30,15 @@ const questionsQuery = gql`
2930
}
3031
`;
3132

32-
const Practice: NextPage<{ searchParams: { url: string; name: string } }> = ({
33-
searchParams,
34-
}) => {
35-
const { url } = searchParams;
36-
const [currentQuestionIndex, setCurrentQuestionIndex] = useState<number>(1);
33+
const Practice: NextPage = () => {
34+
const router = useRouter();
35+
const searchParams = useSearchParams();
36+
37+
const url = searchParams.get("url") || "";
38+
const seqParam = searchParams.get("seq");
39+
const seq = seqParam ? parseInt(seqParam) : 1;
40+
41+
const [currentQuestionIndex, setCurrentQuestionIndex] = useState<number>(seq);
3742
const editedUrl = url.substring(0, url.lastIndexOf("/") + 1);
3843

3944
const { loading, error, data } = useQuery(questionQuery, {
@@ -48,9 +53,26 @@ const Practice: NextPage<{ searchParams: { url: string; name: string } }> = ({
4853
variables: { link: url },
4954
});
5055

56+
const setThisSeqIntoURL = useCallback(
57+
(seq: number) => {
58+
const newSearchParams = new URLSearchParams(searchParams.toString());
59+
newSearchParams.set("seq", seq.toString());
60+
const newUrl = `${
61+
window.location.pathname
62+
}?${newSearchParams.toString()}`;
63+
router.push(newUrl, { shallow: true });
64+
},
65+
[router, searchParams],
66+
);
67+
68+
useEffect(() => {
69+
setCurrentQuestionIndex(seq);
70+
}, [seq]);
71+
5172
const handleNextQuestion = (questionNo: number) => {
5273
if (questionNo > 0 && questionNo - 1 < questionsData?.questions?.count) {
5374
setCurrentQuestionIndex(questionNo);
75+
setThisSeqIntoURL(questionNo);
5476
}
5577
};
5678

components/ExamLink.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface ExamLinkProps
77
LinkProps {
88
heading: string;
99
paragraph: string;
10+
subparagraph?: string;
1011
wrapperClassNames?: string;
1112
headingClassNames?: string;
1213
}
@@ -16,6 +17,7 @@ const ExamLink = ({
1617
paragraph,
1718
wrapperClassNames,
1819
headingClassNames,
20+
subparagraph = "",
1921
...linkProps
2022
}: ExamLinkProps) => {
2123
return (
@@ -36,6 +38,9 @@ const ExamLink = ({
3638
{heading}
3739
</h2>
3840
<p className="text-sm text-slate-400 mt-7">{paragraph}</p>
41+
{subparagraph !== "" && (
42+
<p className="text-sm text-slate-400 mt-2">{subparagraph}</p>
43+
)}
3944
</div>
4045
</Link>
4146
);

components/ExamQuizForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Button } from "@azure-fundamentals/components/Button";
33
import { FC } from "react";
44
import { useForm } from "react-hook-form";
55
import { Question } from "@azure-fundamentals/components/types";
6+
import LoadingIndicator from "./LoadingIndicator";
67

78
type Props = {
89
isLoading: boolean;
@@ -35,7 +36,7 @@ const ExamQuizForm: FC<Props> = ({
3536
handleNextQuestion(currentQuestionIndex + 1);
3637
};
3738

38-
if (isLoading) return <p>Loading...</p>;
39+
if (isLoading) return <LoadingIndicator />;
3940
const { question, options } = questionSet;
4041

4142
const noOfAnswers = options.filter((el) => el.isAnswer).length;

components/LoadingIndicator.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from "react";
2+
3+
const LoadingIndicator: React.FC = () => {
4+
return (
5+
<div className="loading-container">
6+
<div className="spinner"></div>
7+
</div>
8+
);
9+
};
10+
11+
export default LoadingIndicator;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import useDebounce from "@azure-fundamentals/hooks/useDebounce";
2+
import { useState, useEffect } from "react";
3+
4+
interface Props {
5+
totalQuestions: number;
6+
currentQuestionIndex: number;
7+
handleNextQuestion: (index: number) => void;
8+
}
9+
10+
const NumberInputComponent: React.FC<Props> = ({
11+
totalQuestions,
12+
currentQuestionIndex,
13+
handleNextQuestion,
14+
}) => {
15+
const [inputValue, setInputValue] = useState(currentQuestionIndex);
16+
const debouncedInputValue = useDebounce(inputValue, 1000);
17+
18+
useEffect(() => {
19+
if (debouncedInputValue !== currentQuestionIndex) {
20+
handleNextQuestion(debouncedInputValue);
21+
}
22+
}, [debouncedInputValue, currentQuestionIndex, handleNextQuestion]);
23+
24+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
25+
setInputValue(Number(e.target.value));
26+
};
27+
28+
return (
29+
<input
30+
className="w-[40px] text-white rounded-l-md border outline-0 border-slate-700 bg-slate-900 text-center text-md [-moz-appearance:_textfield] [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:m-0 [&::-webkit-outer-spin-button]:appearance-none"
31+
type="number"
32+
min={0}
33+
max={totalQuestions}
34+
value={inputValue}
35+
onChange={handleChange}
36+
/>
37+
);
38+
};
39+
40+
export default NumberInputComponent;

components/QuizExamForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Question } from "./types";
44
import { FieldArray, FormikProvider, Field, useFormik } from "formik";
55
import { Button } from "./Button";
66
import useResults from "@azure-fundamentals/hooks/useResults";
7+
import LoadingIndicator from "./LoadingIndicator";
78

89
type Props = {
910
isLoading: boolean;
@@ -192,7 +193,7 @@ const QuizExamForm: React.FC<Props> = ({
192193
setSavedAnswers(saved);
193194
};
194195

195-
if (isLoading) return <p>Loading...</p>;
196+
if (isLoading) return <LoadingIndicator />;
196197

197198
return (
198199
<FormikProvider value={formik}>

0 commit comments

Comments
 (0)