learning flow fixes

This commit is contained in:
Yared Yemane 2026-03-07 08:15:13 -08:00
parent 6a201a0108
commit 3614244029
11 changed files with 1056 additions and 782 deletions

View File

@ -217,8 +217,41 @@ export const removeSubCoursePrerequisite = (subCourseId: number, prerequisiteId:
export const getLearningPath = (courseId: number) => export const getLearningPath = (courseId: number) =>
http.get<GetLearningPathResponse>(`/course-management/courses/${courseId}/learning-path`) http.get<GetLearningPathResponse>(`/course-management/courses/${courseId}/learning-path`)
export const reorderSubCourses = (courseId: number, items: ReorderItem[]) => const buildReorderPayload = (items: ReorderItem[]) => {
http.put(`/course-management/courses/${courseId}/reorder-sub-courses`, { items }) const normalized = items.map((item, idx) => ({
id: Number(item.id),
position: Number(item.position ?? idx),
}))
const hasInvalid = normalized.some(
(item) =>
Number.isNaN(item.id) ||
Number.isNaN(item.position) ||
!Number.isFinite(item.id) ||
!Number.isFinite(item.position),
)
if (hasInvalid) {
throw new Error("Invalid reorder payload: ids/positions must be numeric.")
}
return { items: normalized }
}
export const reorderCategories = (items: ReorderItem[]) =>
http.put("/course-management/categories/reorder", buildReorderPayload(items))
export const reorderCourses = (items: ReorderItem[]) =>
http.put("/course-management/courses/reorder", buildReorderPayload(items))
export const reorderSubCourses = (items: ReorderItem[]) =>
http.put("/course-management/sub-courses/reorder", buildReorderPayload(items))
export const reorderVideos = (items: ReorderItem[]) =>
http.put("/course-management/videos/reorder", buildReorderPayload(items))
export const reorderPractices = (items: ReorderItem[]) =>
http.put("/course-management/practices/reorder", buildReorderPayload(items))
// Ratings // Ratings
export const getRatings = (params: GetRatingsParams) => export const getRatings = (params: GetRatingsParams) =>

View File

@ -272,20 +272,34 @@ export function ProfilePage() {
const completionPct = profile.profile_completion_percentage ?? 0; const completionPct = profile.profile_completion_percentage ?? 0;
return ( return (
<div className="w-full space-y-6"> <div className="mx-auto w-full max-w-7xl space-y-6 pb-8">
{/* ─── Hero Card ─── */} {/* ─── Hero Card ─── */}
<div className="relative overflow-hidden rounded-2xl border border-grayScale-100 bg-white shadow-sm"> <div className="relative overflow-hidden rounded-3xl border border-grayScale-100 bg-white shadow-sm ring-1 ring-black/5">
{/* Tall dark gradient banner with content inside */} {/* Tall dark gradient banner with content inside */}
<div className="relative flex min-h-[200px] flex-col justify-between bg-gradient-to-br from-[#1a1f4e] via-[#2d2b6b] to-[#3b3480] px-6 py-8 sm:px-8"> <div className="relative flex min-h-[220px] flex-col justify-between bg-gradient-to-br from-[#1a1f4e] via-[#2d2b6b] to-[#3b3480] px-6 py-8 sm:px-8">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,rgba(255,255,255,0.08),transparent_60%)]" /> <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,rgba(255,255,255,0.08),transparent_60%)]" />
<div className="relative z-10 space-y-2"> <div className="relative z-10 space-y-2">
<h2 className="text-2xl font-bold tracking-tight text-white sm:text-3xl"> <h2 className="text-2xl font-bold tracking-tight text-white sm:text-3xl">
Hello {profile.first_name} Hello {profile.first_name}
</h2> </h2>
<p className="max-w-xl text-sm leading-relaxed text-white/70"> <p className="max-w-2xl text-sm leading-relaxed text-white/70">
This is your profile page. You can see the progress you've made with your work and manage your projects or assigned tasks Track your account status, keep profile details up to date, and manage your learning preferences from one place.
</p> </p>
<div className="mt-4 flex flex-wrap items-center gap-2">
<span className="inline-flex items-center gap-1.5 rounded-full border border-white/20 bg-white/10 px-2.5 py-1 text-xs font-medium text-white/90">
<Shield className="h-3.5 w-3.5" />
{profile.role}
</span>
<span className="inline-flex items-center gap-1.5 rounded-full border border-white/20 bg-white/10 px-2.5 py-1 text-xs font-medium text-white/90">
<Clock className="h-3.5 w-3.5" />
Last login {formatDate(profile.last_login)}
</span>
<span className="inline-flex items-center gap-1.5 rounded-full border border-white/20 bg-white/10 px-2.5 py-1 text-xs font-medium text-white/90">
<Target className="h-3.5 w-3.5" />
{completionPct}% complete
</span>
</div>
</div> </div>
<div className="relative z-10 mt-6"> <div className="relative z-10 mt-6">
@ -293,7 +307,7 @@ export function ProfilePage() {
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
className="gap-1.5 border-white/30 bg-white/10 text-xs font-medium text-white shadow-sm backdrop-blur-sm hover:bg-white/20 hover:text-white" className="h-8 gap-1.5 border-white/30 bg-white/10 px-3 text-xs font-medium text-white shadow-sm backdrop-blur-sm hover:bg-white/20 hover:text-white"
onClick={startEditing} onClick={startEditing}
> >
<Pencil className="h-3.5 w-3.5" /> <Pencil className="h-3.5 w-3.5" />
@ -304,7 +318,7 @@ export function ProfilePage() {
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
className="h-8 gap-1.5 border-white/30 bg-white/10 text-xs text-white backdrop-blur-sm hover:bg-white/20 hover:text-white" className="h-8 gap-1.5 border-white/30 bg-white/10 px-3 text-xs text-white backdrop-blur-sm hover:bg-white/20 hover:text-white"
onClick={cancelEditing} onClick={cancelEditing}
disabled={saving} disabled={saving}
> >
@ -330,7 +344,7 @@ export function ProfilePage() {
</div> </div>
{/* Identity info below banner */} {/* Identity info below banner */}
<div className="px-6 py-5 sm:px-8"> <div className="bg-gradient-to-b from-white to-grayScale-50/40 px-6 py-5 sm:px-8">
{editing ? ( {editing ? (
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Input <Input
@ -405,7 +419,7 @@ export function ProfilePage() {
{/* ─── Detail Cards Grid ─── */} {/* ─── Detail Cards Grid ─── */}
<div className="grid gap-6 lg:grid-cols-3"> <div className="grid gap-6 lg:grid-cols-3">
{/* ── Contact & Personal ── */} {/* ── Contact & Personal ── */}
<Card className="overflow-hidden border-grayScale-100 shadow-sm lg:col-span-2"> <Card className="overflow-hidden rounded-2xl border-grayScale-100 shadow-sm transition-shadow hover:shadow-md lg:col-span-2">
<div className="h-1 bg-gradient-to-r from-brand-500 to-brand-400" /> <div className="h-1 bg-gradient-to-r from-brand-500 to-brand-400" />
<CardContent className="p-0"> <CardContent className="p-0">
<div className="grid divide-y divide-grayScale-100 sm:grid-cols-2 sm:divide-x sm:divide-y-0"> <div className="grid divide-y divide-grayScale-100 sm:grid-cols-2 sm:divide-x sm:divide-y-0">
@ -569,9 +583,9 @@ export function ProfilePage() {
</Card> </Card>
{/* ── Right Sidebar ── */} {/* ── Right Sidebar ── */}
<div className="space-y-6"> <div className="space-y-6 lg:sticky lg:top-24 lg:self-start">
{/* Profile Completion */} {/* Profile Completion */}
<Card className="overflow-hidden border-grayScale-100 shadow-sm"> <Card className="overflow-hidden rounded-2xl border-grayScale-100 shadow-sm transition-shadow hover:shadow-md">
<div className="h-1 bg-gradient-to-r from-brand-400 to-mint-400" /> <div className="h-1 bg-gradient-to-r from-brand-400 to-mint-400" />
<CardContent className="flex items-center gap-4 p-5"> <CardContent className="flex items-center gap-4 p-5">
<ProgressRing percent={completionPct} /> <ProgressRing percent={completionPct} />
@ -585,7 +599,7 @@ export function ProfilePage() {
</Card> </Card>
{/* Activity */} {/* Activity */}
<Card className="overflow-hidden border-grayScale-100 shadow-sm"> <Card className="overflow-hidden rounded-2xl border-grayScale-100 shadow-sm transition-shadow hover:shadow-md">
<div className="h-1 bg-gradient-to-r from-grayScale-300 to-grayScale-200" /> <div className="h-1 bg-gradient-to-r from-grayScale-300 to-grayScale-200" />
<CardContent className="space-y-4 p-5"> <CardContent className="space-y-4 p-5">
<p className="text-[11px] font-bold uppercase tracking-widest text-grayScale-400"> <p className="text-[11px] font-bold uppercase tracking-widest text-grayScale-400">
@ -613,7 +627,7 @@ export function ProfilePage() {
</Card> </Card>
{/* Quick Account Info */} {/* Quick Account Info */}
<Card className="overflow-hidden border-grayScale-100 shadow-sm"> <Card className="overflow-hidden rounded-2xl border-grayScale-100 shadow-sm transition-shadow hover:shadow-md">
<div className="h-1 bg-gradient-to-r from-brand-500 to-brand-600" /> <div className="h-1 bg-gradient-to-r from-brand-500 to-brand-600" />
<CardContent className="space-y-3 p-5"> <CardContent className="space-y-3 p-5">
<p className="text-[11px] font-bold uppercase tracking-widest text-grayScale-400"> <p className="text-[11px] font-bold uppercase tracking-widest text-grayScale-400">
@ -675,8 +689,13 @@ export function ProfilePage() {
</div> </div>
{/* ─── Learning & Goals Card ─── */} {/* ─── Learning & Goals Card ─── */}
<Card className="overflow-hidden border-grayScale-100 shadow-sm"> <Card className="overflow-hidden rounded-2xl border-grayScale-100 shadow-sm transition-shadow hover:shadow-md">
<div className="h-1 bg-gradient-to-r from-brand-600 via-brand-500 to-brand-400" /> <div className="h-1 bg-gradient-to-r from-brand-600 via-brand-500 to-brand-400" />
<div className="border-b border-grayScale-100 px-5 py-3">
<p className="text-[11px] font-bold uppercase tracking-widest text-grayScale-400">
Learning & Preferences
</p>
</div>
<CardContent className="p-0"> <CardContent className="p-0">
<div className="grid divide-y divide-grayScale-100 sm:grid-cols-2 sm:divide-x sm:divide-y-0 lg:grid-cols-4 lg:divide-x lg:divide-y-0"> <div className="grid divide-y divide-grayScale-100 sm:grid-cols-2 sm:divide-x sm:divide-y-0 lg:grid-cols-4 lg:divide-x lg:divide-y-0">
<div className="p-5"> <div className="p-5">

View File

@ -889,7 +889,7 @@ export function AddNewPracticePage() {
className="w-full bg-brand-500 hover:bg-brand-600" className="w-full bg-brand-500 hover:bg-brand-600"
onClick={() => navigate(`/content/category/${categoryId}/courses/${courseId}/sub-courses/${subCourseId}`)} onClick={() => navigate(`/content/category/${categoryId}/courses/${courseId}/sub-courses/${subCourseId}`)}
> >
Go back to Sub-course Go back to Course
</Button> </Button>
<Button <Button
variant="outline" variant="outline"

View File

@ -76,7 +76,7 @@ export function AllCoursesPage() {
setCourses(allCourses) setCourses(allCourses)
} catch (err) { } catch (err) {
console.error("Failed to load courses:", err) console.error("Failed to load courses:", err)
setError("Failed to load courses") setError("Failed to load sub-categories")
} finally { } finally {
setLoading(false) setLoading(false)
} }
@ -116,7 +116,7 @@ export function AllCoursesPage() {
description: createDescription.trim(), description: createDescription.trim(),
}) })
toast.success("Course created", { toast.success("Sub-category created", {
description: `"${createTitle.trim()}" has been created.`, description: `"${createTitle.trim()}" has been created.`,
}) })
@ -130,7 +130,7 @@ export function AllCoursesPage() {
await fetchAllCourses() await fetchAllCourses()
} catch (err: any) { } catch (err: any) {
console.error("Failed to create course:", err) console.error("Failed to create course:", err)
toast.error("Failed to create course", { toast.error("Failed to create sub-category", {
description: err?.response?.data?.message || "Please try again.", description: err?.response?.data?.message || "Please try again.",
}) })
} finally { } finally {
@ -145,7 +145,7 @@ export function AllCoursesPage() {
await fetchAllCourses() await fetchAllCourses()
} catch (err) { } catch (err) {
console.error("Failed to update course status:", err) console.error("Failed to update course status:", err)
toast.error("Failed to update course status") toast.error("Failed to update sub-category status")
} finally { } finally {
setTogglingId(null) setTogglingId(null)
} }
@ -173,13 +173,13 @@ export function AllCoursesPage() {
title: editTitle.trim(), title: editTitle.trim(),
description: editDescription.trim(), description: editDescription.trim(),
}) })
toast.success("Course updated") toast.success("Sub-category updated")
setEditOpen(false) setEditOpen(false)
setCourseToEdit(null) setCourseToEdit(null)
await fetchAllCourses() await fetchAllCourses()
} catch (err: any) { } catch (err: any) {
console.error("Failed to update course:", err) console.error("Failed to update course:", err)
toast.error("Failed to update course", { toast.error("Failed to update sub-category", {
description: err?.response?.data?.message || "Please try again.", description: err?.response?.data?.message || "Please try again.",
}) })
} finally { } finally {
@ -193,7 +193,7 @@ export function AllCoursesPage() {
<div className="rounded-2xl bg-white shadow-sm p-6"> <div className="rounded-2xl bg-white shadow-sm p-6">
<RefreshCw className="h-10 w-10 animate-spin text-brand-600" /> <RefreshCw className="h-10 w-10 animate-spin text-brand-600" />
</div> </div>
<p className="mt-4 text-sm font-medium text-grayScale-400">Loading all courses</p> <p className="mt-4 text-sm font-medium text-grayScale-400">Loading all sub-categories</p>
</div> </div>
) )
} }
@ -216,9 +216,9 @@ export function AllCoursesPage() {
{/* Header */} {/* Header */}
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"> <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div> <div>
<h1 className="text-2xl font-bold tracking-tight text-grayScale-700">All Courses</h1> <h1 className="text-2xl font-bold tracking-tight text-grayScale-700">All Sub-categories</h1>
<p className="mt-1 text-sm text-grayScale-400"> <p className="mt-1 text-sm text-grayScale-400">
View and manage courses across all categories. View and manage sub-categories across all categories.
</p> </p>
</div> </div>
<Button <Button
@ -226,14 +226,14 @@ export function AllCoursesPage() {
onClick={() => setCreateOpen(true)} onClick={() => setCreateOpen(true)}
> >
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />
Create Course Create Sub-category
</Button> </Button>
</div> </div>
<Card className="shadow-soft"> <Card className="shadow-soft">
<CardHeader className="border-b border-grayScale-200 pb-4"> <CardHeader className="border-b border-grayScale-200 pb-4">
<CardTitle className="text-base font-semibold text-grayScale-600"> <CardTitle className="text-base font-semibold text-grayScale-600">
Course Management Sub-category Management
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-5 pt-5"> <CardContent className="space-y-5 pt-5">
@ -375,9 +375,9 @@ export function AllCoursesPage() {
<div className="mb-4 grid h-16 w-16 place-items-center rounded-full bg-gradient-to-br from-grayScale-100 to-grayScale-200"> <div className="mb-4 grid h-16 w-16 place-items-center rounded-full bg-gradient-to-br from-grayScale-100 to-grayScale-200">
<BookOpen className="h-8 w-8 text-grayScale-400" /> <BookOpen className="h-8 w-8 text-grayScale-400" />
</div> </div>
<p className="text-base font-semibold text-grayScale-600">No courses found</p> <p className="text-base font-semibold text-grayScale-600">No sub-categories found</p>
<p className="mt-1.5 max-w-sm text-sm leading-relaxed text-grayScale-400"> <p className="mt-1.5 max-w-sm text-sm leading-relaxed text-grayScale-400">
Try adjusting your search or category filter, or create a new course. Try adjusting your search or category filter, or create a new sub-category.
</p> </p>
</div> </div>
)} )}
@ -388,7 +388,7 @@ export function AllCoursesPage() {
<Dialog open={createOpen} onOpenChange={setCreateOpen}> <Dialog open={createOpen} onOpenChange={setCreateOpen}>
<DialogContent className="max-w-2xl"> <DialogContent className="max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Create course</DialogTitle> <DialogTitle>Create sub-category</DialogTitle>
<DialogDescription> <DialogDescription>
Choose a category, add basic details, and optionally attach a thumbnail and intro Choose a category, add basic details, and optionally attach a thumbnail and intro
video. video.
@ -439,7 +439,7 @@ export function AllCoursesPage() {
</div> </div>
<div className="sm:col-span-1"> <div className="sm:col-span-1">
<label className="mb-1.5 block text-sm font-medium text-grayScale-600"> <label className="mb-1.5 block text-sm font-medium text-grayScale-600">
Course title Sub-category title
</label> </label>
<Input <Input
placeholder="e.g. Beginner English A1" placeholder="e.g. Beginner English A1"
@ -455,7 +455,7 @@ export function AllCoursesPage() {
</label> </label>
<Textarea <Textarea
rows={3} rows={3}
placeholder="Short summary of what this course covers." placeholder="Short summary of what this sub-category covers."
value={createDescription} value={createDescription}
onChange={(e) => setCreateDescription(e.target.value)} onChange={(e) => setCreateDescription(e.target.value)}
/> />
@ -514,7 +514,7 @@ export function AllCoursesPage() {
disabled={creating} disabled={creating}
onClick={handleCreateCourse} onClick={handleCreateCourse}
> >
{creating ? "Creating…" : "Create course"} {creating ? "Creating…" : "Create sub-category"}
</Button> </Button>
</div> </div>
</DialogContent> </DialogContent>
@ -524,9 +524,9 @@ export function AllCoursesPage() {
<Dialog open={editOpen} onOpenChange={setEditOpen}> <Dialog open={editOpen} onOpenChange={setEditOpen}>
<DialogContent className="max-w-lg"> <DialogContent className="max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle>Edit course</DialogTitle> <DialogTitle>Edit sub-category</DialogTitle>
<DialogDescription> <DialogDescription>
Update the title and description for this course. Status can be toggled from the Update the title and description for this sub-category. Status can be toggled from the
table. table.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
@ -534,12 +534,12 @@ export function AllCoursesPage() {
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="mb-1.5 block text-sm font-medium text-grayScale-600"> <label className="mb-1.5 block text-sm font-medium text-grayScale-600">
Course title Sub-category title
</label> </label>
<Input <Input
value={editTitle} value={editTitle}
onChange={(e) => setEditTitle(e.target.value)} onChange={(e) => setEditTitle(e.target.value)}
placeholder="Enter course title" placeholder="Enter sub-category title"
/> />
</div> </div>
<div> <div>
@ -550,7 +550,7 @@ export function AllCoursesPage() {
rows={3} rows={3}
value={editDescription} value={editDescription}
onChange={(e) => setEditDescription(e.target.value)} onChange={(e) => setEditDescription(e.target.value)}
placeholder="Short summary of this course." placeholder="Short summary of this sub-category."
/> />
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@ const contentSections = [
pathFn: (categoryId: string | undefined) => `/content/category/${categoryId}/courses`, pathFn: (categoryId: string | undefined) => `/content/category/${categoryId}/courses`,
icon: BookOpen, icon: BookOpen,
title: "Courses", title: "Courses",
description: "Manage course videos and educational content", description: "Manage sub-categories, course videos and educational content",
action: "Manage Courses", action: "Manage Courses",
count: 12, count: 12,
countLabel: "courses", countLabel: "courses",

View File

@ -139,7 +139,7 @@ export function CourseCategoryPage() {
<CardContent> <CardContent>
<span className="inline-flex items-center gap-1.5 text-sm font-medium text-brand-500 transition-colors group-hover:text-brand-600"> <span className="inline-flex items-center gap-1.5 text-sm font-medium text-brand-500 transition-colors group-hover:text-brand-600">
View Courses View Sub-categories
<span className="inline-block transition-transform duration-300 group-hover:translate-x-1"> <span className="inline-block transition-transform duration-300 group-hover:translate-x-1">
</span> </span>

File diff suppressed because it is too large Load Diff

View File

@ -77,7 +77,7 @@ export function CoursesPage() {
setCategory(foundCategory ?? null) setCategory(foundCategory ?? null)
} catch (err) { } catch (err) {
console.error("Failed to fetch courses:", err) console.error("Failed to fetch courses:", err)
setError("Failed to load courses") setError("Failed to load sub-categories")
} finally { } finally {
setLoading(false) setLoading(false)
} }
@ -123,7 +123,7 @@ export function CoursesPage() {
await fetchCourses() await fetchCourses()
} catch (err: any) { } catch (err: any) {
console.error("Failed to create course:", err) console.error("Failed to create course:", err)
setSaveError(err.response?.data?.message || "Failed to create course") setSaveError(err.response?.data?.message || "Failed to create sub-category")
} finally { } finally {
setSaving(false) setSaving(false)
} }
@ -206,7 +206,7 @@ export function CoursesPage() {
await fetchCourses() await fetchCourses()
} catch (err: any) { } catch (err: any) {
console.error("Failed to update course:", err) console.error("Failed to update course:", err)
setUpdateError(err.response?.data?.message || "Failed to update course") setUpdateError(err.response?.data?.message || "Failed to update sub-category")
} finally { } finally {
setUpdating(false) setUpdating(false)
} }
@ -267,16 +267,16 @@ export function CoursesPage() {
</Link> </Link>
<div> <div>
<h1 className="text-xl font-bold text-grayScale-700 sm:text-2xl"> <h1 className="text-xl font-bold text-grayScale-700 sm:text-2xl">
{category?.name} Courses {category?.name} Sub-categories
</h1> </h1>
<p className="mt-0.5 text-sm text-grayScale-400"> <p className="mt-0.5 text-sm text-grayScale-400">
<span className="font-medium text-grayScale-500">{courses.length}</span> courses available <span className="font-medium text-grayScale-500">{courses.length}</span> sub-categories available
</p> </p>
</div> </div>
</div> </div>
<Button className="w-full bg-brand-500 shadow-sm transition-all hover:bg-brand-600 hover:shadow-md sm:w-auto" onClick={handleOpenModal}> <Button className="w-full bg-brand-500 shadow-sm transition-all hover:bg-brand-600 hover:shadow-md sm:w-auto" onClick={handleOpenModal}>
<Plus className="mr-2 h-4 w-4" /> <Plus className="mr-2 h-4 w-4" />
Add New Course Add New Sub-category
</Button> </Button>
</div> </div>
</div> </div>
@ -285,16 +285,16 @@ export function CoursesPage() {
<Card className="shadow-soft"> <Card className="shadow-soft">
<CardHeader className="border-b border-grayScale-200 pb-3"> <CardHeader className="border-b border-grayScale-200 pb-3">
<CardTitle className="text-base font-semibold text-grayScale-600"> <CardTitle className="text-base font-semibold text-grayScale-600">
Course Management Sub-category Management
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="pt-4"> <CardContent className="pt-4">
{courses.length === 0 ? ( {courses.length === 0 ? (
<div className="flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-grayScale-200 py-16 text-center"> <div className="flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-grayScale-200 py-16 text-center">
<img src={practiceSrc} alt="" className="h-16 w-16" /> <img src={practiceSrc} alt="" className="h-16 w-16" />
<h3 className="mt-4 text-base font-semibold text-grayScale-600">No courses yet</h3> <h3 className="mt-4 text-base font-semibold text-grayScale-600">No sub-categories yet</h3>
<p className="mt-1.5 text-sm text-grayScale-400"> <p className="mt-1.5 text-sm text-grayScale-400">
No courses found in this category. No sub-categories found in this category.
</p> </p>
<Button <Button
variant="outline" variant="outline"
@ -302,7 +302,7 @@ export function CoursesPage() {
onClick={handleOpenModal} onClick={handleOpenModal}
> >
<Plus className="mr-2 h-4 w-4" /> <Plus className="mr-2 h-4 w-4" />
Add your first course Add your first sub-category
</Button> </Button>
</div> </div>
) : ( ) : (
@ -311,7 +311,7 @@ export function CoursesPage() {
<TableHeader> <TableHeader>
<TableRow className="bg-grayScale-100 hover:bg-grayScale-100"> <TableRow className="bg-grayScale-100 hover:bg-grayScale-100">
<TableHead className="py-3 text-xs font-semibold uppercase tracking-wider text-grayScale-500"> <TableHead className="py-3 text-xs font-semibold uppercase tracking-wider text-grayScale-500">
Course Sub-category
</TableHead> </TableHead>
<TableHead className="hidden py-3 text-xs font-semibold uppercase tracking-wider text-grayScale-500 md:table-cell"> <TableHead className="hidden py-3 text-xs font-semibold uppercase tracking-wider text-grayScale-500 md:table-cell">
Status Status
@ -415,7 +415,7 @@ export function CoursesPage() {
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="mx-4 w-full max-w-2xl animate-in fade-in zoom-in-95 rounded-2xl bg-white shadow-2xl"> <div className="mx-4 w-full max-w-2xl animate-in fade-in zoom-in-95 rounded-2xl bg-white shadow-2xl">
<div className="flex items-center justify-between border-b border-grayScale-100 px-6 py-5"> <div className="flex items-center justify-between border-b border-grayScale-100 px-6 py-5">
<h2 className="text-lg font-bold text-grayScale-700">Add New Course</h2> <h2 className="text-lg font-bold text-grayScale-700">Add New Sub-category</h2>
<button <button
onClick={handleCloseModal} onClick={handleCloseModal}
className="grid h-8 w-8 place-items-center rounded-lg text-grayScale-400 transition-colors hover:bg-grayScale-100 hover:text-grayScale-600" className="grid h-8 w-8 place-items-center rounded-lg text-grayScale-400 transition-colors hover:bg-grayScale-100 hover:text-grayScale-600"
@ -441,7 +441,7 @@ export function CoursesPage() {
</label> </label>
<Input <Input
id="course-title" id="course-title"
placeholder="Enter course title" placeholder="Enter sub-category title"
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
/> />
@ -456,7 +456,7 @@ export function CoursesPage() {
</label> </label>
<textarea <textarea
id="course-description" id="course-description"
placeholder="Enter course description" placeholder="Enter sub-category description"
value={description} value={description}
onChange={(e) => setDescription(e.target.value)} onChange={(e) => setDescription(e.target.value)}
rows={4} rows={4}
@ -478,7 +478,7 @@ export function CoursesPage() {
onClick={handleSave} onClick={handleSave}
disabled={saving} disabled={saving}
> >
{saving ? "Saving..." : "Save Course"} {saving ? "Saving..." : "Save Sub-category"}
</Button> </Button>
</div> </div>
</div> </div>
@ -490,7 +490,7 @@ export function CoursesPage() {
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="mx-4 w-full max-w-md animate-in fade-in zoom-in-95 rounded-2xl bg-white shadow-2xl"> <div className="mx-4 w-full max-w-md animate-in fade-in zoom-in-95 rounded-2xl bg-white shadow-2xl">
<div className="flex items-center justify-between border-b border-grayScale-100 px-6 py-5"> <div className="flex items-center justify-between border-b border-grayScale-100 px-6 py-5">
<h2 className="text-lg font-bold text-grayScale-700">Edit Course</h2> <h2 className="text-lg font-bold text-grayScale-700">Edit Sub-category</h2>
<button <button
onClick={handleCloseEditModal} onClick={handleCloseEditModal}
className="grid h-8 w-8 place-items-center rounded-lg text-grayScale-400 transition-colors hover:bg-grayScale-100 hover:text-grayScale-600" className="grid h-8 w-8 place-items-center rounded-lg text-grayScale-400 transition-colors hover:bg-grayScale-100 hover:text-grayScale-600"
@ -516,7 +516,7 @@ export function CoursesPage() {
</label> </label>
<Input <Input
id="edit-course-title" id="edit-course-title"
placeholder="Enter course title" placeholder="Enter sub-category title"
value={editTitle} value={editTitle}
onChange={(e) => setEditTitle(e.target.value)} onChange={(e) => setEditTitle(e.target.value)}
/> />
@ -531,7 +531,7 @@ export function CoursesPage() {
</label> </label>
<textarea <textarea
id="edit-course-description" id="edit-course-description"
placeholder="Enter course description" placeholder="Enter sub-category description"
value={editDescription} value={editDescription}
onChange={(e) => setEditDescription(e.target.value)} onChange={(e) => setEditDescription(e.target.value)}
rows={4} rows={4}
@ -564,7 +564,7 @@ export function CoursesPage() {
onClick={handleUpdate} onClick={handleUpdate}
disabled={updating} disabled={updating}
> >
{updating ? "Updating..." : "Update Course"} {updating ? "Updating..." : "Update Sub-category"}
</Button> </Button>
</div> </div>
</div> </div>
@ -576,7 +576,7 @@ export function CoursesPage() {
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="mx-4 w-full max-w-lg animate-in fade-in zoom-in-95 rounded-2xl bg-white shadow-2xl"> <div className="mx-4 w-full max-w-lg animate-in fade-in zoom-in-95 rounded-2xl bg-white shadow-2xl">
<div className="flex items-center justify-between border-b border-grayScale-100 px-6 py-5"> <div className="flex items-center justify-between border-b border-grayScale-100 px-6 py-5">
<h2 className="text-lg font-bold text-grayScale-700">Course Ratings</h2> <h2 className="text-lg font-bold text-grayScale-700">Sub-category Ratings</h2>
<button <button
onClick={() => { onClick={() => {
setShowRatingsModal(false) setShowRatingsModal(false)
@ -602,7 +602,7 @@ export function CoursesPage() {
</div> </div>
<p className="mt-4 text-sm font-semibold text-grayScale-700">No ratings yet</p> <p className="mt-4 text-sm font-semibold text-grayScale-700">No ratings yet</p>
<p className="mt-1 text-sm text-grayScale-400"> <p className="mt-1 text-sm text-grayScale-400">
Ratings will appear here once learners start reviewing this course. Ratings will appear here once learners start reviewing this sub-category.
</p> </p>
</div> </div>
) : ( ) : (
@ -690,7 +690,7 @@ export function CoursesPage() {
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="mx-4 w-full max-w-sm animate-in fade-in zoom-in-95 rounded-2xl bg-white shadow-2xl"> <div className="mx-4 w-full max-w-sm animate-in fade-in zoom-in-95 rounded-2xl bg-white shadow-2xl">
<div className="flex items-center justify-between border-b border-grayScale-100 px-6 py-5"> <div className="flex items-center justify-between border-b border-grayScale-100 px-6 py-5">
<h2 className="text-lg font-bold text-grayScale-700">Delete Course</h2> <h2 className="text-lg font-bold text-grayScale-700">Delete Sub-category</h2>
<button <button
onClick={() => setShowDeleteModal(false)} onClick={() => setShowDeleteModal(false)}
className="grid h-8 w-8 place-items-center rounded-lg text-grayScale-400 transition-colors hover:bg-grayScale-100 hover:text-grayScale-600" className="grid h-8 w-8 place-items-center rounded-lg text-grayScale-400 transition-colors hover:bg-grayScale-100 hover:text-grayScale-600"

View File

@ -97,8 +97,8 @@ export function SubCourseContentPage() {
) )
setSubCourse(foundSubCourse ?? null) setSubCourse(foundSubCourse ?? null)
} catch (err) { } catch (err) {
console.error("Failed to fetch sub-course data:", err) console.error("Failed to fetch course data:", err)
setError("Failed to load sub-course") setError("Failed to load course")
} finally { } finally {
setLoading(false) setLoading(false)
} }
@ -374,7 +374,7 @@ export function SubCourseContentPage() {
return ( return (
<div className="flex flex-col items-center justify-center py-20"> <div className="flex flex-col items-center justify-center py-20">
<img src={spinnerSrc} alt="" className="h-8 w-8 animate-spin" /> <img src={spinnerSrc} alt="" className="h-8 w-8 animate-spin" />
<p className="mt-4 text-sm font-medium text-grayScale-500">Loading sub-course</p> <p className="mt-4 text-sm font-medium text-grayScale-500">Loading course</p>
</div> </div>
) )
} }
@ -396,7 +396,7 @@ export function SubCourseContentPage() {
className="group inline-flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm font-medium text-grayScale-500 transition-all hover:bg-grayScale-50 hover:text-grayScale-900" className="group inline-flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm font-medium text-grayScale-500 transition-all hover:bg-grayScale-50 hover:text-grayScale-900"
> >
<ArrowLeft className="h-4 w-4 transition-transform group-hover:-translate-x-0.5" /> <ArrowLeft className="h-4 w-4 transition-transform group-hover:-translate-x-0.5" />
Back to Sub-courses Back to Courses
</Link> </Link>
{/* SubCourse Header */} {/* SubCourse Header */}
@ -721,7 +721,7 @@ export function SubCourseContentPage() {
</div> </div>
<p className="mt-4 text-sm font-semibold text-grayScale-700">No ratings yet</p> <p className="mt-4 text-sm font-semibold text-grayScale-700">No ratings yet</p>
<p className="mt-1 text-sm text-grayScale-400"> <p className="mt-1 text-sm text-grayScale-400">
Ratings will appear here once learners start reviewing this sub-course. Ratings will appear here once learners start reviewing this course.
</p> </p>
</div> </div>
) : ( ) : (

File diff suppressed because it is too large Load Diff

View File

@ -498,8 +498,8 @@ export interface GetLearningPathResponse {
} }
export interface ReorderItem { export interface ReorderItem {
sub_course_id: number id: number
display_order: number position: number
} }
// Ratings // Ratings