import "./ProfilePage.css";
import { useEffect, useState } from "react";
import { FormattedMessage } from "react-intl";
import { useNavigate, useParams } from "react-router-dom";
import {
encodeTLV,
encodeTLVEntries,
EventKind,
HexKey,
NostrLink,
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/Note";
import Bookmarks from "Element/Bookmarks";
import RelaysMetadata from "Element/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/Zap";
import FollowButton from "Element/FollowButton";
import { parseId, hexToBech32 } from "SnortUtils";
import Avatar from "Element/Avatar";
import Timeline from "Element/Timeline";
import Text from "Element/Text";
import SendSats from "Element/SendSats";
import Nip05 from "Element/Nip05";
import Copy from "Element/Copy";
import ProfileImage from "Element/ProfileImage";
import BlockList from "Element/BlockList";
import MutedList from "Element/MutedList";
import FollowsList from "Element/FollowListBase";
import IconButton from "Element/IconButton";
import FollowsYou from "Element/FollowsYou";
import QrCode from "Element/QrCode";
import Modal from "Element/Modal";
import BadgeList from "Element/BadgeList";
import { ProxyImg } from "Element/ProxyImg";
import useHorizontalScroll from "Hooks/useHorizontalScroll";
import { EmailRegex } from "Const";
import { getNip05PubKey } from "Pages/LoginPage";
import useLogin from "Hooks/useLogin";
import { ZapTarget } from "Zapper";
import { useStatusFeed } from "Feed/StatusFeed";
import messages from "./messages";
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 (
);
}
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() {
const params = useParams();
const navigate = useNavigate();
const [id, setId] = useState();
const user = useUserProfile(id);
const login = useLogin();
const loginPubKey = login.publicKey;
const isMe = loginPubKey === id;
const [showLnQr, setShowLnQr] = useState(false);
const [showProfileQr, setShowProfileQr] = useState(false);
const aboutText = user?.about || "";
const npub = !id?.startsWith(NostrPrefix.PublicKey) ? hexToBech32(NostrPrefix.PublicKey, id || undefined) : id;
const lnurl = (() => {
try {
return new LNURL(user?.lud16 || user?.lud06 || "");
} catch {
// ignored
}
})();
const showBadges = login.preferences.showBadges ?? false;
const showStatus = login.preferences.showStatus ?? true;
const website_url =
user?.website && !user.website.startsWith("http") ? "https://" + user.website : user?.website || "";
// feeds
const { blocked } = useModeration();
const pinned = usePinnedFeed(id);
const muted = useMutedFeed(id);
const badges = useProfileBadges(showBadges ? id : undefined);
const follows = useFollowsFeed(id);
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),
) as Tab[];
const horizontalScroll = useHorizontalScroll();
useEffect(() => {
if (params.id?.match(EmailRegex)) {
getNip05PubKey(params.id).then(a => {
setId(a);
});
} else {
const nav = tryParseNostrLink(params.id ?? "");
if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) {
// todo: use relays if any for nprofile
setId(nav.id);
} else {
setId(parseId(params.id ?? ""));
}
}
setTab(ProfileTab.Notes);
}, [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?.display_name || user?.name || "Nostrich"}
{user?.nip05 && }
{showBadges && }
{showStatus && <>{musicStatus()}>}
{links()}
>
);
}
function tryFormatWebsite(url: string) {
try {
const u = new URL(url);
return `${u.hostname}${u.pathname !== "/" ? u.pathname : ""}`;
} catch {
// ignore
}
return url;
}
function links() {
return (
<>
{user?.website && (
)}
{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 NOTES:
return (
<>
{pinned
.filter(a => a.kind === EventKind.TextNote)
.map(n => {
return (
);
})}
>
);
case ZAPS: {
return ;
}
case FOLLOWS: {
if (isMe) {
return ;
} else {
return ;
}
}
case FOLLOWERS: {
return ;
}
case MUTED: {
return ;
}
case BLOCKED: {
return ;
}
case RELAYS: {
return ;
}
case BOOKMARKS: {
return ;
}
}
}
function avatar() {
return (
{renderIcons()}
{!isMe && id && }
);
}
function renderIcons() {
if (!id) return;
const link = encodeTLV(NostrPrefix.Profile, id);
return (
setShowProfileQr(true)}>
{showProfileQr && (
setShowProfileQr(false)}>
)}
{isMe ? (
<>
>
) : (
<>
{lnurl && (
setShowLnQr(true)}>
)}
{loginPubKey && (
<>
navigate(
`/messages/${encodeTLVEntries("chat4" as NostrPrefix, {
type: TLVEntryType.Author,
length: 32,
value: id,
})}`,
)
}>
>
)}
>
)}
);
}
function userDetails() {
if (!id) return;
return (
{username()}
{bio()}
);
}
function renderTab(v: Tab) {
return ;
}
const w = window.document.querySelector(".page")?.clientWidth;
return (
<>
{user?.banner &&
}
{avatar()}
{userDetails()}
{[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows].map(renderTab)}
{optionalTabs.map(renderTab)}
{isMe && blocked.length > 0 && renderTab(ProfileTab.Blocked)}
{tabContent()}
>
);
}