import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useForm, useFieldArray, useWatch } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { nanoid } from "nanoid"; import { ArrowLeft, ChevronLeft, ChevronRight, Check, FileText, BookOpen, LayoutTemplate, ClipboardCheck, ListChecks, Plus, Trash2, } from "lucide-react"; import { useLibrary } from "@/contexts/AdminLibraryContext"; import { useCourses } from "@/contexts/AdminCoursesContext"; import DraftRequirementsEditor, { DraftRequirementsSummary } from "@/modules/admin/components/courses/DraftRequirementsEditor"; import { useAuth } from "@/contexts/AuthContext"; import { PageMeta } from "@/contexts/MetadataContext"; import api from "@/utils/api.util"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Spinner } from "@/components/ui/spinner"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, DrawerClose, } from "@/components/ui/drawer"; import { BlockList, DEFAULT_CONTENT } from "@/components/generic/BlockList"; import { AddBlockMenu } from "@/components/generic/AddBlockMenu"; import { PreviewContent, PreviewChrome } from "../../../components/courses/LessonsPreview"; import { useUnsavedChangesGuard } from "@/hooks/useUnsavedChangesGuard"; function makeBlock(type) { return { id: nanoid(), type, content: { ...DEFAULT_CONTENT[type] } }; } // ─── Schema ─────────────────────────────────────────────────────────────────── const lessonSchema = z.object({ title: z.string().min(1, "Title is required."), description: z.string().optional(), objectives: z.array( z.object({ value: z.string().min(1, "Objective cannot be empty.") }) ).optional(), blocks: z.array(z.any()).optional(), }); const schema = z.object({ title: z.string().min(1, "Title is required."), description: z.string().optional(), subscription: z.string().optional(), lessons: z.array(lessonSchema).optional(), }); const DEFAULT_VALUES = { title: "", description: "", subscription: "", lessons: [], }; const STEPS = [ { id: 0, label: "Create Unit", icon: FileText }, { id: 1, label: "Lessons", icon: BookOpen }, { id: 2, label: "Page Builder", icon: LayoutTemplate }, { id: 3, label: "Requirements", icon: ListChecks }, { id: 4, label: "Review", icon: ClipboardCheck }, ]; // Fields validated with trigger() before advancing past each step. // Empty array means "validate the whole form" (nothing new to check that step). const STEP_FIELDS = [["title", "description", "subscription"], ["lessons"], [], []]; function FieldError({ message }) { if (!message) return null; return
{message}
; } // ─── Step 1 — Create Unit ───────────────────────────────────────────────────── function StepUnit({ register, errors, control, setValue, tierCategories }) { const watchedSubscr = useWatch({ control, name: "subscription" }); return (Optional. Gates this unit directly, independent of any course it may later be attached to.
No lessons yet. A unit can be created without any, but add one now if you'd like to build its content in this wizard.
)} {fields.map((f, i) => (Add at least one lesson in the previous step to build its page content here.
); } // Clamp instead of syncing via effect — a removed lesson (from the previous // step) can leave rawIndex pointing past the end of the array. const activeIndex = Math.min(rawIndex, lessons.length - 1); const activeLesson = lessons[activeIndex]; const blocks = activeLesson?.blocks ?? []; const setBlocks = (updater) => { const next = typeof updater === "function" ? updater(blocks) : updater; setValue(`lessons.${activeIndex}.blocks`, next, { shouldDirty: true }); }; const addBlock = (type) => setBlocks((prev) => [...prev, makeBlock(type)]); const updateBlock = (id, content) => setBlocks((prev) => prev.map((b) => (b.id === id ? { ...b, content } : b))); const deleteBlock = (id) => setBlocks((prev) => prev.filter((b) => b.id !== id)); const moveBlock = (id, direction) => setBlocks((prev) => { const index = prev.findIndex((b) => b.id === id); const swapIndex = direction === "up" ? index - 1 : index + 1; if (swapIndex < 0 || swapIndex >= prev.length) return prev; const next = [...prev]; [next[index], next[swapIndex]] = [next[swapIndex], next[index]]; return next; }); return ({l.title || `Lesson ${i + 1}`}
{(l.blocks?.length ?? 0)} block{(l.blocks?.length ?? 0) !== 1 ? "s" : ""}
No lessons will be created with this unit.
) : (Configure how learners complete this unit — optional, sensible defaults apply automatically. This is created together with the rest of the unit when you finish.