diff --git a/apps/web/components/Dashboard/Course/EditCourseGeneral/UnsplashImagePicker.tsx b/apps/web/components/Dashboard/Course/EditCourseGeneral/UnsplashImagePicker.tsx index 662974af..b288c507 100644 --- a/apps/web/components/Dashboard/Course/EditCourseGeneral/UnsplashImagePicker.tsx +++ b/apps/web/components/Dashboard/Course/EditCourseGeneral/UnsplashImagePicker.tsx @@ -3,6 +3,7 @@ import { createApi } from 'unsplash-js'; import { Search, X, Cpu, Briefcase, GraduationCap, Heart, Palette, Plane, Utensils, Dumbbell, Music, Shirt, Book, Building, Bike, Camera, Microscope, Coins, Coffee, Gamepad, Flower} from 'lucide-react'; +import Modal from '@components/StyledElements/Modal/Modal'; const unsplash = createApi({ accessKey: process.env.NEXT_PUBLIC_UNSPLASH_ACCESS_KEY as string, @@ -36,9 +37,10 @@ const predefinedLabels = [ interface UnsplashImagePickerProps { onSelect: (imageUrl: string) => void; onClose: () => void; + isOpen?: boolean; } -const UnsplashImagePicker: React.FC = ({ onSelect, onClose }) => { +const UnsplashImagePicker: React.FC = ({ onSelect, onClose, isOpen = true }) => { const [query, setQuery] = useState(''); const [images, setImages] = useState([]); const [page, setPage] = useState(1); @@ -54,8 +56,6 @@ const UnsplashImagePicker: React.FC = ({ onSelect, onC }); if (result && result.response) { setImages(prevImages => pageNum === 1 ? result.response.results : [...prevImages, ...result.response.results]); - } else { - console.error('Unexpected response structure:', result); } } catch (error) { console.error('Error fetching images:', error); @@ -97,16 +97,10 @@ const UnsplashImagePicker: React.FC = ({ onSelect, onC onClose(); }; - return ( -
-
-
-

Choose an image from Unsplash

- -
-
+ const modalContent = ( +
+
+
= ({ onSelect, onC />
-
+
{predefinedLabels.map(label => ( ))}
+
+ +
{images.map(image => (
@@ -135,7 +132,7 @@ const UnsplashImagePicker: React.FC = ({ onSelect, onC src={image.urls.small} alt={image.alt_description} className="absolute inset-0 w-full h-full object-cover rounded-lg cursor-pointer hover:opacity-80 transition-opacity" - onClick={() => handleImageSelect(image.urls.full)} + onClick={() => handleImageSelect(image.urls.regular)} />
))} @@ -144,7 +141,7 @@ const UnsplashImagePicker: React.FC = ({ onSelect, onC {!loading && images.length > 0 && ( @@ -152,6 +149,18 @@ const UnsplashImagePicker: React.FC = ({ onSelect, onC
); + + return ( + + ); }; // Custom debounce function diff --git a/apps/web/components/Objects/Modals/Course/Create/CreateCourse.tsx b/apps/web/components/Objects/Modals/Course/Create/CreateCourse.tsx index f3246ee4..0d388689 100644 --- a/apps/web/components/Objects/Modals/Course/Create/CreateCourse.tsx +++ b/apps/web/components/Objects/Modals/Course/Create/CreateCourse.tsx @@ -1,189 +1,264 @@ 'use client' +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Label } from "@/components/ui/label" import FormLayout, { - ButtonBlack, - Flex, FormField, - FormLabel, - Input, - Textarea, + FormLabelAndMessage, } from '@components/StyledElements/Form/Form' import * as Form from '@radix-ui/react-form' -import { FormMessage } from '@radix-ui/react-form' import { createNewCourse } from '@services/courses/courses' import { getOrganizationContextInfoWithoutCredentials } from '@services/organizations/orgs' -import React, { useState } from 'react' +import React, { useEffect } from 'react' import { BarLoader } from 'react-spinners' import { revalidateTags } from '@services/utils/ts/requests' import { useRouter } from 'next/navigation' import { useLHSession } from '@components/Contexts/LHSessionContext' import toast from 'react-hot-toast' +import { useFormik } from 'formik' +import * as Yup from 'yup' +import { getCourseThumbnailMediaDirectory } from '@services/media/media' +import { ArrowBigUpDash, UploadCloud, Image as ImageIcon } from 'lucide-react' +import UnsplashImagePicker from "@components/Dashboard/Course/EditCourseGeneral/UnsplashImagePicker" + +const validationSchema = Yup.object().shape({ + name: Yup.string() + .required('Course name is required') + .max(100, 'Must be 100 characters or less'), + description: Yup.string() + .max(1000, 'Must be 1000 characters or less'), + learnings: Yup.string(), + tags: Yup.string(), + visibility: Yup.boolean(), + thumbnail: Yup.mixed().nullable() +}) function CreateCourseModal({ closeModal, orgslug }: any) { - const [isSubmitting, setIsSubmitting] = useState(false) - const session = useLHSession() as any; - const [name, setName] = React.useState('') - const [description, setDescription] = React.useState('') - const [learnings, setLearnings] = React.useState('') - const [visibility, setVisibility] = React.useState(true) - const [tags, setTags] = React.useState('') - const [isLoading, setIsLoading] = React.useState(false) - const [thumbnail, setThumbnail] = React.useState(null) as any const router = useRouter() - + const session = useLHSession() as any const [orgId, setOrgId] = React.useState(null) as any - const [org, setOrg] = React.useState(null) as any + const [showUnsplashPicker, setShowUnsplashPicker] = React.useState(false) + const [isUploading, setIsUploading] = React.useState(false) + + const formik = useFormik({ + initialValues: { + name: '', + description: '', + learnings: '', + visibility: true, + tags: '', + thumbnail: null + }, + validationSchema, + onSubmit: async (values, { setSubmitting }) => { + const toast_loading = toast.loading('Creating course...') + + try { + const res = await createNewCourse( + orgId, + { + name: values.name, + description: values.description, + tags: values.tags, + visibility: values.visibility + }, + values.thumbnail, + session.data?.tokens?.access_token + ) + + if (res.success) { + await revalidateTags(['courses'], orgslug) + toast.dismiss(toast_loading) + toast.success('Course created successfully') + + if (res.data.org_id === orgId) { + closeModal() + router.refresh() + await revalidateTags(['courses'], orgslug) + } + } else { + toast.error(res.data.detail) + } + } catch (error) { + toast.error('Failed to create course') + } finally { + setSubmitting(false) + } + } + }) const getOrgMetadata = async () => { const org = await getOrganizationContextInfoWithoutCredentials(orgslug, { revalidate: 360, tags: ['organizations'], }) - setOrgId(org.id) } - const handleNameChange = (event: React.ChangeEvent) => { - setName(event.target.value) - } - - const handleDescriptionChange = (event: React.ChangeEvent) => { - setDescription(event.target.value) - } - - const handleLearningsChange = (event: React.ChangeEvent) => { - setLearnings(event.target.value) - } - - const handleVisibilityChange = (event: React.ChangeEvent) => { - setVisibility(event.target.value) - } - - const handleTagsChange = (event: React.ChangeEvent) => { - setTags(event.target.value) - } - - const handleThumbnailChange = (event: React.ChangeEvent) => { - setThumbnail(event.target.files[0]) - } - - const handleSubmit = async (e: any) => { - e.preventDefault() - setIsSubmitting(true) - - let res = await createNewCourse( - orgId, - { name, description, tags, visibility }, - thumbnail, - session.data?.tokens?.access_token - ) - const toast_loading = toast.loading('Creating course...') - if (res.success) { - await revalidateTags(['courses'], orgslug) - setIsSubmitting(false) - toast.dismiss(toast_loading) - toast.success('Course created successfully') - - if (res.data.org_id == orgId) { - closeModal() - router.refresh() - await revalidateTags(['courses'], orgslug) - } - - } - else { - setIsSubmitting(false) - toast.error(res.data.detail) - } - } - - React.useEffect(() => { + useEffect(() => { if (orgslug) { getOrgMetadata() } - }, [isLoading, orgslug]) + }, [orgslug]) + + const handleFileChange = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0] + if (file) { + formik.setFieldValue('thumbnail', file) + } + } + + const handleUnsplashSelect = async (imageUrl: string) => { + setIsUploading(true) + try { + const response = await fetch(imageUrl) + const blob = await response.blob() + const file = new File([blob], 'unsplash_image.jpg', { type: 'image/jpeg' }) + formik.setFieldValue('thumbnail', file) + } catch (error) { + toast.error('Failed to load image from Unsplash') + } + setIsUploading(false) + } return ( - - - - Course name - - Please provide a course name - - + + + - - - - - - Course description - - Please provide a course description - - - -