diff --git a/src/components/app-body/index.js b/src/components/app-body/index.js index 09a98cd..303fe2a 100644 --- a/src/components/app-body/index.js +++ b/src/components/app-body/index.js @@ -25,7 +25,7 @@ import SignMessage from './sign/index.js' import ServerSelectView from './configuration/select-server-view' import NostrPost from './nostr/nostr-post/index.js' import Profile from './nostr/profile/index.js' -import GlobalFeed from './nostr/global-feed/index.js' +import Feeds from './nostr/feeds/index.js' import ContentCreators from './nostr/content-creators/index.js' import UserDataReview from './user-data-review' import Offers from './offers' @@ -50,7 +50,7 @@ function AppBody (props) { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/components/app-body/nostr/global-feed/feed-card.js b/src/components/app-body/nostr/feeds/feed-card.js similarity index 95% rename from src/components/app-body/nostr/global-feed/feed-card.js rename to src/components/app-body/nostr/feeds/feed-card.js index 95776af..b2560fd 100644 --- a/src/components/app-body/nostr/global-feed/feed-card.js +++ b/src/components/app-body/nostr/feeds/feed-card.js @@ -1,5 +1,5 @@ /** - * Component for reading global nostr posts + * Component for displaying nostr posts */ // Global npm libraries import React, { useCallback, useEffect, useState } from 'react' @@ -61,7 +61,7 @@ function FeedCard (props) { {!profile?.name && ( {post.pubkey.slice(0, 8) + '...'} - + {!profile?.loaded && } )} diff --git a/src/components/app-body/nostr/feeds/feed.js b/src/components/app-body/nostr/feeds/feed.js new file mode 100644 index 0000000..4d04092 --- /dev/null +++ b/src/components/app-body/nostr/feeds/feed.js @@ -0,0 +1,30 @@ +/** + * Component for displaying global nostr posts + */ +// Global npm libraries +import React from 'react' +import { Container } from 'react-bootstrap' + +import FeedCard from './feed-card' + +function Feed (props) { + const { posts, profiles } = props + + return ( + + + {posts.map((post, index) => ( + + ))} + {!posts.length && ( + + + No posts found + + )} + + + ) +} + +export default Feed diff --git a/src/components/app-body/nostr/feeds/following.js b/src/components/app-body/nostr/feeds/following.js new file mode 100644 index 0000000..78dffb1 --- /dev/null +++ b/src/components/app-body/nostr/feeds/following.js @@ -0,0 +1,82 @@ +/** + * Component for displaying following nostr posts + */ +// Global npm libraries +import React, { useEffect, useState } from 'react' +import { Container, Spinner } from 'react-bootstrap' + +import { RelayPool } from 'nostr' + +import FeedCard from './feed-card' + +function Following (props) { + const { appData, posts, profiles } = props + const [followingPosts, setFollowingPosts] = useState([]) + const [loaded, setLoaded] = useState(false) + + useEffect(() => { + const getFollowingPosts = async () => { + const followingList = await new Promise((resolve, reject) => { + let list = [] + const { nostrKeyPair } = appData.bchWalletState + const psf = 'wss://nostr-relay.psfoundation.info' + + const pool = RelayPool([psf]) + pool.on('open', relay => { + relay.subscribe('subid', { limit: 1, kinds: [3], authors: [nostrKeyPair.pubHex] }) + }) + + pool.on('eose', relay => { + relay.close() + /** This ensures to return an empty array if no records are found! + * Applies for new users that don't have a follow list + */ + resolve(list) + }) + + pool.on('event', (relay, subId, ev) => { + list = ev.tags + resolve(list) + }) + }) + + // Filter posts by following list + let filteredPosts = [] + for (let i = 0; i < followingList.length; i++) { + const following = followingList[i] + const filtered = posts.filter(post => { + return following.includes(post.pubkey) + }) + filteredPosts = [...filteredPosts, ...filtered] + } + // Sort filtered posts by created_at + filteredPosts.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)) + setFollowingPosts(filteredPosts) + setLoaded(true) + } + getFollowingPosts() + }, [appData, posts]) + + return ( + + + {loaded && followingPosts.map((post, index) => ( + + ))} + {loaded && !followingPosts.length && ( + + + No posts found + + )} + {!loaded && ( + + + + )} + + + ) +} + +export default Following diff --git a/src/components/app-body/nostr/feeds/index.js b/src/components/app-body/nostr/feeds/index.js new file mode 100644 index 0000000..9cd50c9 --- /dev/null +++ b/src/components/app-body/nostr/feeds/index.js @@ -0,0 +1,152 @@ +/* +Component for reading the nostr feeds. +*/ + +// Global npm libraries +import React, { useState, useEffect, useCallback } from 'react' +import { Container, Nav, Tab, Spinner } from 'react-bootstrap' +import Feed from './feed' +import Following from './following' +import { RelayPool } from 'nostr' + +function Feeds (props) { + const { appData } = props + const [activeTab, setActiveTab] = useState(appData.lastFeedTab) + + const { bchWalletState } = appData + const [posts, setPosts] = useState([]) + const [loaded, setLoaded] = useState(false) + const [profiles, setProfiles] = useState({}) + + // function to fetch profile and set it to profiles state + const fetchProfile = useCallback((pubkey) => { + // no fetch profile again if it exist + setProfiles(currentProfiles => { + if (currentProfiles[pubkey]) { + return currentProfiles + } + return currentProfiles + }) + + const psf = 'wss://nostr-relay.psfoundation.info' + + const pool = RelayPool([psf]) + pool.on('open', relay => { + relay.subscribe('subid', { limit: 5, kinds: [0], authors: [pubkey] }) + }) + + pool.on('eose', relay => { + relay.close() + try { + // Mark unknown profiles. From this way we can know which profile was fetched and not found. + setProfiles(currentProfiles => { + if (!currentProfiles[pubkey]) { + const newProfiles = { ...currentProfiles } + newProfiles[pubkey] = { loaded: true } + return newProfiles + } + return currentProfiles + }) + } catch (error) { + // skip error + } + }) + + pool.on('event', (relay, subId, ev) => { + try { + // update profiles data + const profile = JSON.parse(ev.content) + setProfiles(currentProfiles => { + const newProfiles = { ...currentProfiles } + newProfiles[pubkey] = profile + return newProfiles + }) + } catch (error) { + // skip error + } + }) + }, []) + + // Get global feed posts + useEffect(() => { + const start = () => { + const psf = 'wss://nostr-relay.psfoundation.info' + + const pool = RelayPool([psf]) + pool.on('open', relay => { + relay.subscribe('REQ', { limit: 10, kinds: [1], '#t': ['slpdex-socialmedia'] }) + }) + + pool.on('eose', relay => { + setLoaded(true) + relay.close() + }) + + pool.on('event', async (relay, subId, ev) => { + setPosts(currentPosts => [...currentPosts, ev]) + // fetch post profile and set it to profiles state + fetchProfile(ev.pubkey) + }) + } + + if (!loaded) { + start() + } + }, [bchWalletState, loaded, fetchProfile]) + + const onChangeTab = (tab) => { + setActiveTab(tab) + appData.setLastFeedTab(tab) + appData.updateLocalStorage({ lastFeedTab: tab }) + } + + return ( + <> + + + Feeds + + + {loaded && ( + + + + + Feeds + + + + + Following + + + + + + + + + + + + + + )} + + {!loaded && ( + + + + )} + + > + ) +} + +export default Feeds diff --git a/src/components/app-body/nostr/global-feed/feed.js b/src/components/app-body/nostr/global-feed/feed.js deleted file mode 100644 index 29ef4fd..0000000 --- a/src/components/app-body/nostr/global-feed/feed.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Component for reading global nostr posts - */ -// Global npm libraries -import React, { useEffect, useState, useCallback } from 'react' -import { Container, Spinner } from 'react-bootstrap' - -import { RelayPool } from 'nostr' - -import FeedCard from './feed-card' - -function Feed (props) { - const { appData } = props - const { bchWalletState } = appData - const [posts, setPosts] = useState([]) - const [loaded, setLoaded] = useState(false) - const [profiles, setProfiles] = useState({}) - - // function to fetch profile and set it to profiles state - const fetchProfile = useCallback((pubkey) => { - // no fetch profile again if it exist - if (profiles[pubkey]) { - console.log('profile exists', profiles[pubkey]) - return profiles[pubkey] - } - - const psf = 'wss://nostr-relay.psfoundation.info' - - const pool = RelayPool([psf]) - pool.on('open', relay => { - relay.subscribe('subid', { limit: 5, kinds: [0], authors: [pubkey] }) - }) - - pool.on('eose', relay => { - console.log('Closing Relay') - relay.close() - }) - - pool.on('event', (relay, subId, ev) => { - try { - const profile = JSON.parse(ev.content) - const newProfiles = { ...profiles } - newProfiles[pubkey] = profile - setProfiles(newProfiles) - } catch (error) { - console.log('Error parsing profile', error) - } - }) - }, [profiles]) - - // Get global feed posts - useEffect(() => { - const start = () => { - const psf = 'wss://nostr-relay.psfoundation.info' - - const pool = RelayPool([psf]) - pool.on('open', relay => { - relay.subscribe('REQ', { limit: 10, kinds: [1], '#t': ['slpdex-socialmedia'] }) - }) - - pool.on('eose', relay => { - console.log('Closing Relay') - setLoaded(true) - relay.close() - }) - - pool.on('event', async (relay, subId, ev) => { - console.log('Received event:', ev) - - setPosts(currentPosts => [...currentPosts, ev]) - // fetch post profile and set it to profiles state - fetchProfile(ev.pubkey) - }) - } - - if (!loaded) { - start() - } - }, [bchWalletState, loaded, fetchProfile]) - - return ( - - - {posts.map((post, index) => ( - - ))} - {!posts.length && loaded && ( - - - No posts found - - )} - - {!loaded && ( - - - - )} - - - ) -} - -export default Feed diff --git a/src/components/app-body/nostr/global-feed/index.js b/src/components/app-body/nostr/global-feed/index.js deleted file mode 100644 index 72ac994..0000000 --- a/src/components/app-body/nostr/global-feed/index.js +++ /dev/null @@ -1,31 +0,0 @@ -/* -Component for reading the nostr global feed. -*/ - -// Global npm libraries -import React from 'react' -import { Container } from 'react-bootstrap' -import Feed from './feed' - -function GlobalFeed (props) { - const { appData } = props - - return ( - <> - - - Global Feed - - - - > - ) -} - -export default GlobalFeed diff --git a/src/components/nav-menu/index.js b/src/components/nav-menu/index.js index eda91fb..0fe7d28 100644 --- a/src/components/nav-menu/index.js +++ b/src/components/nav-menu/index.js @@ -58,10 +58,10 @@ function NavMenu (props) { - Global Feed + Feeds { - // console.log(`updateLocalStorage() input: ${JSON.stringify(lsObj, null, 2)}`) + console.log(`updateLocalStorage() input: ${JSON.stringify(lsObj, null, 2)}`) // Progressively overwrite the LocalStorage state. const newObj = Object.assign({}, lsState, lsObj) @@ -149,7 +151,9 @@ function useAppState () { setNostr, nftForSaleCacheData, setNftForSaleCacheData, - updateNFTCachedData + updateNFTCachedData, + lastFeedTab, + setLastFeedTab } }