feat(module): Add practice button on each lesson card

- VideoCard: optional onAddPractice for lesson-scoped CTA
- ModuleDetailPage: navigate to add-practice with lessonId and lessonTitle
- AddPracticeFlow: show context banner when opened from a lesson

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Yared Yemane 2026-05-13 05:27:06 -07:00
parent 77b71abfd8
commit bb6680acfb
3 changed files with 52 additions and 1 deletions

View File

@ -23,6 +23,17 @@ export function AddPracticeFlow() {
const backTo = searchParams.get("backTo");
const courseId = searchParams.get("courseId");
const moduleId = searchParams.get("moduleId");
const lessonId = searchParams.get("lessonId");
const lessonTitleRaw = searchParams.get("lessonTitle");
const lessonTitleDisplay = (() => {
const raw = lessonTitleRaw?.trim();
if (!raw) return null;
try {
return decodeURIComponent(raw);
} catch {
return raw;
}
})();
const isModuleContext = backTo === "module";
const isCourseContext = backTo === "modules";
@ -251,6 +262,23 @@ export function AddPracticeFlow() {
<p className="text-grayScale-400 text-base">
Create a new immersive practice session for students.
</p>
{lessonId ? (
<div className="mt-4 rounded-xl border border-violet-200 bg-violet-50/80 px-4 py-3 text-sm text-violet-950">
<p className="font-semibold text-violet-900">Practice for this lesson</p>
<p className="mt-1 text-violet-800/90">
This session will be associated with lesson{" "}
<span className="font-mono font-bold text-violet-950">#{lessonId}</span>
{lessonTitleDisplay ? (
<>
{" "}
<span className="font-medium">{lessonTitleDisplay}</span>
</>
) : null}
. The module-level flow still uses the same steps; use this context when naming and
configuring the practice.
</p>
</div>
) : null}
</div>
<div className="mx-auto w-[70%] mb-12">

View File

@ -398,6 +398,11 @@ export function ModuleDetailPage() {
thumbnailGradient={LESSON_THUMB_GRADIENTS[i % LESSON_THUMB_GRADIENTS.length]}
onEdit={() => openEditLesson(lesson)}
onDelete={() => setDeletingLesson(lesson)}
onAddPractice={() =>
navigate(
`/new-content/learn-english/${level}/courses/add-practice?backTo=module&courseId=${courseId}&moduleId=${moduleId}&lessonId=${lesson.id}&lessonTitle=${encodeURIComponent(lesson.title)}`,
)
}
/>
))}
</div>

View File

@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from "react";
import { MoreVertical, Edit2, Play, Pencil, Trash2 } from "lucide-react";
import { MoreVertical, Edit2, Play, Pencil, Trash2, Calendar } from "lucide-react";
import { Button } from "../../../components/ui/button";
import {
Dialog,
@ -38,6 +38,8 @@ interface VideoCardProps {
hoverModuleActions?: boolean;
onEdit?: () => void;
onDelete?: () => void;
/** When set (e.g. on module lesson cards), shows an "Add practice" control scoped to this lesson. */
onAddPractice?: () => void;
onPublish?: () => void;
}
@ -51,6 +53,7 @@ export function VideoCard({
onEdit,
onDelete,
onPublish,
onAddPractice,
hoverModuleActions = false,
}: VideoCardProps) {
const [thumbFailed, setThumbFailed] = useState(false);
@ -342,6 +345,21 @@ export function VideoCard({
{title}
</h3>
{hoverModuleActions && onAddPractice ? (
<Button
type="button"
variant="outline"
className="h-9 w-full shrink-0 rounded-lg border-brand-200 text-[12px] font-bold text-brand-600 hover:bg-brand-50"
onClick={(e) => {
e.stopPropagation();
onAddPractice();
}}
>
<Calendar className="mr-1.5 h-3.5 w-3.5" aria-hidden />
Add practice
</Button>
) : null}
{/* Actions (footer) — not used for API lesson cards with hover tools */}
{!hoverModuleActions ? (
<div className="pt-2 space-y-3 mt-auto">