forked from Kieran/snort
feat: discover page
This commit is contained in:
parent
6fa242c5f9
commit
3e5b7ec4a6
@ -1,7 +1,7 @@
|
|||||||
.profile-preview {
|
.profile-preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 40px;
|
min-height: 59px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-preview .pfp {
|
.profile-preview .pfp {
|
||||||
|
36
packages/app/src/Element/SuggestedProfiles.tsx
Normal file
36
packages/app/src/Element/SuggestedProfiles.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { HexKey, NostrPrefix } from "@snort/nostr";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import FollowListBase from "Element/FollowListBase";
|
||||||
|
import PageSpinner from "Element/PageSpinner";
|
||||||
|
import NostrBandApi from "External/NostrBand";
|
||||||
|
import useLogin from "Hooks/useLogin";
|
||||||
|
import { hexToBech32 } from "Util";
|
||||||
|
|
||||||
|
export default function SuggestedProfiles() {
|
||||||
|
const login = useLogin();
|
||||||
|
const [userList, setUserList] = useState<HexKey[]>();
|
||||||
|
|
||||||
|
async function loadSuggestedProfiles() {
|
||||||
|
const api = new NostrBandApi();
|
||||||
|
const users = await api.sugguestedFollows(hexToBech32(NostrPrefix.PublicKey, login.publicKey));
|
||||||
|
const keys = users.profiles.map(a => a.pubkey);
|
||||||
|
setUserList(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadSuggestedProfiles().catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!userList) return <PageSpinner />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>
|
||||||
|
<FormattedMessage defaultMessage="Suggested Follows" />
|
||||||
|
</h3>
|
||||||
|
<FollowListBase pubkeys={userList} showAbout={true} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -4,7 +4,7 @@ import { FormattedMessage } from "react-intl";
|
|||||||
|
|
||||||
import PageSpinner from "Element/PageSpinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
import Note from "Element/Note";
|
import Note from "Element/Note";
|
||||||
import NostrBandApi from "NostrBand";
|
import NostrBandApi from "External/NostrBand";
|
||||||
|
|
||||||
export default function TrendingNotes() {
|
export default function TrendingNotes() {
|
||||||
const [posts, setPosts] = useState<Array<RawEvent>>();
|
const [posts, setPosts] = useState<Array<RawEvent>>();
|
||||||
|
@ -4,7 +4,7 @@ import { FormattedMessage } from "react-intl";
|
|||||||
|
|
||||||
import FollowListBase from "Element/FollowListBase";
|
import FollowListBase from "Element/FollowListBase";
|
||||||
import PageSpinner from "Element/PageSpinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
import NostrBandApi from "NostrBand";
|
import NostrBandApi from "External/NostrBand";
|
||||||
|
|
||||||
export default function TrendingUsers() {
|
export default function TrendingUsers() {
|
||||||
const [userList, setUserList] = useState<HexKey[]>();
|
const [userList, setUserList] = useState<HexKey[]>();
|
||||||
|
@ -17,6 +17,14 @@ export interface TrendingNoteResponse {
|
|||||||
notes: Array<TrendingNote>;
|
notes: Array<TrendingNote>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SuggestedFollow {
|
||||||
|
pubkey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuggestedFollowsResponse {
|
||||||
|
profiles: Array<SuggestedFollow>;
|
||||||
|
}
|
||||||
|
|
||||||
export class NostrBandError extends Error {
|
export class NostrBandError extends Error {
|
||||||
body: string;
|
body: string;
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
@ -29,7 +37,7 @@ export class NostrBandError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class NostrBandApi {
|
export default class NostrBandApi {
|
||||||
#url = "https://api.nostr.band";
|
readonly #url = "https://api.nostr.band";
|
||||||
|
|
||||||
async trendingProfiles() {
|
async trendingProfiles() {
|
||||||
return await this.#json<TrendingUserResponse>("GET", "/v0/trending/profiles");
|
return await this.#json<TrendingUserResponse>("GET", "/v0/trending/profiles");
|
||||||
@ -39,6 +47,10 @@ export default class NostrBandApi {
|
|||||||
return await this.#json<TrendingNoteResponse>("GET", "/v0/trending/notes");
|
return await this.#json<TrendingNoteResponse>("GET", "/v0/trending/notes");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sugguestedFollows(pubkey: string) {
|
||||||
|
return await this.#json<SuggestedFollowsResponse>("GET", `/v0/suggested/profiles/${pubkey}`);
|
||||||
|
}
|
||||||
|
|
||||||
async #json<T>(method: string, path: string) {
|
async #json<T>(method: string, path: string) {
|
||||||
const res = await fetch(`${this.#url}${path}`, {
|
const res = await fetch(`${this.#url}${path}`, {
|
||||||
method: method ?? "GET",
|
method: method ?? "GET",
|
1
packages/app/src/External/index.ts
vendored
Normal file
1
packages/app/src/External/index.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./NostrBand";
|
40
packages/app/src/Pages/Discover.tsx
Normal file
40
packages/app/src/Pages/Discover.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import SuggestedProfiles from "Element/SuggestedProfiles";
|
||||||
|
import { Tab, TabElement } from "Element/Tabs";
|
||||||
|
import TrendingNotes from "Element/TrendingPosts";
|
||||||
|
import TrendingUsers from "Element/TrendingUsers";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
export default function Discover() {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
// tabs
|
||||||
|
const Tabs = {
|
||||||
|
Follows: { text: formatMessage({ defaultMessage: "Suggested Follows" }), value: 0 },
|
||||||
|
Posts: { text: formatMessage({ defaultMessage: "Trending Notes" }), value: 1 },
|
||||||
|
Profiles: { text: formatMessage({ defaultMessage: "Trending People" }), value: 2 },
|
||||||
|
};
|
||||||
|
const [tab, setTab] = useState<Tab>(Tabs.Follows);
|
||||||
|
|
||||||
|
function renderTab() {
|
||||||
|
switch (tab.value) {
|
||||||
|
case 0:
|
||||||
|
return <SuggestedProfiles />;
|
||||||
|
case 1:
|
||||||
|
return <TrendingNotes />;
|
||||||
|
case 2:
|
||||||
|
return <TrendingUsers />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="tabs">
|
||||||
|
{[Tabs.Follows, Tabs.Posts, Tabs.Profiles].map(a => (
|
||||||
|
<TabElement key={a.value} tab={tab} setTab={setTab} t={a} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{renderTab()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -9,6 +9,7 @@ import { System } from "System";
|
|||||||
import { TimelineSubject } from "Feed/TimelineFeed";
|
import { TimelineSubject } from "Feed/TimelineFeed";
|
||||||
import { debounce, getRelayName, sha256, unixNow, unwrap } from "Util";
|
import { debounce, getRelayName, sha256, unixNow, unwrap } from "Util";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
|
import Discover from "Pages/Discover";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
|
||||||
@ -39,12 +40,17 @@ export default function RootPage() {
|
|||||||
value: 2,
|
value: 2,
|
||||||
data: "/global",
|
data: "/global",
|
||||||
},
|
},
|
||||||
|
Discover: {
|
||||||
|
text: formatMessage({ defaultMessage: "Discover" }),
|
||||||
|
value: 3,
|
||||||
|
data: "/discover",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const tagTabs = tags.item.map((t, idx) => {
|
const tagTabs = tags.item.map((t, idx) => {
|
||||||
return { text: `#${t}`, value: idx + 3, data: `/tag/${t}` };
|
return { text: `#${t}`, value: idx + 3, data: `/tag/${t}` };
|
||||||
});
|
});
|
||||||
const tabs = [RootTab.Posts, RootTab.PostsAndReplies, RootTab.Global, ...tagTabs];
|
const tabs = [RootTab.Posts, RootTab.PostsAndReplies, RootTab.Global, RootTab.Discover, ...tagTabs];
|
||||||
const tab = useMemo(() => {
|
const tab = useMemo(() => {
|
||||||
const pTab = location.pathname.split("/").slice(-1)[0];
|
const pTab = location.pathname.split("/").slice(-1)[0];
|
||||||
|
|
||||||
@ -63,6 +69,9 @@ export default function RootPage() {
|
|||||||
case "global": {
|
case "global": {
|
||||||
return RootTab.Global;
|
return RootTab.Global;
|
||||||
}
|
}
|
||||||
|
case "discover": {
|
||||||
|
return RootTab.Discover;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return RootTab.Posts;
|
return RootTab.Posts;
|
||||||
}
|
}
|
||||||
@ -237,6 +246,10 @@ export const RootRoutes = [
|
|||||||
path: "conversations",
|
path: "conversations",
|
||||||
element: <ConversationsTab />,
|
element: <ConversationsTab />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "discover",
|
||||||
|
element: <Discover />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "tag/:tag",
|
path: "tag/:tag",
|
||||||
element: <TagsTab />,
|
element: <TagsTab />,
|
||||||
|
@ -30,6 +30,7 @@ import { WalletRoutes } from "Pages/WalletPage";
|
|||||||
import NostrLinkHandler from "Pages/NostrLinkHandler";
|
import NostrLinkHandler from "Pages/NostrLinkHandler";
|
||||||
import Thread from "Element/Thread";
|
import Thread from "Element/Thread";
|
||||||
import { SubscribeRoutes } from "Pages/subscribe";
|
import { SubscribeRoutes } from "Pages/subscribe";
|
||||||
|
import Discover from "Pages/Discover";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP query provider
|
* HTTP query provider
|
||||||
|
Loading…
Reference in New Issue
Block a user