From b4ab66b4a643554557e30d15b01926aa67167bc0 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Sat, 25 Apr 2026 02:48:52 -0700 Subject: [PATCH] lesson integration --- src/api/courses.api.ts | 72 + src/app/AppRoutes.tsx | 3 +- src/lib/sessionRole.ts | 13 + src/lib/videoPreview.ts | 124 ++ src/pages/content-management/AddVideoFlow.tsx | 128 +- .../ContentManagementLayout.tsx | 41 +- .../content-management/CourseDetailPage.tsx | 6 + .../content-management/ModuleDetailPage.tsx | 457 +++++- .../PracticeDetailsPage.tsx | 1427 +++++++++++++---- .../content-management/ProgramCoursesPage.tsx | 12 +- .../components/CreatePracticeWizard.tsx | 481 ++++++ .../components/LessonMediaUploadField.tsx | 215 +++ .../components/PreviewLimitedFileVideo.tsx | 66 + .../components/VideoCard.tsx | 393 ++++- .../video-steps/ReviewPublishStep.tsx | 272 ++-- .../video-steps/VideoDetailStep.tsx | 223 ++- src/types/course.types.ts | 122 +- 17 files changed, 3295 insertions(+), 760 deletions(-) create mode 100644 src/lib/sessionRole.ts create mode 100644 src/lib/videoPreview.ts create mode 100644 src/pages/content-management/components/CreatePracticeWizard.tsx create mode 100644 src/pages/content-management/components/LessonMediaUploadField.tsx create mode 100644 src/pages/content-management/components/PreviewLimitedFileVideo.tsx diff --git a/src/api/courses.api.ts b/src/api/courses.api.ts index 5ed820d..07c456b 100644 --- a/src/api/courses.api.ts +++ b/src/api/courses.api.ts @@ -78,6 +78,15 @@ import type { CreateTopLevelCourseModuleResponse, CreateProgramCourseRequest, CreateProgramCourseResponse, + GetTopLevelModuleLessonsResponse, + GetPracticesByParentContextResponse, + CreateParentLinkedPracticeRequest, + CreateParentLinkedPracticeResponse, + UpdateParentLinkedPracticeRequest, + UpdateParentLinkedPracticeResponse, + UpdateTopLevelModuleLessonRequest, + CreateTopLevelModuleLessonRequest, + CreateTopLevelModuleLessonResponse, } from "../types/course.types" type UnifiedHierarchyRow = { @@ -473,6 +482,69 @@ export const updateTopLevelCourseModule = ( export const deleteTopLevelCourseModule = (moduleId: number) => http.delete(`/modules/${moduleId}`) +/** Learn English top-level module lessons — GET /modules/:moduleId/lessons */ +export const getModuleLessons = ( + moduleId: number, + params?: { limit?: number; offset?: number }, +) => + http.get(`/modules/${moduleId}/lessons`, { + params, + }) + +/** Learn English top-level module lesson — POST /modules/:moduleId/lessons */ +export const createModuleLesson = ( + moduleId: number, + data: CreateTopLevelModuleLessonRequest, +) => + http.post(`/modules/${moduleId}/lessons`, data) + +/** Learn English top-level module lesson — PUT /lessons/:id */ +export const updateTopLevelModuleLesson = ( + lessonId: number, + data: UpdateTopLevelModuleLessonRequest, +) => http.put(`/lessons/${lessonId}`, data) + +/** Learn English top-level module lesson — DELETE /lessons/:id */ +export const deleteTopLevelModuleLesson = (lessonId: number) => + http.delete(`/lessons/${lessonId}`) + +/** GET /courses/:courseId/practices — practices linked to a top-level course (at most one in normal use). */ +export const getPracticesByParentCourse = ( + courseId: number, + params?: { limit?: number; offset?: number }, +) => + http.get(`/courses/${courseId}/practices`, { params }) + +/** GET /modules/:moduleId/practices */ +export const getPracticesByParentModule = ( + moduleId: number, + params?: { limit?: number; offset?: number }, +) => + http.get(`/modules/${moduleId}/practices`, { params }) + +/** GET /lessons/:lessonId/practices */ +export const getPracticesByParentLesson = ( + lessonId: number, + params?: { limit?: number; offset?: number }, +) => + http.get(`/lessons/${lessonId}/practices`, { params }) + +/** POST /practices — create a practice (story + question set) for course / module / lesson. */ +export const createParentLinkedPractice = (data: CreateParentLinkedPracticeRequest) => + http.post("/practices", data) + +/** PUT /practices/:id */ +export const updateParentLinkedPractice = ( + practiceId: number, + data: UpdateParentLinkedPracticeRequest, +) => http.put(`/practices/${practiceId}`, data) + +/** DELETE /practices/:id */ +export const deleteParentLinkedPractice = (practiceId: number) => + http.delete<{ message: string; success: boolean; status_code: number; metadata: unknown }>( + `/practices/${practiceId}`, + ) + export const updateLearningProgram = (programId: number, data: UpdateLearningProgramRequest) => http.put(`/programs/${programId}`, data) diff --git a/src/app/AppRoutes.tsx b/src/app/AppRoutes.tsx index 5e65cce..e00e62a 100644 --- a/src/app/AppRoutes.tsx +++ b/src/app/AppRoutes.tsx @@ -3,7 +3,6 @@ import { AppLayout } from "../layouts/AppLayout"; import { DashboardPage } from "../pages/DashboardPage"; import { AnalyticsPage } from "../pages/analytics/AnalyticsPage"; import { ContentManagementLayout } from "../pages/content-management/ContentManagementLayout"; -import { CourseCategoryPage } from "../pages/content-management/CourseCategoryPage"; import { AllCoursesPage } from "../pages/content-management/AllCoursesPage"; import { CourseFlowBuilderPage } from "../pages/content-management/CourseFlowBuilderPage"; import { ContentOverviewPage } from "../pages/content-management/ContentOverviewPage"; @@ -89,7 +88,7 @@ export function AppRoutes() { }> - } /> + } /> } /> } /> } /> diff --git a/src/lib/sessionRole.ts b/src/lib/sessionRole.ts new file mode 100644 index 0000000..b58ca9b --- /dev/null +++ b/src/lib/sessionRole.ts @@ -0,0 +1,13 @@ +const ADMIN_OR_SUPER: ReadonlySet = new Set([ + "admin", + "super_admin", +]); + +/** + * True when the stored session role is admin or super_admin (login stores `role` in localStorage). + */ +export function isAdminOrSuperAdminRole(): boolean { + const raw = localStorage.getItem("role"); + if (!raw) return false; + return ADMIN_OR_SUPER.has(raw.trim().toLowerCase()); +} diff --git a/src/lib/videoPreview.ts b/src/lib/videoPreview.ts new file mode 100644 index 0000000..54f8ceb --- /dev/null +++ b/src/lib/videoPreview.ts @@ -0,0 +1,124 @@ +/** + * Resolves a user-facing video URL into something we can preview (iframe or