2024-03-21 12:54:06 -05:00
import React , { useState } from 'react' ;
2024-03-25 10:23:42 -05:00
import axios from 'axios' ;
2024-03-21 12:54:06 -05:00
import { InputText } from 'primereact/inputtext' ;
import { InputNumber } from 'primereact/inputnumber' ;
import { InputSwitch } from 'primereact/inputswitch' ;
2024-03-25 10:23:42 -05:00
import { FileUpload } from 'primereact/fileupload' ;
import { verifyEvent , nip19 } from "nostr-tools"
import { useNostr } from '@/hooks/useNostr' ;
2024-03-21 12:54:06 -05:00
import { Button } from 'primereact/button' ;
2024-03-25 10:23:42 -05:00
import { v4 as uuidv4 } from 'uuid' ;
import { useToast } from '@/hooks/useToast' ;
import { useLocalStorageWithEffect } from '@/hooks/useLocalStorage' ;
2024-03-20 14:20:45 -05:00
import 'primeicons/primeicons.css' ;
const WorkshopForm = ( ) => {
const [ title , setTitle ] = useState ( '' ) ;
const [ summary , setSummary ] = useState ( '' ) ;
const [ checked , setChecked ] = useState ( false ) ;
const [ price , setPrice ] = useState ( 0 ) ;
2024-03-21 12:54:06 -05:00
const [ videoUrl , setVideoUrl ] = useState ( '' ) ;
2024-03-25 10:23:42 -05:00
const [ coverImage , setCoverImage ] = useState ( '' ) ;
2024-03-21 12:54:06 -05:00
const [ topics , setTopics ] = useState ( [ '' ] ) ;
2024-03-20 14:20:45 -05:00
2024-03-25 10:23:42 -05:00
const [ user ] = useLocalStorageWithEffect ( 'user' , { } ) ;
const { showToast } = useToast ( ) ;
const { publishAll } = useNostr ( ) ;
2024-03-20 14:20:45 -05:00
const handleSubmit = ( e ) => {
2024-03-21 12:54:06 -05:00
e . preventDefault ( ) ;
let embedCode = '' ;
// Check if it's a YouTube video
if ( videoUrl . includes ( 'youtube.com' ) || videoUrl . includes ( 'youtu.be' ) ) {
const videoId = videoUrl . split ( 'v=' ) [ 1 ] || videoUrl . split ( '/' ) . pop ( ) ;
embedCode = ` <iframe width="560" height="315" src="https://www.youtube.com/embed/ ${ videoId } " frameborder="0" allowfullscreen></iframe> ` ;
}
// Check if it's a Vimeo video
else if ( videoUrl . includes ( 'vimeo.com' ) ) {
const videoId = videoUrl . split ( '/' ) . pop ( ) ;
embedCode = ` <iframe width="560" height="315" src="https://player.vimeo.com/video/ ${ videoId } " frameborder="0" allowfullscreen></iframe> ` ;
}
// Add more conditions here for other video services
2024-03-20 14:20:45 -05:00
const payload = {
title ,
summary ,
isPaidResource : checked ,
price : checked ? price : null ,
2024-03-25 10:23:42 -05:00
embedCode ,
2024-03-21 12:54:06 -05:00
topics : topics . map ( topic => topic . trim ( ) . toLowerCase ( ) ) // Include topics in the payload
2024-03-20 14:20:45 -05:00
} ;
console . log ( payload ) ;
2024-03-25 10:23:42 -05:00
if ( checked ) {
broadcastPaidWorkshop ( payload ) ;
} else {
broadcastFreeWorkshop ( payload ) ;
}
2024-03-21 12:54:06 -05:00
} ;
2024-03-25 10:23:42 -05:00
const broadcastFreeWorkshop = async ( payload ) => {
const newWorkshopId = uuidv4 ( ) ;
const event = {
kind : 30023 ,
content : payload . embedCode ,
created _at : Math . floor ( Date . now ( ) / 1000 ) ,
tags : [
[ 'd' , newWorkshopId ] ,
[ 'title' , payload . title ] ,
[ 'summary' , payload . summary ] ,
[ 'image' , '' ] ,
[ 't' , ... topics ] ,
[ 'published_at' , Math . floor ( Date . now ( ) / 1000 ) . toString ( ) ] ,
]
} ;
const signedEvent = await window . nostr . signEvent ( event ) ;
const eventVerification = await verifyEvent ( signedEvent ) ;
if ( ! eventVerification ) {
showToast ( 'error' , 'Error' , 'Event verification failed. Please try again.' ) ;
return ;
}
const nAddress = nip19 . naddrEncode ( {
pubkey : signedEvent . pubkey ,
kind : signedEvent . kind ,
identifier : newWorkshopId ,
} )
console . log ( 'nAddress:' , nAddress ) ;
const userResponse = await axios . get ( ` /api/users/ ${ user . pubkey } ` )
if ( ! userResponse . data ) {
showToast ( 'error' , 'Error' , 'User not found' , 'Please try again.' ) ;
return ;
}
const resourcePayload = {
id : newWorkshopId ,
userId : userResponse . data . id ,
price : 0 ,
noteId : nAddress ,
}
const response = await axios . post ( ` /api/resources ` , resourcePayload ) ;
console . log ( 'response:' , response ) ;
if ( response . status !== 201 ) {
showToast ( 'error' , 'Error' , 'Failed to create resource. Please try again.' ) ;
return ;
}
const publishResponse = await publishAll ( signedEvent ) ;
if ( ! publishResponse ) {
showToast ( 'error' , 'Error' , 'Failed to publish resource. Please try again.' ) ;
return ;
} else if ( publishResponse ? . failedRelays ) {
publishResponse ? . failedRelays . map ( relay => {
showToast ( 'warn' , 'Warning' , ` Failed to publish to relay: ${ relay } ` ) ;
} ) ;
}
publishResponse ? . successfulRelays . map ( relay => {
showToast ( 'success' , 'Success' , ` Published to relay: ${ relay } ` ) ;
} )
}
// For images, whether included in the markdown content or not, clients SHOULD use image tags as described in NIP-58. This allows clients to display images in carousel format more easily.
const broadcastPaidWorkshop = async ( payload ) => {
// encrypt the content with NEXT_PUBLIC_APP_PRIV_KEY to NEXT_PUBLIC_APP_PUBLIC_KEY
const encryptedContent = await nip04 . encrypt ( process . env . NEXT _PUBLIC _APP _PRIV _KEY , process . env . NEXT _PUBLIC _APP _PUBLIC _KEY , payload . content ) ;
const newWorkshopId = uuidv4 ( ) ;
const event = {
kind : 30402 ,
content : encryptedContent ,
created _at : Math . floor ( Date . now ( ) / 1000 ) ,
tags : [
[ 'title' , payload . title ] ,
[ 'summary' , payload . summary ] ,
[ 't' , ... topics ] ,
[ 'image' , '' ] ,
[ 'd' , newresourceId ] ,
[ 'location' , ` https://plebdevs.com/resource/ ${ newWorkshopId } ` ] ,
[ 'published_at' , Math . floor ( Date . now ( ) / 1000 ) . toString ( ) ] ,
[ 'price' , payload . price ]
]
} ;
const signedEvent = await window . nostr . signEvent ( event ) ;
const eventVerification = await verifyEvent ( signedEvent ) ;
if ( ! eventVerification ) {
showToast ( 'error' , 'Error' , 'Event verification failed. Please try again.' ) ;
return ;
}
const nAddress = nip19 . naddrEncode ( {
pubkey : signedEvent . pubkey ,
kind : signedEvent . kind ,
identifier : newWorkshopId ,
} )
console . log ( 'nAddress:' , nAddress ) ;
const userResponse = await axios . get ( ` /api/users/ ${ user . pubkey } ` )
if ( ! userResponse . data ) {
showToast ( 'error' , 'Error' , 'User not found' , 'Please try again.' ) ;
return ;
}
const resourcePayload = {
id : newWorkshopId ,
userId : userResponse . data . id ,
price : payload . price || 0 ,
noteId : nAddress ,
}
const response = await axios . post ( ` /api/resources ` , resourcePayload ) ;
if ( response . status !== 201 ) {
showToast ( 'error' , 'Error' , 'Failed to create resource. Please try again.' ) ;
return ;
}
const publishResponse = await publishAll ( signedEvent ) ;
if ( ! publishResponse ) {
showToast ( 'error' , 'Error' , 'Failed to publish resource. Please try again.' ) ;
return ;
} else if ( publishResponse ? . failedRelays ) {
publishResponse ? . failedRelays . map ( relay => {
showToast ( 'warn' , 'Warning' , ` Failed to publish to relay: ${ relay } ` ) ;
} ) ;
}
publishResponse ? . successfulRelays . map ( relay => {
showToast ( 'success' , 'Success' , ` Published to relay: ${ relay } ` ) ;
} )
}
const onUpload = ( event ) => {
showToast ( 'success' , 'Success' , 'File Uploaded' ) ;
console . log ( event . files [ 0 ] ) ;
}
2024-03-21 12:54:06 -05:00
const handleTopicChange = ( index , value ) => {
const updatedTopics = topics . map ( ( topic , i ) => i === index ? value : topic ) ;
setTopics ( updatedTopics ) ;
} ;
const addTopic = ( ) => {
setTopics ( [ ... topics , '' ] ) ; // Add an empty string to the topics array
} ;
const removeTopic = ( index ) => {
const updatedTopics = topics . filter ( ( _ , i ) => i !== index ) ;
setTopics ( updatedTopics ) ;
} ;
2024-03-20 14:20:45 -05:00
return (
< form onSubmit = { handleSubmit } >
< div className = "p-inputgroup flex-1" >
< InputText value = { title } onChange = { ( e ) => setTitle ( e . target . value ) } placeholder = "Title" / >
< / d i v >
< div className = "p-inputgroup flex-1 mt-8" >
< InputText value = { summary } onChange = { ( e ) => setSummary ( e . target . value ) } placeholder = "Summary" / >
< / d i v >
< div className = "p-inputgroup flex-1 mt-8 flex-col" >
< p className = "py-2" > Paid Workshop < / p >
< InputSwitch checked = { checked } onChange = { ( e ) => setChecked ( e . value ) } / >
2024-03-21 12:54:06 -05:00
{ checked && (
< div className = "p-inputgroup flex-1 py-4" >
< i className = "pi pi-bolt p-inputgroup-addon text-2xl text-yellow-500" > < / i >
< InputNumber value = { price } onValueChange = { ( e ) => setPrice ( e . value ) } placeholder = "Price (sats)" / >
< / d i v >
) }
< / d i v >
< div className = "p-inputgroup flex-1 mt-8" >
< InputText value = { videoUrl } onChange = { ( e ) => setVideoUrl ( e . target . value ) } placeholder = "Video URL" / >
2024-03-20 14:20:45 -05:00
< / d i v >
2024-03-25 10:23:42 -05:00
< div className = "p-inputgroup flex-1 mt-8" >
< InputText value = { coverImage } onChange = { ( e ) => setCoverImage ( e . target . value ) } placeholder = "Cover Image URL" / >
< / d i v >
2024-03-21 12:54:06 -05:00
< div className = "mt-8 flex-col w-full" >
{ topics . map ( ( topic , index ) => (
< div className = "p-inputgroup flex-1" key = { index } >
< InputText value = { topic } onChange = { ( e ) => handleTopicChange ( index , e . target . value ) } placeholder = "Topic" className = "w-full mt-2" / >
{ index > 0 && (
< Button icon = "pi pi-times" className = "p-button-danger mt-2" onClick = { ( ) => removeTopic ( index ) } / >
) }
< / d i v >
) ) }
< div className = "w-full flex flex-row items-end justify-end py-2" >
< Button icon = "pi pi-plus" onClick = { addTopic } / >
< / d i v >
2024-03-20 14:20:45 -05:00
< / d i v >
< div className = "flex justify-center mt-8" >
< Button type = "submit" severity = "success" outlined label = "Submit" / >
< / d i v >
< / f o r m >
) ;
}
export default WorkshopForm ;