2024-07-21 14:12:19 -05:00
import React , { useState , useEffect , useCallback } from "react" ;
2024-03-20 19:42:28 -05:00
import axios from "axios" ;
2024-03-20 13:00:05 -05:00
import { InputText } from "primereact/inputtext" ;
import { InputNumber } from "primereact/inputnumber" ;
import { InputSwitch } from "primereact/inputswitch" ;
import { Button } from "primereact/button" ;
2024-03-25 13:39:32 -05:00
import { useRouter } from "next/router" ;
2024-03-20 19:42:28 -05:00
import { useNostr } from "@/hooks/useNostr" ;
import { useLocalStorageWithEffect } from "@/hooks/useLocalStorage" ;
2024-03-21 12:54:06 -05:00
import EditorHeader from "./Editor/EditorHeader" ;
2024-03-24 14:16:55 -05:00
import { useToast } from "@/hooks/useToast" ;
2024-07-21 14:12:19 -05:00
import dynamic from 'next/dynamic' ;
const MDEditor = dynamic (
( ) => import ( "@uiw/react-md-editor" ) ,
{
ssr : false ,
}
) ;
2024-03-20 13:00:05 -05:00
import 'primeicons/primeicons.css' ;
2024-07-20 12:40:53 -05:00
const ResourceForm = ( { draft = null } ) => {
const [ title , setTitle ] = useState ( draft ? . title || '' ) ;
const [ summary , setSummary ] = useState ( draft ? . summary || '' ) ;
const [ isPaidResource , setIsPaidResource ] = useState ( draft ? . price ? true : false ) ;
const [ price , setPrice ] = useState ( draft ? . price || 0 ) ;
const [ coverImage , setCoverImage ] = useState ( draft ? . image || '' ) ;
const [ topics , setTopics ] = useState ( draft ? . topics || [ '' ] ) ;
2024-07-21 14:12:19 -05:00
const [ content , setContent ] = useState ( draft ? . content || '' ) ;
2024-03-20 19:42:28 -05:00
const [ user ] = useLocalStorageWithEffect ( 'user' , { } ) ;
2024-03-24 14:16:55 -05:00
const { showToast } = useToast ( ) ;
2024-03-20 19:42:28 -05:00
const { publishAll } = useNostr ( ) ;
2024-03-25 13:39:32 -05:00
const router = useRouter ( ) ;
2024-03-24 14:16:55 -05:00
2024-07-21 14:12:19 -05:00
const handleContentChange = useCallback ( ( value ) => {
setContent ( value || '' ) ;
} , [ ] ) ;
2024-07-20 12:40:53 -05:00
useEffect ( ( ) => {
if ( draft ) {
setTitle ( draft . title ) ;
setSummary ( draft . summary ) ;
setIsPaidResource ( draft . price ? true : false ) ;
setPrice ( draft . price || 0 ) ;
setText ( draft . content ) ;
setCoverImage ( draft . image ) ;
setTopics ( draft . topics || [ ] ) ;
}
} , [ draft ] ) ;
2024-03-25 13:39:32 -05:00
const handleSubmit = async ( e ) => {
e . preventDefault ( ) ;
2024-07-21 14:12:19 -05:00
2024-03-25 13:39:32 -05:00
const userResponse = await axios . get ( ` /api/users/ ${ user . pubkey } ` ) ;
2024-07-21 14:12:19 -05:00
2024-03-24 14:16:55 -05:00
if ( ! userResponse . data ) {
showToast ( 'error' , 'Error' , 'User not found' , 'Please try again.' ) ;
return ;
}
2024-07-21 14:12:19 -05:00
2024-03-25 13:39:32 -05:00
const payload = {
title ,
summary ,
type : 'resource' ,
price : isPaidResource ? price : null ,
2024-07-21 14:12:19 -05:00
content ,
2024-03-25 13:39:32 -05:00
image : coverImage ,
2024-03-27 14:44:54 -05:00
topics : [ ... topics . map ( topic => topic . trim ( ) . toLowerCase ( ) ) , 'plebdevs' , 'resource' ]
2024-03-20 19:42:28 -05:00
} ;
2024-07-21 14:12:19 -05:00
2024-07-20 12:40:53 -05:00
if ( ! draft ) {
// Only include user when creating a new draft
payload . user = userResponse . data . id ;
}
2024-07-21 14:12:19 -05:00
2024-07-20 12:40:53 -05:00
if ( payload ) {
const url = draft ? ` /api/drafts/ ${ draft . id } ` : '/api/drafts' ;
const method = draft ? 'put' : 'post' ;
2024-07-21 14:12:19 -05:00
2024-07-20 12:40:53 -05:00
axios [ method ] ( url , payload )
2024-03-25 13:39:32 -05:00
. then ( response => {
2024-07-20 12:40:53 -05:00
if ( response . status === 200 || response . status === 201 ) {
showToast ( 'success' , 'Success' , draft ? 'Resource updated successfully.' : 'Resource saved as draft.' ) ;
2024-07-21 14:12:19 -05:00
2024-03-25 13:39:32 -05:00
if ( response . data ? . id ) {
router . push ( ` /draft/ ${ response . data . id } ` ) ;
}
}
} )
. catch ( error => {
console . error ( error ) ;
2024-07-20 12:40:53 -05:00
showToast ( 'error' , 'Error' , 'Failed to save resource. Please try again.' ) ;
2024-03-25 13:39:32 -05:00
} ) ;
2024-03-24 14:16:55 -05:00
}
2024-03-25 13:39:32 -05:00
} ;
2024-03-24 14:16:55 -05:00
2024-03-25 13:39:32 -05:00
// const saveFreeResource = async (payload) => {
// const newresourceId = uuidv4();
// const event = {
// kind: 30023,
// content: payload.content,
// created_at: Math.floor(Date.now() / 1000),
// tags: [
// ['d', newresourceId],
// ['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: newresourceId,
// })
// 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: newresourceId,
// 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 savePaidResource = 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 newresourceId = 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/${newresourceId}`],
// ['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: newresourceId,
// })
// 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: newresourceId,
// 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}`);
// })
// }
2024-03-20 13:00:05 -05:00
2024-03-20 19:42:28 -05:00
const handleTopicChange = ( index , value ) => {
const updatedTopics = topics . map ( ( topic , i ) => i === index ? value : topic ) ;
setTopics ( updatedTopics ) ;
} ;
2024-07-20 12:40:53 -05:00
const addTopic = ( e ) => {
e . preventDefault ( ) ;
2024-03-20 19:42:28 -05:00
setTopics ( [ ... topics , '' ] ) ; // Add an empty string to the topics array
} ;
2024-07-20 12:40:53 -05:00
const removeTopic = ( e , index ) => {
e . preventDefault ( ) ;
2024-03-20 19:42:28 -05:00
const updatedTopics = topics . filter ( ( _ , i ) => i !== index ) ;
setTopics ( updatedTopics ) ;
} ;
2024-03-21 12:54:06 -05:00
// Define custom toolbar for the editor
const customToolbar = (
< div id = "toolbar" >
{ /* Include existing toolbar items */ }
< span className = "ql-formats" >
< select className = "ql-header" defaultValue = "" >
< option value = "1" > Heading < / o p t i o n >
< option value = "2" > Subheading < / o p t i o n >
< option value = "" > Normal < / o p t i o n >
< / s e l e c t >
< / s p a n >
< span className = "ql-formats" >
< button className = "ql-bold" > < / b u t t o n >
< button className = "ql-italic" > < / b u t t o n >
< button className = "ql-underline" > < / b u t t o n >
< / s p a n >
< span className = "ql-formats" >
< button className = "ql-list" value = "ordered" > < / b u t t o n >
< button className = "ql-list" value = "bullet" > < / b u t t o n >
< button className = "ql-indent" value = "-1" > < / b u t t o n >
< button className = "ql-indent" value = "+1" > < / b u t t o n >
< / s p a n >
< span className = "ql-formats" >
< button className = "ql-link" > < / b u t t o n >
< button className = "ql-image" > < / b u t t o n >
< button className = "ql-video" > < /button> {/ * This is your custom video button * / }
< / s p a n >
< span className = "ql-formats" >
< button className = "ql-clean" > < / b u t t o n >
< / s p a n >
< / d i v >
) ;
2024-03-20 13:00:05 -05:00
return (
2024-07-21 17:02:40 -05:00
< 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-4" >
< InputText value = { summary } onChange = { ( e ) => setSummary ( e . target . value ) } placeholder = "Summary" / >
< / d i v >
< div className = "p-inputgroup flex-1 mt-4" >
< InputText value = { coverImage } onChange = { ( e ) => setCoverImage ( e . target . value ) } placeholder = "Cover Image URL" / >
< / d i v >
< div className = "p-inputgroup flex-1 mt-8 flex-col" >
< p className = "py-2" > Paid Resource < / p >
< InputSwitch checked = { isPaidResource } onChange = { ( e ) => setIsPaidResource ( e . value ) } / >
{ isPaidResource && (
< div className = "p-inputgroup flex-1 py-4" >
< InputNumber value = { price } onValueChange = { ( e ) => setPrice ( e . value ) } placeholder = "Price (sats)" / >
< / d i v >
) }
< / d i v >
< div className = "p-inputgroup flex-1 flex-col mt-4" >
< span > Content < / s p a n >
< div data - color - mode = "dark" >
2024-07-21 14:12:19 -05:00
< MDEditor
value = { content }
onChange = { handleContentChange }
2024-07-21 17:02:40 -05:00
height = { 350 }
2024-07-21 14:12:19 -05:00
/ >
< / d i v >
2024-07-21 17:02:40 -05:00
< / d i v >
< 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 = { ( e ) => removeTopic ( e , index ) } / >
) }
2024-03-20 19:42:28 -05:00
< / d i v >
2024-07-21 17:02:40 -05:00
) ) }
< div className = "w-full flex flex-row items-end justify-end py-2" >
< Button icon = "pi pi-plus" onClick = { addTopic } / >
2024-03-20 19:42:28 -05:00
< / d i v >
2024-07-21 17:02:40 -05:00
< / d i v >
< div className = "flex justify-center mt-8" >
< Button type = "submit" severity = "success" outlined label = { draft ? "Update" : "Submit" } / >
< / d i v >
< / f o r m >
2024-03-20 13:00:05 -05:00
) ;
}
export default ResourceForm ;