placed course lessons in accordian, created course sidebar that is in sync with open course, added link to view not on nostr on lesson templates and coure details

This commit is contained in:
austinkelsay 2024-09-14 16:43:03 -05:00
parent b27384c8a2
commit 3b077b542a
7 changed files with 301 additions and 95 deletions

View File

@ -2,19 +2,21 @@ import { useState, useEffect } from "react";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Tag } from "primereact/tag";
import ZapDisplay from "@/components/zaps/ZapDisplay";
import { BookOpen } from "lucide-react"
import { nip19 } from "nostr-tools";
import Image from "next/image"
import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscription";
import { getTotalFromZaps } from "@/utils/lightning";
import { useImageProxy } from "@/hooks/useImageProxy";
import { useRouter } from "next/router";
import { formatTimestampToHowLongAgo } from "@/utils/time";
import { ProgressSpinner } from "primereact/progressspinner";
import GenericButton from "@/components/buttons/GenericButton";
export function CourseTemplate({ course }) {
const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: course });
const [zapAmount, setZapAmount] = useState(0);
const [lessonCount, setLessonCount] = useState(0);
const [nAddress, setNAddress] = useState(null);
const router = useRouter();
const { returnImageProxy } = useImageProxy();
@ -32,6 +34,19 @@ export function CourseTemplate({ course }) {
}
}, [course]);
useEffect(() => {
if (course && course?.id) {
const nAddress = nip19.naddrEncode({
pubkey: course.pubkey,
kind: course.kind,
identifier: course.id,
});
setNAddress(nAddress);
}
}, [course]);
if (!nAddress) return <ProgressSpinner />;
if (zapsError) return <div>Error: {zapsError}</div>;
return (
@ -82,7 +97,7 @@ export function CourseTemplate({ course }) {
) : (
formatTimestampToHowLongAgo(course.created_at)
)}</p>
<GenericButton onClick={() => router.push(`/course/${course.id}`)} size="small" label="Start Learning" icon="pi pi-chevron-right" iconPos="right" outlined className="items-center py-2" />
<GenericButton onClick={() => router.push(`/course/${nAddress}`)} size="small" label="Start Learning" icon="pi pi-chevron-right" iconPos="right" outlined className="items-center py-2" />
</CardFooter>
</Card>
)

View File

@ -7,12 +7,12 @@ import { useRouter } from 'next/router';
import CoursePaymentButton from "@/components/bitcoinConnect/CoursePaymentButton";
import ZapDisplay from '@/components/zaps/ZapDisplay';
import GenericButton from '@/components/buttons/GenericButton';
import { nip19 } from 'nostr-tools';
import { useImageProxy } from '@/hooks/useImageProxy';
import { useZapsSubscription } from '@/hooks/nostrQueries/zaps/useZapsSubscription';
import { getTotalFromZaps } from '@/utils/lightning';
import { useSession } from 'next-auth/react';
import useWindowWidth from "@/hooks/useWindowWidth";
import dynamic from 'next/dynamic';
import { useNDKContext } from "@/context/NDKContext";
import { findKind0Fields } from '@/utils/nostr';
@ -21,6 +21,7 @@ const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS;
export default function CourseDetailsNew({ processedEvent, paidCourse, lessons, decryptionPerformed, handlePaymentSuccess, handlePaymentError }) {
const [zapAmount, setZapAmount] = useState(0);
const [author, setAuthor] = useState(null);
const [nAddress, setNAddress] = useState(null);
const router = useRouter();
const { returnImageProxy } = useImageProxy();
const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: processedEvent });
@ -39,6 +40,17 @@ export default function CourseDetailsNew({ processedEvent, paidCourse, lessons,
}
}, [ndk]);
useEffect(() => {
if (processedEvent) {
const naddr = nip19.naddrEncode({
pubkey: processedEvent.pubkey,
kind: processedEvent.kind,
identifier: processedEvent.d,
});
setNAddress(naddr);
}
}, [processedEvent]);
useEffect(() => {
if (processedEvent) {
fetchAuthor(processedEvent.pubkey);
@ -151,6 +163,11 @@ export default function CourseDetailsNew({ processedEvent, paidCourse, lessons,
<GenericButton onClick={handleDelete} label="Delete" severity='danger' outlined />
</div>
)}
{nAddress && (
<div className='w-full flex flex-row justify-end'>
<GenericButton outlined icon="pi pi-external-link" onClick={() => window.open(`https://nostr.band/${nAddress}`, '_blank')} tooltip="View Nostr Event" tooltipOptions={{ position: 'left' }} />
</div>
)}
</div>
</div>
</div>

View File

@ -4,6 +4,9 @@ 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";
@ -16,6 +19,7 @@ const MDDisplay = dynamic(
const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {
const [zapAmount, setZapAmount] = useState(0);
const [nAddress, setNAddress] = useState(null);
const { zaps, zapsLoading, zapsError } = useZapsQuery({ event: lesson, type: "lesson" });
const { returnImageProxy } = useImageProxy();
@ -25,6 +29,17 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {
setZapAmount(total);
}, [zaps, zapsLoading, zapsError, lesson]);
useEffect(() => {
if (lesson) {
const addr = nip19.naddrEncode({
pubkey: lesson.pubkey,
kind: lesson.kind,
identifier: lesson.d,
})
setNAddress(addr);
}
}, [lesson]);
const renderContent = () => {
if (isPaid && decryptionPerformed) {
return <MDDisplay className='p-4 rounded-lg w-full' source={lesson.content} />;
@ -93,7 +108,19 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {
zapsLoading={zapsLoading}
/>
</div>
<div className="w-full flex flex-row justify-end">
<GenericButton
tooltip={`View Nostr Note`}
tooltipOptions={{ position: 'left' }}
icon="pi pi-external-link"
outlined
onClick={() => {
window.open(`https://nostr.com/${nAddress}`, '_blank');
}}
/>
</div>
</div>
<Divider />
</div>
{renderContent()}
{lesson?.additionalLinks && lesson.additionalLinks.length > 0 && (

View File

@ -3,9 +3,12 @@ import { Tag } from "primereact/tag";
import Image from "next/image";
import ZapDisplay from "@/components/zaps/ZapDisplay";
import { useImageProxy } from "@/hooks/useImageProxy";
import GenericButton from "@/components/buttons/GenericButton";
import { useZapsQuery } from "@/hooks/nostrQueries/zaps/useZapsQuery";
import { nip19 } from "nostr-tools";
import { getTotalFromZaps } from "@/utils/lightning";
import dynamic from "next/dynamic";
import { Divider } from "primereact/divider";
const MDDisplay = dynamic(
() => import("@uiw/react-markdown-preview"),
@ -16,6 +19,7 @@ const MDDisplay = dynamic(
const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {
const [zapAmount, setZapAmount] = useState(0);
const [nAddress, setNAddress] = useState(null);
const { zaps, zapsLoading, zapsError } = useZapsQuery({ event: lesson, type: "lesson" });
const { returnImageProxy } = useImageProxy();
@ -25,6 +29,15 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {
setZapAmount(total);
}, [zaps, zapsLoading, zapsError, lesson]);
useEffect(() => {
const addr = nip19.naddrEncode({
pubkey: lesson.pubkey,
kind: lesson.kind,
identifier: lesson.d,
});
setNAddress(addr);
}, [lesson]);
const renderContent = () => {
if (isPaid && decryptionPerformed) {
return (
@ -70,6 +83,7 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {
return (
<div className="w-full">
{renderContent()}
<Divider />
<div className="bg-gray-800/90 rounded-lg p-4 m-4">
<div className="w-full flex flex-col items-start justify-start mt-2 px-2">
<div className="flex flex-row items-center justify-between w-full">
@ -106,6 +120,15 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid }) => {
</a>
</p>
</div>
<GenericButton
tooltip={`View Nostr Note`}
tooltipOptions={{ position: 'left' }}
icon="pi pi-external-link"
outlined
onClick={() => {
window.open(`https://nostr.com/${nAddress}`, '_blank');
}}
/>
</div>
</div>
{lesson?.additionalLinks && lesson.additionalLinks.length > 0 && (

View File

@ -1,16 +1,22 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import { Accordion, AccordionTab } from 'primereact/accordion';
import { useRouter } from 'next/router';
import { useSession, signOut } from 'next-auth/react';
import { useIsAdmin } from '@/hooks/useIsAdmin';
import { nip19 } from 'nostr-tools';
import { useToast } from '@/hooks/useToast';
import { useNDKContext } from '@/context/NDKContext';
import 'primeicons/primeicons.css';
import styles from "./sidebar.module.css";
import { Divider } from 'primereact/divider';
const Sidebar = () => {
const Sidebar = ({ course = false }) => {
const [isExpanded, setIsExpanded] = useState(true);
const { isAdmin } = useIsAdmin();
const [lessons, setLessons] = useState([]);
const router = useRouter();
const { showToast } = useToast();
const { ndk, addSigner } = useNDKContext();
// Helper function to determine if the path matches the current route
const isActive = (path) => {
@ -30,10 +36,84 @@ const Sidebar = () => {
setIsExpanded(!isExpanded);
};
useEffect(() => {
if (router.isReady) {
const { slug } = router.query;
if (slug) {
const { data } = nip19.decode(slug)
if (!data) {
showToast('error', 'Error', 'Course not found');
return;
}
const id = data?.identifier;
const fetchCourse = async (id) => {
try {
await ndk.connect();
const filter = {
ids: [id]
}
const event = await ndk.fetchEvent(filter);
if (event) {
// all a tags are lessons
const lessons = event.tags.filter(tag => tag[0] === 'a');
const uniqueLessons = [...new Set(lessons.map(lesson => lesson[1]))];
setLessons(uniqueLessons);
}
} catch (error) {
console.error('Error fetching event:', error);
}
};
if (ndk && id) {
fetchCourse(id);
}
}
}
}, [router.isReady, router.query, ndk, course]);
const scrollToLesson = useCallback((index) => {
const lessonElement = document.getElementById(`lesson-${index}`);
if (lessonElement) {
lessonElement.scrollIntoView({ behavior: 'smooth' });
}
}, []);
useEffect(() => {
if (router.isReady && router.query.active) {
const activeIndex = parseInt(router.query.active);
scrollToLesson(activeIndex);
}
}, [router.isReady, router.query.active, scrollToLesson]);
return (
<div className={`max-sidebar:hidden bg-gray-800 p-2 fixed h-[100%] flex flex-col transition-all duration-300 ease-in-out ${isExpanded ? 'w-[14vw]' : 'w-[50px]'}`}>
<div className="flex-grow overflow-y-auto">
{isExpanded ? (
{course && lessons.length > 0 && (
<div className="flex-grow overflow-y-auto">
<div onClick={() => router.push('/')} className={"w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg"}>
<i className="pi pi-arrow-left pl-5" /> <p className="pl-2 rounded-md font-bold text-lg">Home</p>
</div>
{lessons.map((lesson, index) => (
console.log(lesson),
<div
key={lesson}
onClick={() => {
router.push(`/course/${router?.query?.slug}?active=${index}`, undefined, { shallow: true });
scrollToLesson(index);
}}
className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive(`/course/${router?.query?.slug}?active=${index}`) ? 'bg-gray-700' : ''}`}
>
<i className="pi pi-lightbulb text-sm pl-5" /> <p className="pl-2 rounded-md font-bold text-lg">Lesson {index + 1}</p>
</div>
))}
</div>
)}
{isExpanded && !course ? (
<div className="flex-grow overflow-y-auto">
<div onClick={() => router.push('/')} className={`w-full flex flex-row items-center cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/') ? 'bg-gray-700' : ''}`}>
<i className="pi pi-home pl-5" /> <p className="pl-2 rounded-md font-bold text-lg">Home</p>
@ -97,13 +177,15 @@ const Sidebar = () => {
</div>
) : (
// Collapsed sidebar content (icons only)
<div className="flex flex-col items-center">
<i className="pi pi-home my-4 cursor-pointer" onClick={() => router.push('/')} />
<i className="pi pi-list my-4 cursor-pointer" onClick={() => router.push('/content')} />
<i className="pi pi-plus my-4 cursor-pointer" onClick={() => router.push('/create')} />
<i className="pi pi-star my-4 cursor-pointer" onClick={() => session ? router.push('/profile?tab=subscribe') : router.push('/auth/signin')} />
<i className="pi pi-users my-4 cursor-pointer" onClick={() => router.push('/feed')} />
</div>
!course && (
<div className="flex flex-col items-center">
<i className="pi pi-home my-4 cursor-pointer" onClick={() => router.push('/')} />
<i className="pi pi-list my-4 cursor-pointer" onClick={() => router.push('/content')} />
<i className="pi pi-plus my-4 cursor-pointer" onClick={() => router.push('/create')} />
<i className="pi pi-star my-4 cursor-pointer" onClick={() => session ? router.push('/profile?tab=subscribe') : router.push('/auth/signin')} />
<i className="pi pi-users my-4 cursor-pointer" onClick={() => router.push('/feed')} />
</div>
)
)}
</div>
<Divider className='pt-0 mt-0' />

View File

@ -12,6 +12,7 @@ import 'primeicons/primeicons.css';
import "@uiw/react-md-editor/markdown-editor.css";
import "@uiw/react-markdown-preview/markdown.css";
import Sidebar from '@/components/sidebar/Sidebar';
import { useRouter } from 'next/router';
import { NDKProvider } from '@/context/NDKContext';
import {
QueryClient,
@ -24,6 +25,13 @@ const queryClient = new QueryClient()
export default function MyApp({
Component, pageProps: { session, ...pageProps }
}) {
const [isCourseView, setIsCourseView] = useState(false);
const router = useRouter();
useEffect(() => {
setIsCourseView(router.pathname.includes('course'));
}, [router.pathname]);
// const [sidebarExpanded, setSidebarExpanded] = useState(true);
// useEffect(() => {
@ -48,7 +56,7 @@ export default function MyApp({
<div className="flex flex-col min-h-screen">
<Navbar />
<div className='flex'>
<Sidebar />
<Sidebar course={isCourseView} />
<div className='w-[100vw] pl-[14vw] max-sidebar:pl-0 pb-16 max-sidebar:pb-20'>
<Component {...pageProps} />
</div>

View File

@ -1,127 +1,96 @@
import React, { useEffect, useState, useCallback } from "react";
import { useRouter } from "next/router";
import { parseCourseEvent, parseEvent, findKind0Fields } from "@/utils/nostr";
import CourseDetails from "@/components/content/courses/CourseDetails";
import CourseDetailsNew from "@/components/content/courses/CourseDetailsNew";
import VideoLesson from "@/components/content/courses/VideoLesson";
import DocumentLesson from "@/components/content/courses/DocumentLesson";
import CourseDetailsNew from "@/components/content/courses/CourseDetailsNew";
import { Divider } from "primereact/divider";
import dynamic from 'next/dynamic';
import { useNDKContext } from "@/context/NDKContext";
import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react';
import { nip04 } from 'nostr-tools';
import { nip04, nip19 } from 'nostr-tools';
import { ProgressSpinner } from 'primereact/progressspinner';
import { Accordion, AccordionTab } from 'primereact/accordion';
import dynamic from 'next/dynamic';
const MDDisplay = dynamic(
() => import("@uiw/react-markdown-preview"),
{
ssr: false,
}
);
const MDDisplay = dynamic(() => import("@uiw/react-markdown-preview"), { ssr: false });
const Course = () => {
const useCourseData = (ndk, fetchAuthor, router) => {
const [course, setCourse] = useState(null);
const [lessonIds, setLessonIds] = useState([]);
const [lessons, setLessons] = useState([]);
const [paidCourse, setPaidCourse] = useState(false);
const [decryptionPerformed, setDecryptionPerformed] = useState(false);
const [loading, setLoading] = useState(true);
const router = useRouter();
const {ndk, addSigner} = useNDKContext();
const { data: session, update } = useSession();
const { showToast } = useToast();
const privkey = process.env.NEXT_PUBLIC_APP_PRIV_KEY;
const pubkey = process.env.NEXT_PUBLIC_APP_PUBLIC_KEY;
const fetchAuthor = useCallback(async (pubkey) => {
const author = await ndk.getUser({ pubkey });
const profile = await author.fetchProfile();
const fields = await findKind0Fields(profile);
if (fields) {
return fields;
}
}, [ndk]);
useEffect(() => {
if (router.isReady) {
const { slug } = router.query;
const fetchCourse = async (slug) => {
const { data } = nip19.decode(slug);
if (!data) {
showToast('error', 'Error', 'Course not found');
return;
}
const id = data?.identifier;
const fetchCourse = async (id) => {
try {
await ndk.connect();
const filter = {
ids: [slug]
}
const filter = { ids: [id] };
const event = await ndk.fetchEvent(filter);
if (event) {
const author = await fetchAuthor(event.pubkey);
const aTags = event.tags.filter(tag => tag[0] === 'a');
const lessonIds = aTags.map(tag => tag[1].split(':')[2]);
setLessonIds(lessonIds);
const parsedCourse = {
...parseCourseEvent(event),
author
};
const parsedCourse = { ...parseCourseEvent(event), author };
setCourse(parsedCourse);
}
} catch (error) {
console.error('Error fetching event:', error);
}
};
if (ndk) {
fetchCourse(slug);
if (ndk && id) {
fetchCourse(id);
}
}
}, [router.isReady, router.query, ndk, fetchAuthor]);
return { course, lessonIds };
};
const useLessons = (ndk, fetchAuthor, lessonIds) => {
const [lessons, setLessons] = useState([]);
const [uniqueLessons, setUniqueLessons] = useState([]);
useEffect(() => {
if (lessonIds.length > 0) {
const fetchLesson = async (lessonId) => {
try {
await ndk.connect();
const filter = {
"#d": [lessonId]
}
const filter = { "#d": [lessonId] };
const event = await ndk.fetchEvent(filter);
if (event) {
const author = await fetchAuthor(event.pubkey);
const parsedLesson = {
...parseEvent(event),
author
};
const parsedLesson = { ...parseEvent(event), author };
setLessons(prev => [...prev, parsedLesson]);
}
} catch (error) {
console.error('Error fetching event:', error);
}
};
lessonIds.forEach(lessonId => fetchLesson(lessonId));
}
}, [lessonIds, ndk, fetchAuthor]);
useEffect(() => {
console.log('lessons', lessons);
const uniqueLessonSet = new Set(lessons.map(JSON.stringify));
const newUniqueLessons = Array.from(uniqueLessonSet).map(JSON.parse);
setUniqueLessons(newUniqueLessons);
}, [lessons]);
useEffect(() => {
console.log('lessonIds', lessonIds);
}, [lessonIds]);
return { lessons, uniqueLessons, setLessons };
};
useEffect(() => {
if (course?.price && course?.price > 0) {
setPaidCourse(true);
}
}, [course]);
const useDecryption = (session, paidCourse, course, lessons, setLessons) => {
const [decryptionPerformed, setDecryptionPerformed] = useState(false);
const [loading, setLoading] = useState(true);
const privkey = process.env.NEXT_PUBLIC_APP_PRIV_KEY;
const pubkey = process.env.NEXT_PUBLIC_APP_PUBLIC_KEY;
useEffect(() => {
const decryptContent = async () => {
@ -132,8 +101,6 @@ const Course = () => {
session.user?.role?.subscribed ||
session.user?.pubkey === course?.pubkey;
console.log('canAccess', canAccess);
if (canAccess && lessons.length > 0) {
try {
const decryptedLessons = await Promise.all(lessons.map(async (lesson) => {
@ -151,13 +118,57 @@ const Course = () => {
setLoading(false);
}
decryptContent();
}, [session, paidCourse, course, lessons, privkey, pubkey, decryptionPerformed]);
}, [session, paidCourse, course, lessons, privkey, pubkey, decryptionPerformed, setLessons]);
return { decryptionPerformed, loading };
};
const Course = () => {
const router = useRouter();
const { ndk, addSigner } = useNDKContext();
const { data: session, update } = useSession();
const { showToast } = useToast();
const [paidCourse, setPaidCourse] = useState(false);
const [expandedIndex, setExpandedIndex] = useState(null);
const fetchAuthor = useCallback(async (pubkey) => {
const author = await ndk.getUser({ pubkey });
const profile = await author.fetchProfile();
const fields = await findKind0Fields(profile);
return fields;
}, [ndk]);
const { course, lessonIds } = useCourseData(ndk, fetchAuthor, router);
const { lessons, uniqueLessons, setLessons } = useLessons(ndk, fetchAuthor, lessonIds);
const { decryptionPerformed, loading } = useDecryption(session, paidCourse, course, lessons, setLessons);
useEffect(() => {
if (course && lessons.length > 0 && (!paidCourse || decryptionPerformed)) {
setLoading(false);
if (course?.price && course?.price > 0) {
setPaidCourse(true);
}
}, [course, lessons, paidCourse, decryptionPerformed]);
}, [course]);
useEffect(() => {
if (router.isReady) {
const { active } = router.query;
if (active !== undefined) {
setExpandedIndex(parseInt(active, 10));
} else {
setExpandedIndex(null);
}
}
}, [router.isReady, router.query]);
const handleAccordionChange = (e) => {
const newIndex = e.index === expandedIndex ? null : e.index;
setExpandedIndex(newIndex);
if (newIndex !== null) {
router.push(`/course/${router.query.slug}?active=${newIndex}`, undefined, { shallow: true });
} else {
router.push(`/course/${router.query.slug}`, undefined, { shallow: true });
}
};
const handlePaymentSuccess = async (response) => {
if (response && response?.preimage) {
@ -187,18 +198,41 @@ const Course = () => {
<CourseDetailsNew
processedEvent={course}
paidCourse={paidCourse}
lessons={lessons}
lessons={uniqueLessons}
decryptionPerformed={decryptionPerformed}
handlePaymentSuccess={handlePaymentSuccess}
handlePaymentError={handlePaymentError}
/>
{lessons.length > 0 && lessons.map((lesson, index) => (
<div key={index} className="w-full p-4">
<h1 className="text-2xl font-bold text-white">Lesson {index + 1}</h1>
<Divider />
{lesson.type === 'workshop' ? <VideoLesson key={index} lesson={lesson} course={course} decryptionPerformed={decryptionPerformed} isPaid={paidCourse} /> : <DocumentLesson key={index} lesson={lesson} course={course} decryptionPerformed={decryptionPerformed} isPaid={paidCourse} />}
</div>
))}
<Accordion
activeIndex={expandedIndex}
onTabChange={handleAccordionChange}
className="mt-4 px-4"
>
{uniqueLessons.length > 0 && uniqueLessons.map((lesson, index) => (
<AccordionTab
key={index}
pt={{
root: { className: 'border-none' },
header: { className: 'border-none' },
headerAction: { className: 'border-none' },
content: { className: 'border-none' },
accordiontab: { className: 'border-none' },
}}
header={
<div className="flex align-items-center justify-content-between w-full">
<span id={`lesson-${index}`} className="font-bold text-xl">{`Lesson ${index + 1}: ${lesson.title}`}</span>
</div>
}
>
<div className="w-full py-4 rounded-b-lg">
{lesson.type === 'workshop' ?
<VideoLesson lesson={lesson} course={course} decryptionPerformed={decryptionPerformed} isPaid={paidCourse} /> :
<DocumentLesson lesson={lesson} course={course} decryptionPerformed={decryptionPerformed} isPaid={paidCourse} />
}
</div>
</AccordionTab>
))}
</Accordion>
<div className="mx-auto my-6">
{course?.content && <MDDisplay className='p-4 rounded-lg' source={course.content} />}
</div>