Browse Source

feat: added dark theme, codesnippet and image support in question, and quiz topic screen

main
Abdul Basit 1 year ago
parent
commit
ef976ac254
  1. 96
      README.md
  2. 32
      src/App.tsx
  3. 24
      src/assets/icons/app-logo.svg
  4. 14
      src/assets/icons/bulb.svg
  5. 69
      src/assets/icons/colored-logo.svg
  6. 64
      src/assets/icons/logo.svg
  7. 3
      src/assets/icons/moon.svg
  8. 11
      src/assets/icons/sun.svg
  9. BIN
      src/assets/images/Xeven-Quiz-ReactJS-Quiz-App-Template.jpg
  10. BIN
      src/assets/images/add-images-to-questions.png
  11. BIN
      src/assets/images/brand-logo.jpg
  12. BIN
      src/assets/images/car.jpg
  13. BIN
      src/assets/images/code-snippet-1.png
  14. BIN
      src/assets/images/dish.jpg
  15. BIN
      src/assets/images/mosque.jpg
  16. BIN
      src/assets/images/place.jpg
  17. BIN
      src/assets/images/reptile.jpg
  18. 4
      src/components/Main/index.tsx
  19. 6
      src/components/QuestionScreen/Answer/index.tsx
  20. 10
      src/components/QuestionScreen/Question/index.tsx
  21. 19
      src/components/QuestionScreen/index.tsx
  22. 23
      src/components/QuizDetailsScreen/index.tsx
  23. 117
      src/components/QuizTopicsScreen/index.tsx
  24. 3
      src/components/ResultScreen/RightAnswer/index.tsx
  25. 38
      src/components/ResultScreen/index.tsx
  26. 12
      src/components/ui/Button/index.tsx
  27. 27
      src/components/ui/Button/styled.tsx
  28. 29
      src/components/ui/CodeSnippet/index.tsx
  29. 4
      src/components/ui/ModalWrapper/index.tsx
  30. 20
      src/components/ui/QuizImage/index.tsx
  31. 117
      src/components/ui/ToggleTheme/index.tsx
  32. 6
      src/config/icons.ts
  33. 29
      src/context/QuizContext.ts
  34. 31
      src/context/QuizProvider.tsx
  35. 76
      src/data/QuizQuestions/generalKnowledge.ts
  36. 8
      src/data/QuizQuestions/index.ts
  37. 129
      src/data/QuizQuestions/javascript.ts
  38. 178
      src/data/QuizQuestions/python.ts
  39. 105
      src/data/QuizQuestions/react.ts
  40. 5
      src/data/quizTopics.tsx
  41. 3
      src/hooks/index.ts
  42. 17
      src/hooks/useShuffleQuestions.ts
  43. 1
      src/logo.svg
  44. 29
      src/styles/Global.ts
  45. 78
      src/styles/Theme.ts
  46. 68
      src/styles/styled.d.ts
  47. 2
      src/types/index.ts

96
README.md

@ -1,9 +1,13 @@
![React Quiz App Template Cover Image](./src/assets/images/Xeven-Quiz-ReactJS-Quiz-App-Template.jpg) ![React Quiz App Template Cover Image](./src/assets/images/Xeven-Quiz-ReactJS-Quiz-App-Template.jpg)
# Xeven Quiz - ReactJS Quiz App Template # Xeven Quiz - ReactJS Quiz App Template
With **Xeven Quiz**, you don't have to spend hours coding from scratch. Our template provides a solid foundation, eliminating the need to reinvent the wheel. You'll spend less time developing your app, which lets you focus on the unique. Xeven Quiz helps you follow industry best practices and coding conventions as a beginner.
With **Xeven Quiz**, you don't have to spend hours on coding from scratch. This Quiz App template provides a solid foundation, eliminating the need to reinvent the wheel. You'll spend less time developing your app, which lets you focus on the unique.
As a beginner developer, a quiz app is a common project. But without guidance, this simple project can become difficult. Xeven Quiz is here to change that narrative and give you the tools and knowledge you need to create a successful and efficient quiz app.
Xeven Quiz helps you follow industry best practices and coding conventions as a beginner.
As a beginner developer, a quiz app is a common project. But without guidance, this simple project can become difficult.
Xeven Quiz is here to change that narrative and give you the tools and knowledge you need to create a successful and efficient quiz app.
With a strong architecture and modular design, you can easily add new features, expand your question database, and accommodate a growing user base without experiencing any setbacks. With a strong architecture and modular design, you can easily add new features, expand your question database, and accommodate a growing user base without experiencing any setbacks.
@ -21,23 +25,39 @@ With **ReactJS** at its core, it lets you design dynamic interfaces that automat
## React Quiz App's Theme Features ## React Quiz App's Theme Features
Before I explain the technical aspects of the quiz app, let me share the wonderful features of the quiz app that is available in demo GitHub version. You can get all these features in a template and mold them according to your choice.
Before I explain the technical aspects of the quiz app, let me share the wonderful features of the quiz app. You can get all these features in a template and mold them according to your choice.
- Seamlessly switch between Light and Dark modes with just a single click from the top menu.
- The user can pick a quiz topic on the first screen, like JavaScript, React, or General Knowledge.
- Each time the quiz starts, questions will be shuffled or randomized.
- There will be a timer running when the quiz starts. If the timer finishes, the quiz will be stopped, and the user will be asked to see the result. - There will be a timer running when the quiz starts. If the timer finishes, the quiz will be stopped, and the user will be asked to see the result.
- Each question has a score. For example, a difficult question has 10 marks, and an easier one has 5.
- The template also supports three types of questions, MCQs, True/False, and MAQs. - The template also supports three types of questions, MCQs, True/False, and MAQs.
- The template allows for adding code snippets in questions. You can easily assess the users' programming knowledge and skills.
- The template allows you to create questions with images to enhance user engagement.
- Each question has a score. For example, a difficult question has 10 marks, and an easier one has 5.
- The result screen shows how many questions the user attempted, how much he scored, how long it took, and whether he passed or failed. - The result screen shows how many questions the user attempted, how much he scored, how long it took, and whether he passed or failed.
- In result screen user can see which question had the right answer and which was wrong. The user can find the correct answer in case of a wrong answer. - In result screen user can see which question had the right answer and which was wrong. The user can find the correct answer in case of a wrong answer.
## React Quiz App's Code Features ## React Quiz App's Code Features
- **TypeScript powered Components:** All components are TypeScript-built for enhanced development productivity with intelligent code completion and compile-time error checking.
- **Easy Theme Customization:** The template provides easy theme management with IntelliSense support via Styled Components and Typescript. It allows you to customize the app's appearance without relying on hard-coded colors.
- **Flexible Question Data Structure:** The template uses Javascript/TypeScript files to define quiz questions. This format provides a structured and flexible approach. The same format can also be used to fetch questions from an API if desired.
- **Modular and Context Pattern:** The template follows a javascript modular and React Context pattern, promoting component reusability and maintainability.
- **Custom Hooks for Logic Sharing:** The hook pattern lets you share logic across components. It also promotes code reuse and minimizes code clutter.
- **Built with React Best Practices:** App follows industry-leading React practices. Our template ensures optimal structure, scalability, and maintainability.
- **TypeScript powered Components**: All components are TypeScript-built for enhanced development productivity with intelligent code completion and compile-time error checking.
- **Easy Theme Customization**: The template provides easy theme management with IntelliSense support via Styled Components and Typescript. It allows you to customize the app's appearance without relying on hard-coded colors.
- **Flexible Question Data Structure**: The template uses Javascript/TypeScript files to define quiz questions. This format provides a structured and flexible approach. The same format can also be used to fetch questions from an API if desired.
- **Modular and Context Pattern**: The template follows a javascript modular and React Context pattern, promoting component reusability and maintainability.
- **Custom Hooks for Logic Sharing**: The hook pattern lets you share logic across components. It also promotes code reuse and minimizes code clutter.
- **Built with React Best Practices**: App follows industry-leading React practices. Our template ensures optimal structure, scalability, and maintainability.
## Xeven Quiz - ReactJS Quiz App Template Code Documentations ## Xeven Quiz - ReactJS Quiz App Template Code Documentations
@ -74,18 +94,19 @@ Understanding the folder structure is essential for working with the app. Here's
### Components Architecture ### Components Architecture
The **Xeven Quiz App** consists of 4 main screens/components that are displayed conditionally (in light version):
The **Xeven Quiz App** consists of 5 main screens/components that are displayed conditionally:
1. Splash Screen 1. Splash Screen
2. Quiz Details Screen
3. Questions Screen
4. Result Screen
2. Quiz Topics Screen
3. Quiz Details Screen
4. Questions Screen
5. Result Screen
The screens are organized in the **`components`** folder since the app does not utilize routing. If a component is reusable and can be used in multiple places within the app (e.g., Button, ModalWrapper, and CodeSnippet), it is placed in the **`components/UI`** folder. On the other hand, if a component is screen-specific and separated just to make other components smaller and more manageable, it is placed in the relevant components folder. For example, the components `QuizHeader`, `Question`, and `Answer` are inside the **`QuestionScreen`** folder.
The screens are organized in the **`components`** folder since the app does not utilize routing. If a component is reusable and can be used in multiple places within the app (e.g., Button, ModalWrapper, and CodeSnippet), it is placed in the **`components/UI`** folder. On the other hand, if a component is screen-specific and separated just to make other components smaller and more manageable, it is placed in the relevant components folder. For example, the components `**QuizHeader**`, `**Question**`, and `**Answer**` are inside the **`QuestionScreen`** folder.
## How to customize the quiz layout and styling ## How to customize the quiz layout and styling
### **Changing the App Theme**
### Changing the App Theme
To change the theme of the app, follow these steps: To change the theme of the app, follow these steps:
@ -102,7 +123,7 @@ To change the font of the app, follow these steps:
4. Go to the **`theme`** file and change the font name. 4. Go to the **`theme`** file and change the font name.
5. Go to the global styles and update the font in the **`body`** section. 5. Go to the global styles and update the font in the **`body`** section.
### **Modifying the Quiz Topic Screen or Adding New Categories**
### Modifying the Quiz Topic Screen or Adding New Categories
To modify the Quiz Topics Screen or add new categories of topics/icons, follow these steps: To modify the Quiz Topics Screen or add new categories of topics/icons, follow these steps:
@ -198,6 +219,38 @@ function Main() {
export default Main export default Main
``` ```
### Adding Pictures/Images to Questions
In addition to text questions, you can also include images or pictures to enhance your questions. To add an image to a question, follow these steps:
**1. Upload the Image**
Start by placing your image file in the **`src/assets/images`** folder within your project directory.
**2. Import the Image**
Import the image in the quiz category data where you want to show it. For more details see `src/data/QuizQuestions/generalKnowledge.ts`
**3. Link the Image to Your Question**
Within your question object, add an image key. Then, reference the image you imported in step 2 at the top of your question.
![add question with picture](./src/assets/images/add-images-to-questions.png)
### How to Add Code Snippets in Questions
Just like images, each question supports a **`code`** key, which is conditionally shown only if the question contains a code snippet.
### How to format code snippet
In the Xeven Quiz App, code snippets are pieces of code represented as text. To make them look nice and readable, we use an npm package called **`prismjs`**. This tool highlights the code with different colors so that it stands out and is easy to understand.
To display code correctly, we need to pay attention to the spaces and how the code is structured, just like we do with the existing questions. This way, the code will appear neatly formatted and will be easier for users to read and comprehend.
Here's an example image to illustrate the correct format for displaying code snippets:
![code snippet format](./src/assets/images/code-snippet-format.png)
### Implementing Different Types of Quiz Questions ### Implementing Different Types of Quiz Questions
The Code Quiz App supports various types of quiz questions, including Multiple Choice Questions (MCQs), Multiple Answer Questions (MAQs), and True/False questions. To add different question types, you can modify the question components and their associated data structures. You can refer to the existing question formats in the **`data/QuizQuestions`** folder as examples when creating new questions. The Code Quiz App supports various types of quiz questions, including Multiple Choice Questions (MCQs), Multiple Answer Questions (MAQs), and True/False questions. To add different question types, you can modify the question components and their associated data structures. You can refer to the existing question formats in the **`data/QuizQuestions`** folder as examples when creating new questions.
@ -218,10 +271,11 @@ Discover the Premium Version! Elevate your experience with the same amazing feat
### Premium Version Highlights ### Premium Version Highlights
- Elevate user experience by seamlessly toggling between light and dark modes with a single click from the top menu.
- The user can pick a quiz topic on the first screen, like JavaScript, React, or Python.
Almost all the features I have added in this GitHub open source repo.
- Every time the quiz starts, the questions will be shuffled or randomized
- Users can skip a question if they are unsure of the answer. They can return to it later and answer it before submitting the quiz. - Users can skip a question if they are unsure of the answer. They can return to it later and answer it before submitting the quiz.
- The PRO version also allows for adding code snippets in questions. You can easily assess the users' programming knowledge and skills.
## **Deploying the Quiz App to a Production Environment** ## **Deploying the Quiz App to a Production Environment**

32
src/App.tsx

@ -1,17 +1,41 @@
import { useState } from 'react'
import { ThemeProvider } from 'styled-components' import { ThemeProvider } from 'styled-components'
import Main from './components/Main' import Main from './components/Main'
import QuizProvider from './context/QuizContext'
import ToggleTheme from './components/ui/ToggleTheme'
import QuizProvider from './context/QuizProvider'
import { GlobalStyles } from './styles/Global' import { GlobalStyles } from './styles/Global'
import { theme as AppTheme } from './styles/Theme'
import { themes } from './styles/Theme'
const App = () => (
<ThemeProvider theme={AppTheme}>
function App() {
const [currentTheme, setCurrentTheme] = useState(() => {
const savedTheme = localStorage.getItem('theme')
return savedTheme || 'light'
})
const toggleTheme = (e: React.ChangeEvent<HTMLInputElement>) => {
const { checked } = e.target
setCurrentTheme(checked ? 'dark' : 'light')
localStorage.setItem('theme', checked ? 'dark' : 'light')
}
const theme = currentTheme === 'light' ? themes.light : themes.dark
return (
<ThemeProvider theme={theme}>
<GlobalStyles /> <GlobalStyles />
<QuizProvider> <QuizProvider>
<ToggleTheme
onChange={toggleTheme}
currentTheme={currentTheme}
checked={currentTheme === 'dark'}
id="toggleTheme"
value="theme"
/>
<Main /> <Main />
</QuizProvider> </QuizProvider>
</ThemeProvider> </ThemeProvider>
) )
}
export default App export default App

24
src/assets/icons/app-logo.svg
File diff suppressed because it is too large
View File

14
src/assets/icons/bulb.svg
File diff suppressed because it is too large
View File

69
src/assets/icons/colored-logo.svg
File diff suppressed because it is too large
View File

64
src/assets/icons/logo.svg
File diff suppressed because it is too large
View File

3
src/assets/icons/moon.svg

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.65784 3.17857C5.14829 3.35425 3.16699 5.44573 3.16699 7.99996C3.16699 10.6693 5.33095 12.8333 8.00033 12.8333C10.1389 12.8333 11.9535 11.4441 12.5902 9.51907C12.1001 9.72153 11.5631 9.83329 11.0003 9.83329C8.69914 9.83329 6.83366 7.96781 6.83366 5.66663C6.83366 4.73386 7.1404 3.87269 7.65784 3.17857ZM2.16699 7.99996C2.16699 4.7783 4.77866 2.16663 8.00033 2.16663C8.30007 2.16663 8.59484 2.18928 8.88296 2.23304C9.08227 2.26331 9.2439 2.41032 9.29289 2.60587C9.34188 2.80142 9.26863 3.00726 9.10714 3.12792C8.33315 3.70619 7.83366 4.62801 7.83366 5.66663C7.83366 7.41553 9.25142 8.83329 11.0003 8.83329C11.7565 8.83329 12.4495 8.56889 12.994 8.12705C13.1505 8.00009 13.3679 7.97945 13.5454 8.07469C13.723 8.16994 13.826 8.36245 13.8068 8.56303C13.5232 11.5208 11.0321 13.8333 8.00033 13.8333C4.77866 13.8333 2.16699 11.2216 2.16699 7.99996Z" fill="#282526"/>
</svg>

11
src/assets/icons/sun.svg

@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99967 0.833618C8.27582 0.833618 8.49967 1.05748 8.49967 1.33362V2.00028C8.49967 2.27643 8.27582 2.50028 7.99967 2.50028C7.72353 2.50028 7.49967 2.27643 7.49967 2.00028V1.33362C7.49967 1.05748 7.72353 0.833618 7.99967 0.833618Z" fill="#E9EAF0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.16634 8.00029C4.16634 5.88319 5.88258 4.16695 7.99967 4.16695C10.1168 4.16695 11.833 5.88319 11.833 8.00029C11.833 10.1174 10.1168 11.8336 7.99967 11.8336C5.88258 11.8336 4.16634 10.1174 4.16634 8.00029ZM7.99967 5.16695C6.43487 5.16695 5.16634 6.43548 5.16634 8.00029C5.16634 9.56509 6.43487 10.8336 7.99967 10.8336C9.56448 10.8336 10.833 9.56509 10.833 8.00029C10.833 6.43548 9.56448 5.16695 7.99967 5.16695Z" fill="#E9EAF0"/>
<path d="M3.63919 2.9327C3.44393 2.73744 3.12735 2.73744 2.93209 2.9327C2.73682 3.12796 2.73682 3.44455 2.93209 3.63981L3.40349 4.11121C3.59875 4.30648 3.91533 4.30648 4.1106 4.11121C4.30586 3.91595 4.30586 3.59937 4.1106 3.40411L3.63919 2.9327Z" fill="#E9EAF0"/>
<path d="M15.1663 8.00029C15.1663 8.27643 14.9425 8.50029 14.6663 8.50029H13.9997C13.7235 8.50029 13.4997 8.27643 13.4997 8.00029C13.4997 7.72414 13.7235 7.50029 13.9997 7.50029H14.6663C14.9425 7.50029 15.1663 7.72414 15.1663 8.00029Z" fill="#E9EAF0"/>
<path d="M13.0673 3.6398C13.2625 3.44454 13.2625 3.12796 13.0673 2.9327C12.872 2.73743 12.5554 2.73743 12.3602 2.9327L11.8887 3.4041C11.6935 3.59936 11.6935 3.91594 11.8887 4.11121C12.084 4.30647 12.4006 4.30647 12.5959 4.11121L13.0673 3.6398Z" fill="#E9EAF0"/>
<path d="M7.99967 13.5003C8.27582 13.5003 8.49967 13.7241 8.49967 14.0003V14.667C8.49967 14.9431 8.27582 15.167 7.99967 15.167C7.72353 15.167 7.49967 14.9431 7.49967 14.667V14.0003C7.49967 13.7241 7.72353 13.5003 7.99967 13.5003Z" fill="#E9EAF0"/>
<path d="M12.5959 11.8894C12.4006 11.6941 12.0841 11.6941 11.8888 11.8894C11.6935 12.0846 11.6935 12.4012 11.8888 12.5965L12.3602 13.0679C12.5555 13.2631 12.872 13.2631 13.0673 13.0679C13.2626 12.8726 13.2626 12.556 13.0673 12.3608L12.5959 11.8894Z" fill="#E9EAF0"/>
<path d="M2.49967 8.00029C2.49967 8.27643 2.27582 8.50029 1.99967 8.50029H1.33301C1.05687 8.50029 0.833008 8.27643 0.833008 8.00029C0.833008 7.72414 1.05687 7.50029 1.33301 7.50029H1.99967C2.27582 7.50029 2.49967 7.72414 2.49967 8.00029Z" fill="#E9EAF0"/>
<path d="M4.11055 12.5965C4.30581 12.4012 4.30581 12.0846 4.11055 11.8894C3.91529 11.6941 3.59871 11.6941 3.40345 11.8894L2.93204 12.3608C2.73678 12.556 2.73678 12.8726 2.93204 13.0679C3.1273 13.2631 3.44389 13.2631 3.63915 13.0679L4.11055 12.5965Z" fill="#E9EAF0"/>
</svg>

BIN
src/assets/images/Xeven-Quiz-ReactJS-Quiz-App-Template.jpg

Before

Width: 590  |  Height: 300  |  Size: 25 KiB

BIN
src/assets/images/add-images-to-questions.png

After

Width: 871  |  Height: 930  |  Size: 128 KiB

BIN
src/assets/images/brand-logo.jpg

After

Width: 640  |  Height: 427  |  Size: 26 KiB

BIN
src/assets/images/car.jpg

After

Width: 640  |  Height: 800  |  Size: 96 KiB

BIN
src/assets/images/code-snippet-1.png

After

Width: 792  |  Height: 620  |  Size: 50 KiB

BIN
src/assets/images/dish.jpg

After

Width: 640  |  Height: 960  |  Size: 116 KiB

BIN
src/assets/images/mosque.jpg

After

Width: 640  |  Height: 489  |  Size: 55 KiB

BIN
src/assets/images/place.jpg

After

Width: 640  |  Height: 640  |  Size: 86 KiB

BIN
src/assets/images/reptile.jpg

After

Width: 640  |  Height: 400  |  Size: 36 KiB

4
src/components/Main/index.tsx

@ -5,6 +5,7 @@ import { ScreenTypes } from '../../types'
import QuestionScreen from '../QuestionScreen' import QuestionScreen from '../QuestionScreen'
import QuizDetailsScreen from '../QuizDetailsScreen' import QuizDetailsScreen from '../QuizDetailsScreen'
import QuizTopicsScreen from '../QuizTopicsScreen'
import ResultScreen from '../ResultScreen' import ResultScreen from '../ResultScreen'
import SplashScreen from '../SplashScreen' import SplashScreen from '../SplashScreen'
@ -13,12 +14,13 @@ function Main() {
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {
setCurrentScreen(ScreenTypes.QuizDetailsScreen)
setCurrentScreen(ScreenTypes.QuizTopicsScreen)
}, 1000) }, 1000)
}, [setCurrentScreen]) }, [setCurrentScreen])
const screenComponents = { const screenComponents = {
[ScreenTypes.SplashScreen]: <SplashScreen />, [ScreenTypes.SplashScreen]: <SplashScreen />,
[ScreenTypes.QuizTopicsScreen]: <QuizTopicsScreen />,
[ScreenTypes.QuizDetailsScreen]: <QuizDetailsScreen />, [ScreenTypes.QuizDetailsScreen]: <QuizDetailsScreen />,
[ScreenTypes.QuestionScreen]: <QuestionScreen />, [ScreenTypes.QuestionScreen]: <QuestionScreen />,
[ScreenTypes.ResultScreen]: <ResultScreen />, [ScreenTypes.ResultScreen]: <ResultScreen />,

6
src/components/QuestionScreen/Answer/index.tsx

@ -8,9 +8,9 @@ const AnswerStyle = styled.div<{ highlightAnswer: boolean }>`
font-weight: 400; font-weight: 400;
border: 1px solid border: 1px solid
${({ highlightAnswer, theme }) => ${({ highlightAnswer, theme }) =>
highlightAnswer ? `${theme.colors.themeColor}` : `${theme.colors.lightGray}`};
highlightAnswer ? `${theme.colors.themeColor}` : `${theme.colors.border}`};
background-color: ${({ highlightAnswer, theme }) => background-color: ${({ highlightAnswer, theme }) =>
highlightAnswer ? `${theme.colors.lightPink}` : `${theme.colors.white}`};
highlightAnswer ? `${theme.colors.selectedAnswer}` : `${theme.colors.answerBg}`};
border-radius: 16px; border-radius: 16px;
margin-top: clamp(13px, calc(10px + 6 * ((100vw - 600px) / 1320)), 16px); margin-top: clamp(13px, calc(10px + 6 * ((100vw - 600px) / 1320)), 16px);
cursor: pointer; cursor: pointer;
@ -29,7 +29,7 @@ const AnswerStyle = styled.div<{ highlightAnswer: boolean }>`
` `
const AnswerLabel = styled.label` const AnswerLabel = styled.label`
padding: 16px;
padding: 18px;
display: flex; display: flex;
cursor: pointer; cursor: pointer;
@media ${device.md} { @media ${device.md} {

10
src/components/QuestionScreen/Question/index.tsx

@ -3,7 +3,9 @@ import styled from 'styled-components'
import { device } from '../../../styles/BreakPoints' import { device } from '../../../styles/BreakPoints'
import CodeSnippet from '../../ui/CodeSnippet'
import Answer from '../Answer' import Answer from '../Answer'
import QuizImage from '../../ui/QuizImage'
const QuestionContainer = styled.div` const QuestionContainer = styled.div`
margin-top: 30px; margin-top: 30px;
@ -31,6 +33,8 @@ const QuestionStyle = styled.h2`
interface QuestionTypes { interface QuestionTypes {
question: string question: string
code?: string
image?: string
type: string type: string
choices: string[] choices: string[]
selectedAnswer: string[] selectedAnswer: string[]
@ -39,6 +43,8 @@ interface QuestionTypes {
const Question: FC<QuestionTypes> = ({ const Question: FC<QuestionTypes> = ({
question, question,
code,
image,
type, type,
choices, choices,
selectedAnswer, selectedAnswer,
@ -47,6 +53,10 @@ const Question: FC<QuestionTypes> = ({
return ( return (
<QuestionContainer> <QuestionContainer>
<QuestionStyle>{question}</QuestionStyle> <QuestionStyle>{question}</QuestionStyle>
{/* if question contains code snippet then show code */}
{code && <CodeSnippet code={code} language="javascript" />}
{/* if question contains an image */}
{image && <QuizImage image={image} />}
<AnswersContainer> <AnswersContainer>
{choices.map((choice, index) => ( {choices.map((choice, index) => (
<Answer <Answer

19
src/components/QuestionScreen/index.tsx

@ -16,7 +16,7 @@ import QuizHeader from './QuizHeader'
const QuizContainer = styled.div<{ selectedAnswer: boolean }>` const QuizContainer = styled.div<{ selectedAnswer: boolean }>`
width: 900px; width: 900px;
min-height: 500px; min-height: 500px;
background: ${({ theme }) => theme.colors.white};
background: ${({ theme }) => theme.colors.cardBackground};
border-radius: 4px; border-radius: 4px;
padding: 30px 60px 80px 60px; padding: 30px 60px 80px 60px;
margin-bottom: 70px; margin-bottom: 70px;
@ -30,7 +30,7 @@ const QuizContainer = styled.div<{ selectedAnswer: boolean }>`
svg { svg {
path { path {
fill: ${({ selectedAnswer, theme }) => fill: ${({ selectedAnswer, theme }) =>
selectedAnswer ? `${theme.colors.white}` : `${theme.colors.darkGrayText}`};
selectedAnswer ? `${theme.colors.buttonText}` : `${theme.colors.darkGray}`};
} }
} }
} }
@ -71,7 +71,6 @@ const QuestionScreen: FC = () => {
const { const {
questions, questions,
setQuestions,
quizDetails, quizDetails,
result, result,
setResult, setResult,
@ -83,7 +82,7 @@ const QuestionScreen: FC = () => {
const currentQuestion = questions[activeQuestion] const currentQuestion = questions[activeQuestion]
const { question, type, choices, correctAnswers } = currentQuestion
const { question, type, choices, code, image, correctAnswers } = currentQuestion
const onClickNext = () => { const onClickNext = () => {
const isMatch: boolean = const isMatch: boolean =
@ -107,6 +106,16 @@ const QuestionScreen: FC = () => {
const handleAnswerSelection = (e: React.ChangeEvent<HTMLInputElement>) => { const handleAnswerSelection = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, checked } = e.target const { name, checked } = e.target
if (type === 'MAQs') {
if (selectedAnswer.includes(name)) {
setSelectedAnswer((prevSelectedAnswer) =>
prevSelectedAnswer.filter((element) => element !== name)
)
} else {
setSelectedAnswer((prevSelectedAnswer) => [...prevSelectedAnswer, name])
}
}
if (type === 'MCQs' || type === 'boolean') { if (type === 'MCQs' || type === 'boolean') {
if (checked) { if (checked) {
setSelectedAnswer([name]) setSelectedAnswer([name])
@ -142,6 +151,8 @@ const QuestionScreen: FC = () => {
/> />
<Question <Question
question={question} question={question}
code={code}
image={image}
choices={choices} choices={choices}
type={type} type={type}
handleAnswerSelection={handleAnswerSelection} handleAnswerSelection={handleAnswerSelection}

23
src/components/QuizDetailsScreen/index.tsx

@ -1,11 +1,15 @@
import styled from 'styled-components' import styled from 'styled-components'
import { Logo, StartIcon } from '../../config/icons'
import { AppLogo, StartIcon } from '../../config/icons'
import { useQuiz } from '../../context/QuizContext' import { useQuiz } from '../../context/QuizContext'
import { CenterCardContainer, HighlightedText, PageCenter } from '../../styles/Global'
import {
CenterCardContainer,
HighlightedText,
LogoContainer,
PageCenter,
} from '../../styles/Global'
import { ScreenTypes } from '../../types' import { ScreenTypes } from '../../types'
import { convertSeconds } from '../../utils/helpers' import { convertSeconds } from '../../utils/helpers'
import { useShuffleQuestions } from '../../hooks'
import Button from '../ui/Button' import Button from '../ui/Button'
@ -13,7 +17,6 @@ const AppTitle = styled.h2`
font-weight: 700; font-weight: 700;
font-size: 32px; font-size: 32px;
color: ${({ theme }) => theme.colors.themeColor}; color: ${({ theme }) => theme.colors.themeColor};
margin-top: 34px;
` `
const DetailTextContainer = styled.div` const DetailTextContainer = styled.div`
@ -41,13 +44,12 @@ const QuizDetailsScreen = () => {
setCurrentScreen(ScreenTypes.QuestionScreen) setCurrentScreen(ScreenTypes.QuestionScreen)
} }
// to shuffle or randomize quiz questions
useShuffleQuestions()
return ( return (
<PageCenter light justifyCenter> <PageCenter light justifyCenter>
<CenterCardContainer> <CenterCardContainer>
<Logo />
<LogoContainer>
<AppLogo />
</LogoContainer>
<AppTitle>XEVEN QUIZ</AppTitle> <AppTitle>XEVEN QUIZ</AppTitle>
<DetailTextContainer> <DetailTextContainer>
<DetailText> <DetailText>
@ -63,12 +65,17 @@ const QuizDetailsScreen = () => {
<DetailText> <DetailText>
Total time: <HighlightedText>{convertSeconds(totalTime)}</HighlightedText> Total time: <HighlightedText>{convertSeconds(totalTime)}</HighlightedText>
</DetailText> </DetailText>
<DetailText>
To save time, you can skip questions. Skipped questions will show up at the
end of the quiz.
</DetailText>
</DetailTextContainer> </DetailTextContainer>
<Button <Button
text="Start" text="Start"
icon={<StartIcon />} icon={<StartIcon />}
iconPosition="left" iconPosition="left"
onClick={goToQuestionScreen} onClick={goToQuestionScreen}
bold
/> />
</CenterCardContainer> </CenterCardContainer>
</PageCenter> </PageCenter>

117
src/components/QuizTopicsScreen/index.tsx

@ -0,0 +1,117 @@
import styled from 'styled-components'
import { AppLogo } from '../../config/icons'
import { useQuiz } from '../../context/QuizContext'
import { quizTopics } from '../../data/quizTopics'
import { device } from '../../styles/BreakPoints'
import {
CenterCardContainer,
HighlightedText,
LogoContainer,
PageCenter,
} from '../../styles/Global'
import { ScreenTypes } from '../../types'
import Button from '../ui/Button'
const Heading = styled.h2`
font-size: 32px;
font-weight: 700;
margin-bottom: 20px;
text-align: center;
`
const DetailText = styled.p`
font-weight: 500;
font-size: 20px;
line-height: 29px;
text-align: center;
`
const SelectButtonContainer = styled.div`
display: flex;
flex-wrap: wrap;
justify-content: center;
max-width: 60%;
gap: 30px;
margin-top: 40px;
margin-bottom: 45px;
@media ${device.md} {
row-gap: 20px;
column-gap: 20px;
max-width: 100%;
}
`
interface SelectButtonProps {
active: boolean
disabled?: boolean
}
const SelectButton = styled.div<SelectButtonProps>`
background-color: ${({ disabled, theme }) =>
disabled ? `${theme.colors.disabledCard}` : `${theme.colors.selectTopicBg}`};
border: ${({ active, theme }) =>
active
? `2px solid ${theme.colors.themeColor}`
: `1px solid ${theme.colors.disabledButton}`};
transition: background-color 0.4s ease-out;
border-radius: 10px;
padding: 14px 10px;
display: flex;
align-items: center;
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
@media ${device.md} {
padding: 10px;
tap-highlight-color: transparent;
-webkit-tap-highlight-color: transparent;
}
`
const SelectButtonText = styled.span`
font-size: 18px;
font-weight: 600;
margin-left: 10px;
@media ${device.md} {
font-size: 16px;
font-weight: 500;
}
`
const QuizTopicsScreen: React.FC = () => {
const { quizTopic, selectQuizTopic, setCurrentScreen } = useQuiz()
const goToQuizDetailsScreen = () => {
setCurrentScreen(ScreenTypes.QuizDetailsScreen)
}
return (
<PageCenter light justifyCenter>
<CenterCardContainer>
<LogoContainer>
<AppLogo />
</LogoContainer>
<Heading>
WELCOME TO <HighlightedText> XEVEN QUIZ</HighlightedText>
</Heading>
<DetailText>Select topic below to start your Quiz.</DetailText>
<SelectButtonContainer>
{quizTopics.map(({ title, icon, disabled }) => (
<SelectButton
key={title}
active={quizTopic === title}
onClick={() => !disabled && selectQuizTopic(title)}
disabled={disabled}
>
{icon}
<SelectButtonText>{title}</SelectButtonText>
</SelectButton>
))}
</SelectButtonContainer>
<Button text="Continue" onClick={goToQuizDetailsScreen} bold />
</CenterCardContainer>
</PageCenter>
)
}
export default QuizTopicsScreen

3
src/components/ResultScreen/RightAnswer/index.tsx

@ -2,7 +2,6 @@ import { FC } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { HighlightedText } from '../../../styles/Global' import { HighlightedText } from '../../../styles/Global'
import { theme } from '../../../styles/Theme'
interface RightAnswerProps { interface RightAnswerProps {
correctAnswers: string[] correctAnswers: string[]
@ -25,7 +24,7 @@ const RightAnswer: FC<RightAnswerProps> = ({ correctAnswers, choices }) => {
const label = String.fromCharCode(65 + choices.indexOf(item)) const label = String.fromCharCode(65 + choices.indexOf(item))
return ( return (
<HighlightedText key={index} color={theme.colors.primaryText}>
<HighlightedText key={index} themeText>
{`${label} (${item})${index !== correctAnswers.length - 1 ? ', ' : ''}`} {`${label} (${item})${index !== correctAnswers.length - 1 ? ', ' : ''}`}
</HighlightedText> </HighlightedText>
) )

38
src/components/ResultScreen/index.tsx

@ -1,35 +1,30 @@
import { FC } from 'react' import { FC } from 'react'
import styled, { css } from 'styled-components' import styled, { css } from 'styled-components'
import { AppColoredLogo, Refresh } from '../../config/icons'
import { AppLogo, Refresh } from '../../config/icons'
import { useQuiz } from '../../context/QuizContext' import { useQuiz } from '../../context/QuizContext'
import { device } from '../../styles/BreakPoints' import { device } from '../../styles/BreakPoints'
import { Flex, ResizableBox } from '../../styles/Global'
import { Flex, LogoContainer, ResizableBox } from '../../styles/Global'
import { refreshPage } from '../../utils/helpers' import { refreshPage } from '../../utils/helpers'
import Button from '../ui/Button' import Button from '../ui/Button'
import CodeSnippet from '../ui/CodeSnippet'
import QuizImage from '../ui/QuizImage'
import ResultOverview from './ResultOverview' import ResultOverview from './ResultOverview'
import RightAnswer from './RightAnswer' import RightAnswer from './RightAnswer'
const ResultScreenContainer = styled.div` const ResultScreenContainer = styled.div`
max-width: 900px; max-width: 900px;
margin: 80px auto;
margin: 60px auto;
@media ${device.md} { @media ${device.md} {
width: 90%; width: 90%;
margin: 30px auto; margin: 30px auto;
}
`
const LogoContainer = styled.div`
text-align: center;
margin-bottom: 50px;
@media ${device.md} {
margin-bottom: 30px;
padding-top: 40px;
} }
` `
const InnerContainer = styled.div` const InnerContainer = styled.div`
background: ${({ theme }) => theme.colors.white};
background: ${({ theme }) => theme.colors.cardBackground};
border-radius: 4px; border-radius: 4px;
margin: 0 auto; margin: 0 auto;
margin-bottom: 40px; margin-bottom: 40px;
@ -72,11 +67,12 @@ interface AnswerProps {
} }
const Answer = styled.li<AnswerProps>` const Answer = styled.li<AnswerProps>`
border: 1px solid ${({ theme }) => theme.colors.lightGray};
border: 1px solid ${({ theme }) => theme.colors.border};
width: 90%; width: 90%;
@media ${device.md} { @media ${device.md} {
width: 100%; width: 100%;
} }
background: ${({ theme }) => theme.colors.answerBg};
border-radius: 16px; border-radius: 16px;
font-size: clamp(16px, 5vw, 18px); font-size: clamp(16px, 5vw, 18px);
font-weight: 600; font-weight: 600;
@ -132,13 +128,22 @@ const ResultScreen: FC = () => {
return ( return (
<ResultScreenContainer> <ResultScreenContainer>
<LogoContainer> <LogoContainer>
<AppColoredLogo />
<AppLogo />
</LogoContainer> </LogoContainer>
<InnerContainer> <InnerContainer>
<ResultOverview result={result} /> <ResultOverview result={result} />
{result.map( {result.map(
( (
{ question, choices, correctAnswers, selectedAnswer, score, isMatch },
{
question,
choices,
code,
image,
correctAnswers,
selectedAnswer,
score,
isMatch,
},
index: number index: number
) => { ) => {
return ( return (
@ -149,6 +154,8 @@ const ResultScreen: FC = () => {
<QuestionStyle>{question}</QuestionStyle> <QuestionStyle>{question}</QuestionStyle>
</Flex> </Flex>
<div> <div>
{code && <CodeSnippet code={code} language="javascript" />}
{image && <QuizImage image={image} />}
<ul> <ul>
{choices.map((ans: string, index: number) => { {choices.map((ans: string, index: number) => {
// Convert index to alphabet character // Convert index to alphabet character
@ -184,6 +191,7 @@ const ResultScreen: FC = () => {
onClick={onClickRetry} onClick={onClickRetry}
icon={<Refresh />} icon={<Refresh />}
iconPosition="left" iconPosition="left"
bold
/> />
</Flex> </Flex>
</ResultScreenContainer> </ResultScreenContainer>

12
src/components/ui/Button/index.tsx

@ -7,6 +7,8 @@ interface ButtonTypes {
icon?: ReactNode icon?: ReactNode
iconPosition?: 'left' | 'right' iconPosition?: 'left' | 'right'
outline?: boolean outline?: boolean
bold?: boolean
big?: boolean
disabled?: boolean disabled?: boolean
} }
@ -16,10 +18,18 @@ const Button: FC<ButtonTypes> = ({
icon, icon,
iconPosition, iconPosition,
outline, outline,
bold,
big,
disabled, disabled,
}) => { }) => {
return ( return (
<ButtonStyle onClick={onClick} outline={outline} disabled={disabled}>
<ButtonStyle
onClick={onClick}
outline={outline}
bold={bold}
big={big}
disabled={disabled}
>
{icon && iconPosition === 'left' && <IconLeft>{icon}</IconLeft>} {icon && iconPosition === 'left' && <IconLeft>{icon}</IconLeft>}
{text} {text}
{icon && iconPosition === 'right' && <IconRight>{icon}</IconRight>} {icon && iconPosition === 'right' && <IconRight>{icon}</IconRight>}

27
src/components/ui/Button/styled.tsx

@ -2,31 +2,36 @@ import styled from 'styled-components'
import { device } from '../../../styles/BreakPoints' import { device } from '../../../styles/BreakPoints'
interface ButtonType { interface ButtonType {
outline?: boolean outline?: boolean
bold?: boolean
big?: boolean
} }
export const ButtonStyle = styled.button.attrs(({ outline }: ButtonType) => ({
export const ButtonStyle = styled.button.attrs(({ outline, bold, big }: ButtonType) => ({
outline, outline,
bold,
big,
}))` }))`
width: 195px; width: 195px;
min-height: 50px; min-height: 50px;
color: ${({ theme, outline }) => color: ${({ theme, outline }) =>
outline ? theme.colors.themeColor : theme.colors.white};
outline ? theme.colors.outlineButtonText : theme.colors.buttonText};
background: ${({ theme, outline }) => background: ${({ theme, outline }) =>
outline ? theme.colors.white : theme.colors.themeGradient};
outline ? theme.colors.cardBackground : theme.colors.buttonBackground};
font-size: clamp(16px, 5vw, 24px); font-size: clamp(16px, 5vw, 24px);
border: 1px solid border: 1px solid
${({ theme, outline }) => (!outline ? 'none' : theme.colors.themeColor)}; ${({ theme, outline }) => (!outline ? 'none' : theme.colors.themeColor)};
font-weight: 400;
font-weight: ${({ bold }) => (bold ? '700' : '400')};
border-radius: 9px; border-radius: 9px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@media ${device.md} { @media ${device.md} {
width: 150px;
width: ${({ big }) => (big ? '180px' : '150px')};
min-height: 40px; min-height: 40px;
tap-highlight-color: transparent; tap-highlight-color: transparent;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
} }
&:active { &:active {
transform: scale(0.98); transform: scale(0.98);
box-shadow: ${({ theme }) => theme.shadows.activeButton}; box-shadow: ${({ theme }) => theme.shadows.activeButton};
@ -34,7 +39,7 @@ export const ButtonStyle = styled.button.attrs(({ outline }: ButtonType) => ({
} }
&:disabled { &:disabled {
background: ${({ theme }) => theme.colors.disabledButton}; background: ${({ theme }) => theme.colors.disabledButton};
color: ${({ theme }) => theme.colors.darkGrayText};
color: ${({ theme }) => theme.colors.darkGray};
cursor: not-allowed; cursor: not-allowed;
transform: unset; transform: unset;
box-shadow: unset; box-shadow: unset;
@ -44,9 +49,19 @@ export const ButtonStyle = styled.button.attrs(({ outline }: ButtonType) => ({
export const IconLeft = styled.span` export const IconLeft = styled.span`
margin-right: 10px; margin-right: 10px;
display: flex; display: flex;
svg {
path {
fill: ${({ theme }) => theme.colors.buttonText};
}
}
` `
export const IconRight = styled.span` export const IconRight = styled.span`
margin-left: 20px; margin-left: 20px;
display: flex; display: flex;
svg {
path {
fill: ${({ theme }) => theme.colors.buttonText};
}
}
` `

29
src/components/ui/CodeSnippet/index.tsx

@ -0,0 +1,29 @@
import Prism from 'prismjs'
import 'prismjs/themes/prism-solarizedlight.css'
import React, { useEffect, useRef } from 'react'
interface CodeSnippetProps {
code: string
language: string
}
const CodeSnippet: React.FC<CodeSnippetProps> = ({ code, language }) => {
const codeRef = useRef<HTMLElement | null>(null)
useEffect(() => {
if (codeRef.current) {
Prism.highlightElement(codeRef.current)
}
}, [code])
return (
<pre className={`language-${language}`}>
<code className={`language-${language}`} ref={codeRef}>
{code}
</code>
</pre>
)
}
export default CodeSnippet

4
src/components/ui/ModalWrapper/index.tsx

@ -19,7 +19,7 @@ const ModalContainer = styled.div`
const ModalContent = styled.div` const ModalContent = styled.div`
width: 600px; width: 600px;
padding: 50px 25px; padding: 50px 25px;
background-color: ${({ theme }) => theme.colors.white};
background: ${({ theme }) => theme.colors.cardBackground};
border-radius: 10px; border-radius: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
@ -65,7 +65,7 @@ const ModalWrapper: FC<ModalWrapperProps> = ({
{icon} {icon}
<ModalTitle>{title}</ModalTitle> <ModalTitle>{title}</ModalTitle>
<ModalSubtitle>{subtitle}</ModalSubtitle> <ModalSubtitle>{subtitle}</ModalSubtitle>
<Button text={buttonTitle} onClick={onClick} />
<Button text={buttonTitle} onClick={onClick} bold big />
</ModalContent> </ModalContent>
</ModalContainer> </ModalContainer>
) )

20
src/components/ui/QuizImage/index.tsx

@ -0,0 +1,20 @@
import { FC } from 'react'
import styled from 'styled-components'
interface QuizImageProps {
image: string
}
const ImageStyle = styled.img`
border-radius: 10px;
height: 400px;
max-width: 100%;
box-shadow: 6px 6px 2px ${({ theme }) => theme.colors.themeColor};
margin-bottom: 20px;
`
const QuizImage: FC<QuizImageProps> = ({ image }) => (
<ImageStyle src={image} alt="picture quiz" />
)
export default QuizImage

117
src/components/ui/ToggleTheme/index.tsx

@ -0,0 +1,117 @@
import React, { FC } from 'react'
import styled, { keyframes } from 'styled-components'
import { Moon, Sun } from '../../../config/icons'
const ToggleLabel = styled.label`
font-size: 16px;
font-weight: 700;
color: ${({ theme }) => theme.colors.themeText};
display: flex;
align-items: center;
position: absolute;
top: 12px;
right: 25px;
`
const SlideOn = keyframes`
0% {
transform: translateX(0) scale(1);
}
50% {
transform: translateX(15px) scale(1.1);
}
100% {
transform: translateX(23px) scale(1);
}
`
const SlideOff = keyframes`
0% {
transform: translateX(23px) scale(1);
}
50% {
transform: translateX(15px) scale(1.1);
}
100% {
transform: translateX(0px) scale(1);
}
`
const ToggleInput = styled.input`
visibility: hidden;
border: 1px solid red;
margin: 0;
&:checked + span {
background-color: ${({ theme }) => theme.colors.dark};
svg {
animation: ${SlideOn} 0.2s linear forwards;
}
}
&:checked + span::after {
background-color: ${({ theme }) => theme.colors.white};
animation: ${SlideOn} 0.2s linear forwards;
}
`
const Ball = styled.span`
width: 52px;
height: 26px;
background-color: ${({ theme }) => theme.colors.white};
display: inline-block;
cursor: pointer;
border-radius: 50px;
position: relative;
svg {
position: absolute;
top: 5px;
left: 7px;
z-index: 1;
animation: ${SlideOff} 0.2s linear forwards;
}
&::after {
content: '';
width: 21px;
height: 21px;
background-color: ${({ theme }) => theme.colors.dark};
border-radius: 50%;
display: inline-block;
position: absolute;
top: 3px;
left: 5px;
align-items: center;
justify-content: center;
animation: ${SlideOff} 0.2s linear forwards;
}
`
interface ToggleThemeProps {
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
id: string
value: string
checked: boolean
currentTheme: string
}
const ToggleTheme: FC<ToggleThemeProps> = ({
onChange,
id,
value,
checked,
currentTheme,
}) => {
return (
<ToggleLabel htmlFor={id}>
Mode:
<ToggleInput
type="checkbox"
id={id}
onChange={onChange}
value={value}
checked={checked}
/>
<Ball>{currentTheme === 'light' ? <Sun /> : <Moon />}</Ball>
</ToggleLabel>
)
}
export default ToggleTheme

6
src/config/icons.ts

@ -1,10 +1,10 @@
import { ReactComponent as AppLogo } from '../assets/icons/app-logo.svg' import { ReactComponent as AppLogo } from '../assets/icons/app-logo.svg'
import { ReactComponent as CheckIcon } from '../assets/icons/check.svg' import { ReactComponent as CheckIcon } from '../assets/icons/check.svg'
import { ReactComponent as AppColoredLogo } from '../assets/icons/colored-logo.svg'
import { ReactComponent as Next } from '../assets/icons/next.svg' import { ReactComponent as Next } from '../assets/icons/next.svg'
import { ReactComponent as Refresh } from '../assets/icons/refresh.svg' import { ReactComponent as Refresh } from '../assets/icons/refresh.svg'
import { ReactComponent as TimerIcon } from '../assets/icons/timer.svg' import { ReactComponent as TimerIcon } from '../assets/icons/timer.svg'
import { ReactComponent as Logo } from '../assets/icons/logo.svg'
import { ReactComponent as StartIcon } from '../assets/icons/start.svg' import { ReactComponent as StartIcon } from '../assets/icons/start.svg'
import { ReactComponent as Sun } from '../assets/icons/sun.svg'
import { ReactComponent as Moon } from '../assets/icons/moon.svg'
export { AppColoredLogo, AppLogo, CheckIcon, Next, Refresh, TimerIcon, Logo, StartIcon }
export { AppLogo, CheckIcon, Next, Refresh, TimerIcon, StartIcon, Sun, Moon }

29
src/context/QuizContext.ts

@ -0,0 +1,29 @@
import { createContext, useContext } from 'react'
import { QuizContextTypes, ScreenTypes } from '../types'
export const initialState: QuizContextTypes = {
currentScreen: ScreenTypes.SplashScreen,
setCurrentScreen: () => {},
quizTopic: 'React',
selectQuizTopic: () => {},
questions: [],
setQuestions: () => {},
result: [],
setResult: () => {},
timer: 15,
setTimer: () => {},
endTime: 0,
setEndTime: () => {},
quizDetails: {
totalQuestions: 0,
totalScore: 0,
totalTime: 0,
selectedQuizTopic: 'React',
},
}
export const QuizContext = createContext<QuizContextTypes>(initialState)
export function useQuiz() {
return useContext(QuizContext)
}

31
src/context/QuizContext.tsx → src/context/QuizProvider.tsx

@ -1,34 +1,7 @@
import { ReactNode, createContext, useContext, useEffect, useState } from 'react'
import { ReactNode, useEffect, useState } from 'react'
import { quiz } from '../data/QuizQuestions' import { quiz } from '../data/QuizQuestions'
import { QuizContextTypes, Result, ScreenTypes } from '../types' import { QuizContextTypes, Result, ScreenTypes } from '../types'
const initialState: QuizContextTypes = {
currentScreen: ScreenTypes.SplashScreen,
setCurrentScreen: () => {},
quizTopic: 'React',
selectQuizTopic: () => {},
questions: [],
setQuestions: () => {},
result: [],
setResult: () => {},
timer: 15,
setTimer: () => {},
endTime: 0,
setEndTime: () => {},
quizDetails: {
totalQuestions: 0,
totalScore: 0,
totalTime: 0,
selectedQuizTopic: 'React',
},
}
export const QuizContext = createContext<QuizContextTypes>(initialState)
export function useQuiz() {
return useContext(QuizContext)
}
import { QuizContext, initialState } from './QuizContext'
type QuizProviderProps = { type QuizProviderProps = {
children: ReactNode children: ReactNode

76
src/data/QuizQuestions/generalKnowledge.ts

@ -0,0 +1,76 @@
// Question Types
// 1. MCQs | Multiple Choice | single
// 2. boolean | true/false | single
// 3. MAQs | Multiple Answers | multiple
import { Topic } from '.'
import BrandLogo from '../../assets/images/brand-logo.jpg'
import Car from '../../assets/images/car.jpg'
import Dish from '../../assets/images/dish.jpg'
import Mosque from '../../assets/images/mosque.jpg'
import Place from '../../assets/images/place.jpg'
import Reptile from '../../assets/images/reptile.jpg'
export const generalKnowledge: Topic = {
topic: 'GeneralKnowledge',
level: 'Beginner',
totalQuestions: 6,
totalScore: 60,
totalTime: 60,
questions: [
{
question: 'What is the name of this reptile?',
image: Reptile,
choices: ['Snake', 'Turtle', 'Crocodile', 'Lizard'],
type: 'MCQs',
correctAnswers: ['Turtle'],
score: 10,
},
{
question: 'In which country is this historical place located?',
image: Place,
choices: ['China', 'Greece', 'India', 'Egypt'],
type: 'MCQs',
correctAnswers: ['China'],
score: 10,
},
{
question: 'This is a famous Pakistani dish. What is the name of this dish?',
image: Dish,
choices: ['Kebab', 'Haleem', 'Paya', 'Biryani'],
type: 'MCQs',
correctAnswers: ['Biryani'],
score: 10,
},
{
question: 'Which famous car is this?',
image: Car,
choices: ['Ford', 'Toyota', 'Mercedes', 'Honda'],
type: 'MCQs',
correctAnswers: ['Mercedes'],
score: 10,
},
{
question: 'To which renowned automobile brand does this logo belong?',
image: BrandLogo,
choices: ['Audi', 'Tesla', 'BMW', 'Hyundai'],
type: 'MCQs',
correctAnswers: ['Tesla'],
score: 10,
},
{
question: 'Do you recognize this iconic mosque? If so, where is it situated?',
image: Mosque,
choices: [
'Faisal Mosque, Islamabad',
'Sheikh Zayed Grand Mosque, UAE',
'Taj Mahal, India',
'Blue Mosque, Turkey',
],
type: 'MCQs',
correctAnswers: ['Faisal Mosque, Islamabad'],
score: 10,
},
],
}

8
src/data/QuizQuestions/index.ts

@ -1,3 +1,6 @@
import { generalKnowledge } from './generalKnowledge'
import { javascript } from './javascript'
import { python } from './python'
import { react } from './react' import { react } from './react'
// Question Types // Question Types
@ -14,6 +17,8 @@ export type Question = {
type: 'MCQs' | 'MAQs' | 'boolean' type: 'MCQs' | 'MAQs' | 'boolean'
correctAnswers: CorrectAnswers correctAnswers: CorrectAnswers
score: number score: number
code?: string
image?: string
} }
export type Topic = { export type Topic = {
@ -26,5 +31,8 @@ export type Topic = {
} }
export const quiz: Record<string, Topic> = { export const quiz: Record<string, Topic> = {
JavaScript: javascript,
React: react, React: react,
Python: python,
'General Knowledge': generalKnowledge,
} }

129
src/data/QuizQuestions/javascript.ts

@ -0,0 +1,129 @@
// Question Types
// 1. MCQs | Multiple Choice | single
// 2. boolean | true/false | single
// 3. MAQs | Multiple Answers | multiple
import { Topic } from '.'
export const javascript: Topic = {
topic: 'Javascript',
level: 'Beginner',
totalQuestions: 14,
totalScore: 125,
totalTime: 240,
questions: [
{
question:
'Which of the following are JavaScript data types? (Select all that apply)',
choices: ['String', 'Number', 'Function', 'Array'],
type: 'MAQs',
correctAnswers: ['String', 'Number', 'Array'],
score: 10,
},
{
question: 'The "this" keyword in JavaScript refers to the current function.',
choices: ['True', 'False'],
type: 'boolean',
correctAnswers: ['False'],
score: 5,
},
{
question: 'Which operator is used for strict equality comparison in JavaScript?',
choices: ['==', '===', '=', '!='],
type: 'MCQs',
correctAnswers: ['==='],
score: 10,
},
{
question:
'Which of the following methods is used to add an element to the end of an array in JavaScript?',
choices: ['push()', 'pop()', 'shift()', 'unshift()'],
type: 'MCQs',
correctAnswers: ['push()'],
score: 10,
},
{
question: 'What is the value of x after executing the following code snippet?',
code: `let x = 5;
x += 2;
x *= 3;`,
choices: ['21', '25', '33', '35'],
type: 'MCQs',
correctAnswers: ['25'],
score: 10,
},
{
question: 'What is the output of the following code snippet?',
code: `console.log(typeof null);`,
choices: ['Object', 'Null', 'Undefined', 'NullObject'],
type: 'MCQs',
correctAnswers: ['Object'],
score: 10,
},
{
question: 'Which of the following is NOT a valid JavaScript variable name?',
choices: ['myVariable', '_variable', '123variable', '$variable'],
type: 'MCQs',
correctAnswers: ['123variable'],
score: 10,
},
{
question:
'Which of the following methods is used to remove the last element from an array in JavaScript?',
choices: ['push()', 'pop()', 'shift()', 'unshift()'],
type: 'MCQs',
correctAnswers: ['pop()'],
score: 10,
},
{
question: 'JavaScript is a case-sensitive language.',
choices: ['True', 'False'],
type: 'boolean',
correctAnswers: ['True'],
score: 5,
},
{
question: 'What is the output of the following code snippet?',
code: `console.log(2 + '2');`,
choices: ['4', '22', '24', "'22'"],
type: 'MCQs',
correctAnswers: ['22'],
score: 10,
},
{
question: 'Which of the following is NOT a JavaScript data type?',
choices: ['String', 'Boolean', 'Integer', 'Object'],
type: 'MCQs',
correctAnswers: ['Integer'],
score: 10,
},
{
question:
'Which of the following are valid JavaScript loop statements? (Select all that apply)',
choices: ['for', 'while', 'loop', 'do...while'],
type: 'MAQs',
correctAnswers: ['for', 'while', 'do...while'],
score: 10,
},
{
question: 'JavaScript is a statically typed language.',
choices: ['True', 'False'],
type: 'boolean',
correctAnswers: ['False'],
score: 5,
},
{
question:
'Which of the following is a valid way to comment a single line in JavaScript?',
choices: [
'// This is a comment',
'/* This is a comment */',
'<!-- This is a comment -->',
'# This is a comment',
],
type: 'MCQs',
correctAnswers: ['// This is a comment'],
score: 10,
},
],
}

178
src/data/QuizQuestions/python.ts

@ -0,0 +1,178 @@
// Question Types
// 1. MCQs | Multiple Choice | single
// 2. boolean | true/false | single
// 3. MAQs | Multiple Answers | multiple
import { Topic } from '.'
export const python: Topic = {
topic: 'Python',
level: 'Advanced',
totalQuestions: 14,
totalScore: 125,
totalTime: 360,
questions: [
{
question:
'Which of the following are valid ways to handle exceptions in Python? (Select all that apply)',
choices: [
'Using try-except blocks',
'Using the finally block',
'Using the raise statement',
'Using assert statements',
],
type: 'MAQs',
correctAnswers: [
'Using try-except blocks',
'Using the finally block',
'Using the raise statement',
],
score: 10,
},
{
question: 'In Python, strings are mutable objects.',
choices: ['True', 'False'],
type: 'boolean',
correctAnswers: ['False'],
score: 5,
},
{
question:
'What is the difference between a shallow copy and a deep copy in Python?',
choices: [
'A shallow copy creates a new object and copies the references to the original elements, while a deep copy creates a new object and recursively copies the elements',
'A shallow copy creates a new object and copies the elements, while a deep copy creates a new object and references the original elements',
'A shallow copy modifies the original object, while a deep copy creates a new object without modifying the original',
'There is no difference between a shallow copy and a deep copy in Python',
],
type: 'MCQs',
correctAnswers: [
'A shallow copy creates a new object and copies the references to the original elements, while a deep copy creates a new object and recursively copies the elements',
],
score: 10,
},
{
question:
'Python supports multiple inheritance, allowing a class to inherit from multiple parent classes.',
choices: ['True', 'False'],
type: 'boolean',
correctAnswers: ['True'],
score: 5,
},
{
question: 'What is the output of the following Python code?',
code: 'print(list(filter(lambda x: x % 2 == 0, range(10))))',
choices: [
'[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]',
'[0, 2, 4, 6, 8]',
'[1, 3, 5, 7, 9]',
'[0, 1, 2, 3, 4, 9]',
],
type: 'MCQs',
correctAnswers: ['[0, 2, 4, 6, 8]'],
score: 10,
},
{
question: 'What is the purpose of the "__init__" method in a Python class?',
choices: [
'To initialize the class attributes',
'To define the constructor of the class',
'To create a new instance of the class',
'To define the destructor of the class',
],
type: 'MCQs',
correctAnswers: ['To define the constructor of the class'],
score: 10,
},
{
question: 'Which of the following is NOT a built-in decorator in Python?',
choices: ['@staticmethod', '@classmethod', '@property', '@inheritance'],
type: 'MCQs',
correctAnswers: ['@inheritance'],
score: 10,
},
{
question:
'The "pass" statement in Python is used as a placeholder and does nothing when executed.',
choices: ['True', 'False'],
type: 'boolean',
correctAnswers: ['True'],
score: 5,
},
{
question:
'Which of the following are true about Python generators? (Select all that apply)',
choices: [
'Generators are functions that can be paused and resumed',
'Generators can only be used to generate numerical sequences',
'Generators save memory by generating values on-the-fly',
'Generators use the "yield" keyword to return values',
],
type: 'MAQs',
correctAnswers: [
'Generators are functions that can be paused and resumed',
'Generators save memory by generating values on-the-fly',
'Generators use the "yield" keyword to return values',
],
score: 10,
},
{
question: 'What is the output of the following Python code?',
code: 'print(len({True: 1, False: 0, "True": 2}))',
choices: ['0', '1', '2', '3'],
type: 'MCQs',
correctAnswers: ['2'],
score: 10,
},
{
question: 'What is the purpose of the "with" statement in Python?',
choices: [
'To define a context manager',
'To handle exceptions',
'To control the flow of a loop',
'To create a new file',
],
type: 'MCQs',
correctAnswers: ['To define a context manager'],
score: 10,
},
{
question:
'Which of the following is NOT a valid method for string formatting in Python?',
choices: [
'String interpolation using f-strings',
'String interpolation using % operator',
'String interpolation using format() method',
'String interpolation using printf() function',
],
type: 'MCQs',
correctAnswers: ['String interpolation using printf() function'],
score: 10,
},
{
question: 'What is the purpose of the "is" operator in Python?',
choices: [
'To check if two objects have the same value',
'To check if two objects have the same identity',
'To check if two objects have the same type',
'To check if two objects have the same length',
],
type: 'MCQs',
correctAnswers: ['To check if two objects have the same identity'],
score: 10,
},
{
question:
'Which of the following are valid ways to create a virtual environment in Python? (Select all that apply)',
choices: [
'Using the "venv" module',
'Using the "conda" package manager',
'Using the "pipenv" package',
'Using the "virtualenv" package',
],
type: 'MAQs',
correctAnswers: ['Using the "venv" module', 'Using the "virtualenv" package'],
score: 10,
},
],
}

105
src/data/QuizQuestions/react.ts

@ -4,13 +4,14 @@
// 3. MAQs | Multiple Answers | multiple // 3. MAQs | Multiple Answers | multiple
import { Topic } from '.' import { Topic } from '.'
import CodeSnippet1 from '../../assets/images/code-snippet-1.png'
export const react: Topic = { export const react: Topic = {
topic: 'React', topic: 'React',
level: 'Intermediate', level: 'Intermediate',
totalQuestions: 6,
totalScore: 45,
totalTime: 180,
totalQuestions: 11,
totalScore: 95,
totalTime: 600,
questions: [ questions: [
{ {
question: 'What is JSX in React?', question: 'What is JSX in React?',
@ -45,6 +46,48 @@ export const react: Topic = {
correctAnswers: ['To define the structure and appearance of the user interface'], correctAnswers: ['To define the structure and appearance of the user interface'],
score: 10, score: 10,
}, },
{
question:
'Which of the following are valid React lifecycle methods? (Select all that apply)',
choices: [
'componentWillMount',
'componentDidMount',
'componentWillUpdate',
'componentDidUpdate',
],
type: 'MAQs',
correctAnswers: ['componentDidMount', 'componentWillUpdate', 'componentDidUpdate'],
score: 10,
},
{
question: 'What will be the output of the following React code?',
code: `import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
const App = () => {
return <Counter />;
};
export default App;`,
choices: ['Count: 0', 'Count: 1', 'Count: undefined', 'An error will occur'],
type: 'MCQs',
correctAnswers: ['Count: 0'],
score: 10,
},
{ {
question: question:
'In React, props are used to pass data from parent components to child components.', 'In React, props are used to pass data from parent components to child components.',
@ -53,6 +96,31 @@ export const react: Topic = {
correctAnswers: ['True'], correctAnswers: ['True'],
score: 5, score: 5,
}, },
{
question: 'What is the output of the following code snippet?',
image: CodeSnippet1,
choices: ['0', '1', '2', 'undefined'],
type: 'MCQs',
correctAnswers: ['0'],
score: 10,
},
{
question:
'Which of the following are valid ways to conditionally render content in React? (Select all that apply)',
choices: [
'Using the if-else statement',
'Using the ternary operator',
'Using the switch statement',
'Using the && operator',
],
type: 'MAQs',
correctAnswers: [
'Using the if-else statement',
'Using the ternary operator',
'Using the && operator',
],
score: 10,
},
{ {
question: 'In React, what is the purpose of keys in lists?', question: 'In React, what is the purpose of keys in lists?',
choices: [ choices: [
@ -65,6 +133,37 @@ export const react: Topic = {
correctAnswers: ['To provide a unique identifier for each item in the list'], correctAnswers: ['To provide a unique identifier for each item in the list'],
score: 10, score: 10,
}, },
{
question: 'What will be the result of the following React code?',
code: `import React from 'react';
class Button extends React.Component {
handleClick() {
console.log('Button clicked');
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
const App = () => {
return <Button />;
};
export default App;`,
choices: [
'The "Button clicked" message will be logged to the console when the button is clicked',
'The button will not respond to the click event',
'An error will occur due to the incorrect usage of onClick',
'The button will display but nothing will happen when clicked',
],
type: 'MCQs',
correctAnswers: [
'The "Button clicked" message will be logged to the console when the button is clicked',
],
score: 10,
},
{ {
question: 'React uses a virtual DOM to optimize rendering performance.', question: 'React uses a virtual DOM to optimize rendering performance.',
choices: ['True', 'False'], choices: ['True', 'False'],

5
src/data/quizTopics.tsx

@ -8,6 +8,7 @@ import { ReactComponent as Kotlin } from '../assets/icons/kotlin.svg'
import { ReactComponent as Laravel } from '../assets/icons/laravel.svg' import { ReactComponent as Laravel } from '../assets/icons/laravel.svg'
import { ReactComponent as Python } from '../assets/icons/python.svg' import { ReactComponent as Python } from '../assets/icons/python.svg'
import { ReactComponent as ReactIcon } from '../assets/icons/react.svg' import { ReactComponent as ReactIcon } from '../assets/icons/react.svg'
import { ReactComponent as BulbIcon } from '../assets/icons/bulb.svg'
type QuizTopic = { type QuizTopic = {
title: string title: string
@ -28,6 +29,10 @@ export const quizTopics: QuizTopic[] = [
title: 'Python', title: 'Python',
icon: <Python />, icon: <Python />,
}, },
{
title: 'General Knowledge',
icon: <BulbIcon />,
},
{ {
title: 'Gatsby', title: 'Gatsby',
icon: <Gatsby />, icon: <Gatsby />,

3
src/hooks/index.ts

@ -1,4 +1,3 @@
import useShuffleQuestions from './useShuffleQuestions'
import useTimer from './useTimer' import useTimer from './useTimer'
export { useShuffleQuestions, useTimer }
export { useTimer }

17
src/hooks/useShuffleQuestions.ts

@ -1,17 +0,0 @@
import { useEffect } from 'react'
import { useQuiz } from '../context/QuizContext'
import { ScreenTypes } from '../types'
import { shuffleArray } from '../utils/helpers'
export const useShuffleQuestions = () => {
const { setQuestions, currentScreen, questions } = useQuiz()
useEffect(() => {
if (currentScreen === ScreenTypes.QuizDetailsScreen) {
setQuestions(shuffleArray(questions))
}
}, [currentScreen])
}
export default useShuffleQuestions

1
src/logo.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

29
src/styles/Global.ts

@ -24,8 +24,8 @@ body {
text-rendering: optimizeSpeed; text-rendering: optimizeSpeed;
font-family: ${({ theme }) => theme.fonts.anekMalayalam}, sans-serif; font-family: ${({ theme }) => theme.fonts.anekMalayalam}, sans-serif;
font-size: 1rem; font-size: 1rem;
color: ${({ theme }) => theme.colors.text};
background-color: ${({ theme }) => theme.colors.background};
color: ${({ theme }) => theme.colors.themeText};
background: ${({ theme }) => theme.colors.background};
line-height: 1; line-height: 1;
overflow-x: hidden; overflow-x: hidden;
position: relative; position: relative;
@ -136,6 +136,7 @@ export const PageCenter = styled.div<PageCenterTypes>`
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 20px; padding: 20px;
padding-top: 50px;
// due to flex direction column // due to flex direction column
${({ justifyCenter }) => ${({ justifyCenter }) =>
justifyCenter && justifyCenter &&
@ -180,7 +181,7 @@ export const Flex = styled.div<FlexProps>`
` `
export const CenterCardContainer = styled.div` export const CenterCardContainer = styled.div`
background: ${({ theme }) => theme.colors.white};
background: ${({ theme }) => theme.colors.cardBackground};
border-radius: 4px; border-radius: 4px;
min-width: 773px; min-width: 773px;
min-height: 620px; min-height: 620px;
@ -194,8 +195,26 @@ export const CenterCardContainer = styled.div`
} }
` `
export const HighlightedText = styled.span`
color: ${({ color, theme }) => (color ? color : theme.colors.themeColor)};
export const HighlightedText = styled.span<{ themeText?: boolean }>`
color: ${({ themeText, theme }) =>
themeText ? theme.colors.themeText : theme.colors.themeColor};
`
export const LogoContainer = styled.div`
text-align: center;
margin-bottom: 50px;
@media ${device.md} {
margin-bottom: 30px;
}
svg {
width: 220px;
rect {
stroke: ${({ theme }) => theme.colors.appLogo};
}
path {
fill: ${({ theme }) => theme.colors.appLogo};
}
}
` `
interface ResizableBoxProps { interface ResizableBoxProps {

78
src/styles/Theme.ts

@ -1,30 +1,81 @@
import { DefaultTheme } from 'styled-components'
import { Theme } from './styled'
export const theme: DefaultTheme = {
export const themes: Record<string, Theme> = {
light: {
colors: { colors: {
primaryText: '#11052C', // question text color primaryText: '#11052C', // question text color
secondaryText: '#2D264B', // answer text color secondaryText: '#2D264B', // answer text color
themeText: '#000000',
themeColor: '#800080', themeColor: '#800080',
themeGradient: 'linear-gradient(to right,#800080, #FFC0CB)', themeGradient: 'linear-gradient(to right,#800080, #FFC0CB)',
text: '#000000',
background: '#E5E5E5', background: '#E5E5E5',
cardBackground: '#FFFFFF',
selectTopicBg: '#FFFFFF',
appLogo: '#000000',
buttonText: '#FFFFFF',
outlineButtonText: '#800080',
buttonBackground: 'linear-gradient(90.04deg, #800080 0.03%, #FFC0CB 99.96%)',
selectedAnswer: '#FFD6FF',
infoText: '#FF783F', // skip tag text
infoBackground: '#ffb23f26', // skip tag background
border: '#EAEAEA',
answerBg: '#ffffff',
disabledCard: '#fbf4ecbc',
disabledButton: '#e7e8e9',
success: '#12B40E', success: '#12B40E',
successLight: '#DDFFDC', successLight: '#DDFFDC',
danger: '#FF143E', danger: '#FF143E',
dangerLight: '#FFD7DE', dangerLight: '#FFD7DE',
lightPink: '#FFD6FF',
infoText: '#FF783F', // skip button text
infoBackground: '#ffb23f26', // skip button background
codeBackground: '#F9F9F9',
disabledCard: '#fbf4ecbc',
disabledButton: '#e7e8e9',
white: '#FFFFFF', white: '#FFFFFF',
black: '#000000', black: '#000000',
grayText: '#9993A3',
darkGrayText: '#9fa3a9',
dark: '#282526',
darkGray: '#9fa3a9',
darkerGray: '#817a8e',
},
fonts: {
anekMalayalam: 'Anek Malayalam',
},
shadows: {
activeButton: '3px 2px 22px 1px rgba(0, 0, 0, 0.24)',
},
paddings: {
container: '15px',
pageTop: '30px',
},
margins: {
pageTop: '30px',
},
},
dark: {
colors: {
primaryText: '#FFFFFF', // question text color
secondaryText: '#FFFFFF', // answer text color
themeText: '#FFFFFF',
themeColor: '#C000C0',
themeGradient: 'linear-gradient(90deg, #0e050e 0%, #281e20 100%)',
background: 'linear-gradient(90deg, #0e050e 0%, #281e20 100%)',
cardBackground: '#241a1a',
selectTopicBg: '#21191C',
appLogo: '#FFFFFF',
buttonText: '#000000',
outlineButtonText: '#ffffff',
buttonBackground: 'linear-gradient(90.04deg, #800080 0.03%, #FFC0CB 99.96%)',
selectedAnswer: '#151113',
infoText: '#FF783F', // skip tag text
infoBackground: '#ffb23f26', // skip tag background
border: 'transparent',
answerBg: '#151113',
disabledCard: '#00000080',
disabledButton: '#181214',
success: '#12B40E',
successLight: '#151113',
danger: '#FF143E',
dangerLight: '#151113',
white: '#FFFFFF',
black: '#000000',
dark: '#282526',
darkGray: '#9fa3a9',
darkerGray: '#817a8e', darkerGray: '#817a8e',
lightGray: '#eaeaea',
darkText: '',
}, },
fonts: { fonts: {
anekMalayalam: 'Anek Malayalam', anekMalayalam: 'Anek Malayalam',
@ -39,4 +90,5 @@ export const theme: DefaultTheme = {
margins: { margins: {
pageTop: '30px', pageTop: '30px',
}, },
},
} }

68
src/styles/styled.d.ts

@ -1,46 +1,52 @@
// import original module declarations // import original module declarations
import 'styled-components' import 'styled-components'
// and extend them!
declare module 'styled-components' {
export interface DefaultTheme {
export interface Theme {
colors: { colors: {
primaryText: '#11052C' // question text color
secondaryText: '#2D264B' // answer text color
themeColor: '#800080'
themeGradient: 'linear-gradient(to right,#800080, #FFC0CB)'
text: '#000000'
background: '#E5E5E5'
success: '#12B40E'
successLight: '#DDFFDC'
danger: '#FF143E'
dangerLight: '#FFD7DE'
lightPink: '#FFD6FF'
infoText: '#FF783F' // skip button text
infoBackground: '#ffb23f26' // skip button background
codeBackground: '#F9F9F9'
disabledCard: '#fbf4ecbc'
disabledButton: '#e7e8e9'
white: '#FFFFFF'
black: '#000000'
grayText: '#9993A3'
darkGrayText: '#9fa3a9'
darkerGray: '#817a8e'
lightGray: '#eaeaea'
darkText: ''
primaryText: string
secondaryText: string
themeText: string
themeColor: string
themeGradient: string
background: string
cardBackground: string
selectTopicBg: string
appLogo: string
buttonText: string
outlineButtonText: string
buttonBackground: string
selectedAnswer: string
infoText: string
infoBackground: string
border: string
answerBg: string
disabledCard: string
disabledButton: string
success: string
successLight: string
danger: string
dangerLight: string
white: string
black: string
dark: string
darkGray: string
darkerGray: string
} }
fonts: { fonts: {
anekMalayalam: 'Anek Malayalam'
anekMalayalam: string
} }
shadows: { shadows: {
activeButton: '3px 2px 22px 1px rgba(0, 0, 0, 0.24)'
activeButton: string
} }
paddings: { paddings: {
container: '15px'
pageTop: '30px'
container: string
pageTop: string
} }
margins: { margins: {
pageTop: '30px'
pageTop: string
} }
} }
declare module 'styled-components' {
export interface DefaultTheme extends Theme {}
} }

2
src/types/index.ts

@ -3,6 +3,7 @@ import { Question } from '../data/QuizQuestions'
export enum ScreenTypes { export enum ScreenTypes {
SplashScreen, SplashScreen,
QuizTopicsScreen,
QuizDetailsScreen, QuizDetailsScreen,
QuestionScreen, QuestionScreen,
ResultScreen, ResultScreen,
@ -11,7 +12,6 @@ export enum ScreenTypes {
export interface Result extends Question { export interface Result extends Question {
selectedAnswer: string[] selectedAnswer: string[]
isMatch: boolean isMatch: boolean
score: number
} }
export type QuizContextTypes = { export type QuizContextTypes = {

Loading…
Cancel
Save