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, Video } from 'lucide-react' import { useLHSession } from '@components/Contexts/LHSessionContext' import React, { useState, useEffect, useRef } from 'react' import { mutate } from 'swr' import UnsplashImagePicker from './UnsplashImagePicker' import toast from 'react-hot-toast' const MAX_FILE_SIZE = 8_000_000; // 8MB for images const MAX_VIDEO_FILE_SIZE = 100_000_000; // 100MB for videos const VALID_IMAGE_MIME_TYPES = ['image/jpeg', 'image/jpg', 'image/png'] as const; const VALID_VIDEO_MIME_TYPES = ['video/mp4', 'video/webm'] as const; type ValidImageMimeType = typeof VALID_IMAGE_MIME_TYPES[number]; type ValidVideoMimeType = typeof VALID_VIDEO_MIME_TYPES[number]; type ThumbnailUpdateProps = { thumbnailType: 'image' | 'video' | 'both'; } type TabType = 'image' | 'video'; function ThumbnailUpdate({ thumbnailType }: ThumbnailUpdateProps) { const imageInputRef = useRef(null); const videoInputRef = useRef(null); const course = useCourse() as any const session = useLHSession() as any; const org = useOrg() as any const [localThumbnail, setLocalThumbnail] = useState<{ file: File; url: string; type: 'image' | 'video' } | null>(null) const [isLoading, setIsLoading] = useState(false) const [showUnsplashPicker, setShowUnsplashPicker] = useState(false) const [activeTab, setActiveTab] = useState('image') const withUnpublishedActivities = course ? course.withUnpublishedActivities : false // Set initial active tab based on thumbnailType useEffect(() => { if (thumbnailType === 'video') { setActiveTab('video'); } else { setActiveTab('image'); } }, [thumbnailType]); // Cleanup blob URLs when component unmounts or when thumbnail changes useEffect(() => { return () => { if (localThumbnail?.url) { URL.revokeObjectURL(localThumbnail.url); } }; }, [localThumbnail]); const showError = (message: string) => { toast.error(message, { duration: 3000, position: 'top-center', }); }; const validateFile = (file: File, type: 'image' | 'video'): boolean => { if (type === 'image') { if (!VALID_IMAGE_MIME_TYPES.includes(file.type as ValidImageMimeType)) { showError(`Invalid file type: ${file.type}. Please upload only PNG or JPG/JPEG images`); return false; } if (file.size > MAX_FILE_SIZE) { showError(`File size (${(file.size / 1024 / 1024).toFixed(2)}MB) exceeds the 8MB limit`); return false; } } else { if (!VALID_VIDEO_MIME_TYPES.includes(file.type as ValidVideoMimeType)) { showError(`Invalid file type: ${file.type}. Please upload only MP4 or WebM videos`); return false; } if (file.size > MAX_VIDEO_FILE_SIZE) { showError(`File size (${(file.size / 1024 / 1024).toFixed(2)}MB) exceeds the 100MB limit`); return false; } } return true; } const handleFileChange = async (event: React.ChangeEvent, type: 'image' | 'video') => { const file = event.target.files?.[0]; if (!file) { showError('Please select a file'); return; } if (!validateFile(file, type)) { event.target.value = ''; return; } const blobUrl = URL.createObjectURL(file); setLocalThumbnail({ file, url: blobUrl, type }); await updateThumbnail(file, type); } const handleUnsplashSelect = async (imageUrl: string) => { try { setIsLoading(true); const response = await fetch(imageUrl); const blob = await response.blob(); if (!VALID_IMAGE_MIME_TYPES.includes(blob.type as ValidImageMimeType)) { throw new Error('Invalid image format from Unsplash'); } const file = new File([blob], `unsplash_${Date.now()}.jpg`, { type: blob.type }); if (!validateFile(file, 'image')) { return; } const blobUrl = URL.createObjectURL(file); setLocalThumbnail({ file, url: blobUrl, type: 'image' }); await updateThumbnail(file, 'image'); } catch (err) { showError('Failed to process Unsplash image'); setIsLoading(false); } } const updateThumbnail = async (file: File, type: 'image' | 'video') => { setIsLoading(true); try { const formData = new FormData(); formData.append('thumbnail', file); formData.append('thumbnail_type', type); const res = await updateCourseThumbnail( course.courseStructure.course_uuid, formData, 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) { showError(res.HTTPmessage); } else { setLocalThumbnail(null); toast.success('Thumbnail updated successfully', { duration: 3000, position: 'top-center', }); } } catch (err) { showError('Failed to update thumbnail'); } finally { setIsLoading(false); } } const getThumbnailUrl = (type: 'image' | 'video') => { if (type === 'image') { return course.courseStructure.thumbnail_image ? getCourseThumbnailMediaDirectory( org?.org_uuid, course.courseStructure.course_uuid, course.courseStructure.thumbnail_image ) : '/empty_thumbnail.png'; } else { return course.courseStructure.thumbnail_video ? getCourseThumbnailMediaDirectory( org?.org_uuid, course.courseStructure.course_uuid, course.courseStructure.thumbnail_video ) : undefined; } }; const renderThumbnailPreview = () => { if (localThumbnail) { if (localThumbnail.type === 'video') { return (
); } else { return (
Course thumbnail preview
); } } const currentThumbnailUrl = getThumbnailUrl(activeTab); if (activeTab === 'video' && currentThumbnailUrl) { return (
); } else if (currentThumbnailUrl) { return (
Current course thumbnail
); } return null; }; const renderTabContent = () => { if (isLoading) { return (
Uploading...
); } if (activeTab === 'image') { return (
handleFileChange(e, 'image')} />
); } return (
handleFileChange(e, 'video')} />
); }; return (
{/* Tabs Navigation */} {thumbnailType === 'both' && (
)}
{renderThumbnailPreview()} {renderTabContent()}

{activeTab === 'image' && 'Supported formats: PNG, JPG/JPEG (max 8MB)'} {activeTab === 'video' && 'Supported formats: MP4, WebM (max 100MB)'}

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