Study smarter for your G1, M1, or Commercial licence — with practice tests, mock exams, flashcards, road signs, and progress tracking. All in one place. Always free.
WiseDrive is a full-stack Ontario DriveTest preparation app built with Next.js 16 and React 19. It covers the G1 (car), M1 (motorcycle), and Commercial (A/D) knowledge tests with government-standard content sourced from the Ontario Driver's Handbook (MTO).
Everything runs in the browser — progress is saved to localStorage, content is static JSON, and there is no external database. Authentication is handled by NextAuth.js, making the app trivially self-hostable.
| Mode | Description | Feedback |
|---|---|---|
| Practice | Unlimited questions with instant explanations | ✅ Immediate |
| Mock Test | 20-question timed exam (20 min, 80% pass mark) | ❌ After submit |
| Flashcards | 3D flip cards — All, Weak Spots, or By Category | ✅ On reveal |
| Weak Spots | Targeted review of previously missed questions | ✅ Immediate |
| Road Signs | SVG-rendered gallery of 25 Ontario signs | — |
| Lessons | Structured study reading for each licence class | — |
- Readiness Score — Rolling average of last 5 mock test results
- Category Accuracy — Tracks correct/total per topic across all sessions
- Weak Spots System — Wrong answers are automatically queued for review; correct answers remove them
- Daily Streak — Increments on any activity; shown on dashboard
- Mock Test History — Chart with real dates on the X-axis, score on the Y-axis
- 64 G1 questions (Rules of the Road, Signs, Novice Driver Rules, Sharing the Road)
- 40 M1 questions (Motorcycle Operation, Licensing & Safety Gear)
- 30 Commercial questions (Air Brakes, Inspection, Cargo, Hours of Service)
- 25 Ontario road signs — SVG-rendered with authentic shapes, colours, and labels
- 10 comprehensive HTML lessons covering all 3 licence classes
- Dark / light mode — persisted in
localStorage, respects system preference on first load - Fully responsive — mobile-first layout with collapsible sidebar
- Zero external fonts or analytics — no third-party tracking
- Keyboard hints — flashcards support keyboard navigation
- Session summaries — practice and weak spots sessions end with a detailed results screen showing every missed question, correct answer, and explanation
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js App Router | 16.1.6 |
| UI Library | React | 19 |
| Language | TypeScript | 5 (strict mode) |
| Styling | Tailwind CSS v4 + shadcn/ui | 4.x |
| Animations | Framer Motion | 12.x |
| Charts | Recharts | 3.x |
| Auth | NextAuth.js (Credentials + OAuth) | 5.0 beta |
| Icons | Lucide React | 0.57x |
| Password Hashing | bcryptjs | 3.x |
WiseDrive is intentionally simple and dependency-light:
User → NextAuth Session → Middleware (route guard) → App Router Pages
↓
Static JSON (src/data/)
useUserProgress hook
localStorage (wisedrive_progress)
No external database. All question content, lessons, signs, and licence metadata live as static JSON files in src/data/. User credentials are stored in a local .wisedrive-users.json file (file-based, intended for development and self-hosting). User progress (scores, streaks, weak spots, category accuracy) is stored entirely in the browser via localStorage.
useUserProgresshook — Single source of truth for all progress state. Reads from and writes tolocalStoragesynchronously. ExposesaddWeakSpots(ids[])as a batch method to prevent React state-update race conditions when saving multiple wrong answers at once (e.g. after a mock test).answersRefpattern in mock test — AuseRefmirrors theanswersstate so thefinishTestcallback always reads current data regardless of React's render cycle. AfinishedRefguard prevents double-invocation when the timer and the Submit button fire simultaneously.- Static-first data — No API routes for content.
src/utils/data.tsloads JSON at module level, making every page server/client compatible with zero fetch latency. - CSS variable design tokens — All colours use Tailwind v4 CSS custom properties (
--border,--muted,--foreground, etc.) so dark/light mode is a singledarkclass toggle on<html>.
wisedrive/
├── src/
│ ├── app/
│ │ ├── (app)/ # Protected routes (auth required)
│ │ │ ├── dashboard/ # KPIs, readiness score, charts
│ │ │ ├── practice/ # Study hub + category selector
│ │ │ │ ├── page.tsx
│ │ │ │ ├── study/ # Question practice (immediate feedback)
│ │ │ │ └── read/ # Lesson viewer
│ │ │ │ └── [licenceId]/[slug]/
│ │ │ ├── mock-test/ # Timed exam (no feedback until done)
│ │ │ ├── flashcards/ # 3D flip card sessions by mode
│ │ │ ├── weak-spots/ # Targeted review of missed questions
│ │ │ ├── signs/ # SVG Ontario road sign gallery
│ │ │ ├── onboarding/ # Licence class selection
│ │ │ └── settings/ # Change licence, reset progress
│ │ ├── api/auth/[...nextauth]/ # NextAuth route handler
│ │ ├── sign-in/ # Credentials sign-in page
│ │ ├── sign-up/ # Account creation page
│ │ └── page.tsx # Public marketing/landing page
│ │
│ ├── components/
│ │ ├── features/
│ │ │ ├── QuestionCard.tsx # Multiple-choice card w/ optional feedback
│ │ │ └── Timer.tsx # Countdown timer (turns red < 5 min)
│ │ ├── layout/
│ │ │ ├── AppSidebar.tsx # Collapsible nav with tooltip labels
│ │ │ └── AppHeader.tsx # Theme toggle + user dropdown
│ │ ├── providers/ # SessionProvider wrapper
│ │ └── ui/ # shadcn/ui components
│ │
│ ├── data/ # Static content (no API calls)
│ │ ├── g1.json # 64 G1 knowledge questions
│ │ ├── m1.json # 40 M1 motorcycle questions
│ │ ├── commercial.json # 30 Commercial (A/D) questions
│ │ ├── lessons.json # 10 structured study lessons (HTML)
│ │ ├── signs.json # 25 Ontario road signs (shape/colour/SVG)
│ │ └── licences.json # Licence class metadata
│ │
│ ├── hooks/
│ │ └── useUserProgress.ts # localStorage read/write + all state methods
│ ├── types/index.ts # Question, LicenceClass, UserProgress
│ ├── utils/
│ │ ├── data.ts # JSON loaders + category filters
│ │ └── quiz.ts # shuffleArray, getRandomItems, calculateReadiness
│ ├── lib/utils.ts # cn() helper
│ ├── auth.ts # NextAuth config (credentials + OAuth)
│ └── middleware.ts # Redirect unauthenticated users to /sign-in
│
├── .wisedrive-users.json # File-based user store (gitignored)
├── public/ # Logos, static SVGs
└── .env.local # Environment variables (not committed)
| Licence | Vehicle | Questions | Key Topics |
|---|---|---|---|
| G1 | Car / Light truck | 64 | Rules of the Road, Road Signs & Markings, Novice Driver System, Sharing the Road |
| M1 | Motorcycle | 40 | Motorcycle Operation, Hazard Avoidance, M1/M2/M Licensing, T-CLOCS Inspection, Gear |
| Commercial A/D | Truck / Bus | 30 | Air Brakes, Pre-Trip Inspection, Cargo Securement, Hours of Service & Logbooks |
Content is aligned with the Ontario Driver's Handbook and MTO Commercial Vehicle Operator's Registration guidelines.
- Node.js 18.18+ (LTS recommended)
- npm, yarn, pnpm, or bun
# 1. Clone the repository
git clone https://github.com/kabir2004/WiseDrive.git
cd WiseDrive
# 2. Install dependencies
npm install
# 3. Set up environment variables
cp .env.example .env.local
# Edit .env.local and add your AUTH_SECRET (see below)
# 4. Start the development server
npm run devOpen http://localhost:3000 in your browser.
Create a .env.local file in the project root:
# Required — any random string (min 32 chars)
# Generate one: openssl rand -base64 32
AUTH_SECRET=your-random-secret-here
# Optional — Google OAuth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Optional — Azure AD OAuth
AZURE_AD_CLIENT_ID=
AZURE_AD_CLIENT_SECRET=
AZURE_AD_TENANT_ID=Note: Without OAuth credentials, the app falls back to email/password authentication using the file-based user store (
.wisedrive-users.json). This is created automatically on the first sign-up.
npm run build
npm startnpm run lintinterface Question {
id: string;
category: string;
difficulty: 'Easy' | 'Medium' | 'Hard';
text: string;
options: string[];
correctAnswer: string;
explanation: string;
image?: string;
}
interface UserProgress {
activeLicence: string;
streak: {
current: number;
lastActiveDate: string | null;
};
weakSpots: string[]; // Question IDs answered incorrectly
history: {
date: string; // ISO date string
mode: 'MockTest' | 'Practice';
score: number;
total: number;
passed: boolean;
}[];
categoryAccuracy: Record<string, {
correct: number;
total: number;
}>;
}| Metric | Calculation |
|---|---|
| Readiness Score | Average score across the last 5 mock tests |
| Category Accuracy | (correct / total) × 100 per category, rolling across all sessions |
| Pass Mark | 80% — matches the Ontario G1/M1/Commercial knowledge test standard |
| Weak Spots | Added on wrong answer; removed when answered correctly in Practice or Weak Spots review |
| Daily Streak | Increments if any activity is completed and the previous active date was yesterday |
WiseDrive is designed to be trivially self-hostable on any Node.js server or PaaS platform.
Vercel (recommended):
Set AUTH_SECRET in your Vercel project environment variables and deploy.
Docker / VPS:
npm run build
AUTH_SECRET=your-secret node .next/standalone/server.js- Spaced repetition algorithm (SM-2) for flashcards
- G2 exit test content
- Offline PWA support
- Question image support (road sign photos)
- Shareable progress reports
- Admin content panel for question management
- Supabase/Postgres option for multi-device sync
Contributions are welcome! If you spot a content error, have a question suggestion, or want to add a feature:
- Fork the repository
- Create a feature branch:
git checkout -b feat/your-feature - Commit your changes:
git commit -m "feat: describe your change" - Push to the branch:
git push origin feat/your-feature - Open a Pull Request
For content corrections (questions, lessons, signs), open an issue with the MTO handbook reference so changes can be verified against the official source.
This project is licensed under the MIT License — see LICENSE for details.
Content (questions, lessons, signs) is based on publicly available Ontario Ministry of Transportation materials and is provided for educational purposes.
Built with care for Ontario drivers studying for their licence.
⭐ Star this repo if WiseDrive helped you pass your test!