2024-08-06 15:50:19 -05:00
import React , { useEffect , useState , useCallback } from 'react' ;
2024-10-05 16:13:01 -05:00
import axios from 'axios' ;
import { useToast } from "@/hooks/useToast" ;
import { Tag } from 'primereact/tag' ;
import Image from 'next/image' ;
2024-07-30 17:16:09 -05:00
import { useRouter } from 'next/router' ;
2024-10-05 16:13:01 -05:00
import CoursePaymentButton from "@/components/bitcoinConnect/CoursePaymentButton" ;
2024-08-01 16:31:52 -05:00
import ZapDisplay from '@/components/zaps/ZapDisplay' ;
2024-10-05 16:13:01 -05:00
import GenericButton from '@/components/buttons/GenericButton' ;
2024-09-12 09:17:22 -05:00
import { nip19 } from 'nostr-tools' ;
2024-10-05 16:13:01 -05:00
import { useImageProxy } from '@/hooks/useImageProxy' ;
import { useZapsSubscription } from '@/hooks/nostrQueries/zaps/useZapsSubscription' ;
import { getTotalFromZaps } from '@/utils/lightning' ;
2024-08-07 16:02:13 -05:00
import { useSession } from 'next-auth/react' ;
2024-10-05 16:13:01 -05:00
import useWindowWidth from "@/hooks/useWindowWidth" ;
2024-08-06 15:50:19 -05:00
import { useNDKContext } from "@/context/NDKContext" ;
import { findKind0Fields } from '@/utils/nostr' ;
2024-09-17 13:55:51 -05:00
import appConfig from "@/config/appConfig" ;
2024-10-05 16:13:01 -05:00
import useTrackCourse from '@/hooks/tracking/useTrackCourse' ;
import { ProgressSpinner } from 'primereact/progressspinner' ;
2024-07-30 17:16:09 -05:00
2024-08-17 12:56:27 -05:00
export default function CourseDetails ( { processedEvent , paidCourse , lessons , decryptionPerformed , handlePaymentSuccess , handlePaymentError } ) {
2024-08-01 16:31:52 -05:00
const [ zapAmount , setZapAmount ] = useState ( 0 ) ;
2024-10-05 16:13:01 -05:00
const [ author , setAuthor ] = useState ( null ) ;
const [ nAddress , setNAddress ] = useState ( null ) ;
const router = useRouter ( ) ;
2024-08-06 15:50:19 -05:00
const { returnImageProxy } = useImageProxy ( ) ;
2024-10-05 16:13:01 -05:00
const { zaps , zapsLoading , zapsError } = useZapsSubscription ( { event : processedEvent } ) ;
2024-08-07 16:02:13 -05:00
const { data : session , status } = useSession ( ) ;
2024-10-05 16:13:01 -05:00
const { showToast } = useToast ( ) ;
2024-09-15 15:53:27 -05:00
const windowWidth = useWindowWidth ( ) ;
const isMobileView = windowWidth <= 768 ;
2024-10-05 16:13:01 -05:00
const { ndk } = useNDKContext ( ) ;
2024-07-30 17:16:09 -05:00
2024-10-05 16:13:01 -05:00
const { isCompleted } = useTrackCourse ( {
courseId : processedEvent ? . d ,
paidCourse ,
decryptionPerformed
} ) ;
2024-08-07 16:02:13 -05:00
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 ) {
const naddr = nip19 . naddrEncode ( {
pubkey : processedEvent . pubkey ,
kind : processedEvent . kind ,
identifier : processedEvent . d ,
2024-11-16 12:33:30 -06:00
relays : appConfig . defaultRelayUrls
2024-07-30 17:16:09 -05:00
} ) ;
setNAddress ( naddr ) ;
}
} , [ processedEvent ] ) ;
2024-10-05 16:13:01 -05:00
useEffect ( ( ) => {
if ( processedEvent ) {
fetchAuthor ( processedEvent . pubkey ) ;
}
} , [ fetchAuthor , 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-10-05 16:13:01 -05:00
const handleDelete = async ( ) => {
try {
const response = await axios . delete ( ` /api/courses/ ${ processedEvent . d } ` ) ;
if ( response . status === 204 ) {
showToast ( 'success' , 'Success' , 'Course deleted successfully.' ) ;
router . push ( '/' ) ;
}
} catch ( error ) {
showToast ( 'error' , 'Error' , 'Failed to delete course. Please try again.' ) ;
}
}
2024-09-12 09:17:22 -05:00
const renderPaymentMessage = ( ) => {
2024-10-05 16:13:01 -05:00
if ( session ? . user && session . user ? . role ? . subscribed && decryptionPerformed ) {
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 && decryptionPerformed && author && processedEvent ? . pubkey !== session ? . user ? . pubkey && ! session ? . user ? . role ? . subscribed ) {
2024-11-18 13:34:46 -06:00
return < GenericButton icon = "pi pi-check" label = { ` Paid ` } severity = "success" outlined size = "small" tooltip = { ` You paid ${ processedEvent . price } sats to access this course (or potentially less if a discount was applied) ` } tooltipOptions = { { position : 'top' } } className = "cursor-default hover:opacity-100 hover:bg-transparent focus:ring-0" / >
2024-10-05 16:13:01 -05:00
}
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" / >
}
2024-09-12 09:17:22 -05:00
if ( paidCourse && ! decryptionPerformed ) {
return (
< CoursePaymentButton
2024-10-05 16:13:01 -05:00
lnAddress = { author ? . lud16 }
2024-09-12 09:17:22 -05:00
amount = { processedEvent . price }
onSuccess = { handlePaymentSuccess }
onError = { handlePaymentError }
courseId = { processedEvent . d }
/ >
) ;
}
return null ;
} ;
2024-10-23 14:48:11 -05:00
const renderAdditionalLinks = ( ) => {
if ( processedEvent ? . additionalLinks && processedEvent . additionalLinks . length > 0 ) {
return (
< div className = "my-4" >
< p > Additional Links : < / p >
{ processedEvent . additionalLinks . map ( ( link , index ) => (
< div key = { index } className = "mb-2" >
< a
className = "text-blue-500 hover:underline hover:text-blue-600 break-words"
href = { link }
target = "_blank"
rel = "noopener noreferrer"
style = { {
wordBreak : 'break-word' ,
overflowWrap : 'break-word' ,
display : 'inline-block' ,
maxWidth : '100%'
} }
>
{ link }
< / a >
< / d i v >
) ) }
< / d i v >
) ;
}
return null ;
} ;
2024-08-17 12:56:27 -05:00
if ( ! processedEvent || ! author ) {
2024-10-05 16:13:01 -05:00
return < 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 (
2024-10-05 16:13:01 -05:00
< div className = "w-full" >
< div className = "relative w-full h-[400px] mb-8" >
< Image
alt = "course image"
src = { returnImageProxy ( processedEvent . image ) }
fill
className = "object-cover"
/ >
< div className = "absolute inset-0 bg-black bg-opacity-20" > < / d i v >
< / d i v >
< div className = "w-full mx-auto px-4 py-8 -mt-32 relative z-10 max-mob:px-0 max-tab:px-0" >
< i className = { ` pi pi-arrow-left cursor-pointer hover:opacity-75 absolute top-0 left-4 ` } onClick = { ( ) => router . push ( '/' ) } / >
< div className = "mb-8 bg-gray-800/70 rounded-lg p-4 max-mob:rounded-t-none max-tab:rounded-t-none" >
{ isCompleted && < Tag severity = "success" value = "Completed" / > }
< div className = "flex flex-row items-center justify-between w-full" >
< h1 className = 'text-4xl font-bold text-white' > { processedEvent . name } < / h 1 >
< div className = "flex flex-wrap gap-2" >
{ processedEvent . topics && processedEvent . topics . length > 0 && (
2024-07-30 17:16:09 -05:00
processedEvent . topics . map ( ( topic , index ) => (
2024-10-05 16:13:01 -05:00
< Tag className = 'text-white' key = { index } value = { topic } > < / T a g >
2024-07-30 17:16:09 -05:00
) )
2024-08-06 15:50:19 -05:00
) }
2024-07-30 17:16:09 -05:00
< / d i v >
2024-10-05 16:13:01 -05:00
< / d i v >
< div className = 'text-xl text-gray-200 mb-4 mt-4 max-mob:text-base' > { processedEvent . description && (
processedEvent . description . split ( '\n' ) . map ( ( line , index ) => (
< p key = { index } > { line } < / p >
) )
) }
< / d i v >
< div className = 'flex items-center justify-between' >
< div className = 'flex items-center' >
2024-07-30 17:16:09 -05:00
< Image
2024-10-05 16:13:01 -05:00
alt = "avatar image"
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"
/ >
2024-10-05 16:13:01 -05:00
< p className = 'text-lg text-white' >
By { ' ' }
< a rel = 'noreferrer noopener' target = '_blank' className = 'text-blue-300 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 >
2024-10-05 16:13:01 -05:00
< ZapDisplay
zapAmount = { zapAmount }
event = { processedEvent }
zapsLoading = { zapsLoading && zapAmount === 0 }
/ >
2024-07-30 17:16:09 -05:00
< / d i v >
2024-10-05 16:13:01 -05:00
< div className = 'w-full mt-8 flex flex-wrap justify-between items-center' >
{ renderPaymentMessage ( ) }
{ processedEvent ? . pubkey === session ? . user ? . pubkey ? (
< div className = 'flex space-x-2 mt-4 sm:mt-0' >
< GenericButton onClick = { ( ) => router . push ( ` /details/ ${ processedEvent . id } /edit ` ) } label = "Edit" severity = 'warning' outlined / >
< GenericButton onClick = { handleDelete } label = "Delete" severity = 'danger' outlined / >
< GenericButton outlined icon = "pi pi-external-link" onClick = { ( ) => window . open ( ` https://nostr.band/ ${ nAddress } ` , '_blank' ) } tooltip = { isMobileView ? null : "View Nostr Event" } tooltipOptions = { { position : paidCourse ? 'left' : 'right' } } / >
< / d i v >
) : (
< div className = 'flex space-x-2 mt-4 sm:mt-0' >
< GenericButton className = 'my-2' outlined icon = "pi pi-external-link" onClick = { ( ) => window . open ( ` https://nostr.band/ ${ nAddress } ` , '_blank' ) } tooltip = { isMobileView ? null : "View Nostr Event" } tooltipOptions = { { position : paidCourse ? 'left' : 'right' } } / >
2024-07-30 17:16:09 -05:00
< / d i v >
) }
2024-10-23 14:48:11 -05:00
{ renderAdditionalLinks ( ) }
2024-07-30 17:16:09 -05:00
< / d i v >
< / d i v >
< / d i v >
< / d i v >
) ;
2024-10-23 14:48:11 -05:00
}