A modern, responsive Learning Management System built with Next.js, TypeScript, Redux Toolkit, and Tailwind CSS. Features separate Admin and Student portals with full CRUD operations, video lesson playback with progress tracking, and analytics dashboards.
- Setup Instructions
- Environment Variables
- Available Scripts
- Redux State Structure
- Component Architecture
- API Integration
- Design Decisions
# 1. Clone the repository
git clone <repo-url>
cd aaft
# 2. Install dependencies
npm install
# 3. Copy environment file (no variables required for mock mode)
cp .env.example .env.local
# 4. Start the dev server
npm run devOpen http://localhost:3000 in your browser.
| Role | Password | |
|---|---|---|
| Admin | admin@lms.io | admin123 |
| Student | aarav@lms.io | student123 |
| Student | priya@lms.io | student123 |
See .env.example for reference. Currently no environment variables are required — all API calls are mocked in src/services/api.ts.
If connecting to a real backend, set:
NEXT_PUBLIC_API_URL=http://localhost:3001/api
| Script | Command | Description |
|---|---|---|
dev |
npm run dev |
Start development server |
build |
npm run build |
Create production build |
start |
npm start |
Serve production build |
lint |
npm run lint |
Run ESLint |
The store uses feature-based slices via Redux Toolkit's createSlice and createAsyncThunk:
store/
├── store.ts # configureStore with all reducers
├── hooks.ts # Typed useAppDispatch / useAppSelector
├── selectors.ts # Reusable memoized selectors (createSelector)
└── slices/
├── authSlice.ts # Auth: user, token, isAuthenticated, loading, error
├── coursesSlice.ts # Courses: courses[], selectedCourse, CRUD thunks
├── studentsSlice.ts # Students: students[], selectedStudent, enrollment thunks
├── progressSlice.ts # Progress: videoProgress{}, courseProgress{}
└── uiSlice.ts # UI: sidebarOpen, theme, notifications[]
- Auth —
{ user, token, isAuthenticated, loading, error } - Courses —
{ courses: Course[], selectedCourse, loading, error } - Students —
{ students: Student[], selectedStudent, loading, error } - Progress —
{ videoProgress: Record<lessonId, VideoProgress>, courseProgress: Record<courseId, CourseProgressInfo> } - UI —
{ sidebarOpen, theme: 'light'|'dark', notifications[] }
Reusable selectors built with createSelector for memoized derived data:
selectCurrentUser,selectUserRole,selectIsAuthenticatedmakeSelectFilteredCourses(searchTerm),makeSelectFilteredStudents(searchTerm)selectCompletedLessonsCount,selectTotalWatchTime,selectCompletedCoursesCountselectAnyLoading— combined loading state across all slices
src/
├── app/ # Next.js App Router pages
│ ├── admin/ # Admin portal (protected)
│ │ ├── page.tsx # Dashboard with charts
│ │ ├── students/ # Student CRUD + profile
│ │ ├── courses/ # Course CRUD + lesson management
│ │ ├── assignments/ # Multi-select course enrollment
│ │ └── reports/ # Analytics with real data
│ ├── student/ # Student portal (protected)
│ │ ├── dashboard/ # Personal stats + progress
│ │ └── courses/ # Course library + video player
│ └── login/ # Auth page with demo presets
├── components/
│ ├── common/ # Shared: Sidebar, Header, Modal, etc.
│ ├── admin/ # Admin-specific: AdminCourseCard
│ └── student/ # Student-specific: StudentCourseCard, StudentStatsRow
├── hooks/ # Custom hooks
│ ├── useAuth.ts # Login/logout logic
│ └── useVideoPlayer.ts # YouTube iframe + progress tracking
├── store/ # Redux store + slices + selectors
├── services/
│ └── api.ts # Centralized mock API (all endpoints)
├── types/
│ ├── index.ts # All TypeScript interfaces
│ └── youtube.d.ts # YouTube IFrame API types
└── utils/
└── index.ts # Shared helpers (formatDuration, getInitials, etc.)
All API calls go through src/services/api.ts, a centralized mock layer that simulates REST endpoints with realistic delays:
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/login |
Authenticate user |
| GET | /api/admin/students |
List all students |
| POST | /api/admin/students |
Create student |
| PUT | /api/admin/students/:id |
Update student |
| DELETE | /api/admin/students/:id |
Delete student |
| GET | /api/admin/courses |
List all courses |
| POST | /api/admin/courses |
Create course |
| POST | /api/admin/enrollments |
Assign courses to student |
| GET | /api/admin/reports/students/:id |
Student progress report |
| GET | /api/student/courses |
Get enrolled courses |
| POST | /api/student/progress |
Update video watch progress |
Pattern: Redux thunks (createAsyncThunk) call API functions → API returns mock data → slice reducers update state → components re-render via selectors.
To switch to a real backend: replace the mock functions in api.ts with actual fetch/axios calls. The thunks and reducers stay the same.
The app has deeply nested state relationships (student enrollments → course progress → video tracking) that benefit from centralized state. RTK's createAsyncThunk handles async lifecycle (pending/fulfilled/rejected) cleanly.
A single api.ts file with in-memory data is zero-config — no external process needed. It simulates realistic delays and error cases, and the mock data stays consistent for demos.
The YouTube IFrame API integration is encapsulated in a custom hook (useVideoPlayer) to keep the page component focused on layout. Key decisions:
- Refs over state for the player instance and completion flags to avoid stale closures in the progress interval
- 5-second progress interval balances accuracy vs. API call frequency
- 90% completion threshold automatically marks lessons complete
- Auto-resume seeks to
lastWatchedposition on player ready
Using Tailwind CSS v4 with CSS custom properties for theming (dark/light). All colors are semantic tokens (--primary, --surface, --border-color) so switching themes is a single data-theme attribute change. No hardcoded colors in components.
tsconfig.json has "strict": true. All state shapes, API payloads, and component props use explicit interfaces from types/index.ts.