Files
starr-philproperties/src/modules/admin/pages/courses/lessons/AddLesson.jsx
T
kennethobsequio 17326b2c2e testing 101
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-06-30 19:32:00 +08:00

158 lines
5.9 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useForm, useFieldArray } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { ArrowLeft, Plus, Trash2 } from "lucide-react";
import { useCourses } from "@/contexts/AdminCoursesContext";
import { useAuth } from "@/contexts/AuthContext";
import { PageMeta } from "@/contexts/MetadataContext";
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";
const schema = z.object({
title: z.string().min(1, "Title is required."),
description: z.string().optional(),
order: z.coerce.number().min(0).default(0),
objectives: z.array(
z.object({ value: z.string().min(1, "Objective cannot be empty.") })
).optional(),
});
function FieldError({ message }) {
if (!message) return null;
return <p className="text-xs text-destructive mt-1">{message}</p>;
}
export default function AddLesson() {
const navigate = useNavigate();
const { courseId, unitId } = useParams();
const { createLesson, fetchUnit, course, unit, loading } = useCourses();
const { user } = useAuth();
const { register, handleSubmit, control, formState: { errors } } = useForm({
resolver: zodResolver(schema),
defaultValues: { title: "", description: "", order: 0, objectives: [] },
});
const { fields, append, remove } = useFieldArray({ control, name: "objectives" });
useEffect(() => {
fetchUnit(courseId, unitId);
}, [courseId, unitId]);
const onSubmit = async (data) => {
const payload = {
...data,
objectives: data.objectives?.map((o) => o.value) ?? [],
createdBy: user?.user_id,
};
const result = await createLesson(courseId, unitId, payload);
if (!result) return;
navigate(`/admin/courses/${courseId}/units/${unitId}/lessons`);
};
return (
<section className="bg-muted h-full">
<PageMeta title={unit ? `Add Lesson – ${unit.title} - STARR` : undefined} />
<div className="lg:container lg:mx-auto lg:px-0 flex flex-col items-center px-4 py-10">
<div className="w-full max-w-2xl mx-auto">
<div className="flex items-center gap-3 mb-6">
<Button type="button" variant="ghost" size="icon" onClick={() => navigate(-1)}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div>
<h1 className="text-xl font-semibold">Create Lesson</h1>
<p className="text-sm text-muted-foreground">Add a new lesson to this unit.</p>
</div>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<div className="rounded-lg border bg-card p-6 space-y-5">
<div className="space-y-1.5">
<Label htmlFor="title">Title <span className="text-destructive">*</span></Label>
<Input id="title" placeholder="Lesson title" {...register("title")} />
<FieldError message={errors.title?.message} />
</div>
<div className="space-y-1.5">
<Label htmlFor="description">Description</Label>
<Textarea id="description" placeholder="Optional description" rows={3} {...register("description")} />
</div>
<div className="space-y-1.5 max-w-[120px]">
<Label htmlFor="order">Order</Label>
<Input id="order" type="number" min={0} {...register("order")} />
</div>
</div>
{/* Objectives */}
<div className="rounded-lg border bg-card p-6 space-y-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium">Objectives</p>
<p className="text-xs text-muted-foreground">What learners will achieve from this lesson.</p>
</div>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => append({ value: "" })}
>
<Plus className="h-4 w-4 mr-1" />
Add
</Button>
</div>
{fields.length === 0 && (
<p className="text-sm text-muted-foreground text-center py-4">
No objectives yet. Click Add to get started.
</p>
)}
<div className="space-y-3">
{fields.map((field, index) => (
<div key={field.id} className="flex items-start gap-2">
<div className="flex-1 space-y-1">
<Input
placeholder={`Objective ${index + 1}`}
{...register(`objectives.${index}.value`)}
/>
<FieldError message={errors.objectives?.[index]?.value?.message} />
</div>
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => remove(index)}
className="text-muted-foreground hover:text-destructive mt-0.5"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
))}
</div>
</div>
<div className="flex justify-end gap-3">
<Button type="button" variant="outline" onClick={() => navigate(-1)} disabled={loading}>
Cancel
</Button>
<Button type="submit" disabled={loading}>
{loading && <Spinner className="h-4 w-4 mr-2" />}
Create Lesson
</Button>
</div>
</form>
</div>
</div>
</section>
);
}