import { useCourse } from '@components/Contexts/CourseContext' import { useOrg } from '@components/Contexts/OrgContext' import { getAPIUrl } from '@services/config/config' import { updateCourseThumbnail } from '@services/courses/courses' import { getCourseThumbnailMediaDirectory } from '@services/media/media' import { ArrowBigUpDash, UploadCloud, Image as ImageIcon } from 'lucide-react' import { useLHSession } from '@components/Contexts/LHSessionContext' import React, { useState, useEffect } from 'react' import { mutate } from 'swr' import UnsplashImagePicker from './UnsplashImagePicker' const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const VALID_MIME_TYPES = ['image/jpeg', 'image/jpg', 'image/png'] as const; type ValidMimeType = typeof VALID_MIME_TYPES[number]; function ThumbnailUpdate() { const course = useCourse() as any const session = useLHSession() as any; const org = useOrg() as any const [localThumbnail, setLocalThumbnail] = useState<{ file: File; url: string } | null>(null) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState('') const [showUnsplashPicker, setShowUnsplashPicker] = useState(false) const withUnpublishedActivities = course ? course.withUnpublishedActivities : false // Cleanup blob URLs when component unmounts or when thumbnail changes useEffect(() => { return () => { if (localThumbnail?.url) { URL.revokeObjectURL(localThumbnail.url); } }; }, [localThumbnail]); const validateFile = (file: File): boolean => { if (!VALID_MIME_TYPES.includes(file.type as ValidMimeType)) { setError('Please upload only PNG or JPG/JPEG images'); return false; } if (file.size > MAX_FILE_SIZE) { setError('File size should be less than 5MB'); return false; } return true; } const handleFileChange = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; if (!validateFile(file)) { event.target.value = ''; return; } const blobUrl = URL.createObjectURL(file); setLocalThumbnail({ file, url: blobUrl }); await updateThumbnail(file); } const handleUnsplashSelect = async (imageUrl: string) => { try { setIsLoading(true); const response = await fetch(imageUrl); const blob = await response.blob(); if (!VALID_MIME_TYPES.includes(blob.type as ValidMimeType)) { throw new Error('Invalid image format from Unsplash'); } const file = new File([blob], `unsplash_${Date.now()}.jpg`, { type: blob.type }); if (!validateFile(file)) { return; } const blobUrl = URL.createObjectURL(file); setLocalThumbnail({ file, url: blobUrl }); await updateThumbnail(file); } catch (err) { setError('Failed to process Unsplash image'); setIsLoading(false); } } const updateThumbnail = async (file: File) => { setIsLoading(true); try { const res = await updateCourseThumbnail( course.courseStructure.course_uuid, file, session.data?.tokens?.access_token ); await mutate(`${getAPIUrl()}courses/${course.courseStructure.course_uuid}/meta?with_unpublished_activities=${withUnpublishedActivities}`); await new Promise((r) => setTimeout(r, 1500)); if (res.success === false) { setError(res.HTTPmessage); } else { setError(''); } } catch (err) { setError('Failed to update thumbnail'); } finally { setIsLoading(false); } } return (
{error && (
{error}
)}
{localThumbnail ? ( Course thumbnail ) : ( Course thumbnail )} {!isLoading && (
)}
{isLoading && (
Uploading...
)}

Supported formats: PNG, JPG/JPEG

{showUnsplashPicker && ( setShowUnsplashPicker(false)} /> )}
) } export default ThumbnailUpdate