import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useFormik, Form, FormikProvider } from 'formik';
import { toast } from 'sonner';
import * as Yup from 'yup';
import Fallback from '@/components/Fallback';
import { useNavigate, useParams } from 'react-router-dom';
import {
useCreateMarketCourseMutation,
useGetMarketCourseByIdQuery,
useGetTagsQuery,
useUpdateMarketCourseMutation,
} from '@/app/backend/endpoints/courses';
import { useEffect, useRef, useState } from 'react';
import { apiUrl } from '@/app/backend';
import { UploadCloudIcon } from 'lucide-react';
import CreatableSelect from 'react-select/creatable';
import useUser from '@/hooks/useUser';
import useTitle from '@/hooks/useTitle';
function CreateCourseByInst({ isEdit = false }: { isEdit?: boolean }) {
useTitle('Create Course');
const { courseId } = useParams();
const { user } = useUser();
const navigate = useNavigate();
const { data: courseDataWrap, isLoading: isGetLoading } =
useGetMarketCourseByIdQuery(parseInt(courseId ?? ''), { skip: !isEdit });
const courseData = courseDataWrap?.data;
const [fileInputKey, setFileInputKey] = useState(
new Date().getTime().toString()
);
const [isLoadingImage, setIsLoadingImage] = useState(false);
const [createCourse, { isLoading }] = useCreateMarketCourseMutation();
const [updateCourse, { isLoading: isUpdating }] =
useUpdateMarketCourseMutation();
const fileInputRef = useRef<HTMLInputElement>(null);
const handleButtonClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
fileInputRef.current?.click();
};
const { data: tagsDataWrap, isLoading: isGetTags } = useGetTagsQuery();
const tagsData = tagsDataWrap?.data;
const [tags, setTags] = useState<
{
label: string;
value: string;
}[]
>([]);
useEffect(() => {
if (tagsData) {
setTags(
tagsData?.map((tag) => ({
label: tag,
value: tag,
}))
);
}
}, [tagsData]);
const formik = useFormik<CEMarketCourseI>({
initialValues: {
inst_designer_id: (user as InstructionalDesigner)?.user_id.toString(),
inst_designer_firstName: (user as InstructionalDesigner)?.firstName,
inst_designer_lastName: (user as InstructionalDesigner)?.lastName,
title: courseData?.title ?? '',
description: courseData?.description ?? '',
price: courseData?.price ?? 0,
tags: courseData?.tags ?? [],
videoThumbnail: courseData?.videoThumbnail ?? '',
thumbnail: courseData?.thumbnail ?? '',
totalHours: courseData?.totalHours ?? 0,
chaptersCount: courseData?.chaptersCount ?? 0,
type: 'market',
},
validationSchema: Yup.object().shape({
title: Yup.string().required('Title is required'),
description: Yup.string().required('Description is required'),
price: Yup.number()
.min(1, 'Price is required')
.required('Price is required'),
totalHours: Yup.number()
.min(1, 'Total hours is required')
.required('Total hours is required'),
chaptersCount: Yup.number()
.min(1, 'Chapters count is required')
.required('Chapters count is required'),
inst_designer_id: Yup.string().required(
'Instructor designer is required'
),
thumbnail: Yup.string()
.min(1, 'Thumbnail is required')
.required('Thumbnail is required'),
}),
enableReinitialize: true,
validateOnMount: false,
validateOnBlur: false,
validateOnChange: false,
onSubmit: (course: CEMarketCourseI) => {
if (isEdit) {
updateCourse({ course_id: Number(courseId), ...course })
.unwrap()
.then((response) => {
toast.success(response.message);
navigate('/dashboard/courses/instructor');
})
.catch((error: any) => {
toast.error(error?.data?.message ?? error?.error.toString());
});
} else {
createCourse(course)
.unwrap()
.then((response) => {
toast.success(response.message);
navigate('/dashboard/courses/instructor');
})
.catch((error: any) => {
toast.error(error?.data?.message ?? error?.error.toString());
});
}
},
});
const {
getFieldProps,
handleSubmit,
touched,
errors,
setFieldValue,
values,
} = formik;
const handleFileChange = (e: any) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
if (reader.readyState === 2) {
// upload file to server
const formData = new FormData();
setIsLoadingImage(true);
formData.append('file', file);
fetch(`${apiUrl}/file/thumbnails/upload`, {
method: 'POST',
body: formData,
credentials: 'include',
})
.then((res) => res.json())
.then((response) => {
// console.log(response.data.fileUrl);
setFieldValue('thumbnail', response.data.fileUrl);
setIsLoadingImage(false);
})
.catch(() => {
toast.error('Error uploading thumbnail');
setIsLoadingImage(false);
});
}
};
reader.readAsDataURL(file);
}
};
const handleDeleteThumbnail = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
setIsLoadingImage(true);
fetch(`${apiUrl}/file/thumbnails/delete?file=${values.thumbnail}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
})
.then(() => {
setFileInputKey(new Date().getTime().toString());
setFieldValue('thumbnail', '');
setIsLoadingImage(false);
})
.catch(() => {
setFileInputKey(new Date().getTime().toString());
setFieldValue('thumbnail', '');
setIsLoadingImage(false);
});
};
if (isEdit && (isGetLoading || isGetTags)) return <Fallback />;
return (
<>
{/* <PageTitle title="Create User" /> */}
<div className="flex justify-center items-center">
<div className="w-full px-6">
<FormikProvider value={formik}>
<Form onSubmit={handleSubmit}>
<div className="flex justify-between items-center gap-4 py-2">
<span className="text-slate-600 text-[25px] font-semibold">
{isEdit ? 'Edit' : 'Create'} Course
</span>
<div className="space-x-2">
<Button
variant="outline"
className="font-semibold font-inter rounded-md"
type="button"
onClick={(e) => {
const res = window.confirm(
'Are you sure you want to cancel?'
);
if (!isEdit && !res) {
return;
}
if (!isEdit && res && values.thumbnail) {
handleDeleteThumbnail(e);
}
navigate('/dashboard/courses/instructor');
}}
>
Cancel
</Button>
<Button
type="submit"
className="bg-amber-500 rounded-md font-semibold font-inter text-white"
isLoading={isLoading || isGetLoading || isUpdating}
disabled={isLoadingImage}
>
Save
</Button>
</div>
</div>
<div className="grid grid-cols-2 gap-y-4 gap-x-6 py-5">
{inputs.map(
({ name, type = 'text', label, required, ...props }, i) => {
// if (isEdit && name === 'password') return null;
return (
<div
className={`grid gap-2 ${
type === 'image' && 'col-span-2'
}`}
key={i}
>
<div className="flex items-center space-x-1">
<Label htmlFor={name}>{label}</Label>
{required! && <span className="text-red-500">*</span>}
</div>
{type !== 'image' && type !== 'multiSelect' ? (
<Input
className="bg-white"
type={type}
{...props}
{...getFieldProps(name)}
error={
touched[name] && errors[name]
? errors[name]
: undefined
}
/>
) : type !== 'multiSelect' ? (
<div className="space-y-1 flex flex-col">
<input
key={fileInputKey}
type="file"
accept="image/*"
onChange={handleFileChange}
ref={fileInputRef}
className="hidden"
/>
{touched[name] && errors[name] && (
<p className="text-red-500 text-sm">
Thumbnail is required
</p>
)}
{values.thumbnail ? (
<>
<Button
className="ml-1 w-44 bg-red-500 hover:bg-red-600 text-white font-semibold rounded-md"
onClick={handleDeleteThumbnail}
isLoading={isLoadingImage}
>
Delete
</Button>
<img
draggable={false}
src={values.thumbnail}
alt="thumbnail"
className="max-w-[700px] object-cover rounded-md"
/>
</>
) : (
<Button
onClick={handleButtonClick}
isLoading={isLoadingImage}
className="w-44 bg-amber-50 hover:bg-amber-100 hover:border-2 text-amber-800 rounded-md font-semibold font-inter border border-amber-100"
loadingSpinnerColor="amber-800"
>
<UploadCloudIcon className="h-5 w-5 mr-2" />
Upload an Image
</Button>
)}
</div>
) : (
<CreatableSelect
value={values?.tags?.map((tag) => ({
label: tag,
value: tag,
}))}
isMulti
options={tags}
onChange={(value) => {
setFieldValue(
'tags',
value.map((v) => v.value)
);
}}
/>
)}
</div>
);
}
)}
</div>
</Form>
</FormikProvider>
</div>
</div>
</>
);
}
const inputs: InputRequiredFields<CEMarketCourseI>[] = [
{
required: true,
name: 'title',
placeholder: 'Enter the course title',
type: 'text',
id: 'title',
label: 'Title',
autoFocus: true,
},
{
required: true,
name: 'description',
placeholder: 'Enter the course description',
type: 'text',
id: 'description',
label: 'Description',
},
{
required: true,
name: 'price',
placeholder: 'Enter the course price',
type: 'number',
id: 'price',
label: 'Price',
},
{
required: false,
name: 'tags',
placeholder: 'Enter the course tags',
type: 'multiSelect',
id: 'tags',
label: 'Tags',
},
{
required: false,
name: 'videoThumbnail',
placeholder: 'Enter youtube link',
type: 'text',
id: 'videoThumbnail',
label: 'Video Thumbnail',
},
{
required: true,
name: 'totalHours',
placeholder: 'Enter the course total hours',
type: 'number',
id: 'totalHours',
label: 'Total Hours',
},
{
required: true,
name: 'chaptersCount',
placeholder: 'Enter the course chapters count',
type: 'number',
id: 'chaptersCount',
label: 'Chapters Count',
},
{
required: true,
name: 'thumbnail',
placeholder: 'Upload a thumbnail',
type: 'image',
id: 'thumbnail',
label: 'Thumbnail',
},
];
export default CreateCourseByInst;