diff --git a/src/components/content/courses/CombinedLesson.js b/src/components/content/courses/CombinedLesson.js new file mode 100644 index 0000000..d3ce060 --- /dev/null +++ b/src/components/content/courses/CombinedLesson.js @@ -0,0 +1,259 @@ +import React, { useEffect, useState, useRef, useCallback } from "react"; +import { Tag } from "primereact/tag"; +import Image from "next/image"; +import ZapDisplay from "@/components/zaps/ZapDisplay"; +import { useImageProxy } from "@/hooks/useImageProxy"; +import { useZapsQuery } from "@/hooks/nostrQueries/zaps/useZapsQuery"; +import GenericButton from "@/components/buttons/GenericButton"; +import { nip19 } from "nostr-tools"; +import { Divider } from "primereact/divider"; +import { getTotalFromZaps } from "@/utils/lightning"; +import dynamic from "next/dynamic"; +import useWindowWidth from "@/hooks/useWindowWidth"; +import appConfig from "@/config/appConfig"; +import useTrackVideoLesson from '@/hooks/tracking/useTrackVideoLesson'; + +const MDDisplay = dynamic( + () => import("@uiw/react-markdown-preview"), + { + ssr: false, + } +); + +const CombinedLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted }) => { + const [zapAmount, setZapAmount] = useState(0); + const [nAddress, setNAddress] = useState(null); + const [videoDuration, setVideoDuration] = useState(null); + const [videoPlayed, setVideoPlayed] = useState(false); + const mdDisplayRef = useRef(null); + const { zaps, zapsLoading, zapsError } = useZapsQuery({ event: lesson, type: "lesson" }); + const { returnImageProxy } = useImageProxy(); + const windowWidth = useWindowWidth(); + const isMobileView = windowWidth <= 768; + const isVideo = lesson?.type === 'video'; + + const { isCompleted: videoCompleted, isTracking: videoTracking } = useTrackVideoLesson({ + lessonId: lesson?.d, + videoDuration, + courseId: course?.d, + videoPlayed, + paidCourse: isPaid, + decryptionPerformed + }); + + useEffect(() => { + const handleYouTubeMessage = (event) => { + if (event.origin !== "https://www.youtube.com") return; + + try { + const data = JSON.parse(event.data); + if (data.event === "onReady") { + event.source.postMessage('{"event":"listening"}', "https://www.youtube.com"); + } else if (data.event === "infoDelivery" && data?.info?.currentTime) { + setVideoPlayed(true); + setVideoDuration(data.info?.progressState?.duration); + } + } catch (error) { + console.error("Error parsing YouTube message:", error); + } + }; + + if (isVideo) { + window.addEventListener("message", handleYouTubeMessage); + return () => window.removeEventListener("message", handleYouTubeMessage); + } + }, [isVideo]); + + const checkDuration = useCallback(() => { + if (!isVideo) return; + + const videoElement = mdDisplayRef.current?.querySelector('video'); + const youtubeIframe = mdDisplayRef.current?.querySelector('iframe[src*="youtube.com"]'); + + if (videoElement && videoElement.readyState >= 1) { + setVideoDuration(Math.round(videoElement.duration)); + setVideoPlayed(true); + } else if (youtubeIframe) { + youtubeIframe.contentWindow.postMessage('{"event":"listening"}', '*'); + } + }, [isVideo]); + + useEffect(() => { + if (!zaps || zapsLoading || zapsError) return; + const total = getTotalFromZaps(zaps, lesson); + setZapAmount(total); + }, [zaps, zapsLoading, zapsError, lesson]); + + useEffect(() => { + if (lesson) { + const addr = nip19.naddrEncode({ + pubkey: lesson.pubkey, + kind: lesson.kind, + identifier: lesson.d, + relays: appConfig.defaultRelayUrls + }); + setNAddress(addr); + } + }, [lesson]); + + useEffect(() => { + if (decryptionPerformed && isPaid) { + const timer = setTimeout(checkDuration, 500); + return () => clearTimeout(timer); + } else { + const timer = setTimeout(checkDuration, 3000); + return () => clearTimeout(timer); + } + }, [decryptionPerformed, isPaid, checkDuration]); + + useEffect(() => { + if (isVideo && videoCompleted && !videoTracking) { + setCompleted(lesson.id); + } + }, [videoCompleted, videoTracking, lesson.id, setCompleted, isVideo]); + + const renderContent = () => { + if (isPaid && decryptionPerformed) { + return ( +
+ This content is paid and needs to be purchased before viewing. +
++ This content is paid and needs to be purchased before viewing. +
+{line}
+ ))} ++ By{' '} + + {lesson.author?.username || lesson.author?.name || lesson.author?.pubkey} + +
+