import "./ProfilePage.css"; import { useEffect, useState } from "react"; import { FormattedMessage } from "react-intl"; import { Link, useLocation, useNavigate, useParams } from "react-router-dom"; import { encodeTLVEntries, EventKind, MetadataCache, NostrLink, NostrPrefix, TLVEntryType, tryParseNostrLink, } from "@snort/system"; import { fetchNip05Pubkey, LNURL } from "@snort/shared"; import { useUserProfile } from "@snort/system-react"; import { findTag, getLinkReactions, hexToBech32, parseId, unwrap } from "@/SnortUtils"; import Note from "@/Element/Event/Note"; import { Tab, TabElement } from "@/Element/Tabs"; import Icon from "@/Icons/Icon"; import useFollowsFeed from "@/Feed/FollowsFeed"; import useProfileBadges from "@/Feed/BadgesFeed"; import useModeration from "@/Hooks/useModeration"; import FollowButton from "@/Element/User/FollowButton"; import Avatar from "@/Element/User/Avatar"; import Timeline from "@/Element/Feed/Timeline"; import Text from "@/Element/Text"; import SendSats from "@/Element/SendSats"; import Nip05 from "@/Element/User/Nip05"; import Copy from "@/Element/Copy"; import ProfileImage from "@/Element/User/ProfileImage"; import BlockList from "@/Element/User/BlockList"; import MutedList from "@/Element/User/MutedList"; import FollowsList from "@/Element/User/FollowListBase"; import IconButton from "@/Element/Button/IconButton"; import FollowsYou from "@/Element/User/FollowsYou"; import QrCode from "@/Element/QrCode"; import Modal from "@/Element/Modal"; import BadgeList from "@/Element/User/BadgeList"; import { ProxyImg } from "@/Element/ProxyImg"; import useHorizontalScroll from "@/Hooks/useHorizontalScroll"; import { EmailRegex } from "@/Const"; import useLogin from "@/Hooks/useLogin"; import { ZapTarget } from "@/Zapper"; import { useStatusFeed } from "@/Feed/StatusFeed"; import { SpotlightMediaModal } from "@/Element/Spotlight/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"; import { useMuteList, usePinList } from "@/Hooks/useLists"; import FollowedBy from "@/Element/User/FollowedBy"; interface ProfilePageProps { id?: string; state?: MetadataCache; } export default function ProfilePage({ id: propId, state }: ProfilePageProps) { const params = useParams(); const location = useLocation(); const profileState = (location.state as MetadataCache | undefined) || state; const navigate = useNavigate(); const [id, setId] = useState(profileState?.pubkey); const [relays, setRelays] = useState>(); const user = useUserProfile(profileState ? undefined : id) || profileState; const login = useLogin(); const loginPubKey = login.publicKey; const isMe = loginPubKey === id; const [showLnQr, setShowLnQr] = useState(false); const [showProfileQr, setShowProfileQr] = useState(false); const [modalImage, setModalImage] = useState(""); const aboutText = user?.about || ""; const lnurl = (() => { try { return new LNURL(user?.lud16 || user?.lud06 || ""); } catch { // ignored } })(); const showBadges = login.appData.item.preferences.showBadges ?? false; const showStatus = login.appData.item.preferences.showStatus ?? true; // feeds const { blocked } = useModeration(); const pinned = usePinList(id); const muted = useMuteList(id); const badges = useProfileBadges(showBadges ? id : undefined); const follows = useFollowsFeed(id); const status = useStatusFeed(showStatus ? id : undefined, true); // tabs const [tab, setTab] = useState(ProfileTab.Notes); const optionalTabs = [ProfileTab.Zaps, ProfileTab.Relays, ProfileTab.Bookmarks, ProfileTab.Muted].filter(a => unwrap(a), ) as Tab[]; const horizontalScroll = useHorizontalScroll(); useEffect(() => { if ( user?.nip05 && user.nip05.endsWith(`@${CONFIG.nip05Domain}`) && (!("isNostrAddressValid" in user) || user.isNostrAddressValid) ) { const [username] = user.nip05.split("@"); navigate(`/${username}`, { replace: true }); } }, [user]); useEffect(() => { if (!id) { const resolvedId = propId || params.id; if (resolvedId?.match(EmailRegex)) { const [name, domain] = resolvedId.split("@"); fetchNip05Pubkey(name, domain).then(a => { setId(a); }); } else { const nav = tryParseNostrLink(resolvedId ?? ""); if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) { setId(nav.id); setRelays(nav.relays); } else { setId(parseId(resolvedId ?? "")); } } } setTab(ProfileTab.Notes); }, [id, propId, params]); function musicStatus() { if (!status.music) return; const link = findTag(status.music, "r"); const cover = findTag(status.music, "cover"); const inner = () => { return (
{cover && } 🎵 {unwrap(status.music).content}
); }; if (link) { return ( {inner()} ); } return inner(); } function username() { return ( <>

{user?.nip05 && }
{showBadges && } {showStatus && <>{musicStatus()}}
{links()}
); } function links() { return ( <> {lnurl && (
setShowLnQr(true)}> {lnurl.name}
)} setShowLnQr(false)} /> ); } function bio() { if (!id) return null; return ( aboutText.length > 0 && (
) ); } function tabContent() { if (!id) return null; switch (tab.value) { case ProfileTabType.NOTES: return ( <> {pinned .filter(a => a.kind === EventKind.TextNote) .map(n => { return ( ); })} ); case ProfileTabType.ZAPS: { return ; } case ProfileTabType.FOLLOWS: { if (isMe) { return ; } else { return ; } } case ProfileTabType.FOLLOWERS: { return ; } case ProfileTabType.MUTED: { return a.id)} />; } case ProfileTabType.BLOCKED: { return ; } case ProfileTabType.RELAYS: { return ; } case ProfileTabType.BOOKMARKS: { return ; } } } function avatar() { return (
setModalImage(user?.picture || "")} className="pointer" />
{renderIcons()} {!isMe && id && }
); } function renderIcons() { if (!id) return; const profileId = hexToBech32(CONFIG.profileLinkPrefix, id); return ( <> setShowProfileQr(true)} icon={{ name: "qr", size: 16 }} /> {showProfileQr && ( setShowProfileQr(false)}>
)} {isMe ? ( <> ) : ( <> {lnurl && setShowLnQr(true)} icon={{ name: "zap", size: 16 }} />} {loginPubKey && !login.readonly && ( <> navigate( `/messages/${encodeTLVEntries("chat4" as NostrPrefix, { type: TLVEntryType.Author, length: 32, value: id, })}`, ) } icon={{ name: "envelope", size: 16 }} /> )} )} ); } function userDetails() { if (!id) return; return (
{username()} {bio()} {user?.pubkey && loginPubKey && }
); } function renderTab(v: Tab) { return ; } const bannerWidth = Math.min(window.innerWidth, 940); return ( <>
{user?.banner && ( setModalImage(user.banner || "")} /> )}
{avatar()} {userDetails()}
{[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows].map(renderTab)} {optionalTabs.map(renderTab)} {isMe && blocked.length > 0 && renderTab(ProfileTab.Blocked)}
{tabContent()}
{modalImage && setModalImage("")} images={[modalImage]} idx={0} />} ); }