feat(nostr): Created UI for Kind 1 Nostr posts

This commit is contained in:
Daniel Gonzalez
2025-05-27 19:25:38 -04:00
parent 64b1aec4cf
commit 131720ed58
7 changed files with 299 additions and 15 deletions
+2 -2
View File
@@ -23,8 +23,8 @@ import SlpTokens from './slp-tokens'
import SweepWif from './sweep/index.js' import SweepWif from './sweep/index.js'
import SignMessage from './sign/index.js' import SignMessage from './sign/index.js'
import ServerSelectView from './configuration/select-server-view' import ServerSelectView from './configuration/select-server-view'
import NostrPost from './nostr/nostr-post.js' import NostrPost from './nostr/nostr-post/index.js'
import NostrRead from './nostr/nostr-read.js' import NostrRead from './nostr/nostr-read/index.js'
function AppBody (props) { function AppBody (props) {
// Dependency injection through props // Dependency injection through props
@@ -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 (
<>
<Container>
<h2 style={{
textAlign: 'center',
margin: '20px 0',
padding: '10px',
borderBottom: '2px solid #ccc'
}}
>
Nostr Post
</h2>
<ProfilePost {...props} />
<PublicPost {...props} />
</Container>
</>
)
}
export default NostrPost
@@ -1,5 +1,5 @@
/* /*
Component for posting nostr information. Component for posting nostr information on kind 0.
*/ */
// Global npm libraries // Global npm libraries
@@ -10,8 +10,8 @@ import { finalizeEvent } from 'nostr-tools/pure'
import { Relay } from 'nostr-tools/relay' import { Relay } from 'nostr-tools/relay'
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
function NostrPost (props) { function ProfilePost (props) {
const [accordionKey, setAccordionKey] = useState('1') const [accordionKey, setAccordionKey] = useState(null)
const [onFetch, setOnFetch] = useState(false) const [onFetch, setOnFetch] = useState(false)
const { bchWalletState } = props.appData const { bchWalletState } = props.appData
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
@@ -96,10 +96,10 @@ function NostrPost (props) {
return ( return (
<> <>
<Container> <Container className='mt-4'>
<Accordion activeKey={accordionKey} onSelect={handleAccordionChange}> <Accordion activeKey={accordionKey} onSelect={handleAccordionChange}>
<Accordion.Item eventKey='1'> <Accordion.Item eventKey='0'>
<Accordion.Header>Post</Accordion.Header> <Accordion.Header>Profile Post</Accordion.Header>
<Accordion.Body> <Accordion.Body>
{errorMsg && ( {errorMsg && (
<div className='alert alert-danger' role='alert'> <div className='alert alert-danger' role='alert'>
@@ -155,4 +155,4 @@ function NostrPost (props) {
) )
} }
export default NostrPost export default ProfilePost
@@ -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 (
<>
<Container className='mt-4'>
<Accordion activeKey={accordionKey} onSelect={handleAccordionChange}>
<Accordion.Item eventKey='0'>
<Accordion.Header>Public Post</Accordion.Header>
<Accordion.Body>
{errorMsg && (
<div className='alert alert-danger' role='alert'>
{errorMsg}
</div>
)}
{successMsg && (
<div className='alert alert-success' role='alert'>
{successMsg}
</div>
)}
<Form onSubmit={handleSubmit}>
<Form.Group className='mb-3'>
<Form.Label>Message</Form.Label>
<Form.Control
as='textarea'
rows={7}
placeholder="What's happening?"
name='content'
value={formData.content}
onChange={handleInputChange}
required
/>
</Form.Group>
<div className='d-flex justify-content-center'>
{!onFetch && (
<Button variant='primary' type='submit' disabled={onFetch}>
Post
</Button>
)}
{onFetch && <Spinner animation='border' variant='primary' />}
</div>
</Form>
</Accordion.Body>
</Accordion.Item>
</Accordion>
</Container>
</>
)
}
export default PublicPost
@@ -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 (
<>
<Container>
<h2 style={{
textAlign: 'center',
margin: '20px 0',
padding: '10px',
borderBottom: '2px solid #ccc'
}}
>
Nostr Read
</h2>
<ProfileRead {...props} />
<PublicRead {...props} />
</Container>
</>
)
}
export default NostrRead
@@ -1,3 +1,6 @@
/**
* Component for read nostr information kind 0
*/
// Global npm libraries // Global npm libraries
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Container } from 'react-bootstrap' import { Container } from 'react-bootstrap'
@@ -6,10 +9,10 @@ import Accordion from 'react-bootstrap/Accordion'
import { RelayPool } from 'nostr' import { RelayPool } from 'nostr'
function NostrRead (props) { function ProfileRead (props) {
const { bchWalletState } = props.appData const { bchWalletState } = props.appData
const [posts, setPosts] = useState([]) const [posts, setPosts] = useState([])
const [accordionKey, setAccordionKey] = useState('0') const [accordionKey, setAccordionKey] = useState(null)
const [loaded, setLoaded] = useState(false) const [loaded, setLoaded] = useState(false)
useEffect(() => { useEffect(() => {
@@ -47,16 +50,19 @@ function NostrRead (props) {
} }
return ( return (
<Container> <Container className='mt-4'>
<Accordion activeKey={accordionKey} onSelect={handleAccordionChange}> <Accordion activeKey={accordionKey} onSelect={handleAccordionChange}>
<Accordion.Item eventKey='0'> <Accordion.Item eventKey='0'>
<Accordion.Header>Read ( {posts.length} post found )</Accordion.Header> <Accordion.Header>Profile </Accordion.Header>
<Accordion.Body> <Accordion.Body style={{ wordBreak: 'break-word' }}>
{posts.map((post, index) => ( {posts.map((post, index) => (
<div key={index}> <div key={index}>
<span>{post.content}</span> <span>{post.content}</span>
</div> </div>
))} ))}
{!posts.length && loaded && (
<span>Posts not found</span>
)}
</Accordion.Body> </Accordion.Body>
</Accordion.Item> </Accordion.Item>
</Accordion> </Accordion>
@@ -64,4 +70,4 @@ function NostrRead (props) {
) )
} }
export default NostrRead export default ProfileRead
@@ -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 (
<Container className='mt-4'>
<Accordion activeKey={accordionKey} onSelect={handleAccordionChange}>
<Accordion.Item eventKey='0'>
<Accordion.Header>Public Post ( {posts.length} post found )</Accordion.Header>
<Accordion.Body style={{ wordBreak: 'break-word' }}>
{posts.map((post, index) => (
<div key={index}>
<span>{post.content}</span>
<hr />
</div>
))}
{!posts.length && loaded && (
<span>Not Post Found!</span>
)}
</Accordion.Body>
</Accordion.Item>
</Accordion>
</Container>
)
}
export default PublicRead