Peer tutoring office hours for Texas A&M University courses.
TAs from any section of a class can open live office-hour queues. Students from any section of that same class can browse those queues and join one. The goal is simple: office hours should not be trapped behind section walls.
This project is not affiliated with Texas A&M University.
In large courses, each section often has its own TAs and office hours. A student whose TA is busy (or offline) cannot easily see that another TA for the same course is available right now. Students waste time hunting for help; TAs under-serve students outside their section.
Queueble treats the course, not the section, as the unit of help:
- A TA signs in, selects a course, and opens a queue (location and optional Zoom).
- Any student enrolled in that course sees live queues for that course.
- The student joins a waitlist; the TA works tickets in order (waiting → helping → done).
- Realtime notifications keep both sides updated when someone joins, gets helped, or when a queue closes.
| Role | What they do |
|---|---|
| Student | Pick a class, join open queues, leave when done, get notified when helped |
| TA | Open/manage queues, run the waitlist + workspace, set location/Zoom |
| Professor | Elevated access for course-wide management (admin-style routes) |
┌─────────────────────┐ same-origin /api + cookies ┌─────────────────────┐
│ Frontend (Vercel) │ ──────────────────────────────────────────► │ Backend (Render) │
│ React + Vite │ ◄──── Socket.IO (prod: direct to Render) ── │ Express + Socket.IO│
└─────────┬───────────┘ └──────────┬──────────┘
│ Google OAuth (PKCE) │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Supabase Auth │ │ PostgreSQL │
│ (identity only) │ │ (Prisma models) │
└─────────────────────┘ └─────────────────────┘
- Frontend on Vercel, API on Render — static SPA + long-lived Socket.IO process.
- Same-origin
/apiin the browser — Vercel rewrites/api/*to Render so auth cookies stay first-party (critical for Safari). Never call the Render host directly from the client for cookie auth. - Supabase for identity, Postgres for app data — Auth users and
Userrows share the same UUID; roles, queues, tickets, and preferences live in Prisma models. - httpOnly cookies as the session source of truth — The browser Supabase session is only for the OAuth PKCE handshake. After login, Express cookies authenticate API calls.
my-app/
frontend/ React SPA (landing, auth, dashboard)
backend/ Express API, Socket.IO, cron jobs
shared/ Types + Zoom URL helpers used by both sides
| Concept | Meaning |
|---|---|
| Course | Class code students/TAs select (e.g. CSCE 221) |
| Queue | One TA’s live office hours for a course (open/closed, location, Zoom, schedule window) |
| QueueTicket | A student’s place in a queue (WAITING → HELPING → COMPLETED / LEFT / REMOVED) |
| Notification | Per-user alert (join, leave, assist, close) with preference toggles |
| Layer | Responsibility |
|---|---|
routes/ |
HTTP endpoints, validation wiring, status codes |
schemas/ |
Zod input validation |
services/ |
Queue lifecycle, tickets, notifications, TA presence |
middlewares/ |
Auth (JWT cookies), authz (role/ownership), rate limits |
jobs/ |
Cron cleanup of old tickets/notifications |
Notable service behavior:
- Queue schedule — open queues outside their time window are closed (rows kept, not deleted).
- TA presence — if a TA’s last socket disconnects, open queues auto-close after a grace period; explicit leave/sign-out closes immediately.
- Concurrency — join / move-to-helping paths use transactions so two clicks cannot double-book the same student or ticket.
| Area | Purpose |
|---|---|
| Landing | Product pitch + Features / Privacy |
| Auth | Google sign-in → /auth/callback → cookie session |
| Class selector | Browse live queues for a course and join |
| Home | Student’s active tickets |
| Queue manager | TA create/edit queues, waitlist, helping workspace |
| Settings | Name, notification prefs, default location, account delete |
- User starts Google OAuth on
queueble.app(apex domain — notwww). - Callback exchanges the PKCE
codefor a Supabase session. - Frontend posts tokens to
POST /api/auth/session; Express sets httpOnly cookies. GET /api/auth/meloads the app profile; user lands on the dashboard.
Deeper auth notes (cookie vs localStorage, signOut pitfalls, Safari ITP) are kept in local engineering notes outside this repo.
- Socket.IO delivers
notification-created(and related) events to the right user room. - Dev: same-origin; Vite proxies
/socket.io→ local API. - Prod: client connects to the Render host (Vercel does not proxy WebSockets).
| Layer | Tech |
|---|---|
| Frontend | React 19, Vite, Tailwind CSS, React Router, Motion |
| Backend | Express, Socket.IO, Zod, Helmet, express-rate-limit |
| Data | PostgreSQL, Prisma |
| Auth | Supabase Auth (Google OAuth / PKCE) |
| Hosting | Vercel (web) · Render (API) |
| CI | GitHub Actions — lint/build frontend, build + Vitest backend |
Requirements: Node.js 22+, npm, a Supabase project, and a Postgres database.
# API
cd my-app/backend
cp .env.example .env # if present — otherwise create .env with the vars below
npm install
npx prisma generate
npm run dev # http://localhost:3000
# Web (separate terminal)
cd my-app/frontend
npm install
npm run dev # http://localhost:5173DATABASE_URLSUPABASE_URLSUPABASE_PUBLISHABLE_KEYSUPABASE_SERVICE_ROLE_KEYCORS_ORIGINS(e.g.http://localhost:5173)- JWT / cookie secrets as used by your auth middleware
VITE_SUPABASE_URLVITE_SUPABASE_PUBLISHABLE_KEYVITE_API_URL(prod Socket.IO host; local sockets use same-origin via Vite proxy)
cd my-app/backend
npm testAPI tests mock Prisma, auth, and Supabase. They do not need a live database or real Supabase credentials.
cd my-app/frontend
npm run lint
npm run build- Course-scoped queues — help is shared across sections of the same class.
- Closing ≠ deleting — closed queues remain so TAs can review/manage history; cleanup jobs purge old finished tickets later.
- Same-origin API in production — cookie auth and mobile Safari compatibility.
- Cookies over client-held access tokens for the API — reduces XSS session theft surface for app requests.
- Ownership checks in middleware — routes reuse already-fetched
queue/ticket/appUserrows instead of re-querying and re-checking.
Proprietary — All Rights Reserved.
Copyright (c) 2026 Bao Le. All rights reserved.
This software and associated documentation files (the "Software") are proprietary to Bao Le.
Unauthorized copying, modification, distribution, public display, or performance of this Software, via any medium, is strictly prohibited.
The Software is provided "as is", without warranty of any kind, express or implied. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability.