diff --git a/packages/app/src/Element/TrendingPosts.tsx b/packages/app/src/Element/TrendingPosts.tsx new file mode 100644 index 00000000..340232e8 --- /dev/null +++ b/packages/app/src/Element/TrendingPosts.tsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react"; +import { RawEvent, TaggedRawEvent } from "@snort/nostr"; +import { FormattedMessage } from "react-intl"; + +import PageSpinner from "Element/PageSpinner"; +import Note from "Element/Note"; +import NostrBandApi from "NostrBand"; + +export default function TrendingNotes() { + const [posts, setPosts] = useState>(); + + async function loadTrendingNotes() { + const api = new NostrBandApi(); + const trending = await api.trendingNotes(); + setPosts(trending.notes.map(a => a.event)); + } + + useEffect(() => { + loadTrendingNotes().catch(console.error); + }, []); + + if (!posts) return ; + + return ( + <> +

+ +

+ {posts.map(e => ( + + ))} + + ); +} diff --git a/packages/app/src/Element/TrendingUsers.tsx b/packages/app/src/Element/TrendingUsers.tsx index 28227fd9..6d7a0ad6 100644 --- a/packages/app/src/Element/TrendingUsers.tsx +++ b/packages/app/src/Element/TrendingUsers.tsx @@ -4,37 +4,20 @@ import { FormattedMessage } from "react-intl"; import FollowListBase from "Element/FollowListBase"; import PageSpinner from "Element/PageSpinner"; +import NostrBandApi from "NostrBand"; -interface TrendingUser { - pubkey: HexKey; -} - -interface TrendingUserResponse { - profiles: Array; -} - -async function fetchTrendingUsers() { - try { - const res = await fetch(`https://api.nostr.band/v0/trending/profiles`); - if (res.ok) { - const data = (await res.json()) as TrendingUserResponse; - return data.profiles.map(a => a.pubkey); - } - } catch (e) { - console.warn(`Failed to load link preview`); - } -} - -const TrendingUsers = () => { +export default function TrendingUsers() { const [userList, setUserList] = useState(); + async function loadTrendingUsers() { + const api = new NostrBandApi(); + const users = await api.trendingProfiles(); + const keys = users.profiles.map(a => a.pubkey); + setUserList(keys); + } + useEffect(() => { - (async () => { - const data = await fetchTrendingUsers(); - if (data) { - setUserList(data); - } - })(); + loadTrendingUsers().catch(console.error); }, []); if (!userList) return ; @@ -42,11 +25,9 @@ const TrendingUsers = () => { return ( <>

- +

); -}; - -export default TrendingUsers; +} diff --git a/packages/app/src/NostrBand.tsx b/packages/app/src/NostrBand.tsx new file mode 100644 index 00000000..6c74b183 --- /dev/null +++ b/packages/app/src/NostrBand.tsx @@ -0,0 +1,52 @@ +import { RawEvent } from "@snort/nostr"; + +export interface TrendingUser { + pubkey: string; +} + +export interface TrendingUserResponse { + profiles: Array; +} + +export interface TrendingNote { + event: RawEvent; + author: RawEvent; // kind0 event +} + +export interface TrendingNoteResponse { + notes: Array; +} + +export class NostrBandError extends Error { + body: string; + statusCode: number; + + constructor(message: string, body: string, status: number) { + super(message); + this.body = body; + this.statusCode = status; + } +} + +export default class NostrBandApi { + #url = "https://api.nostr.band"; + + async trendingProfiles() { + return await this.#json("GET", "/v0/trending/profiles"); + } + + async trendingNotes() { + return await this.#json("GET", "/v0/trending/notes"); + } + + async #json(method: string, path: string) { + const res = await fetch(`${this.#url}${path}`, { + method: method ?? "GET", + }); + if (res.ok) { + return (await res.json()) as T; + } else { + throw new NostrBandError("Failed to load content from nostr.band", await res.text(), res.status); + } + } +} diff --git a/packages/app/src/Pages/SearchPage.tsx b/packages/app/src/Pages/SearchPage.tsx index 70655450..09ef2e2b 100644 --- a/packages/app/src/Pages/SearchPage.tsx +++ b/packages/app/src/Pages/SearchPage.tsx @@ -8,11 +8,10 @@ import { router } from "index"; import { SearchRelays } from "Const"; import { System } from "System"; import TrendingUsers from "Element/TrendingUsers"; -import useHorizontalScroll from "Hooks/useHorizontalScroll"; -import messages from "./messages"; +import TrendingNotes from "Element/TrendingPosts"; -const POSTS = 0; +const NOTES = 0; const PROFILES = 1; const SearchPage = () => { @@ -23,8 +22,8 @@ const SearchPage = () => { const [sortPopular, setSortPopular] = useState(true); // tabs const SearchTab = { - Posts: { text: formatMessage(messages.Posts), value: POSTS }, - Profiles: { text: formatMessage(messages.People), value: PROFILES }, + Posts: { text: formatMessage({ defaultMessage: "Notes" }), value: NOTES }, + Profiles: { text: formatMessage({ defaultMessage: "People" }), value: PROFILES }, }; const [tab, setTab] = useState(SearchTab.Posts); @@ -57,7 +56,16 @@ const SearchPage = () => { }, []); function tabContent() { - if (!keyword) return null; + if (!keyword) { + switch (tab.value) { + case PROFILES: + return ; + case NOTES: + return ; + } + return null; + } + const pf = tab.value == PROFILES; return ( <> @@ -72,6 +80,7 @@ const SearchPage = () => { postsOnly={false} noSort={pf && sortPopular} method={"LIMIT_UNTIL"} + loadMore={false} /> ); @@ -102,20 +111,19 @@ const SearchPage = () => { return (

- +

setSearch(e.target.value)} autoFocus={true} />
{[SearchTab.Posts, SearchTab.Profiles].map(renderTab)}
- {!keyword && } {tabContent()}
); diff --git a/packages/app/src/Pages/messages.ts b/packages/app/src/Pages/messages.ts index 5b9ac6fb..6cd7ae4b 100644 --- a/packages/app/src/Pages/messages.ts +++ b/packages/app/src/Pages/messages.ts @@ -22,8 +22,6 @@ export default defineMessages({ Sats: { defaultMessage: "{n} {n, plural, =1 {sat} other {sats}}" }, Following: { defaultMessage: "Following {n}" }, Settings: { defaultMessage: "Settings" }, - Search: { defaultMessage: "Search" }, - SearchPlaceholder: { defaultMessage: "Search..." }, Messages: { defaultMessage: "Messages" }, MarkAllRead: { defaultMessage: "Mark All Read" }, GetVerified: { defaultMessage: "Get Verified" }, @@ -47,5 +45,4 @@ export default defineMessages({ Bookmarks: { defaultMessage: "Bookmarks" }, BookmarksCount: { defaultMessage: "{n} Bookmarks" }, KeyPlaceholder: { defaultMessage: "nsec, npub, nip-05, hex" }, - People: { defaultMessage: "People" }, }); diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index f5626bff..fb973067 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -244,6 +244,9 @@ "B6+XJy": { "defaultMessage": "zapped" }, + "B6H7eJ": { + "defaultMessage": "nsec, npub, nip-05, hex" + }, "BGCM48": { "defaultMessage": "Write access to Snort relay, with 1 year of event retention" }, @@ -268,6 +271,9 @@ "CHTbO3": { "defaultMessage": "Failed to load invoice" }, + "CVWeJ6": { + "defaultMessage": "Trending People" + }, "CmZ9ls": { "defaultMessage": "{n} Muted" }, @@ -337,9 +343,6 @@ "FS3b54": { "defaultMessage": "Done!" }, - "FSYL8G": { - "defaultMessage": "Trending Users" - }, "FdhSU2": { "defaultMessage": "Claim Now" }, @@ -413,6 +416,9 @@ "Iwm6o2": { "defaultMessage": "NIP-05 Shop" }, + "Ix8l+B": { + "defaultMessage": "Trending Notes" + }, "J+dIsA": { "defaultMessage": "Subscriptions" }, @@ -422,9 +428,6 @@ "JHEHCk": { "defaultMessage": "Zaps ({n})" }, - "JTgbT0": { - "defaultMessage": "Find Twitter follows" - }, "JXtsQW": { "defaultMessage": "Fast Zap Donation" }, @@ -516,6 +519,9 @@ "NndBJE": { "defaultMessage": "New users page" }, + "O9GTIc": { + "defaultMessage": "Profile picture" + }, "OEW7yJ": { "defaultMessage": "Zaps" }, @@ -589,12 +595,20 @@ "RhDAoS": { "defaultMessage": "Are you sure you want to delete {id}" }, + "RjpoYG": { + "defaultMessage": "Recent", + "description": "Sort order name" + }, "RoOyAh": { "defaultMessage": "Relays" }, "Rs4kCE": { "defaultMessage": "Bookmark" }, + "RwFaYs": { + "defaultMessage": "Sort", + "description": "Label for sorting options for people search" + }, "SOqbe9": { "defaultMessage": "Update Lightning Address" }, @@ -618,6 +632,9 @@ "defaultMessage": "Hex Salt..", "description": "Hexidecimal 'salt' input for imgproxy" }, + "Tpy00S": { + "defaultMessage": "People" + }, "TwyMau": { "defaultMessage": "Account" }, @@ -627,9 +644,6 @@ "ULotH9": { "defaultMessage": "Amount: {amount} sats" }, - "UQ3pOC": { - "defaultMessage": "On Nostr, many people have the same username. User names and identity are separate things. You can get a unique identifier in the next step." - }, "UUPFlt": { "defaultMessage": "Users must accept the content warning to show the content of your note." }, @@ -713,6 +727,9 @@ "ZUZedV": { "defaultMessage": "Lightning Donation:" }, + "Zr5TMx": { + "defaultMessage": "Setup profile" + }, "a5UPxh": { "defaultMessage": "Fund developers and platforms providing NIP-05 verification services" }, @@ -963,6 +980,10 @@ "mKhgP9": { "defaultMessage": "{n,plural,=0{} =1{zapped} other{zapped}}" }, + "mTJFgF": { + "defaultMessage": "Popular", + "description": "Sort order name" + }, "mfe8RW": { "defaultMessage": "Option: {n}" }, diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index 7bbb4959..8ca095f4 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -79,6 +79,7 @@ "AyGauy": "Login", "B4C47Y": "name too short", "B6+XJy": "zapped", + "B6H7eJ": "nsec, npub, nip-05, hex", "BGCM48": "Write access to Snort relay, with 1 year of event retention", "BOUMjw": "No nostr users found for {twitterUsername}", "BOr9z/": "Snort is an open source project built by passionate people in their free time", @@ -87,6 +88,7 @@ "C5xzTC": "Premium", "C81/uG": "Logout", "CHTbO3": "Failed to load invoice", + "CVWeJ6": "Trending People", "CmZ9ls": "{n} Muted", "Cu/K85": "Translated from {lang}", "D+KzKd": "Automatically zap every note when loaded", @@ -110,7 +112,6 @@ "FDguSC": "{n} Zaps", "FP+D3H": "LNURL to forward zaps to", "FS3b54": "Done!", - "FSYL8G": "Trending Users", "FdhSU2": "Claim Now", "FfYsOb": "An error has occured!", "FmXUJg": "follows you", @@ -135,10 +136,10 @@ "INSqIz": "Twitter username...", "IUZC+0": "This means that nobody can modify notes which you have created and everybody can easily verify that the notes they are reading are created by you.", "Iwm6o2": "NIP-05 Shop", + "Ix8l+B": "Trending Notes", "J+dIsA": "Subscriptions", "JCIgkj": "Username", "JHEHCk": "Zaps ({n})", - "JTgbT0": "Find Twitter follows", "JXtsQW": "Fast Zap Donation", "JkLHGw": "Website", "JymXbw": "Private Key", @@ -169,6 +170,7 @@ "NepkXH": "Can't vote with {amount} sats, please set a different default zap amount", "NfNk2V": "Your private key", "NndBJE": "New users page", + "O9GTIc": "Profile picture", "OEW7yJ": "Zaps", "OKhRC6": "Share", "OLEm6z": "Unknown login error", @@ -193,8 +195,10 @@ "RahCRH": "Expired", "RfhLwC": "By: {author}", "RhDAoS": "Are you sure you want to delete {id}", + "RjpoYG": "Recent", "RoOyAh": "Relays", "Rs4kCE": "Bookmark", + "RwFaYs": "Sort", "SOqbe9": "Update Lightning Address", "SX58hM": "Copy", "SYQtZ7": "LN Address Proxy", @@ -202,10 +206,10 @@ "Ss0sWu": "Pay Now", "TMfYfY": "Cashu token", "TpgeGw": "Hex Salt..", + "Tpy00S": "People", "TwyMau": "Account", "UDYlxu": "Pending Subscriptions", "ULotH9": "Amount: {amount} sats", - "UQ3pOC": "On Nostr, many people have the same username. User names and identity are separate things. You can get a unique identifier in the next step.", "UUPFlt": "Users must accept the content warning to show the content of your note.", "Up5U7K": "Block", "VBadwB": "Hmm, can't find a key manager extension.. try reloading the page.", @@ -233,6 +237,7 @@ "ZKORll": "Activate Now", "ZLmyG9": "Contributors", "ZUZedV": "Lightning Donation:", + "Zr5TMx": "Setup profile", "a5UPxh": "Fund developers and platforms providing NIP-05 verification services", "aWpBzj": "Show more", "b12Goz": "Mnemonic", @@ -315,6 +320,7 @@ "mKAr6h": "Follow all", "mKh2HS": "File upload service", "mKhgP9": "{n,plural,=0{} =1{zapped} other{zapped}}", + "mTJFgF": "Popular", "mfe8RW": "Option: {n}", "n1Whvj": "Switch", "nDejmx": "Unblock",