From 95b7cca4cbb84877f29fe6fbda9b8b8ab634f1ad Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 16 Nov 2023 14:01:12 +0000 Subject: [PATCH] feat: trending hashtags --- packages/app/src/Element/RootTabs.tsx | 11 +++++ packages/app/src/Element/TrendingHashtags.tsx | 35 ++++++++++++++ packages/app/src/External/NostrBand.ts | 16 +++++-- packages/app/src/Pages/HashTagsPage.tsx | 47 ++++++++++--------- packages/app/src/Pages/Root.tsx | 5 ++ 5 files changed, 88 insertions(+), 26 deletions(-) create mode 100644 packages/app/src/Element/TrendingHashtags.tsx diff --git a/packages/app/src/Element/RootTabs.tsx b/packages/app/src/Element/RootTabs.tsx index 3a417963..b27a3fe4 100644 --- a/packages/app/src/Element/RootTabs.tsx +++ b/packages/app/src/Element/RootTabs.tsx @@ -78,6 +78,17 @@ export function RootTabs({ base }: { base?: string }) { ), }, + { + tab: "trending-hashtags", + path: `${base}/trending/hashtags`, + show: true, + element: ( + <> + + + + ), + }, { tab: "global", path: `${base}/global`, diff --git a/packages/app/src/Element/TrendingHashtags.tsx b/packages/app/src/Element/TrendingHashtags.tsx new file mode 100644 index 00000000..f17fdf7d --- /dev/null +++ b/packages/app/src/Element/TrendingHashtags.tsx @@ -0,0 +1,35 @@ +import { ReactNode, useEffect, useState } from "react"; + +import PageSpinner from "Element/PageSpinner"; +import NostrBandApi from "External/NostrBand"; +import { ErrorOrOffline } from "./ErrorOrOffline"; +import { HashTagHeader } from "Pages/HashTagsPage"; +import { useLocale } from "IntlProvider"; + +export default function TrendingHashtags({ title }: { title?: ReactNode }) { + const [hashtags, setHashtags] = useState(); + const [error, setError] = useState(); + const { lang } = useLocale(); + + async function loadTrendingHashtags() { + const api = new NostrBandApi(); + const rsp = await api.trendingHashtags(lang); + setHashtags(rsp.hashtags); + } + + useEffect(() => { + loadTrendingHashtags().catch(e => { + if (e instanceof Error) { + setError(e); + } + }); + }, []); + + if (error) return ; + if (!hashtags) return ; + + return <> + {title} + {hashtags.map(a => )} + +} diff --git a/packages/app/src/External/NostrBand.ts b/packages/app/src/External/NostrBand.ts index 694e4cc5..664b92ec 100644 --- a/packages/app/src/External/NostrBand.ts +++ b/packages/app/src/External/NostrBand.ts @@ -18,6 +18,10 @@ export interface TrendingNoteResponse { notes: Array; } +export interface TrendingHashtagsResponse { + hashtags: Array +} + export interface SuggestedFollow { pubkey: string; } @@ -39,14 +43,13 @@ export class NostrBandError extends Error { export default class NostrBandApi { readonly #url = "https://api.nostr.band"; - + readonly #supportedLangs = ["en", "de", "ja", "zh", "th", "pt", "es", "fr"]; async trendingProfiles() { return await this.#json("GET", "/v0/trending/profiles"); } async trendingNotes(lang?: string) { - const supportedLangs = ["en", "de", "ja", "zh", "th", "pt", "es", "fr"]; - if (lang && supportedLangs.includes(lang)) { + if (lang && this.#supportedLangs.includes(lang)) { return await this.#json("GET", `/v0/trending/notes?lang=${lang}`); } return await this.#json("GET", "/v0/trending/notes"); @@ -56,6 +59,13 @@ export default class NostrBandApi { return await this.#json("GET", `/v0/suggested/profiles/${pubkey}`); } + async trendingHashtags(lang?: string) { + if (lang && this.#supportedLangs.includes(lang)) { + return await this.#json("GET", `/v0/trending/hashtags?lang=${lang}`); + } + return await this.#json("GET", "/v0/trending/hashtags"); + } + async #json(method: string, path: string) { throwIfOffline(); const res = await fetch(`${this.#url}${path}`, { diff --git a/packages/app/src/Pages/HashTagsPage.tsx b/packages/app/src/Pages/HashTagsPage.tsx index 7eee2647..472d5871 100644 --- a/packages/app/src/Pages/HashTagsPage.tsx +++ b/packages/app/src/Pages/HashTagsPage.tsx @@ -11,6 +11,7 @@ import useLogin from "Hooks/useLogin"; import { setTags } from "Login"; import AsyncButton from "Element/AsyncButton"; import ProfileImage from "Element/User/ProfileImage"; +import classNames from "classnames"; const HashTagsPage = () => { const params = useParams(); @@ -33,7 +34,7 @@ const HashTagsPage = () => { export default HashTagsPage; -export function HashTagHeader({ tag }: { tag: string }) { +export function HashTagHeader({ tag, className }: { tag: string, className?: string }) { const login = useLogin(); const isFollowing = useMemo(() => { return login.tags.item.includes(tag); @@ -60,29 +61,29 @@ export function HashTagHeader({ tag }: { tag: string }) { const pubkeys = dedupe((followsTag.data ?? []).map(a => a.pubkey)); return ( -
-
-

#{tag}

-
- {pubkeys.slice(0, 5).map(a => ( - - ))} - {pubkeys.length > 5 && ( - - + - - )} -
+
+
+ #{tag} + {isFollowing ? ( + followTags(login.tags.item.filter(t => t !== tag))}> + + + ) : ( + followTags(login.tags.item.concat([tag]))}> + + + )} +
+
+ {pubkeys.slice(0, 5).map(a => ( + + ))} + {pubkeys.length > 5 && ( + + + + + )}
- {isFollowing ? ( - followTags(login.tags.item.filter(t => t !== tag))}> - - - ) : ( - followTags(login.tags.item.concat([tag]))}> - - - )}
); } diff --git a/packages/app/src/Pages/Root.tsx b/packages/app/src/Pages/Root.tsx index 19f01f87..94eb637a 100644 --- a/packages/app/src/Pages/Root.tsx +++ b/packages/app/src/Pages/Root.tsx @@ -19,6 +19,7 @@ import TimelineFollows from "Element/Feed/TimelineFollows"; import { RootTabs } from "Element/RootTabs"; import { DeckContext } from "Pages/DeckLayout"; import { TopicsPage } from "./TopicsPage"; +import TrendingHashtags from "Element/TrendingHashtags"; import messages from "./messages"; @@ -228,6 +229,10 @@ export const RootTabRoutes = [
), }, + { + path: "trending/hashtags", + element: , + }, { path: "suggested", element: (