Create a pure SPA quiz application for JavaScript with three difficulty levels (easy, intermediate, hard), deployed to GitHub Pages at godmar.github.io/jsquiz.
Tech Stack:
- Vite 7 (build tool) - modern, fast, recommended over deprecated CRA
- React 19 (plain JavaScript, no TypeScript)
- Material UI 7 (styling)
- React Router 7 with HashRouter (GitHub Pages compatible)
jsquiz/
├── .github/workflows/deploy.yml # GitHub Actions deployment
├── public/
├── src/
│ ├── components/
│ │ ├── Quiz/
│ │ │ ├── Quiz.jsx # Main quiz container
│ │ │ ├── StartScreen.jsx # Welcome + difficulty selection
│ │ │ ├── Question.jsx # Single question display
│ │ │ └── Results.jsx # Score + review
│ │ └── common/
│ │ └── Header.jsx # App header (dynamic difficulty badge)
│ ├── context/
│ │ └── QuizContext.jsx # Quiz state management
│ ├── data/
│ │ ├── index.js # Question set exports
│ │ ├── easyQuestions.js # 10 easy questions
│ │ ├── questions.js # 10 intermediate questions
│ │ └── hardQuestions.js # 10 hard questions
│ ├── theme/
│ │ └── theme.js # MUI theme (JS yellow colors)
│ ├── App.jsx # Routes configuration
│ └── main.jsx # Entry point with providers
├── index.html
├── package.json
├── vite.config.js # base: '/jsquiz/' for GH Pages
└── PLAN.md
npm create vite@latest . -- --template react
npm install @mui/material @emotion/react @emotion/styled @mui/icons-material react-router-domvite.config.js:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
base: '/jsquiz/', // Critical for GH Pages subdirectory
});src/main.jsx:
import { HashRouter } from 'react-router-dom';
// URLs will be: godmar.github.io/jsquiz/#/easy/1Question Sets:
- Easy (
easyQuestions.js): Basics - types, arrays, functions, operators - Intermediate (
questions.js): Closures, promises, event loop, hoisting - Hard (
hardQuestions.js): Edge cases, generators, proxies, TDZ
Each question format:
{
id: 1,
question: "What is the output?",
code: `console.log(typeof null);`,
options: [
{ id: 'a', text: '"null"' },
{ id: 'b', text: '"object"' },
// ...
],
correctAnswer: 'b',
explanation: "This is a known JavaScript bug..."
}State management for:
- User answers (object:
{questionId: answerId}) - Score calculation (function that takes questions array)
- Submit and reset
- StartScreen: Instructions, three difficulty buttons (Easy/Intermediate/Hard)
- Quiz: Progress bar, Question component, nav buttons
- Question: Code block, radio group for options
- Results: Score display, difficulty badge, question review with explanations
- Header: Dynamic difficulty badge, clickable title linking to home
.github/workflows/deploy.yml:
name: Deploy to GitHub Pages
on:
push:
branches: ['master']
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: actions/configure-pages@v4
- uses: actions/upload-pages-artifact@v3
with:
path: './dist'
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/deploy-pages@v4
id: deployment- Start Screen (
/#/) - Instructions, difficulty selection (Easy/Intermediate/Hard buttons) - Quiz (
/#/easy/1,/#/intermediate/5,/#/hard/3, etc.) - One question at a time, progress bar, prev/next nav - Results (
/#/easy/results,/#/intermediate/results,/#/hard/results) - Score, percentage, difficulty badge, full review with explanations
All routes are bookmarkable - refreshing preserves the current question.
Container,Card,CardContent- LayoutTypography- Text stylingButton,Stack- Actions and difficulty selectionRadioGroup,Radio,FormControlLabel- Answer selectionLinearProgress- Progress indicatorAppBar,Toolbar,Chip- Header with difficulty badgeBox- Spacing and layout
After pushing code:
- Go to repo Settings > Pages
- Under "Build and deployment", select GitHub Actions as source
- Push to
masterbranch triggers deployment
GitHub Pages has fixed cache headers (~10 minutes). Vite generates hashed asset filenames for cache busting, but index.html may be cached by the CDN. For immediate updates during development, test locally with npm run dev.
- Run
npm run dev- test locally atlocalhost:5173/jsquiz/ - Test all routes work with HashRouter (easy/intermediate/hard, question numbers, results)
- Test bookmarkability - refresh should stay on current question
- Push to GitHub, verify Actions workflow runs
- Check deployed site at
godmar.github.io/jsquiz
Initial implementation of the complete quiz application:
- Initialized npm project and installed dependencies (Vite, React, MUI, React Router)
- Created
vite.config.jswithbase: '/jsquiz/'for GitHub Pages - Created
index.htmlentry point - Set up project directory structure
- Implemented all components:
src/main.jsx- Entry point with HashRouter, ThemeProvider, QuizProvidersrc/App.jsx- Route configurationsrc/theme/theme.js- MUI dark theme with JavaScript yellow (#f7df1e)src/context/QuizContext.jsx- Quiz state managementsrc/data/questions.js- 10 intermediate JavaScript questionssrc/components/common/Header.jsx- App header with logosrc/components/Quiz/Quiz.jsx- Main quiz containersrc/components/Quiz/StartScreen.jsx- Welcome screen with instructionssrc/components/Quiz/Question.jsx- Question display with code block and optionssrc/components/Quiz/Results.jsx- Score and review with explanations
- Created
.github/workflows/deploy.ymlfor GitHub Actions deployment - Created
.gitignoreto exclude node_modules and dist
- Added note clarifying that questions assume ES6+ (ECMAScript 2015+) in strict mode
- Styled as a highlighted callout box on the start screen
- Changed routing so each question has its own URL (
/#/1,/#/2, etc.) - Updated
App.jsxto use/:questionNumroute parameter - Updated
Quiz.jsxto read question number from URL and navigate between questions - Simplified
QuizContext.jsxby removing currentIndex state (now URL-driven)
- Removed
isStartedstate requirement - URL now determines which question to show - Page refresh now correctly stays on the current question
- Direct linking to specific questions works (e.g.,
/#/3shows question 3) - Simplified context by removing
isStartedandstartQuizfunction
- Created PLAN.md documenting the implementation plan and commit history
- Corrected tech stack to reflect React 19, MUI 7, React Router 7, Vite 7
- Created
src/data/easyQuestions.jswith 10 basic JavaScript questions - Created
src/data/hardQuestions.jswith 10 advanced JavaScript questions - Created
src/data/index.jsto export question sets and utilities - Updated routing to
/:difficulty/:questionNumpattern - Updated StartScreen with three difficulty buttons (Easy/Intermediate/Hard)
- Updated Header to show dynamic difficulty badge based on current route
- Made Header title clickable to return to landing page
- Updated Results to show difficulty badge
- Updated QuizContext to accept questions as parameter for score/percentage/isComplete
- Added Cache-Control, Pragma, and Expires meta tags
- Note: Limited effectiveness due to GitHub Pages CDN caching (~10 minutes)