diff --git a/packages/app/src/Element/Event/Thread.tsx b/packages/app/src/Element/Event/Thread.tsx index 6fd8192c..20ccb721 100644 --- a/packages/app/src/Element/Event/Thread.tsx +++ b/packages/app/src/Element/Event/Thread.tsx @@ -205,9 +205,10 @@ const TierThree = ({ active, isLastSubthread, notes, related, chains, onNavigate ); }; -export function ThreadRoute() { +export function ThreadRoute({ id }: { id?: string }) { const params = useParams(); - const link = parseNostrLink(params.id ?? "", NostrPrefix.Note); + const resolvedId = id ?? params.id; + const link = parseNostrLink(resolvedId ?? "", NostrPrefix.Note); return ( diff --git a/packages/app/src/Element/Text.css b/packages/app/src/Element/Text.css index 4a458edb..238a2682 100644 --- a/packages/app/src/Element/Text.css +++ b/packages/app/src/Element/Text.css @@ -113,3 +113,10 @@ height: 100%; display: block; } + +.gallery:not(:first-child), +img:not(:first-child), +video:not(:first-child), +.link-preview-container:not(:first-child) { + margin-top: 10px; +} diff --git a/packages/app/src/Element/User/Nip05.tsx b/packages/app/src/Element/User/Nip05.tsx index 2f9da25a..be43cee5 100644 --- a/packages/app/src/Element/User/Nip05.tsx +++ b/packages/app/src/Element/User/Nip05.tsx @@ -2,7 +2,7 @@ import "./Nip05.css"; import { HexKey } from "@snort/system"; import { useUserProfile } from "@snort/system-react"; -export function useIsVerified(pubkey: HexKey, bypassCheck?: boolean) { +export function useIsVerified(pubkey?: HexKey, bypassCheck?: boolean) { const profile = useUserProfile(pubkey); return { isVerified: bypassCheck || profile?.isNostrAddressValid }; } diff --git a/packages/app/src/Pages/NostrLinkHandler.tsx b/packages/app/src/Pages/NostrLinkHandler.tsx index 99217d26..801ab794 100644 --- a/packages/app/src/Pages/NostrLinkHandler.tsx +++ b/packages/app/src/Pages/NostrLinkHandler.tsx @@ -1,32 +1,33 @@ import { NostrPrefix, tryParseNostrLink } from "@snort/system"; import { useEffect, useState } from "react"; import FormattedMessage from "Element/FormattedMessage"; -import { useNavigate, useParams } from "react-router-dom"; +import { useParams } from "react-router-dom"; import Spinner from "Icons/Spinner"; -import { profileLink } from "SnortUtils"; import { getNip05PubKey } from "Pages/LoginPage"; +import ProfilePage from "Pages/Profile/ProfilePage"; +import { ThreadRoute } from "Element/Event/Thread"; export default function NostrLinkHandler() { const params = useParams(); - const navigate = useNavigate(); - const [loading, setLoading] = useState(true); + const [renderComponent, setRenderComponent] = useState(null); + const link = decodeURIComponent(params["*"] ?? "").toLowerCase(); async function handleLink(link: string) { const nav = tryParseNostrLink(link); if (nav) { if (nav.type === NostrPrefix.Event || nav.type === NostrPrefix.Note || nav.type === NostrPrefix.Address) { - navigate(`/e/${nav.encode()}`); + setRenderComponent(); // Directly render ThreadRoute } else if (nav.type === NostrPrefix.PublicKey || nav.type === NostrPrefix.Profile) { - navigate(`/p/${nav.encode()}`); + setRenderComponent(); // Directly render ProfilePage } } else { try { const pubkey = await getNip05PubKey(`${link}@${process.env.NIP05_DOMAIN}`); if (pubkey) { - navigate(profileLink(pubkey)); + setRenderComponent(); // Directly render ProfilePage } } catch { //ignored @@ -41,6 +42,10 @@ export default function NostrLinkHandler() { } }, [link]); + if (renderComponent) { + return renderComponent; + } + return (
{loading ? ( diff --git a/packages/app/src/Pages/ProfilePage.css b/packages/app/src/Pages/Profile/ProfilePage.css similarity index 100% rename from packages/app/src/Pages/ProfilePage.css rename to packages/app/src/Pages/Profile/ProfilePage.css diff --git a/packages/app/src/Pages/ProfilePage.tsx b/packages/app/src/Pages/Profile/ProfilePage.tsx similarity index 71% rename from packages/app/src/Pages/ProfilePage.tsx rename to packages/app/src/Pages/Profile/ProfilePage.tsx index 1660f462..81b15b6e 100644 --- a/packages/app/src/Pages/ProfilePage.tsx +++ b/packages/app/src/Pages/Profile/ProfilePage.tsx @@ -2,36 +2,19 @@ import "./ProfilePage.css"; import { useEffect, useState } from "react"; import FormattedMessage from "Element/FormattedMessage"; import { useNavigate, useParams } from "react-router-dom"; -import { - encodeTLV, - encodeTLVEntries, - EventKind, - HexKey, - NostrLink, - NostrPrefix, - TLVEntryType, - tryParseNostrLink, -} from "@snort/system"; +import { encodeTLV, encodeTLVEntries, EventKind, NostrPrefix, TLVEntryType, tryParseNostrLink } from "@snort/system"; import { LNURL } from "@snort/shared"; import { useUserProfile } from "@snort/system-react"; import { findTag, getReactions, unwrap } from "SnortUtils"; -import { formatShort } from "Number"; import Note from "Element/Event/Note"; -import Bookmarks from "Element/Bookmarks"; -import RelaysMetadata from "Element/Relay/RelaysMetadata"; import { Tab, TabElement } from "Element/Tabs"; import Icon from "Icons/Icon"; import useMutedFeed from "Feed/MuteList"; -import useRelaysFeed from "Feed/RelaysFeed"; import usePinnedFeed from "Feed/PinnedFeed"; -import useBookmarkFeed from "Feed/BookmarkFeed"; -import useFollowersFeed from "Feed/FollowersFeed"; import useFollowsFeed from "Feed/FollowsFeed"; import useProfileBadges from "Feed/BadgesFeed"; import useModeration from "Hooks/useModeration"; -import useZapsFeed from "Feed/ZapsFeed"; -import { default as ZapElement } from "Element/Event/Zap"; import FollowButton from "Element/User/FollowButton"; import { parseId, hexToBech32 } from "SnortUtils"; import Avatar from "Element/User/Avatar"; @@ -57,62 +40,24 @@ import useLogin from "Hooks/useLogin"; import { ZapTarget } from "Zapper"; import { useStatusFeed } from "Feed/StatusFeed"; -import messages from "./messages"; +import messages from "../messages"; import { SpotlightMediaModal } from "Element/Deck/SpotlightMedia"; +import ProfileTab, { + BookMarksTab, + FollowersTab, + FollowsTab, + ProfileTabType, + RelaysTab, + ZapsProfileTab, +} from "Pages/Profile/ProfileTab"; +import DisplayName from "../../Element/User/DisplayName"; import { UserWebsiteLink } from "Element/User/UserWebsiteLink"; -const NOTES = 0; -const REACTIONS = 1; -const FOLLOWERS = 2; -const FOLLOWS = 3; -const ZAPS = 4; -const MUTED = 5; -const BLOCKED = 6; -const RELAYS = 7; -const BOOKMARKS = 8; - -function ZapsProfileTab({ id }: { id: HexKey }) { - const zaps = useZapsFeed(new NostrLink(NostrPrefix.PublicKey, id)); - const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0); - return ( -
-

- -

- {zaps.map(z => ( - - ))} -
- ); +interface ProfilePageProps { + id?: string; } -function FollowersTab({ id }: { id: HexKey }) { - const followers = useFollowersFeed(id); - return ; -} - -function FollowsTab({ id }: { id: HexKey }) { - const follows = useFollowsFeed(id); - return ; -} - -function RelaysTab({ id }: { id: HexKey }) { - const relays = useRelaysFeed(id); - return ; -} - -function BookMarksTab({ id }: { id: HexKey }) { - const bookmarks = useBookmarkFeed(id); - return ( - e.kind === EventKind.TextNote)} - related={bookmarks.filter(e => e.kind !== EventKind.TextNote)} - /> - ); -} - -export default function ProfilePage() { +export default function ProfilePage({ id: propId }: ProfilePageProps) { const params = useParams(); const navigate = useNavigate(); const [id, setId] = useState(); @@ -145,89 +90,6 @@ export default function ProfilePage() { const status = useStatusFeed(showStatus ? id : undefined, true); // tabs - const ProfileTab = { - Notes: { - text: ( - <> - - - - ), - value: NOTES, - }, - Reactions: { - text: ( - <> - - - - ), - value: REACTIONS, - }, - Followers: { - text: ( - <> - - - - ), - value: FOLLOWERS, - }, - Follows: { - text: ( - <> - - - - ), - value: FOLLOWS, - }, - Zaps: { - text: ( - <> - - - - ), - value: ZAPS, - }, - Muted: { - text: ( - <> - - - - ), - value: MUTED, - }, - Blocked: { - text: ( - <> - - - - ), - value: BLOCKED, - }, - Relays: { - text: ( - <> - - - - ), - value: RELAYS, - }, - Bookmarks: { - text: ( - <> - - - - ), - value: BOOKMARKS, - }, - } as { [key: string]: Tab }; const [tab, setTab] = useState(ProfileTab.Notes); const optionalTabs = [ProfileTab.Zaps, ProfileTab.Relays, ProfileTab.Bookmarks, ProfileTab.Muted].filter(a => unwrap(a), @@ -235,21 +97,22 @@ export default function ProfilePage() { const horizontalScroll = useHorizontalScroll(); useEffect(() => { - if (params.id?.match(EmailRegex)) { - getNip05PubKey(params.id).then(a => { + const resolvedId = propId || params.id; + if (resolvedId?.match(EmailRegex)) { + getNip05PubKey(resolvedId).then(a => { setId(a); }); } else { - const nav = tryParseNostrLink(params.id ?? ""); + const nav = tryParseNostrLink(resolvedId ?? ""); if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) { // todo: use relays if any for nprofile setId(nav.id); } else { - setId(parseId(params.id ?? "")); + setId(parseId(resolvedId ?? "")); } } setTab(ProfileTab.Notes); - }, [params]); + }, [propId, params]); function musicStatus() { if (!status.music) return; @@ -274,12 +137,21 @@ export default function ProfilePage() { return inner(); } + useEffect(() => { + if (user?.nip05 && user?.isNostrAddressValid) { + if (user.nip05.endsWith(`@${process.env.NIP05_DOMAIN}`)) { + const username = user.nip05?.replace(`@${process.env.NIP05_DOMAIN}`, ""); + navigate(`/${username}`, { replace: true }); + } + } + }, [user?.isNostrAddressValid, user?.nip05]); + function username() { return ( <>

- {user?.display_name || user?.name || "Nostrich"} +

{user?.nip05 && } @@ -350,7 +222,7 @@ export default function ProfilePage() { if (!id) return null; switch (tab.value) { - case NOTES: + case ProfileTabType.NOTES: return ( <> {pinned @@ -380,29 +252,29 @@ export default function ProfilePage() { /> ); - case ZAPS: { + case ProfileTabType.ZAPS: { return ; } - case FOLLOWS: { + case ProfileTabType.FOLLOWS: { if (isMe) { return ; } else { return ; } } - case FOLLOWERS: { + case ProfileTabType.FOLLOWERS: { return ; } - case MUTED: { + case ProfileTabType.MUTED: { return ; } - case BLOCKED: { + case ProfileTabType.BLOCKED: { return ; } - case RELAYS: { + case ProfileTabType.RELAYS: { return ; } - case BOOKMARKS: { + case ProfileTabType.BOOKMARKS: { return ; } } diff --git a/packages/app/src/Pages/Profile/ProfileTab.tsx b/packages/app/src/Pages/Profile/ProfileTab.tsx new file mode 100644 index 00000000..c03a536a --- /dev/null +++ b/packages/app/src/Pages/Profile/ProfileTab.tsx @@ -0,0 +1,154 @@ +import useZapsFeed from "../../Feed/ZapsFeed"; +import FormattedMessage from "../../Element/FormattedMessage"; +import messages from "../messages"; +import { formatShort } from "../../Number"; +import useFollowersFeed from "../../Feed/FollowersFeed"; +import FollowsList from "../../Element/User/FollowListBase"; +import useFollowsFeed from "../../Feed/FollowsFeed"; +import useRelaysFeed from "../../Feed/RelaysFeed"; +import RelaysMetadata from "../../Element/Relay/RelaysMetadata"; +import useBookmarkFeed from "../../Feed/BookmarkFeed"; +import Bookmarks from "../../Element/Bookmarks"; +import Icon from "../../Icons/Icon"; +import { Tab } from "../../Element/Tabs"; +import { EventKind, HexKey, NostrLink, NostrPrefix } from "@snort/system"; +import { default as ZapElement } from "Element/Event/Zap"; + +export enum ProfileTabType { + NOTES = 0, + REACTIONS = 1, + FOLLOWERS = 2, + FOLLOWS = 3, + ZAPS = 4, + MUTED = 5, + BLOCKED = 6, + RELAYS = 7, + BOOKMARKS = 8, +} + +export function ZapsProfileTab({ id }: { id: HexKey }) { + const zaps = useZapsFeed(new NostrLink(NostrPrefix.PublicKey, id)); + const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0); + return ( +
+

+ +

+ {zaps.map(z => ( + + ))} +
+ ); +} + +export function FollowersTab({ id }: { id: HexKey }) { + const followers = useFollowersFeed(id); + return ; +} + +export function FollowsTab({ id }: { id: HexKey }) { + const follows = useFollowsFeed(id); + return ; +} + +export function RelaysTab({ id }: { id: HexKey }) { + const relays = useRelaysFeed(id); + return ; +} + +export function BookMarksTab({ id }: { id: HexKey }) { + const bookmarks = useBookmarkFeed(id); + return ( + e.kind === EventKind.TextNote)} + related={bookmarks.filter(e => e.kind !== EventKind.TextNote)} + /> + ); +} + +const ProfileTab = { + Notes: { + text: ( + <> + + + + ), + value: ProfileTabType.NOTES, + }, + Reactions: { + text: ( + <> + + + + ), + value: ProfileTabType.REACTIONS, + }, + Followers: { + text: ( + <> + + + + ), + value: ProfileTabType.FOLLOWERS, + }, + Follows: { + text: ( + <> + + + + ), + value: ProfileTabType.FOLLOWS, + }, + Zaps: { + text: ( + <> + + + + ), + value: ProfileTabType.ZAPS, + }, + Muted: { + text: ( + <> + + + + ), + value: ProfileTabType.MUTED, + }, + Blocked: { + text: ( + <> + + + + ), + value: ProfileTabType.BLOCKED, + }, + Relays: { + text: ( + <> + + + + ), + value: ProfileTabType.RELAYS, + }, + Bookmarks: { + text: ( + <> + + + + ), + value: ProfileTabType.BOOKMARKS, + }, +} as { [key: string]: Tab }; + +export default ProfileTab; diff --git a/packages/app/src/index.css b/packages/app/src/index.css index 3f498f57..4b11a8a4 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -107,6 +107,7 @@ body { color: var(--font-color); font-size: var(--font-size); overflow-x: hidden; + overflow-y: scroll; } code { diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index d63bad18..417a9395 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -24,7 +24,7 @@ import { IntlProvider } from "IntlProvider"; import { unwrap } from "SnortUtils"; import Layout from "Pages/Layout"; import LoginPage from "Pages/LoginPage"; -import ProfilePage from "Pages/ProfilePage"; +import ProfilePage from "Pages/Profile/ProfilePage"; import { RootRoutes, RootTabRoutes } from "Pages/Root"; import NotificationsPage from "Pages/Notifications"; import SettingsPage, { SettingsRoutes } from "Pages/SettingsPage";