Skip to content

Repository files navigation

Orbit Academy

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.

Stack

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

What's included in this slice

  • Navigation — auth-aware gate in app/_layout.tsx that routes users by auth state and role.
  • Auth flow — register, login, forgot password, email verification (app/(auth)/*).
  • RBACsuper_admin / admin / teacher / student with a role hierarchy (src/types/roles.ts) and a client-side RoleGate guard.
  • Role dashboards — starter screens for student / teacher / admin.
  • Design systemButton, TextField, Screen, Card with light + dark mode.
  • Security rules — starter firestore.rules and storage.rules enforcing role claims and owner-only writes.
  • Public certificate verification route scaffold at /verify/:certificateId.

Getting started

1. Install dependencies

cd orbit-academy
npm install
# Align native package versions with the installed Expo SDK:
npx expo install --fix

2. Configure Firebase

  1. Create a Firebase project and a Web app in the console.
  2. Enable Authentication → Email/Password.
  3. Create a Cloud Firestore database and a Storage bucket.
  4. Copy .env.example to .env and fill in the web app credentials:
cp .env.example .env

3. Run

npm start        # then press i (iOS), a (Android), or w (web)

4. Deploy security rules

firebase deploy --only firestore:rules,storage

Role assignment (important)

Self-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.

Project structure

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

Payments (OPay)

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 functions

Then 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.

Next slices (suggested order)

  1. Extended student profile + avatar upload.
  2. Courses: catalog, detail, enrollment.
  3. Learning: modules, lessons, progress tracking.
  4. Assessments: assignments, quizzes, exams.
  5. Payments: Paystack + verification Cloud Function + webhook.
  6. Certificates & transcripts: PDF + QR generation Cloud Functions.
  7. Admin & teacher management tooling.
  8. Notifications (FCM), analytics, App Check, CI/CD.

About

Orbit Academy — production-ready LMS (Expo + React Native + Firebase)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages