Created
January 10, 2022 17:06
-
-
Save gauravpan/8bc490b73728a3e86161687e3055646b to your computer and use it in GitHub Desktop.
MCQ Test Page in next js page
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Head from "next/head"; | |
| import { HiClock } from "react-icons/hi"; | |
| import { | |
| Box, | |
| Button, | |
| Container, | |
| Heading, | |
| HStack, | |
| Radio, | |
| RadioGroup, | |
| Spacer, | |
| Stack, | |
| BoxProps, | |
| Icon, | |
| useToast, | |
| } from "@chakra-ui/react"; | |
| import React, { FC, ReactNode, useEffect, useState } from "react"; | |
| import { AppPage } from "../../types"; | |
| import { AnimatePresence, motion } from "framer-motion"; | |
| const MotionBox = motion<BoxProps>(Box); | |
| const item = { | |
| question: "This is a question.", | |
| a: "a", | |
| b: "b", | |
| c: "c", | |
| d: "d", | |
| id: 1, | |
| currentCount: 1, | |
| }; | |
| const list: typeof item[] = Array(200) | |
| .fill(item) | |
| .map((item, i) => ({ | |
| ...item, | |
| currentCount: i, | |
| id: i, | |
| })); | |
| const Home: AppPage = () => { | |
| const [mcq, setmcq] = React.useState(list[0]); | |
| return ( | |
| <div> | |
| <Head> | |
| <title>Test</title> | |
| <meta name="description" content="" /> | |
| <link rel="icon" href="/favicon.ico" /> | |
| </Head> | |
| <TestHeader /> | |
| <Box h="8" /> | |
| <Box pos="relative" overflowX={"hidden"} h="50vh"> | |
| <AnimatePresence initial={false}> | |
| <ListItem | |
| totalCount={list.length} | |
| {...mcq} | |
| key={mcq.id} | |
| onNext={(selectedOption, currentCount) => | |
| setmcq(() => list[currentCount + 1]) | |
| } | |
| onSkip={(currentCount) => { | |
| setmcq(() => list[currentCount + 1]); | |
| }} | |
| /> | |
| </AnimatePresence> | |
| </Box> | |
| </div> | |
| ); | |
| }; | |
| const timeOutDate = new Date().getTime() / 1000 + 30 * 60; | |
| const TestHeader = () => { | |
| const [timeLeft, setTimeLeft] = useState<string | undefined>(undefined); | |
| useEffect(() => { | |
| const updateTime = () => { | |
| let timestart = new Date().getTime() / 1000; | |
| let diff = timeOutDate - timestart; | |
| let readableDiff = `${(diff / 60).toFixed(0)}:${(diff % 60).toFixed(0)}`; | |
| setTimeLeft(() => readableDiff); | |
| console.log({ readableDiff }); | |
| }; | |
| let interval = window.setInterval(updateTime, 1000); | |
| return () => window.clearInterval(interval); | |
| }, []); | |
| return ( | |
| <HStack h="16"> | |
| <Heading size="lg">Scholarship test</Heading> | |
| <Spacer /> | |
| <HStack fontSize="lg" color="gray.700"> | |
| <Icon as={HiClock} boxSize="6" /> | |
| <Box> {timeLeft}</Box> | |
| </HStack> | |
| </HStack> | |
| ); | |
| }; | |
| type listItemProps = { | |
| question: string; | |
| a: string; | |
| b: string; | |
| c: string; | |
| d: string; | |
| totalCount: number; | |
| currentCount: number; | |
| onNext: (selectedOption: string, currentCount: number) => void; | |
| onSkip: (currentCount: number) => void; | |
| }; | |
| const ListItem = ({ | |
| totalCount, | |
| a, | |
| b, | |
| c, | |
| d, | |
| question, | |
| onNext, | |
| onSkip, | |
| currentCount, | |
| }: listItemProps) => { | |
| const [value, setValue] = React.useState<string | undefined>(undefined); | |
| const isThereNextItem = currentCount + 1 !== totalCount; | |
| const toast = useToast(); | |
| useEffect(() => { | |
| let timeOut: number; | |
| if (isThereNextItem) { | |
| timeOut = window.setTimeout(() => { | |
| onSkip(currentCount); | |
| toast({ | |
| title: "Sorry, time out!", | |
| description: "Only 5 seconds for a question.", | |
| }); | |
| }, 5 * 1000); | |
| } | |
| return () => window.clearTimeout(timeOut); | |
| }, []); | |
| return ( | |
| <MotionBox | |
| pos="absolute" | |
| w="full" | |
| className="box" | |
| animate={{ left: 0 }} | |
| initial={{ left: `100%` }} | |
| exit={{ left: `-100%` }} | |
| > | |
| <HStack bg="gray.50" rounded={"md"} p="3"> | |
| <Box | |
| color="gray.600" | |
| fontWeight={"medium"} | |
| fontSize={"sm"} | |
| alignSelf={"flex-start"} | |
| > | |
| {currentCount}/{totalCount} | |
| </Box> | |
| <Box | |
| flex={"1"} | |
| borderLeftWidth={"thick"} | |
| borderLeftRadius={"md"} | |
| px="3" | |
| > | |
| <Heading size={"md"}>{question}</Heading> | |
| <RadioGroup onChange={setValue} value={value}> | |
| <Stack py="3"> | |
| <Radio py="3" _hover={{ bg: "gray.100" }} value="1"> | |
| {a} | |
| </Radio> | |
| <Radio py="3" _hover={{ bg: "gray.100" }} value="2"> | |
| {b} | |
| </Radio> | |
| <Radio py="3" _hover={{ bg: "gray.100" }} value="3"> | |
| {c} | |
| </Radio> | |
| <Radio py="3" _hover={{ bg: "gray.100" }} value="4"> | |
| {d} | |
| </Radio> | |
| </Stack> | |
| </RadioGroup> | |
| <HStack> | |
| <Spacer /> | |
| <Button | |
| minW="20" | |
| onClick={() => onSkip(currentCount)} | |
| disabled={!isThereNextItem} | |
| > | |
| Skip | |
| </Button> | |
| <Button | |
| minW="20" | |
| onClick={() => { | |
| if (value) onNext(value, currentCount); | |
| }} | |
| disabled={!value || !isThereNextItem} | |
| > | |
| Next | |
| </Button> | |
| </HStack> | |
| </Box> | |
| </HStack> | |
| </MotionBox> | |
| ); | |
| }; | |
| Home.getLayout = (page: ReactNode) => <Layout> {page} </Layout>; | |
| const Layout: FC = ({ children }) => { | |
| return ( | |
| <Box bg={"gray.200"} h="100vh" overflow={"auto"}> | |
| <Box h="16"></Box> | |
| <Container maxW={"container.lg"} py="6" px="2"> | |
| {children} | |
| </Container> | |
| </Box> | |
| ); | |
| }; | |
| export default Home; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment