Locus is a web-based attendance management platform built for educational institutions. It enables lecturers to create time-limited attendance sessions and students to check in using a one-time password (OTP) combined with geolocation verification — all in real time.
- Overview
- Features
- Tech Stack
- Getting Started
- Available Scripts
- Project Structure
- How It Works
- Deployment
- Supabase Keep-Alive Cron
- Contributing
Locus replaces manual paper-based or spreadsheet roll-calls with a seamless digital workflow:
- A lecturer starts an attendance session for one of their courses, choosing how long the session lasts (e.g. 5 minutes). Locus generates a 6-digit OTP and displays it to the class.
- Students open the attendance portal, enter the OTP, and allow the browser to capture their location.
- Locus validates the OTP, confirms the student is within range of the classroom, and records the check-in — all in real time.
Both roles get live dashboards powered by Supabase real-time subscriptions so stats update the moment a student checks in.
- Course management — create and manage multiple courses with codes, titles, departments, and levels.
- Session creation — launch a timed OTP-based attendance session for any course in one click.
- Live attendance list — watch students check in as it happens without refreshing the page.
- Dashboard analytics — overall attendance rate, total sessions, active session count, and per-session performance.
- Student roster — view all enrolled students across all courses.
- Attendance audit — review full historical attendance logs per course.
- Export — download attendance records as CSV or Excel (
.xlsx).
- OTP check-in — enter the 6-digit code displayed by the lecturer plus allow location capture.
- Geolocation validation — the system confirms the student is physically present within an acceptable radius of the classroom before recording attendance.
- Attendance history — view all past check-ins with timestamps and locations.
- Attendance statistics — total classes attended, overall attendance rate, and most recent check-in details.
- Course resources — access course-related material shared by the lecturer.
- Role-based access — separate lecturer and student dashboards with distinct capabilities.
- Authentication — email/password sign-up, login, forgot/reset password, and OAuth callback support.
- Onboarding flow — guided setup for new users to complete their profile and role selection.
- Responsive design — fully usable on desktop and mobile browsers.
- Animated UI — smooth transitions powered by Framer Motion.
- Toast notifications — non-intrusive feedback for every user action.
| Layer | Technology |
|---|---|
| Framework | React 19 + Vite 7 |
| Styling | Tailwind CSS 4 |
| Routing | React Router v7 |
| State management | Zustand |
| Backend / Database | Supabase (PostgreSQL + real-time) |
| Authentication | Supabase Auth |
| Forms & validation | React Hook Form + Zod |
| Charts | Recharts |
| Animations | Framer Motion |
| Notifications | Sonner |
| Icons | Lucide React |
| Export | ExcelJS (styled .xlsx), xlsx / SheetJS (raw .xlsx), csv (.csv) |
| Resend | |
| Date utilities | date-fns |
| Testing | Vitest |
| Deployment | Vercel |
- Node.js v18 or later (v20+ recommended; comes with npm v10)
- npm v8 or later
- A Supabase project (free tier works)
SUPABASE_URLSUPABASE_SERVICE_ROLE_KEY(preferred) orSUPABASE_ANON_KEYKEEPALIVE_CRON(default:0 3 * * *)KEEPALIVE_PING_INTERVAL_SECONDS(default:10)KEEPALIVE_RUN_WINDOW_SECONDS(default:60)KEEPALIVE_TABLE(default:classes)KEEPALIVE_TRANSPORT(databaseorrealtime, default:database)KEEPALIVE_REALTIME_CHANNEL(default:keepalive-heartbeat)KEEPALIVE_REALTIME_EVENT(default:heartbeat)KEEPALIVE_SUBSCRIBE_TIMEOUT_MS(default:10000)
# 1. Clone the repository
git clone https://github.com/davidadeluola/Locus.git
cd Locus
# 2. Install dependencies
npm install
# 3. Copy the example environment file and fill in your values
cp .env.example .envCreate a .env file in the project root with the following variables:
# Supabase — required for the app to work
VITE_SUPABASE_URL=https://<your-project-ref>.supabase.co
VITE_SUPABASE_ANON_KEY=<your-anon-key>
# Keep-alive cron (server-side only — not exposed to the browser)
SUPABASE_URL=https://<your-project-ref>.supabase.co
SUPABASE_SERVICE_ROLE_KEY=<your-service-role-key>
# Optional keep-alive overrides
KEEPALIVE_CRON=0 3 * * *
KEEPALIVE_PING_INTERVAL_SECONDS=10
KEEPALIVE_RUN_WINDOW_SECONDS=60
KEEPALIVE_TABLE=classesExample for websocket heartbeat mode (30-minute window, ping every 30 seconds):
KEEPALIVE_TRANSPORT=realtime
KEEPALIVE_CRON=0 3 * * *
KEEPALIVE_RUN_WINDOW_SECONDS=1800
KEEPALIVE_PING_INTERVAL_SECONDS=30
KEEPALIVE_REALTIME_CHANNEL=dashboard-heartbeat
KEEPALIVE_REALTIME_EVENT=heartbeatSecurity note: Only variables prefixed with
VITE_are bundled into the browser build. Never put secrets (service role keys, API tokens) inVITE_variables.
| Command | Description |
|---|---|
npm run dev |
Start the local development server (hot module replacement) |
npm run build |
Create an optimised production build in dist/ |
npm run preview |
Serve the production build locally |
npm run lint |
Run ESLint across all source files |
npm run test |
Run the test suite once with Vitest |
npm run test:watch |
Run Vitest in watch mode |
npm run keepalive:cron |
Start the Supabase keep-alive background worker |
Locus/
├── public/ # Static assets (favicon, fonts)
├── server/
│ └── keepalive/ # Supabase keep-alive cron worker
├── src/
│ ├── api/ # Low-level API helpers
│ ├── components/ # Shared/reusable UI components
│ │ ├── dashboard/ # Dashboard-specific components
│ │ ├── shared/ # Layout, ProtectedRoute, etc.
│ │ └── ui/ # Generic UI primitives (buttons, badges, etc.)
│ ├── context/ # React context providers (AuthContext)
│ ├── features/ # Feature-based modules
│ │ ├── attendance/ # OTP portal, verification, attendance list
│ │ ├── auth/ # Login, Signup, ForgotPassword
│ │ ├── courses/ # Course management services
│ │ ├── dashboard/ # Lecturer & Student dashboards
│ │ ├── onboarding/ # New-user onboarding flow
│ │ └── sessions/ # Session creation & management
│ ├── hooks/ # Custom React hooks (data fetching, realtime)
│ ├── lib/
│ │ └── utils/ # Utility functions (attendance, formatting)
│ ├── pages/ # Top-level page components and auth callbacks
│ ├── repositories/ # Repository pattern wrappers (data access layer)
│ ├── routes/ # App route definitions
│ ├── services/ # Supabase client, repository implementations, realtime manager
│ ├── store/ # Zustand global stores
│ ├── styles/ # Global CSS overrides
│ └── types/ # Shared type definitions
├── supabase/ # Supabase SQL migrations and RLS policies
├── index.html
├── vite.config.js
├── vitest.config.js
├── eslint.config.js
└── vercel.json
Lecturer creates session
│
▼
Supabase generates OTP + expiry timestamp
│
▼
OTP displayed in Lecturer Dashboard (live countdown timer)
│
▼
Student enters OTP + allows geolocation capture
│
▼
Server validates: OTP correct? Session active? Student in range?
│
┌────┴────┐
YES NO
│ │
Check-in Error feedback
recorded shown to student
│
▼
Real-time update pushes new check-in to Lecturer Dashboard
Locus uses Supabase Postgres real-time change subscriptions to push live updates without polling. A central realtimeSubscriptionManager handles channel lifecycle and deduplication, ensuring components subscribe and unsubscribe cleanly.
When a student submits attendance, the browser captures their GPS coordinates. Locus computes the distance between the student's location and the session's registered classroom coordinates. If the student is outside the configured radius, the check-in is rejected with an OUT_OF_RANGE error.
Locus is configured for one-click deployment to Vercel. The vercel.json at the project root rewrites all routes to index.html to support client-side routing.
Steps:
- Push the repository to GitHub.
- Import the project into Vercel.
- Add the required environment variables (
VITE_SUPABASE_URL,VITE_SUPABASE_ANON_KEY) in the Vercel project settings. - Deploy. Vercel will automatically run
npm run buildand serve thedist/output.
Free-tier Supabase projects pause after a period of inactivity. The keep-alive worker prevents this by sending lightweight queries on a configurable schedule.
npm run keepalive:cronOptional environment variable overrides:
| Variable | Default | Description |
|---|---|---|
SUPABASE_URL |
— | Your Supabase project URL |
SUPABASE_SERVICE_ROLE_KEY |
— | Service role key (preferred over anon key) |
SUPABASE_ANON_KEY |
— | Fallback if service role key is absent |
KEEPALIVE_CRON |
0 3 * * * |
Cron schedule expression |
KEEPALIVE_PING_INTERVAL_SECONDS |
10 |
Interval between pings within a run window |
KEEPALIVE_RUN_WINDOW_SECONDS |
60 |
Duration of each cron run |
KEEPALIVE_TABLE |
classes |
Table queried for the keep-alive ping |
Contributions are welcome! Please follow these steps:
- Fork the repository and create a feature branch (
git checkout -b feat/your-feature). - Make your changes and ensure the linter passes (
npm run lint). - Add or update tests where applicable (
npm run test). - Open a pull request describing your changes.
Built with ❤️ using React, Supabase, and Tailwind CSS.