A production-grade Learning Management System built with Expo (React Native) + TypeScript + Firebase.
Status: Foundation + Auth slice. This is a real, runnable base — not the full 24-deliverable spec. It ships the project scaffold, design system, Firebase wiring, role-based access control (RBAC), and the complete authentication flow. Feature slices (courses, learning, assignments, quizzes, exams, certificates, payments, admin/teacher tooling, Cloud Functions) build on top of this.
| Layer | Choice |
|---|---|
| App framework | Expo SDK 52, Expo Router (file-based, typed routes) |
| Language | TypeScript (strict) |
| Styling | NativeWind v4 (Tailwind CSS) |
| Forms / validation | React Hook Form + Zod |
| Server state | TanStack Query |
| Client state | Zustand |
| Backend | Firebase Auth, Cloud Firestore, Storage |
- Navigation — auth-aware gate in
app/_layout.tsxthat routes users by auth state and role. - Auth flow — register, login, forgot password, email verification (
app/(auth)/*). - RBAC —
super_admin/admin/teacher/studentwith a role hierarchy (src/types/roles.ts) and a client-sideRoleGateguard. - Role dashboards — starter screens for student / teacher / admin.
- Design system —
Button,TextField,Screen,Cardwith light + dark mode. - Security rules — starter
firestore.rulesandstorage.rulesenforcing role claims and owner-only writes. - Public certificate verification route scaffold at
/verify/:certificateId.
cd orbit-academy
npm install
# Align native package versions with the installed Expo SDK:
npx expo install --fix- Create a Firebase project and a Web app in the console.
- Enable Authentication → Email/Password.
- Create a Cloud Firestore database and a Storage bucket.
- Copy
.env.exampleto.envand fill in the web app credentials:
cp .env.example .envnpm start # then press i (iOS), a (Android), or w (web)firebase deploy --only firestore:rules,storageSelf-service signup always creates a student. Elevated roles are granted by
setting a Firebase Auth custom claim (role) via the Admin SDK / a Cloud
Function — never from the client. The Firestore rules read request.auth.token.role.
Until the admin Cloud Function slice lands, promote a user manually:
// Node script using firebase-admin
await admin.auth().setCustomUserClaims(uid, { role: "admin" });
// Also mirror it on their users/{uid} doc for the client UI.The user must sign out/in (or refresh their ID token) for the claim to take effect.
app/ Expo Router routes
_layout.tsx Providers + auth gate
index.tsx Role-based entry redirect
(auth)/ login, register, forgot-password, verify-email
(student)/ (teacher)/ (admin)/ Role dashboards (RoleGate-protected)
verify/[certificateId] Public certificate verification portal
src/
components/ui/ Reusable design-system primitives
config/ env validation + Firebase singletons
features/auth/ Zod schemas
hooks/ useAuthListener
services/ auth.service, user.service
store/ Zustand auth store
theme/ color tokens
types/ models + RBAC roles
firestore.rules, storage.rules, firebase.json
Paid-course enrollment runs through OPay Checkout (hosted "Cashier" flow) via Cloud Functions — the client never handles keys or trusts amounts.
Flow: app calls initOpayPayment({ courseId }) → backend reads the price,
creates a payments/{reference} doc + OPay order, returns the hosted
cashierUrl → app opens it with expo-web-browser → user pays → OPay POSTs a
signed webhook to opayWebhook (HMAC-SHA3-512 verified) and redirects back
→ backend marks the payment success and creates the enrollment idempotently.
verifyOpayPayment({ reference }) is a status-query fallback if the webhook is delayed.
Setup:
cd functions && npm install
cp .env.example .env # fill OPAY_MERCHANT_ID, OPAY_PUBLIC_KEY, OPAY_ENV, OPAY_RETURN_URL
firebase functions:secrets:set OPAY_SECRET_KEY # from OPay merchant dashboard
# firebase functions:secrets:set OPAY_PRIVATE_KEY # only if distinct from the secret key
firebase deploy --only functionsThen set your deployed opayWebhook URL as the callback in the OPay dashboard
(or rely on the per-payment callbackUrl we pass on create).
Signing is implemented to OPay's documented spec (public key bearer for create; HMAC-SHA512 sorted-JSON for status; HMAC-SHA3-512 field-string for the webhook). Confirm the exact key names + a sample callback against your OPay dashboard before going live — see comments in
functions/src/opay.ts.
- Extended student profile + avatar upload.
- Courses: catalog, detail, enrollment.
- Learning: modules, lessons, progress tracking.
- Assessments: assignments, quizzes, exams.
- Payments: Paystack + verification Cloud Function + webhook.
- Certificates & transcripts: PDF + QR generation Cloud Functions.
- Admin & teacher management tooling.
- Notifications (FCM), analytics, App Check, CI/CD.