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";
|
2023-01-27 21:38:41 +00:00
|
|
|
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";
|
2023-02-07 20:04:50 +00:00
|
|
|
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";
|
2023-01-27 21:10:14 +00:00
|
|
|
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";
|
2023-02-07 20:04:50 +00:00
|
|
|
import FollowsYou from "Element/FollowsYou";
|
2023-01-29 22:11:04 +00:00
|
|
|
import QrCode from "Element/QrCode";
|
|
|
|
import Modal from "Element/Modal";
|
2023-02-07 20:04:50 +00:00
|
|
|
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();
|
|
|
|
const id = useMemo(() => parseId(params.id!), [params]);
|
|
|
|
const user = useUserProfile(id);
|
2023-02-07 20:04:50 +00:00
|
|
|
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);
|
2023-02-07 20:04:50 +00:00
|
|
|
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 || "");
|
2023-02-07 20:04:50 +00:00
|
|
|
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(() => {
|
2023-02-07 20:04:50 +00:00
|
|
|
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>
|
2023-02-07 20:04:50 +00:00
|
|
|
{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-02-07 20:04:50 +00:00
|
|
|
);
|
2023-01-31 16:07:46 +00:00
|
|
|
}
|
2023-01-19 18:00:56 +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>
|
2023-02-07 20:04:50 +00:00
|
|
|
<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-02-07 20:04:50 +00:00
|
|
|
);
|
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() {
|
2023-02-07 20:04:50 +00:00
|
|
|
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:
|
2023-02-07 20:04:50 +00:00
|
|
|
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>
|
2023-02-07 20:04:50 +00:00
|
|
|
{zaps.map((z) => (
|
|
|
|
<ZapElement showZapped={false} zap={z} />
|
|
|
|
))}
|
2023-02-04 10:16:08 +00:00
|
|
|
</div>
|
2023-02-07 20:04:50 +00:00
|
|
|
);
|
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>
|
2023-02-07 20:04:50 +00:00
|
|
|
{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: {
|
2023-02-07 20:04:50 +00:00
|
|
|
return <FollowersList pubkey={id} />;
|
2023-01-31 16:07:46 +00:00
|
|
|
}
|
2023-02-08 21:10:26 +00:00
|
|
|
case MUTED: {
|
2023-02-07 20:04:50 +00:00
|
|
|
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: {
|
2023-02-07 20:04:50 +00:00
|
|
|
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-02-07 20:04:50 +00:00
|
|
|
);
|
2023-01-31 16:07:46 +00:00
|
|
|
}
|
2023-01-14 13:28:22 +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} />
|
2023-02-07 20:04:50 +00:00
|
|
|
<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>
|
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<>
|
2023-02-05 11:30:26 +00:00
|
|
|
{lnurl && (
|
|
|
|
<IconButton onClick={() => setShowLnQr(true)}>
|
|
|
|
<Zap width={14} height={16} />
|
|
|
|
</IconButton>
|
|
|
|
)}
|
2023-01-29 22:11:04 +00:00
|
|
|
{!loggedOut && (
|
|
|
|
<>
|
2023-02-07 20:04:50 +00:00
|
|
|
<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-02-07 20:04:50 +00:00
|
|
|
);
|
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-02-07 20:04:50 +00:00
|
|
|
);
|
2023-01-31 16:07:46 +00:00
|
|
|
}
|
|
|
|
|
2023-02-06 21:42:47 +00:00
|
|
|
function renderTab(v: Tab) {
|
2023-02-07 20:04:50 +00:00
|
|
|
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">
|
2023-02-07 20:04:50 +00:00
|
|
|
{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}>
|
2023-02-07 20:04:50 +00:00
|
|
|
{[
|
|
|
|
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()}
|
|
|
|
</>
|
2023-02-07 20:04:50 +00:00
|
|
|
);
|
2023-01-10 09:18:46 +00:00
|
|
|
}
|