UniFlow – Real-Time University Class & Learning Platform
Role-based class and learning management app for students, teachers, and administrators, built on Quasar and Firebase.
Designed and built the entire platform alone: architecture, Firestore data model, role-based routing, Pinia state layer, and all 77 Vue components.
My contribution
Solo owner & full-stack engineerDesigned and built the entire platform alone: architecture, Firestore data model, role-based routing, Pinia state layer, and all 77 Vue components.
What I owned
- Live class sessions with human-readable numeric session codes
- Role-based workspaces for Student, Teacher, and Admin
- Collaborative study groups with shared resources
- Task announcements, submissions, and multi-criteria grading rubrics
- Admin panel for faculties, departments, courses, users, and activity logs
Product context
University Students, Educators & Academic AdministratorsOne workspace for live classes, groups, tasks, and administration.
Delivered a role-based university learning platform: live class sessions, collaborative groups, graded tasks with rubrics, and an admin control panel, as a Quasar (Vue 3) SPA on Firebase.
Core challenge
Evidence: assumptionUniversity academic workflows are fragmented: schedules, group discussions, task submissions, and lecture notes live in disconnected apps. Teachers lack one tool for live sessions and graded multi-criteria tasks, and administrators lack a central view of faculties, departments, courses, and activity logs.
User benefit matrix
The Value Proposition Canvas
Expand value canvas
Joining sessions and tracking tasks, groups, and notes across separate tools
designedCourse info, group chats, and submissions scattered across disconnected apps; random IDs to join a class
One workspace with a short numeric code to join a live session, plus groups, tasks, and todos in the same app
No single tool to run live sessions and grade against several criteria
designedAnnouncements, submissions, and grading handled in separate places
Task announcements, submissions, and rubric-based grading in one role-specific dashboard
No central place to manage the university hierarchy or audit activity
measuredFaculties, departments, courses, and logs not managed or audited in one place
A 16-page admin suite with CRUD across the hierarchy, analytics charts, and activity logs
Proof
Impact and product screens
My results
Click any preview to open full size. Use arrow keys to browse.
Build
Stack and engineering choices
Technology
Frontend
- Vue 3.4
- Quasar Framework 2 (Vite)
- Pinia + pinia-plugin-persistedstate
- Vue Router 4
- ApexCharts
- Chart.js
Backend
- Firebase Authentication
- Firebase service layer (queries and atomic counters)
Database
- Cloud Firestore (12 entity collections)
DevOps & Infra
- Vite
- Quasar CLI
- Git & GitHub
- ESLint & Prettier
Key decisions
University academic workflows are fragmented: schedules, group discussions, task submissions, and lecture notes live in disconnected apps. Teachers lack one tool for live sessions and graded multi-criteria tasks, and administrators lack a central view of faculties, departments, courses, and activity logs.
A multi-role app with dozens of pages and dialogs leads to a bloated router file and scattered components.
Decision: Split the codebase into 9 isolated modules under src/modules/, each with its own pages/, components/, and routes.js, aggregated into the central router. Trade-off: manual route aggregation in src/router/index.js.
Outcome: Clear domain boundaries and room to add admin and student features without touching unrelated code.
Random Firestore document IDs are hard for students to type when joining an in-person session.
Decision: Added a Firestore transaction in firebaseService.js that atomically increments a metadata/counters document to assign human-readable session numbers. Trade-off: one extra read/write transaction per session creation.
Outcome: Short numeric session codes students can enter to join live classes.
Three roles (Student, Teacher, Admin) needed route protection without repeating authorization logic in every view.
Decision: Centralized access control in the Quasar boot/auth.js plugin, checking to.meta.requiresAuth and to.meta.roles against the Pinia user store's current role. Trade-off: every protected route must declare its metadata.
Outcome: Unauthorized users are redirected to /unauthorized or /auth/login before a view mounts.
Direct Firestore SDK calls inside components caused tight coupling, duplicated queries, and inconsistent loading and error handling.
Decision: Split each of the 10 Pinia stores into index.js (state/getters) and action.js, keeping Firestore calls in the service layer and store actions. Trade-off: extra action files per store.
Outcome: Network mutations and state caching are centralized, so components stay declarative.
Trust
Security and roadmap
Security & reliability
- Role-based navigation guard (boot/auth.js) checking requiresAuth and roles route metadata before views mount
- Firebase Authentication for sign-in; Firebase security rules authored for Firestore data access
- Pinia persisted state for session continuity across reloads
- Atomic Firestore transaction for session counter increments
Future roadmap
- In-app video conferencing and screen sharing during live sessions
- Push notifications for upcoming session deadlines and task submissions
Technical Appendix
UniFlow is a modular single-page application built on Quasar and Vue 3, with Firebase Authentication and Cloud Firestore as a serverless backend. Route transitions pass through a boot-plugin guard that checks authentication and role, views dispatch to domain-specific Pinia stores, and the stores talk to Firebase through a service layer.
Architecture
flowchart TD
subgraph Client ["Client (Vue 3 + Quasar)"]
Guard["boot/auth.js Route Guard"]
Router["Vue Router (44 routes, 9 modules)"]
Views["77 Vue Components"]
Guard --> Router --> Views
end
subgraph State ["State (Pinia)"]
Stores["10 Domain Stores"]
Persist["Persisted State Plugin"]
Views --> Stores --> Persist
end
subgraph Firebase ["Firebase"]
Auth["Authentication"]
Svc["Service Layer (queries, atomic counters)"]
DB[("Cloud Firestore (12 entities)")]
Stores --> Svc --> DB
Stores --> Auth
endRequest flow: Route (modules/*/routes.js) → Guard (boot/auth.js) → Page/Component → Pinia Store → Firebase Service → Firestore.
Module Map
| Module | Responsibility |
|---|---|
admin | 16 pages and 6 dialogs for faculties, departments, courses, users, and logs |
auth | Login, signup, and logout views |
dashboard | Role-specific dashboards for Admin, Student, and Teacher |
group | Collaborative group views, members, and shared resources |
home | Landing overview page |
liveShare | Real-time collaborative live share and chat panel |
session | Classroom session creation, joining, and management |
task | Task announcements, submissions, and grading rubrics |
todo | Personal todo lists and item trackers |
Route Protection
- Each protected route declares
meta.requiresAuthand ameta.rolesarray in its module’sroutes.js. - The
boot/auth.jsplugin runs before each navigation and compares the route metadata with the current role in the Pinia user store. - Unauthenticated users are sent to
/auth/login; users without the right role are sent to/unauthorized, before the view mounts.
Session Numbering
Session creation runs a Firestore transaction in firebaseService.js that reads and increments a single metadata/counters document, so two sessions created at the same time never receive the same number. The cost is one extra read and write per session creation.
By the Numbers
| Metric | Count |
|---|---|
| Git commits (solo) | 181 |
| Feature modules | 9 |
| Configured routes | 44 |
| Vue files | 77 (40 pages, 34 components, 2 layouts, 1 root App) |
| Pinia stores | 10 |
| Firestore entity schemas | 12 |
| Quasar boot plugins | 2 (boot/auth.js, boot/firebase.js) |
State Layer Convention
Each store is split into index.js (state and getters) and action.js (mutations and Firestore calls). Components never call the Firestore SDK directly, which keeps loading and error handling in one place per domain.
Case closed
More work.