A live, instructor-driven quiz app for Lansing Tech Studio
workshops — like Kahoot, but it loads questions straight from a URL (typically a
quiz.json committed into a workshop repo).
An instructor starts a quiz, students join with a 5-character code on their own devices, and the instructor advances question-by-question. After each question the room sees a bar chart of how many students picked each answer with the correct answer highlighted, then moves on. A live leaderboard keeps it fun.
Built to run entirely on Firebase's free Spark plan: React + Vite static hosting, Firestore for live state, and Firebase Auth — no Cloud Functions, no billing required.
- Instructor signs in with Google, pastes a quiz URL, and gets a join code.
- Students open the app, enter the code + a nickname (anonymous auth), and play.
- Correct answers are never sent to students — they live in a host-only Firestore
document and are only revealed when the instructor clicks Reveal. This is enforced
by
firestore.rules, which act as the backend.
See docs/quiz-schema.md for the quiz file format and
docs/sample-quiz.json for a working example.
You can link straight into host mode with a quiz pre-loaded — handy for a workshop slide:
https://<your-app>/host?quiz=<url-encoded quiz URL>
You can also set the name-policy default in the link:
https://<your-app>/host?quiz=<url-encoded quiz URL>&randomNamesOnly=1
Accepted truthy values are 1, true, yes, on, or random.
For example, to host the JavaScript Basics quiz:
https://learning-check.web.app/host?quiz=https%3A%2F%2Flansingtechstudio.org%2Fworkshops%2Fjavascript-basics%2Fquiz.json
Opening it signs you in (if needed), fetches the quiz, and drops you straight into the
lobby with a join code — no pasting. The URL then becomes …/host?session=CODE, so a
refresh resumes the same live session instead of starting a new one.
The easiest way to get one: on the Start a quiz screen, paste your quiz URL and copy the generated 📎 Slide link.
Hosts can enforce safer naming in two ways:
- Moderation filter: offensive names are blocked.
- Random names only: students must use generated names like
PixelNinja.
How random-name policy works:
- You can set it before launch on Start a quiz.
- You can include it in static host links via
randomNamesOnly=1. - While the session is still in Lobby, the host can toggle it on/off.
- Once the quiz has started, the policy is locked for that session.
When enabled:
- Student join UI makes nickname entry read-only and directs students to the random button.
- Host rename controls include a 🎲 Random shortcut.
- Join/rename is validated in app logic and Firestore rules.
Use Node 24 for local development.
npm install
# Terminal 1 — Firebase Emulator Suite (Auth + Firestore)
npm run emulators
# Terminal 2 — Vite dev server pointed at the emulators
npm run dev:emulatorsOpen the printed URL. Use one tab as the instructor (/host) and another as a student
(/play). Load the bundled sample quiz with the URL http://localhost:5173/sample-quiz.json.
The emulators don't need real Firebase credentials. To run against a real project, copy
.env.exampleto.env.local, fill in your web config, and usenpm run dev.
-
Create a project at https://console.firebase.google.com (Spark/free plan is fine).
-
Build → Firestore Database → create database (production mode).
-
Build → Authentication → enable the Google and Anonymous providers.
-
Project settings → Your apps → register a Web app; copy its config into
.env.local. -
Deploy rules and the app:
npm run build firebase deploy --only firestore:rules,hosting
(Update the project id in
.firebasercfirst.)
| Script | What it does |
|---|---|
npm run dev |
Vite dev server (uses .env.local). |
npm run dev:emulators |
Vite dev server pointed at local emulators. |
npm run emulators |
Start the Firebase Emulator Suite. |
npm run build |
Type-check and build to dist/. |
npm test |
Run unit + rules tests (Vitest). |
This repo includes a workflow at .github/workflows/deploy-firebase.yml that:
- Runs tests and build on every push to
main. - Deploys to Firebase only if those checks pass.
- Uses Node 24 in CI.
Set the following repository secret before using it:
GCP_SERVICE_ACCOUNT_EMAIL: Service account email used for deploys.- this can be found under Service Accounts
GCP_WORKLOAD_IDENTITY_PROVIDER: Full Workload Identity Provider resource name.- find this using GCP cloud shell and running
gcloud iam workload-identity-pools providers list --project=learning-check --location=global --workload-identity-pool=github-actions-pool --format="value(name)"
- find this using GCP cloud shell and running
The build also needs your Firebase web app config as repository secrets (same values you
use in .env.local):
VITE_FIREBASE_API_KEYVITE_FIREBASE_AUTH_DOMAINVITE_FIREBASE_PROJECT_IDVITE_FIREBASE_STORAGE_BUCKETVITE_FIREBASE_MESSAGING_SENDER_IDVITE_FIREBASE_APP_ID
Before the auth with work, also need to enable IAM Service Account Credentials API.
The workflow deploys hosting, firestore.rules, and firestore.indexes.json.
React 19 · Vite · TypeScript · Tailwind CSS · Firebase (Firestore + Auth + Hosting) · Zod.