snort/src/Pages/ProfilePage.tsx

331 lines
9.3 KiB
TypeScript
Raw Normal View History

2022-12-27 23:46:13 +00:00
import "./ProfilePage.css";
2023-01-10 10:30:33 +00:00
import { useEffect, useMemo, useState } from "react";
2023-02-08 21:10:26 +00:00
import { useIntl, FormattedMessage } from "react-intl";
2023-01-09 11:00:23 +00:00
import { useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
2022-12-29 22:23:41 +00:00
2023-02-03 21:38:14 +00:00
import { formatShort } from "Number";
2023-02-06 21:42:47 +00:00
import { Tab, TabElement } from "Element/Tabs";
2023-01-25 18:08:53 +00:00
import Link from "Icons/Link";
2023-01-29 22:11:04 +00:00
import Qr from "Icons/Qr";
2023-01-25 18:08:53 +00:00
import Zap from "Icons/Zap";
2023-01-28 21:43:56 +00:00
import Envelope from "Icons/Envelope";
import { useUserProfile } from "Feed/ProfileFeed";
2023-02-03 21:38:14 +00:00
import useZapsFeed from "Feed/ZapsFeed";
2023-02-04 10:16:08 +00:00
import { default as ZapElement, parseZap } from "Element/Zap";
2023-01-20 11:11:50 +00:00
import FollowButton from "Element/FollowButton";
import { extractLnAddress, parseId, hexToBech32 } from "Util";
import Avatar from "Element/Avatar";
2023-01-26 11:34:18 +00:00
import LogoutButton from "Element/LogoutButton";
2023-01-20 11:11:50 +00:00
import Timeline from "Element/Timeline";
import Text from "Element/Text";
2023-02-07 13:32:32 +00:00
import SendSats from "Element/SendSats";
2023-01-20 11:11:50 +00:00
import Nip05 from "Element/Nip05";
import Copy from "Element/Copy";
import ProfilePreview from "Element/ProfilePreview";
2023-02-06 21:42:47 +00:00
import ProfileImage from "Element/ProfileImage";
2023-01-20 11:11:50 +00:00
import FollowersList from "Element/FollowersList";
import BlockList from "Element/BlockList";
2023-01-26 11:34:18 +00:00
import MutedList from "Element/MutedList";
2023-01-20 11:11:50 +00:00
import FollowsList from "Element/FollowsList";
2023-01-29 22:11:04 +00:00
import IconButton from "Element/IconButton";
2023-01-20 11:11:50 +00:00
import { RootState } from "State/Store";
import { HexKey } from "Nostr";
import FollowsYou from "Element/FollowsYou";
2023-01-29 22:11:04 +00:00
import QrCode from "Element/QrCode";
import Modal from "Element/Modal";
import { ProxyImg } from "Element/ProxyImg";
2023-02-07 13:32:32 +00:00
import useHorizontalScroll from "Hooks/useHorizontalScroll";
2023-01-10 10:30:33 +00:00
2023-02-08 21:10:26 +00:00
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;
2022-12-18 14:51:32 +00:00
export default function ProfilePage() {
2023-02-08 21:10:26 +00:00
const { formatMessage } = useIntl();
2023-01-31 16:07:46 +00:00
const params = useParams();
const navigate = useNavigate();
2023-02-07 19:47:57 +00:00
const id = useMemo(() => parseId(params.id ?? ""), [params]);
2023-01-31 16:07:46 +00:00
const user = useUserProfile(id);
const loggedOut = useSelector<RootState, boolean | undefined>(
(s) => s.login.loggedOut
);
const loginPubKey = useSelector<RootState, HexKey | undefined>(
(s) => s.login.publicKey
);
const follows = useSelector<RootState, HexKey[]>((s) => s.login.follows);
2023-01-31 16:07:46 +00:00
const isMe = loginPubKey === id;
const [showLnQr, setShowLnQr] = useState<boolean>(false);
const [showProfileQr, setShowProfileQr] = useState<boolean>(false);
const aboutText = user?.about || "";
const about = Text({
content: aboutText,
tags: [],
users: new Map(),
creator: "",
});
2023-01-31 16:07:46 +00:00
const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || "");
const website_url =
user?.website && !user.website.startsWith("http")
? "https://" + user.website
: user?.website || "";
const zapFeed = useZapsFeed(id);
2023-02-03 21:38:14 +00:00
const zaps = useMemo(() => {
const profileZaps = zapFeed.store.notes
.map(parseZap)
.filter((z) => z.valid && z.p === id && !z.e && z.zapper !== id);
profileZaps.sort((a, b) => b.amount - a.amount);
return profileZaps;
}, [zapFeed.store, id]);
const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0);
const horizontalScroll = useHorizontalScroll();
2023-02-08 21:10:26 +00:00
const ProfileTab = {
Notes: { text: formatMessage(messages.Notes), value: NOTES },
Reactions: { text: formatMessage(messages.Reactions), value: REACTIONS },
Followers: { text: formatMessage(messages.Followers), value: FOLLOWERS },
Follows: { text: formatMessage(messages.Follows), value: FOLLOWS },
Zaps: { text: formatMessage(messages.Zaps), value: ZAPS },
Muted: { text: formatMessage(messages.Muted), value: MUTED },
Blocked: { text: formatMessage(messages.Blocked), value: BLOCKED },
};
const [tab, setTab] = useState<Tab>(ProfileTab.Notes);
2023-01-10 10:30:33 +00:00
2023-01-31 16:07:46 +00:00
useEffect(() => {
setTab(ProfileTab.Notes);
}, [params]);
2022-12-28 14:51:33 +00:00
2023-01-31 16:07:46 +00:00
function username() {
return (
<div className="name">
<h2>
{user?.display_name || user?.name || "Nostrich"}
2023-01-31 16:07:46 +00:00
<FollowsYou pubkey={id} />
</h2>
{user?.nip05 && <Nip05 nip05={user.nip05} pubkey={user.pubkey} />}
<Copy text={params.id || ""} />
{links()}
</div>
);
2023-01-31 16:07:46 +00:00
}
2023-01-31 16:07:46 +00:00
function links() {
return (
<div className="links">
{user?.website && (
<div className="website f-ellipsis">
<span className="link-icon">
<Link />
</span>
<a href={website_url} target="_blank" rel="noreferrer">
{user.website}
</a>
2023-01-31 16:07:46 +00:00
</div>
)}
2023-01-10 07:09:09 +00:00
2023-02-06 21:42:47 +00:00
{lnurl && (
<div className="lnurl f-ellipsis" onClick={() => setShowLnQr(true)}>
<span className="link-icon">
<Zap />
</span>
{lnurl}
</div>
)}
2023-02-07 13:32:32 +00:00
<SendSats
svc={lnurl}
show={showLnQr}
onClose={() => setShowLnQr(false)}
author={id}
target={user?.display_name || user?.name}
/>
2023-01-31 16:07:46 +00:00
</div>
);
2023-01-31 16:07:46 +00:00
}
2023-01-01 20:31:09 +00:00
2023-01-31 16:07:46 +00:00
function bio() {
return (
aboutText.length > 0 && (
<>
<div className="details">{about}</div>
</>
)
);
2023-01-31 16:07:46 +00:00
}
2022-12-28 23:28:28 +00:00
2023-01-31 16:07:46 +00:00
function tabContent() {
2023-02-08 21:10:26 +00:00
switch (tab.value) {
case NOTES:
return (
<Timeline
key={id}
subject={{
type: "pubkey",
items: [id],
discriminator: id.slice(0, 12),
}}
postsOnly={false}
method={"TIME_RANGE"}
ignoreModeration={true}
/>
);
2023-02-08 21:10:26 +00:00
case ZAPS: {
2023-02-04 10:16:08 +00:00
return (
<div className="main-content">
2023-02-08 21:10:26 +00:00
<h4 className="zaps-total">
<FormattedMessage
{...messages.Sats}
values={{ n: formatShort(zapsTotal) }}
/>
</h4>
{zaps.map((z) => (
<ZapElement showZapped={false} zap={z} />
))}
2023-02-04 10:16:08 +00:00
</div>
);
2023-02-04 10:16:08 +00:00
}
2023-02-08 21:10:26 +00:00
case FOLLOWS: {
2023-01-31 16:07:46 +00:00
if (isMe) {
return (
<div className="main-content">
2023-02-08 21:10:26 +00:00
<h4>
<FormattedMessage
{...messages.Following}
values={{ n: follows.length }}
/>
</h4>
{follows.map((a) => (
<ProfilePreview
key={a}
pubkey={a.toLowerCase()}
options={{ about: false }}
/>
))}
2023-01-31 16:07:46 +00:00
</div>
);
} else {
return <FollowsList pubkey={id} />;
2023-01-10 10:30:33 +00:00
}
2023-01-31 16:07:46 +00:00
}
2023-02-08 21:10:26 +00:00
case FOLLOWERS: {
return <FollowersList pubkey={id} />;
2023-01-31 16:07:46 +00:00
}
2023-02-08 21:10:26 +00:00
case MUTED: {
return isMe ? <BlockList variant="muted" /> : <MutedList pubkey={id} />;
2023-01-31 16:07:46 +00:00
}
2023-02-08 21:10:26 +00:00
case BLOCKED: {
return isMe ? <BlockList variant="blocked" /> : null;
2023-01-31 16:07:46 +00:00
}
2023-01-10 10:30:33 +00:00
}
2023-01-31 16:07:46 +00:00
}
2023-01-10 10:30:33 +00:00
2023-01-31 16:07:46 +00:00
function avatar() {
return (
<div className="avatar-wrapper">
<Avatar user={user} />
</div>
);
2023-01-31 16:07:46 +00:00
}
2023-01-29 22:11:04 +00:00
function renderIcons() {
return (
<div className="icon-actions">
<IconButton onClick={() => setShowProfileQr(true)}>
<Qr width={14} height={16} />
</IconButton>
{showProfileQr && (
<Modal className="qr-modal" onClose={() => setShowProfileQr(false)}>
2023-02-06 21:42:47 +00:00
<ProfileImage pubkey={id} />
<QrCode
data={`nostr:${hexToBech32("npub", id)}`}
link={undefined}
className="m10"
/>
2023-01-29 22:11:04 +00:00
</Modal>
)}
{isMe ? (
<>
<LogoutButton />
<button type="button" onClick={() => navigate("/settings")}>
2023-02-08 21:10:26 +00:00
<FormattedMessage {...messages.Settings} />
2023-01-29 22:11:04 +00:00
</button>
</>
) : (
<>
{lnurl && (
<IconButton onClick={() => setShowLnQr(true)}>
<Zap width={14} height={16} />
</IconButton>
)}
2023-01-29 22:11:04 +00:00
{!loggedOut && (
<>
<IconButton
onClick={() =>
navigate(`/messages/${hexToBech32("npub", id)}`)
}
>
2023-01-31 16:07:46 +00:00
<Envelope width={16} height={13} />
2023-01-29 22:11:04 +00:00
</IconButton>
</>
)}
</>
)}
</div>
);
2023-01-29 22:11:04 +00:00
}
2023-01-31 16:07:46 +00:00
function userDetails() {
2022-12-18 14:51:32 +00:00
return (
2023-01-31 16:07:46 +00:00
<div className="details-wrapper">
{username()}
<div className="profile-actions">
{renderIcons()}
{!isMe && <FollowButton pubkey={id} />}
</div>
{bio()}
</div>
);
2023-01-31 16:07:46 +00:00
}
2023-02-06 21:42:47 +00:00
function renderTab(v: Tab) {
return <TabElement t={v} tab={tab} setTab={setTab} />;
2023-01-31 16:07:46 +00:00
}
const w = window.document.querySelector(".page")?.clientWidth;
return (
<>
<div className="profile flex">
{user?.banner && (
<ProxyImg
alt="banner"
className="banner"
src={user.banner}
size={w}
/>
)}
2023-01-31 16:07:46 +00:00
<div className="profile-wrapper flex">
{avatar()}
{userDetails()}
</div>
</div>
2023-02-07 13:32:32 +00:00
<div className="tabs main-content" ref={horizontalScroll}>
{[
ProfileTab.Notes,
ProfileTab.Followers,
ProfileTab.Follows,
ProfileTab.Zaps,
ProfileTab.Muted,
].map(renderTab)}
2023-01-31 16:07:46 +00:00
{isMe && renderTab(ProfileTab.Blocked)}
</div>
{tabContent()}
</>
);
}