2024-08-06 15:50:19 -05:00
import React , { useEffect , useState , useCallback } from 'react' ;
2024-07-30 17:16:09 -05:00
import { useRouter } from 'next/router' ;
import { useImageProxy } from '@/hooks/useImageProxy' ;
2024-08-01 16:31:52 -05:00
import ZapDisplay from '@/components/zaps/ZapDisplay' ;
2024-08-13 14:42:36 -05:00
import { getTotalFromZaps } from '@/utils/lightning' ;
2024-07-30 17:16:09 -05:00
import { Tag } from 'primereact/tag' ;
2024-09-12 09:17:22 -05:00
import { nip19 } from 'nostr-tools' ;
2024-08-07 16:02:13 -05:00
import { useSession } from 'next-auth/react' ;
2024-09-12 09:17:22 -05:00
import GenericButton from '@/components/buttons/GenericButton' ;
2024-07-30 17:16:09 -05:00
import Image from 'next/image' ;
import dynamic from 'next/dynamic' ;
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper' ;
2024-08-06 15:50:19 -05:00
import { useNDKContext } from "@/context/NDKContext" ;
import { useZapsSubscription } from '@/hooks/nostrQueries/zaps/useZapsSubscription' ;
import { findKind0Fields } from '@/utils/nostr' ;
2024-07-30 17:16:09 -05:00
import 'primeicons/primeicons.css' ;
2024-08-17 12:56:27 -05:00
import CoursePaymentButton from "@/components/bitcoinConnect/CoursePaymentButton" ;
import { ProgressSpinner } from 'primereact/progressspinner' ;
2024-09-17 13:55:51 -05:00
import appConfig from "@/config/appConfig" ;
2024-09-15 15:53:27 -05:00
import useWindowWidth from '@/hooks/useWindowWidth' ;
2024-08-06 15:50:19 -05:00
2024-07-30 17:16:09 -05:00
const MDDisplay = dynamic (
( ) => import ( "@uiw/react-markdown-preview" ) ,
{
ssr : false ,
}
) ;
2024-08-17 12:56:27 -05:00
export default function CourseDetails ( { processedEvent , paidCourse , lessons , decryptionPerformed , handlePaymentSuccess , handlePaymentError } ) {
2024-07-30 17:16:09 -05:00
const [ author , setAuthor ] = useState ( null ) ;
2024-08-06 15:50:19 -05:00
const [ nAddress , setNAddress ] = useState ( null ) ;
2024-08-01 16:31:52 -05:00
const [ zapAmount , setZapAmount ] = useState ( 0 ) ;
2024-08-07 16:02:13 -05:00
const [ user , setUser ] = useState ( null ) ;
2024-08-06 15:50:19 -05:00
const { zaps , zapsLoading , zapsError } = useZapsSubscription ( { event : processedEvent } ) ;
const { returnImageProxy } = useImageProxy ( ) ;
2024-08-07 16:02:13 -05:00
const { data : session , status } = useSession ( ) ;
2024-07-30 17:16:09 -05:00
const router = useRouter ( ) ;
2024-08-13 16:28:25 -05:00
const { ndk , addSigner } = useNDKContext ( ) ;
2024-09-15 15:53:27 -05:00
const windowWidth = useWindowWidth ( ) ;
const isMobileView = windowWidth <= 768 ;
2024-07-30 17:16:09 -05:00
2024-08-07 16:02:13 -05:00
useEffect ( ( ) => {
if ( session ) {
setUser ( session . user ) ;
}
} , [ session ] ) ;
2024-08-06 15:50:19 -05:00
const fetchAuthor = useCallback ( async ( pubkey ) => {
2024-08-09 14:28:57 -05:00
if ( ! pubkey ) return ;
2024-08-06 15:50:19 -05:00
const author = await ndk . getUser ( { pubkey } ) ;
const profile = await author . fetchProfile ( ) ;
const fields = await findKind0Fields ( profile ) ;
if ( fields ) {
setAuthor ( fields ) ;
}
} , [ ndk ] ) ;
2024-07-30 17:16:09 -05:00
useEffect ( ( ) => {
if ( processedEvent ) {
fetchAuthor ( processedEvent . pubkey ) ;
}
2024-08-06 15:50:19 -05:00
} , [ fetchAuthor , processedEvent ] ) ;
2024-07-30 17:16:09 -05:00
useEffect ( ( ) => {
if ( processedEvent ? . d ) {
const naddr = nip19 . naddrEncode ( {
pubkey : processedEvent . pubkey ,
kind : processedEvent . kind ,
identifier : processedEvent . d ,
2024-09-17 13:55:51 -05:00
relayUrls : appConfig . defaultRelayUrls
2024-07-30 17:16:09 -05:00
} ) ;
setNAddress ( naddr ) ;
}
} , [ processedEvent ] ) ;
2024-08-01 17:10:43 -05:00
useEffect ( ( ) => {
2024-08-25 18:15:45 -05:00
if ( zaps . length > 0 ) {
const total = getTotalFromZaps ( zaps , processedEvent ) ;
setZapAmount ( total ) ;
}
2024-08-13 14:42:36 -05:00
} , [ zaps , processedEvent ] ) ;
2024-08-01 17:10:43 -05:00
2024-09-12 09:17:22 -05:00
const renderPaymentMessage = ( ) => {
if ( paidCourse && ! decryptionPerformed ) {
return (
< CoursePaymentButton
2024-09-30 14:50:10 -05:00
lnAddress = { author ? . lnAddress }
2024-09-12 09:17:22 -05:00
amount = { processedEvent . price }
onSuccess = { handlePaymentSuccess }
onError = { handlePaymentError }
courseId = { processedEvent . d }
/ >
) ;
}
const coursePurchased = session ? . user ? . purchased ? . some ( purchase =>
purchase ? . courseId === processedEvent . d
) ;
if ( paidCourse && decryptionPerformed && author && session ? . user ? . role ? . subscribed && processedEvent ? . pubkey !== session ? . user ? . pubkey ) {
return < GenericButton tooltipOptions = { { position : 'top' } } tooltip = { ` You are subscribed so you can access all paid content ` } icon = "pi pi-check" label = "Subscribed" severity = "success" outlined size = "small" className = "cursor-default hover:opacity-100 hover:bg-transparent focus:ring-0" / >
}
if ( paidCourse && author && processedEvent ? . pubkey === session ? . user ? . pubkey ) {
return < GenericButton tooltipOptions = { { position : 'top' } } tooltip = { ` You created this paid course, users must pay ${ processedEvent . price } sats to access it ` } icon = "pi pi-check" label = { ` Price ${ processedEvent . price } sats ` } severity = "success" outlined size = "small" className = "cursor-default hover:opacity-100 hover:bg-transparent focus:ring-0" / >
}
if ( paidCourse && decryptionPerformed && author && coursePurchased ) {
return < GenericButton tooltipOptions = { { position : 'top' } } tooltip = { ` You have purchased this course ` } icon = "pi pi-check" label = "Purchased" severity = "success" outlined size = "small" className = "cursor-default hover:opacity-100 hover:bg-transparent focus:ring-0" / >
}
return null ;
} ;
2024-08-17 12:56:27 -05:00
if ( ! processedEvent || ! author ) {
return (
2024-09-17 13:28:58 -05:00
< div className = 'w-full h-full flex items-center justify-center' > < ProgressSpinner / > < / d i v >
2024-08-17 12:56:27 -05:00
) ;
}
2024-07-30 17:16:09 -05:00
return (
< div className = 'w-full px-24 pt-12 mx-auto mt-4 max-tab:px-0 max-mob:px-0 max-tab:pt-2 max-mob:pt-2' >
< div className = 'w-full flex flex-row justify-between max-tab:flex-col max-mob:flex-col' >
2024-09-03 17:02:24 -05:00
< i className = 'pi pi-arrow-left pr-8 cursor-pointer hover:opacity-75' onClick = { ( ) => router . push ( '/' ) } / >
2024-07-30 17:16:09 -05:00
< div className = 'w-[75vw] mx-auto flex flex-row items-start justify-between max-tab:flex-col max-mob:flex-col max-tab:w-[95vw] max-mob:w-[95vw]' >
< div className = 'flex flex-col items-start max-w-[45vw] max-tab:max-w-[100vw] max-mob:max-w-[100vw]' >
< div className = 'pt-2 flex flex-row justify-start w-full' >
{ processedEvent && processedEvent . topics && processedEvent . topics . length > 0 && (
processedEvent . topics . map ( ( topic , index ) => (
< Tag className = 'mr-2 text-white' key = { index } value = { topic } > < / T a g >
) )
2024-08-06 15:50:19 -05:00
) }
2024-07-30 17:16:09 -05:00
< / d i v >
2024-08-25 13:41:32 -05:00
< h1 className = 'text-4xl mt-6' > { processedEvent ? . name } < / h 1 >
< p className = 'text-xl mt-6' > { processedEvent ? . description } < / p >
2024-07-30 17:16:09 -05:00
< div className = 'flex flex-row w-full mt-6 items-center' >
< Image
alt = "avatar thumbnail"
2024-08-01 16:31:52 -05:00
src = { returnImageProxy ( author ? . avatar , author ? . pubkey ) }
2024-07-30 17:16:09 -05:00
width = { 50 }
height = { 50 }
className = "rounded-full mr-4"
/ >
< p className = 'text-lg' >
Created by { ' ' }
< a rel = 'noreferrer noopener' target = '_blank' className = 'text-blue-500 hover:underline' >
2024-08-06 15:50:19 -05:00
{ author ? . username || author ? . name || author ? . pubkey }
2024-07-30 17:16:09 -05:00
< / a >
< / p >
< / d i v >
< / d i v >
< div className = 'flex flex-col max-tab:mt-12 max-mob:mt-12' >
{ processedEvent && (
< div className = 'flex flex-col items-center justify-between rounded-lg h-72 p-4 bg-gray-700 drop-shadow-md' >
< Image
2024-08-16 18:00:46 -05:00
alt = "course thumbnail"
2024-07-30 17:16:09 -05:00
src = { returnImageProxy ( processedEvent . image ) }
width = { 344 }
height = { 194 }
className = "w-[344px] h-[194px] object-cover object-top rounded-lg"
/ >
2024-08-16 18:00:46 -05:00
< div className = 'w-full flex justify-between items-center' >
2024-09-12 09:17:22 -05:00
{ renderPaymentMessage ( ) }
2024-08-25 18:15:45 -05:00
< ZapDisplay
zapAmount = { zapAmount }
event = { processedEvent }
zapsLoading = { zapsLoading && zapAmount === 0 }
/ >
2024-08-16 18:00:46 -05:00
< / d i v >
2024-07-30 17:16:09 -05:00
< / d i v >
) }
< / d i v >
< / d i v >
< / d i v >
{ typeof window !== 'undefined' && nAddress !== null && (
< div className = 'px-24' >
< ZapThreadsWrapper
anchor = { nAddress }
user = { user ? . pubkey || null }
2024-09-12 12:07:38 -05:00
relays = "wss://nos.lol/, wss://relay.damus.io/, wss://relay.snort.social/, wss://relay.nostr.band/, wss://relay.mutinywallet.com/, wss://relay.primal.net/"
2024-09-14 18:05:59 -05:00
disable = "zaps"
2024-07-30 17:16:09 -05:00
/ >
< / d i v >
) }
< div className = 'w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]' >
{
2024-09-03 17:02:24 -05:00
processedEvent ? . content && < MDDisplay className = 'p-4 rounded-lg' source = { processedEvent . content } / >
2024-07-30 17:16:09 -05:00
}
< / d i v >
< / d i v >
) ;
2024-08-16 18:00:46 -05:00
}