Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ All data stays in your own Firebase project.
- View poll history and attendance, grouped by poll set and session
- CSV export of poll results (per-poll and per-session)
- Delete individual polls from history
- Password-protected teacher and history access
- Password-protected instructor and history access

### For students
- Join with just a name — no account needed
Expand Down Expand Up @@ -137,7 +137,7 @@ Copy the example file and fill in your values:
cp .env.example .env.local
```

Edit `.env.local` with your Firebase config values and a teacher password of your choice:
Edit `.env.local` with your Firebase config values and an instructor password of your choice:

```
VITE_FIREBASE_API_KEY=your_api_key
Expand All @@ -148,7 +148,7 @@ VITE_FIREBASE_STORAGE_BUCKET=your_project.firebasestorage.app
VITE_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
VITE_FIREBASE_APP_ID=your_app_id

VITE_TEACHER_PASSWORD=your_password_here
VITE_INSTRUCTOR_PASSWORD=your_password_here
```
Comment on lines 140 to 152

**Never commit `.env.local` to git.** It is already listed in `.gitignore`.
Expand All @@ -166,7 +166,7 @@ npm run dev
```

Open [http://localhost:5173/classroom-polling/](http://localhost:5173/classroom-polling/).
Open two browser tabs — one as teacher, one as student — to verify everything works.
Open two browser tabs — one as instructor, one as student — to verify everything works.

### Step 9 — Set your repo name in vite.config.js

Expand Down Expand Up @@ -223,7 +223,7 @@ git merge upstream/main

### Running a poll

1. Open the app → **I'm the Teacher** → enter your password
1. Open the app → **I'm the Instructor** → enter your password
2. Click **New Poll** → enter question and options → click **Start Poll**
3. Share your app URL with students — they click **I'm a Student**, enter their name, and wait
4. Students see the poll instantly; results update live on your dashboard
Expand All @@ -239,15 +239,15 @@ git merge upstream/main

### Viewing history and attendance

1. Click **History** → enter teacher password
1. Click **History** → enter instructor password
2. **Polls tab**: past polls grouped by set, expandable with per-option results
3. **Attendance tab**: students who joined, grouped by date

## Customization

| File | What to change |
|------|---------------|
| `.env.local` | Teacher password, Firebase config |
| `.env.local` | Instructor password, Firebase config |
| `firebase-rules.json` | Database security rules |
| `vite.config.js` | Repository name for GitHub Pages base path |
| `src/index.css` | Colors, fonts, visual design |
Expand All @@ -264,7 +264,7 @@ classroom-polling/
│ │ └── csvExport.js CSV export utilities
│ ├── pages/
│ │ ├── RoleSelector.jsx Landing page
│ │ ├── TeacherPage.jsx Teacher dashboard
│ │ ├── InstructorPage.jsx Instructor dashboard
│ │ ├── StudentPage.jsx Student poll experience
│ │ ├── PollHistory.jsx History and attendance
│ │ ├── PollSets.jsx Poll set list and creation
Expand All @@ -290,7 +290,7 @@ The Firebase Spark (free) plan is sufficient for typical classroom use:

## Known limitations

- Teacher password is a single shared secret; not suitable for multiple instructors sharing one instance
- Instructor password is a single shared secret; not suitable for multiple instructors sharing one instance
- Code formatting in questions is plain text only (markdown support planned)
- Student names are self-reported and not authenticated

Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/classroom-polling/assets/index-BEzP6REc.js"></script>
<script type="module" crossorigin src="/classroom-polling/assets/index-CLC7Q4lX.js"></script>
<link rel="stylesheet" crossorigin href="/classroom-polling/assets/index-DZ0sPA0V.css">
</head>
<body>
Expand Down
18 changes: 9 additions & 9 deletions node_modules/.vite/deps/_metadata.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import RoleSelector from './pages/RoleSelector';
import TeacherPage from './pages/TeacherPage';
import InstructorPage from './pages/InstructorPage';
import StudentPage from './pages/StudentPage';
import PollSetDetail from './pages/PollSetDetail';
import PollSets from './pages/PollSets';
Expand All @@ -21,7 +21,7 @@ export default function App() {
<BrowserRouter basename="/classroom-polling">
<Routes>
<Route path="/" element={<RoleSelector />} />
<Route path="/teacher" element={<TeacherPage />} />
<Route path="/instructor" element={<InstructorPage />} />
<Route path="/student" element={<StudentPage />} />
Comment on lines 23 to 25
<Route path="/pollsets/:id" element={<PollSetDetail />} />
<Route path="/pollsets" element={<PollSets />} />
Expand Down
4 changes: 2 additions & 2 deletions src/pages/TeacherPage.jsx → src/pages/InstructorPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const CORRECT_OPTIONS = [
{ value: 'never', label: 'Never' },
];

export default function TeacherPage() {
export default function InstructorPage() {
const navigate = useNavigate();
const [students, setStudents] = useState([]);
const [activePoll, setActivePoll] = useState(null);
Expand All @@ -48,7 +48,7 @@ export default function TeacherPage() {
const [correctPolicy, setCorrectPolicy] = useState('with_results');

useEffect(() => {
if (localStorage.getItem("role") !== 'teacher') navigate('/');
if (localStorage.getItem("role") !== 'instructor') navigate('/');
}, []);

useEffect(() => {
Expand Down
8 changes: 4 additions & 4 deletions src/pages/PollHistory.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { db } from '../firebase';
import { ref, onValue, remove } from 'firebase/database';
import { pollToCsv, sessionToCsv, downloadCsv } from '../utils/csvExport';

const HISTORY_PASSWORD = import.meta.env.VITE_TEACHER_PASSWORD || 'changeme';
const HISTORY_PASSWORD = import.meta.env.VITE_INSTRUCTOR_PASSWORD || 'changeme';

export default function PollHistory() {
const navigate = useNavigate();
Expand All @@ -33,7 +33,7 @@ export default function PollHistory() {
const [copyFeedback, setCopyFeedback] = useState({}); // key -> true

useEffect(() => {
if (localStorage.getItem("historyAuth") === 'true' || localStorage.getItem("role") === 'teacher') setAuth(true);
if (localStorage.getItem("historyAuth") === 'true' || localStorage.getItem("role") === 'instructor') setAuth(true);
}, []);

useEffect(() => {
Expand Down Expand Up @@ -153,11 +153,11 @@ export default function PollHistory() {
</div>
<h2 style={{fontSize:'1.4rem', marginBottom:'0.25rem'}}>Poll History</h2>
<p style={{color:'var(--muted)', marginBottom:'1.5rem', fontSize:'0.9rem'}}>
Enter the teacher password to view history and attendance.
Enter the instructor password to view history and attendance.
</p>
<form onSubmit={handleLogin}
style={{display:'flex', flexDirection:'column', gap:'0.75rem'}}>
<input className="input" type="password" placeholder="Teacher password"
<input className="input" type="password" placeholder="Instructor password"
value={pw} onChange={e => { setPw(e.target.value); setErr(''); }}
autoFocus style={{textAlign:'center'}} />
{err && <span style={styles.err}>{err}</span>}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/PollSetDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default function PollSetDetail() {
const [nameValue, setNameValue] = useState('');

useEffect(() => {
if (localStorage.getItem('role') !== 'teacher') navigate('/');
if (localStorage.getItem('role') !== 'instructor') navigate('/');
return watchPollSet(id, s => {
if (!s) return;
setPollSet(s);
Expand Down Expand Up @@ -155,7 +155,7 @@ export default function PollSetDetail() {
resultPolicy: first.resultPolicy ?? d.resultPolicy ?? 'on_submit',
correctPolicy: first.correctPolicy ?? d.correctPolicy ?? 'with_results',
});
navigate('/teacher');
navigate('/instructor');
}

if (!pollSet) return (
Expand Down
4 changes: 2 additions & 2 deletions src/pages/PollSets.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function PollSets() {
const [parseErr, setParseErr] = useState('');

useEffect(() => {
if (localStorage.getItem('role') !== 'teacher') navigate('/');
if (localStorage.getItem('role') !== 'instructor') navigate('/');
return watchPollSets(setSets);
}, []);

Expand Down Expand Up @@ -113,7 +113,7 @@ export default function PollSets() {
return (
<div style={styles.page}>
<header style={styles.header}>
<button style={styles.back} onClick={() => navigate('/teacher')}>← Dashboard</button>
<button style={styles.back} onClick={() => navigate('/instructor')}>← Dashboard</button>
<span style={styles.title}>Poll Sets</span>
{view === 'list' && (
<button className="btn btn-primary" onClick={() => setView('create')}>
Expand Down
24 changes: 12 additions & 12 deletions src/pages/RoleSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

const TEACHER_PASSWORD = import.meta.env.VITE_TEACHER_PASSWORD || 'changeme';
const INSTRUCTOR_PASSWORD = import.meta.env.VITE_INSTRUCTOR_PASSWORD || 'changeme';

export default function RoleSelector() {
const navigate = useNavigate();
const [showTeacherLogin, setShowTeacherLogin] = useState(false);
const [showInstructorLogin, setShowInstructorLogin] = useState(false);
const [pw, setPw] = useState('');
const [err, setErr] = useState('');

function handleTeacherSubmit(e) {
function handleInstructorSubmit(e) {
e.preventDefault();
if (pw === TEACHER_PASSWORD) {
localStorage.setItem("role", "teacher");
navigate('/teacher');
if (pw === INSTRUCTOR_PASSWORD) {
localStorage.setItem("role", "instructor");
navigate('/instructor');
} else {
setErr('Incorrect password.');
setPw('');
Expand All @@ -46,16 +46,16 @@ export default function RoleSelector() {
<span style={styles.roleLabel}>I'm a Student</span>
<span style={styles.roleHint}>Join and answer polls</span>
</button>
{!showTeacherLogin ? (
<button style={styles.roleCard} onClick={() => setShowTeacherLogin(true)}>
{!showInstructorLogin ? (
<button style={styles.roleCard} onClick={() => setShowInstructorLogin(true)}>
<span style={styles.roleIcon}>📋</span>
<span style={styles.roleLabel}>I'm the Teacher</span>
<span style={styles.roleLabel}>I'm the Instructor</span>
<span style={styles.roleHint}>Create and manage polls</span>
</button>
) : (
<form style={{...styles.roleCard, gap:'0.75rem'}} onSubmit={handleTeacherSubmit}>
<form style={{...styles.roleCard, gap:'0.75rem'}} onSubmit={handleInstructorSubmit}>
<span style={styles.roleIcon}>🔑</span>
<span style={styles.roleLabel}>Teacher Password</span>
<span style={styles.roleLabel}>Instructor Password</span>
<input
className="input"
type="password"
Expand All @@ -72,7 +72,7 @@ export default function RoleSelector() {
</button>
<button type="button" className="btn btn-secondary"
style={{width:'100%', justifyContent:'center', fontSize:'0.85rem'}}
onClick={() => { setShowTeacherLogin(false); setErr(''); setPw(''); }}>
onClick={() => { setShowInstructorLogin(false); setErr(''); setPw(''); }}>
Cancel
</button>
</form>
Expand Down
12 changes: 6 additions & 6 deletions src/pages/StudentPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ export default function StudentPage() {
const responseCount = activePoll ? Object.keys(activePoll.responses || {}).length : 0;
const pollStopped = activePoll?.ended || timeLeft === 0;

// Determine what to show based on teacher policy + manual overrides
// Determine what to show based on instructor policy + manual overrides
function shouldShowResults() {
if (!activePoll) return false;
if (activePoll.revealResults) return true; // teacher manually revealed
if (activePoll.revealResults) return true; // instructor manually revealed
if (activePoll.resultPolicy === 'never') return false;
if (activePoll.resultPolicy === 'manual') return false;
if (activePoll.resultPolicy === 'on_submit') return submitted || alreadyAnswered;
Expand All @@ -100,7 +100,7 @@ export default function StudentPage() {
function shouldShowCorrect() {
if (!activePoll) return false;
if (activePoll.correctIndex == null) return false;
if (activePoll.revealCorrect) return true; // teacher manually revealed
if (activePoll.revealCorrect) return true; // instructor manually revealed
if (activePoll.correctPolicy === 'never') return false;
if (activePoll.correctPolicy === 'manual') return false;
if (activePoll.correctPolicy === 'with_results') return shouldShowResults();
Expand Down Expand Up @@ -135,7 +135,7 @@ export default function StudentPage() {
<div style={styles.waitCard} className="fade-up">
<div style={styles.pulse} />
<h2 style={{fontFamily:'var(--font-display)', fontSize:'1.5rem'}}>Hi, {name}! 👋</h2>
<p style={{color:'var(--muted)'}}>Waiting for the teacher to start a poll…</p>
<p style={{color:'var(--muted)'}}>Waiting for the instructor to start a poll…</p>
<button style={styles.backLink}
onClick={() => { leaveSession(name); navigate('/'); }}>Leave session</button>
</div>
Expand Down Expand Up @@ -186,7 +186,7 @@ export default function StudentPage() {
)}
{activePoll.ended && (
<div style={styles.stoppedBanner}>
⏰ Time's up — waiting for teacher
⏰ Time's up — waiting for instructor
</div>
)}

Expand Down Expand Up @@ -241,7 +241,7 @@ export default function StudentPage() {
</div>
)}
{pollStopped && (alreadyAnswered || submitted) && (
<div style={styles.submittedBadge}>✓ Answer submitted — waiting for teacher</div>
<div style={styles.submittedBadge}>✓ Answer submitted — waiting for instructor</div>
)}
</div>
</div>
Expand Down
Loading