fix(admin): restore description fields on Learn English course and module forms
Re-add description inputs to course create/edit and module create/edit dialogs so descriptions are sent to the API instead of empty strings. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a10d7684d5
commit
c9959cce23
|
|
@ -21,6 +21,7 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "../../components/ui/dialog";
|
} from "../../components/ui/dialog";
|
||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
import { cn } from "../../lib/utils";
|
import { cn } from "../../lib/utils";
|
||||||
import spinnerSrc from "../../assets/Circular-indeterminate progress indicator.svg";
|
import spinnerSrc from "../../assets/Circular-indeterminate progress indicator.svg";
|
||||||
import alertSrc from "../../assets/Alert.svg";
|
import alertSrc from "../../assets/Alert.svg";
|
||||||
|
|
@ -162,6 +163,7 @@ export function CourseDetailPage() {
|
||||||
const [editingModule, setEditingModule] =
|
const [editingModule, setEditingModule] =
|
||||||
useState<TopLevelCourseModuleItem | null>(null);
|
useState<TopLevelCourseModuleItem | null>(null);
|
||||||
const [editModuleName, setEditModuleName] = useState("");
|
const [editModuleName, setEditModuleName] = useState("");
|
||||||
|
const [editModuleDescription, setEditModuleDescription] = useState("");
|
||||||
const [editModuleSortOrder, setEditModuleSortOrder] = useState("");
|
const [editModuleSortOrder, setEditModuleSortOrder] = useState("");
|
||||||
const [editModuleIcon, setEditModuleIcon] = useState("");
|
const [editModuleIcon, setEditModuleIcon] = useState("");
|
||||||
const [editModuleIconUploadBusy, setEditModuleIconUploadBusy] =
|
const [editModuleIconUploadBusy, setEditModuleIconUploadBusy] =
|
||||||
|
|
@ -197,6 +199,7 @@ export function CourseDetailPage() {
|
||||||
const openEditModule = (module: TopLevelCourseModuleItem) => {
|
const openEditModule = (module: TopLevelCourseModuleItem) => {
|
||||||
setEditingModule(module);
|
setEditingModule(module);
|
||||||
setEditModuleName(module.name ?? "");
|
setEditModuleName(module.name ?? "");
|
||||||
|
setEditModuleDescription(module.description ?? "");
|
||||||
setEditModuleSortOrder(String(module.sort_order ?? 0));
|
setEditModuleSortOrder(String(module.sort_order ?? 0));
|
||||||
setEditModuleIcon(module.icon?.trim() ?? "");
|
setEditModuleIcon(module.icon?.trim() ?? "");
|
||||||
setEditModuleIconUploadBusy(false);
|
setEditModuleIconUploadBusy(false);
|
||||||
|
|
@ -460,7 +463,7 @@ export function CourseDetailPage() {
|
||||||
try {
|
try {
|
||||||
await updateTopLevelCourseModule(editingModule.id, {
|
await updateTopLevelCourseModule(editingModule.id, {
|
||||||
name,
|
name,
|
||||||
description: editingModule.description?.trim() ?? "",
|
description: editModuleDescription.trim(),
|
||||||
icon: editModuleIcon.trim(),
|
icon: editModuleIcon.trim(),
|
||||||
sort_order,
|
sort_order,
|
||||||
});
|
});
|
||||||
|
|
@ -618,7 +621,7 @@ export function CourseDetailPage() {
|
||||||
<DialogHeader className="shrink-0 space-y-1.5 border-b border-grayScale-100 px-6 pb-4 pt-6 pr-12">
|
<DialogHeader className="shrink-0 space-y-1.5 border-b border-grayScale-100 px-6 pb-4 pt-6 pr-12">
|
||||||
<DialogTitle>Edit module</DialogTitle>
|
<DialogTitle>Edit module</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Update name, sort order, and icon (upload or URL).
|
Update name, description, sort order, and icon (upload or URL).
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-4">
|
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-4">
|
||||||
|
|
@ -635,6 +638,19 @@ export function CourseDetailPage() {
|
||||||
disabled={savingModuleEdit}
|
disabled={savingModuleEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium text-grayScale-700">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
value={editModuleDescription}
|
||||||
|
onChange={(e) => setEditModuleDescription(e.target.value)}
|
||||||
|
rows={4}
|
||||||
|
className="min-h-[100px] resize-y rounded-xl"
|
||||||
|
placeholder="Short summary of the module"
|
||||||
|
disabled={savingModuleEdit || editModuleIconUploadBusy}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label
|
<label
|
||||||
htmlFor="edit-module-sort-order"
|
htmlFor="edit-module-sort-order"
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "../../components/ui/dialog";
|
} from "../../components/ui/dialog";
|
||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
import uploadIcon from "../../assets/icons/upload.png";
|
import uploadIcon from "../../assets/icons/upload.png";
|
||||||
import spinnerSrc from "../../assets/Circular-indeterminate progress indicator.svg";
|
import spinnerSrc from "../../assets/Circular-indeterminate progress indicator.svg";
|
||||||
import alertSrc from "../../assets/Alert.svg";
|
import alertSrc from "../../assets/Alert.svg";
|
||||||
|
|
@ -64,6 +65,7 @@ export function ProgramCoursesPage() {
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const [editName, setEditName] = useState("");
|
const [editName, setEditName] = useState("");
|
||||||
|
const [editDescription, setEditDescription] = useState("");
|
||||||
const [editSortOrder, setEditSortOrder] = useState("");
|
const [editSortOrder, setEditSortOrder] = useState("");
|
||||||
const [editThumbnail, setEditThumbnail] = useState("");
|
const [editThumbnail, setEditThumbnail] = useState("");
|
||||||
const [savingEdit, setSavingEdit] = useState(false);
|
const [savingEdit, setSavingEdit] = useState(false);
|
||||||
|
|
@ -72,6 +74,7 @@ export function ProgramCoursesPage() {
|
||||||
|
|
||||||
const [createCourseOpen, setCreateCourseOpen] = useState(false);
|
const [createCourseOpen, setCreateCourseOpen] = useState(false);
|
||||||
const [createName, setCreateName] = useState("");
|
const [createName, setCreateName] = useState("");
|
||||||
|
const [createDescription, setCreateDescription] = useState("");
|
||||||
const [createSortOrder, setCreateSortOrder] = useState("");
|
const [createSortOrder, setCreateSortOrder] = useState("");
|
||||||
const [createThumbnail, setCreateThumbnail] = useState("");
|
const [createThumbnail, setCreateThumbnail] = useState("");
|
||||||
const [createSaving, setCreateSaving] = useState(false);
|
const [createSaving, setCreateSaving] = useState(false);
|
||||||
|
|
@ -218,6 +221,7 @@ export function ProgramCoursesPage() {
|
||||||
const openEditCourse = (course: ProgramCourseListItem) => {
|
const openEditCourse = (course: ProgramCourseListItem) => {
|
||||||
setEditingCourse(course);
|
setEditingCourse(course);
|
||||||
setEditName(course.name ?? "");
|
setEditName(course.name ?? "");
|
||||||
|
setEditDescription(course.description?.trim() ?? "");
|
||||||
setEditThumbnail(
|
setEditThumbnail(
|
||||||
course.thumbnail?.trim() || course.thumbnail_url?.trim() || "",
|
course.thumbnail?.trim() || course.thumbnail_url?.trim() || "",
|
||||||
);
|
);
|
||||||
|
|
@ -227,6 +231,7 @@ export function ProgramCoursesPage() {
|
||||||
const closeEditCourse = () => {
|
const closeEditCourse = () => {
|
||||||
setEditingCourse(null);
|
setEditingCourse(null);
|
||||||
setEditName("");
|
setEditName("");
|
||||||
|
setEditDescription("");
|
||||||
setEditSortOrder("");
|
setEditSortOrder("");
|
||||||
setEditThumbnail("");
|
setEditThumbnail("");
|
||||||
setUploadingEditThumbnail(false);
|
setUploadingEditThumbnail(false);
|
||||||
|
|
@ -291,7 +296,7 @@ export function ProgramCoursesPage() {
|
||||||
try {
|
try {
|
||||||
await updateTopLevelCourse(editingCourse.id, {
|
await updateTopLevelCourse(editingCourse.id, {
|
||||||
name,
|
name,
|
||||||
description: editingCourse.description?.trim() ?? "",
|
description: editDescription.trim(),
|
||||||
thumbnail: editThumbnail.trim(),
|
thumbnail: editThumbnail.trim(),
|
||||||
sort_order,
|
sort_order,
|
||||||
});
|
});
|
||||||
|
|
@ -311,6 +316,7 @@ export function ProgramCoursesPage() {
|
||||||
|
|
||||||
const clearCreateCourseForm = () => {
|
const clearCreateCourseForm = () => {
|
||||||
setCreateName("");
|
setCreateName("");
|
||||||
|
setCreateDescription("");
|
||||||
setCreateSortOrder("");
|
setCreateSortOrder("");
|
||||||
setCreateThumbnail("");
|
setCreateThumbnail("");
|
||||||
setCreateUploadingThumbnail(false);
|
setCreateUploadingThumbnail(false);
|
||||||
|
|
@ -381,7 +387,7 @@ export function ProgramCoursesPage() {
|
||||||
try {
|
try {
|
||||||
await createProgramCourse(programId, {
|
await createProgramCourse(programId, {
|
||||||
name,
|
name,
|
||||||
description: "",
|
description: createDescription.trim(),
|
||||||
thumbnail: createThumbnail.trim(),
|
thumbnail: createThumbnail.trim(),
|
||||||
sort_order,
|
sort_order,
|
||||||
});
|
});
|
||||||
|
|
@ -509,6 +515,20 @@ export function ProgramCoursesPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-[15px] font-medium text-grayScale-700">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
value={createDescription}
|
||||||
|
onChange={(e) => setCreateDescription(e.target.value)}
|
||||||
|
placeholder="Short summary of the course"
|
||||||
|
rows={3}
|
||||||
|
className="min-h-[88px] resize-y rounded-xl"
|
||||||
|
disabled={createSaving || createUploadingThumbnail}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label
|
<label
|
||||||
htmlFor="create-course-sort-order"
|
htmlFor="create-course-sort-order"
|
||||||
|
|
@ -822,7 +842,7 @@ export function ProgramCoursesPage() {
|
||||||
<DialogHeader className="shrink-0 space-y-1.5 border-b border-grayScale-100 px-6 pb-4 pt-6 pr-12">
|
<DialogHeader className="shrink-0 space-y-1.5 border-b border-grayScale-100 px-6 pb-4 pt-6 pr-12">
|
||||||
<DialogTitle>Edit course</DialogTitle>
|
<DialogTitle>Edit course</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Update name, sort order, and thumbnail.
|
Update name, description, sort order, and thumbnail.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-4">
|
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-4">
|
||||||
|
|
@ -839,6 +859,19 @@ export function ProgramCoursesPage() {
|
||||||
disabled={savingEdit || uploadingEditThumbnail}
|
disabled={savingEdit || uploadingEditThumbnail}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium text-grayScale-700">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
value={editDescription}
|
||||||
|
onChange={(e) => setEditDescription(e.target.value)}
|
||||||
|
rows={4}
|
||||||
|
className="min-h-[100px] resize-y rounded-xl"
|
||||||
|
placeholder="Short summary of the course"
|
||||||
|
disabled={savingEdit || uploadingEditThumbnail}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label
|
<label
|
||||||
htmlFor="edit-course-sort-order"
|
htmlFor="edit-course-sort-order"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
DialogClose,
|
DialogClose,
|
||||||
} from "../../../components/ui/dialog";
|
} from "../../../components/ui/dialog";
|
||||||
import { Input } from "../../../components/ui/input";
|
import { Input } from "../../../components/ui/input";
|
||||||
|
import { Textarea } from "../../../components/ui/textarea";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { createTopLevelCourseModule } from "../../../api/courses.api";
|
import { createTopLevelCourseModule } from "../../../api/courses.api";
|
||||||
import { ModuleIconUploadField } from "./ModuleIconUploadField";
|
import { ModuleIconUploadField } from "./ModuleIconUploadField";
|
||||||
|
|
@ -27,6 +28,7 @@ export function AddModuleModal({
|
||||||
onCreated,
|
onCreated,
|
||||||
}: AddModuleModalProps) {
|
}: AddModuleModalProps) {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
|
const [description, setDescription] = useState("");
|
||||||
const [sortOrder, setSortOrder] = useState("");
|
const [sortOrder, setSortOrder] = useState("");
|
||||||
const [icon, setIcon] = useState("");
|
const [icon, setIcon] = useState("");
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
@ -35,6 +37,7 @@ export function AddModuleModal({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
setName("");
|
setName("");
|
||||||
|
setDescription("");
|
||||||
setSortOrder("");
|
setSortOrder("");
|
||||||
setIcon("");
|
setIcon("");
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
|
@ -44,6 +47,7 @@ export function AddModuleModal({
|
||||||
|
|
||||||
const resetAndClose = () => {
|
const resetAndClose = () => {
|
||||||
setName("");
|
setName("");
|
||||||
|
setDescription("");
|
||||||
setSortOrder("");
|
setSortOrder("");
|
||||||
setIcon("");
|
setIcon("");
|
||||||
setIconUploadBusy(false);
|
setIconUploadBusy(false);
|
||||||
|
|
@ -82,7 +86,7 @@ export function AddModuleModal({
|
||||||
try {
|
try {
|
||||||
await createTopLevelCourseModule(courseId, {
|
await createTopLevelCourseModule(courseId, {
|
||||||
name: trimmedName,
|
name: trimmedName,
|
||||||
description: "",
|
description: description.trim(),
|
||||||
icon: icon.trim(),
|
icon: icon.trim(),
|
||||||
sort_order,
|
sort_order,
|
||||||
});
|
});
|
||||||
|
|
@ -149,6 +153,20 @@ export function AddModuleModal({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-[15px] font-medium text-grayScale-700">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
placeholder="Short summary of the module"
|
||||||
|
rows={3}
|
||||||
|
className="min-h-[88px] resize-y rounded-xl"
|
||||||
|
disabled={submitting || iconUploadBusy}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label
|
<label
|
||||||
htmlFor="create-module-sort-order"
|
htmlFor="create-module-sort-order"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user