A full e-learning module built on top of a multi-purpose SaaS platform — supporting three roles (student, teacher, admin) with course management, lesson video uploads, Chargily card and wallet payments, real-time notifications via WebSocket, and a multilingual interface in Arabic, English, and French.
Open Desk is a multi-vertical SaaS platform. I designed and built its e-learning module from scratch as a self-contained vertical within the same Laravel + Next.js monorepo, sharing the authentication layer, notification system, and wallet infrastructure with the rest of the product.
Students land on a public course catalog with live search and category filtering. The search bar is debounced (280 ms) and uses an AbortController cancel pattern so stale results from slow requests never overwrite newer ones — results appear as a dropdown with thumbnails, titles, and instructor names. Clicking a result navigates to the course detail page.
The course detail page shows a full overview, lesson list, reviews, and attachments. Students can enroll for free immediately or go through a payment flow for paid courses. Paid enrollment supports two paths: deduct from an in-app wallet balance (instant, no redirect), or pay by card through Chargily — the main Algerian payment gateway supporting CIB and EDAHABIA cards. The card path creates a checkout on Chargily's hosted page and confirms enrollment via a signed webhook when payment completes.
Inside a course, students watch lesson videos, complete exercises, download resources, and track their overall progress percentage. Watch time is logged and completion is detected when a threshold is crossed, updating the enrollment's progress automatically. The student dashboard displays stats (enrolled, in-progress, completed courses), a study time chart, and a recent courses section.
Any user can apply to become a teacher through a multi-step application form: bio, specialty, academic studies, work experience, and social links — all stored with multilingual support (Arabic, English, French) using JSON columns. The application goes to an admin review queue. On approval, the user's role is upgraded and they gain access to the teacher dashboard.
Teachers create courses through a stepper form: general info (title, category, level, price, language), then a cover thumbnail, then lessons. Each lesson supports a video upload, a PDF resource, a description, and a configurable sort order. The teacher can edit or reorder lessons at any time, and resubmit a course after it has been rejected with a note.
Submitted courses start in an "under review" state. The teacher sees the status live on their dashboard and receives a real-time notification and email when the admin approves or rejects their course. The analytics page shows views and enrollment counts per period (week, month, year) in an interactive bar chart, alongside a full student list with progress data.
Admins see a unified dashboard across all teachers and students: total courses, breakdowns by status (accepted, under review, rejected), total users, platform-wide earnings, and unread messages. The course review page lets admins inspect the full course and approve or reject it with a written note. Teacher applications are handled in a separate section with the applicant's full profile visible before decision. Category management allows creating and editing course categories with multilingual names (en/ar/fr) and icons.
All three dashboards share a live notification bell powered by Laravel Reverb (self-hosted WebSocket server) and Laravel Echo on the frontend. Notifications are sent on a private channel scoped to each user, authenticated via Sanctum token. The system covers 15 event types: student enrolled, lesson completed, course approved/rejected, teacher application approved, payment confirmed, new message, new review, comment reply, new follower, certificate issued, and more. Notification messages are localized on the frontend so the language always matches the current UI locale.
The entire frontend is internationalized in Arabic, English, and French using next-intl with Next.js app router route groups. Arabic triggers full RTL layout — every component uses Tailwind's logical properties (ms-*, me-*, ps-*, pe-*) so spacing and alignment flip correctly without separate stylesheets. The backend resolves locale from the Accept-Language request header, returning translatable content in the right language automatically.
PHP 8.2, Laravel 11, Laravel Sanctum, Laravel Reverb (WebSocket), Laravel Queues, Spatie Permission, Chargily Pay, Mailtrap — Next.js 14, TypeScript, Tailwind CSS, Jotai, next-intl, Laravel Echo, Pusher-js, Recharts, Lucide