From 131720ed58ec8e9d89e5880a9742dc1834916017 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Date: Tue, 27 May 2025 19:25:38 -0400 Subject: [PATCH] feat(nostr): Created UI for Kind 1 Nostr posts --- src/components/app-body/index.js | 4 +- .../app-body/nostr/nostr-post/index.js.js | 31 ++++ .../profile-post.js} | 14 +- .../app-body/nostr/nostr-post/public-post.js | 142 ++++++++++++++++++ .../app-body/nostr/nostr-read/index.js | 31 ++++ .../profile-read.js} | 18 ++- .../app-body/nostr/nostr-read/public-read.js | 74 +++++++++ 7 files changed, 299 insertions(+), 15 deletions(-) create mode 100644 src/components/app-body/nostr/nostr-post/index.js.js rename src/components/app-body/nostr/{nostr-post.js => nostr-post/profile-post.js} (93%) create mode 100644 src/components/app-body/nostr/nostr-post/public-post.js create mode 100644 src/components/app-body/nostr/nostr-read/index.js rename src/components/app-body/nostr/{nostr-read.js => nostr-read/profile-read.js} (78%) create mode 100644 src/components/app-body/nostr/nostr-read/public-read.js diff --git a/src/components/app-body/index.js b/src/components/app-body/index.js index f52238a..d3e0ddb 100644 --- a/src/components/app-body/index.js +++ b/src/components/app-body/index.js @@ -23,8 +23,8 @@ import SlpTokens from './slp-tokens' import SweepWif from './sweep/index.js' import SignMessage from './sign/index.js' import ServerSelectView from './configuration/select-server-view' -import NostrPost from './nostr/nostr-post.js' -import NostrRead from './nostr/nostr-read.js' +import NostrPost from './nostr/nostr-post/index.js' +import NostrRead from './nostr/nostr-read/index.js' function AppBody (props) { // Dependency injection through props diff --git a/src/components/app-body/nostr/nostr-post/index.js.js b/src/components/app-body/nostr/nostr-post/index.js.js new file mode 100644 index 0000000..dca4d1e --- /dev/null +++ b/src/components/app-body/nostr/nostr-post/index.js.js @@ -0,0 +1,31 @@ +/* +Component for posting nostr information. +*/ + +// Global npm libraries +import React from 'react' +import { Container } from 'react-bootstrap' +import ProfilePost from './profile-post' +import PublicPost from './public-post' + +function NostrPost (props) { + return ( + <> + +

+ Nostr Post +

+ + +
+ + ) +} + +export default NostrPost diff --git a/src/components/app-body/nostr/nostr-post.js b/src/components/app-body/nostr/nostr-post/profile-post.js similarity index 93% rename from src/components/app-body/nostr/nostr-post.js rename to src/components/app-body/nostr/nostr-post/profile-post.js index 4b2b126..4e54755 100644 --- a/src/components/app-body/nostr/nostr-post.js +++ b/src/components/app-body/nostr/nostr-post/profile-post.js @@ -1,5 +1,5 @@ /* -Component for posting nostr information. +Component for posting nostr information on kind 0. */ // Global npm libraries @@ -10,8 +10,8 @@ import { finalizeEvent } from 'nostr-tools/pure' import { Relay } from 'nostr-tools/relay' import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency -function NostrPost (props) { - const [accordionKey, setAccordionKey] = useState('1') +function ProfilePost (props) { + const [accordionKey, setAccordionKey] = useState(null) const [onFetch, setOnFetch] = useState(false) const { bchWalletState } = props.appData const [formData, setFormData] = useState({ @@ -96,10 +96,10 @@ function NostrPost (props) { return ( <> - + - - Post + + Profile Post {errorMsg && (
@@ -155,4 +155,4 @@ function NostrPost (props) { ) } -export default NostrPost +export default ProfilePost diff --git a/src/components/app-body/nostr/nostr-post/public-post.js b/src/components/app-body/nostr/nostr-post/public-post.js new file mode 100644 index 0000000..ee42e0f --- /dev/null +++ b/src/components/app-body/nostr/nostr-post/public-post.js @@ -0,0 +1,142 @@ +/* +Component for posting nostr information on kind 1. +*/ + +// Global npm libraries +import React, { useState } from 'react' +import { Container, Form, Button, Spinner } from 'react-bootstrap' +import Accordion from 'react-bootstrap/Accordion' +import { finalizeEvent } from 'nostr-tools/pure' +import { Relay } from 'nostr-tools/relay' +import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency + +function PublicPost (props) { + const [accordionKey, setAccordionKey] = useState(null) + const [onFetch, setOnFetch] = useState(false) + const { bchWalletState } = props.appData + const [formData, setFormData] = useState({ + content: '' + }) + + const [errorMsg, setErrorMsg] = useState('') + const [successMsg, setSuccessMsg] = useState('') + + const handleInputChange = (e) => { + const { name, value } = e.target + setFormData({ + ...formData, + [name]: value + }) + + setSuccessMsg('') + setErrorMsg('') + } + // Post on nostr network + const handleSubmit = async (e) => { + e.preventDefault() + try { + setErrorMsg('') + setSuccessMsg('') + setOnFetch(true) + + const { nostrKeyPair } = bchWalletState + + // Convert private key to binary + const privateKeyBin = hexToBytes(nostrKeyPair.privHex) + + // Relay list + const psf = 'wss://nostr-relay.psfoundation.info' + + // Generate a post. + const eventTemplate = { + kind: 1, + created_at: Math.floor(Date.now() / 1000), + tags: [], + content: formData.content + } + console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`) + + // Sign the post + const signedEvent = finalizeEvent(eventTemplate, privateKeyBin) + console.log('signedEvent: ', signedEvent) + + // Connect to a relay. + const relay = await Relay.connect(psf) + console.log(`connected to ${relay.url}`) + + // Publish the message to the relay. + const result = await relay.publish(signedEvent) + console.log('result: ', result) + + // Close the connection to the relay. + relay.close() + resetForm() + setSuccessMsg('Post successfully published!') + setOnFetch(false) + } catch (error) { + console.warn(error) + setErrorMsg(error.message || 'An error occurred while posting') + setOnFetch(false) + } + } + + const handleAccordionChange = (key) => { + setAccordionKey(key) + } + + const resetForm = () => { + setFormData({ + content: '' + }) + } + + return ( + <> + + + + Public Post + + {errorMsg && ( +
+ {errorMsg} +
+ )} + {successMsg && ( +
+ {successMsg} +
+ )} +
+ + Message + + + +
+ {!onFetch && ( + + )} + {onFetch && } +
+ +
+
+
+
+
+ + ) +} + +export default PublicPost diff --git a/src/components/app-body/nostr/nostr-read/index.js b/src/components/app-body/nostr/nostr-read/index.js new file mode 100644 index 0000000..9881ec1 --- /dev/null +++ b/src/components/app-body/nostr/nostr-read/index.js @@ -0,0 +1,31 @@ +/* +Component for read nostr information. +*/ + +// Global npm libraries +import React from 'react' +import { Container } from 'react-bootstrap' +import ProfileRead from './profile-read.js' +import PublicRead from './public-read.js' + +function NostrRead (props) { + return ( + <> + +

+ Nostr Read +

+ + +
+ + ) +} + +export default NostrRead diff --git a/src/components/app-body/nostr/nostr-read.js b/src/components/app-body/nostr/nostr-read/profile-read.js similarity index 78% rename from src/components/app-body/nostr/nostr-read.js rename to src/components/app-body/nostr/nostr-read/profile-read.js index bf83f94..e534cb0 100644 --- a/src/components/app-body/nostr/nostr-read.js +++ b/src/components/app-body/nostr/nostr-read/profile-read.js @@ -1,3 +1,6 @@ +/** + * Component for read nostr information kind 0 + */ // Global npm libraries import React, { useEffect, useState } from 'react' import { Container } from 'react-bootstrap' @@ -6,10 +9,10 @@ import Accordion from 'react-bootstrap/Accordion' import { RelayPool } from 'nostr' -function NostrRead (props) { +function ProfileRead (props) { const { bchWalletState } = props.appData const [posts, setPosts] = useState([]) - const [accordionKey, setAccordionKey] = useState('0') + const [accordionKey, setAccordionKey] = useState(null) const [loaded, setLoaded] = useState(false) useEffect(() => { @@ -47,16 +50,19 @@ function NostrRead (props) { } return ( - + - Read ( {posts.length} post found ) - + Profile + {posts.map((post, index) => (
{post.content}
))} + {!posts.length && loaded && ( + Posts not found + )}
@@ -64,4 +70,4 @@ function NostrRead (props) { ) } -export default NostrRead +export default ProfileRead diff --git a/src/components/app-body/nostr/nostr-read/public-read.js b/src/components/app-body/nostr/nostr-read/public-read.js new file mode 100644 index 0000000..8b23027 --- /dev/null +++ b/src/components/app-body/nostr/nostr-read/public-read.js @@ -0,0 +1,74 @@ +/** + * Component for read nostr information kind 1 + */ +// Global npm libraries +import React, { useEffect, useState } from 'react' +import { Container } from 'react-bootstrap' + +import Accordion from 'react-bootstrap/Accordion' + +import { RelayPool } from 'nostr' + +function PublicRead (props) { + const { bchWalletState } = props.appData + const [posts, setPosts] = useState([]) + const [accordionKey, setAccordionKey] = useState(null) + const [loaded, setLoaded] = useState(false) + + useEffect(() => { + // Get Last post from a author + const start = () => { + const { nostrKeyPair } = bchWalletState + + const psf = 'wss://nostr-relay.psfoundation.info' + + const pool = RelayPool([psf]) + pool.on('open', relay => { + relay.subscribe('subid', { limit: 5, kinds: [1], authors: [nostrKeyPair.pubHex] }) + }) + + pool.on('eose', relay => { + console.log('Closing Relay') + relay.close() + }) + + pool.on('event', (relay, subId, ev) => { + console.log('Received event:', ev) + setPosts(currentPosts => [...currentPosts, ev]) + }) + + setLoaded(true) + } + + if (!loaded) { + start() + } + }, [bchWalletState, loaded]) + + const handleAccordionChange = (key) => { + setAccordionKey(key) + } + + return ( + + + + Public Post ( {posts.length} post found ) + + {posts.map((post, index) => ( +
+ {post.content} +
+
+ ))} + {!posts.length && loaded && ( + Not Post Found! + )} +
+
+
+
+ ) +} + +export default PublicRead