2022-12-27 23:46:13 +00:00
|
|
|
import "./ProfilePage.css";
|
2023-01-09 11:00:23 +00:00
|
|
|
|
2023-01-10 10:30:33 +00:00
|
|
|
import { useEffect, useMemo, useState } from "react";
|
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";
|
|
|
|
import Text from 'Element/Text'
|
|
|
|
import LNURLTip from "Element/LNURLTip";
|
|
|
|
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";
|
|
|
|
import FollowsYou from "Element/FollowsYou"
|
2023-01-29 22:11:04 +00:00
|
|
|
import QrCode from "Element/QrCode";
|
|
|
|
import Modal from "Element/Modal";
|
2023-01-31 15:53:47 +00:00
|
|
|
import { ProxyImg } from "Element/ProxyImg"
|
2023-01-10 10:30:33 +00:00
|
|
|
|
2023-02-06 21:42:47 +00:00
|
|
|
const ProfileTab = {
|
|
|
|
Notes: { text: "Notes", value: 0 },
|
|
|
|
Reactions: { text: "Reactions", value: 1 },
|
|
|
|
Followers: { text: "Followers", value: 2 },
|
|
|
|
Follows: { text: "Follows", value: 3 },
|
|
|
|
Zaps: { text: "Zaps", value: 4 },
|
|
|
|
Muted: { text: "Muted", value: 5 },
|
|
|
|
Blocked: { text: "Blocked", value: 6 },
|
|
|
|
}
|
2022-12-18 14:51:32 +00:00
|
|
|
|
|
|
|
export default function ProfilePage() {
|
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);
|
|
|
|
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);
|
|
|
|
const isMe = loginPubKey === id;
|
|
|
|
const [showLnQr, setShowLnQr] = useState<boolean>(false);
|
2023-02-06 21:42:47 +00:00
|
|
|
const [tab, setTab] = useState<Tab>(ProfileTab.Notes);
|
2023-01-31 16:07:46 +00:00
|
|
|
const [showProfileQr, setShowProfileQr] = useState<boolean>(false);
|
|
|
|
const aboutText = user?.about || ''
|
2023-02-03 16:43:21 +00:00
|
|
|
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-03 13:01:09 +00:00
|
|
|
const website_url = (user?.website && !user.website.startsWith("http"))
|
2023-02-06 22:33:01 +00:00
|
|
|
? "https://" + user.website
|
|
|
|
: user?.website || "";
|
2023-02-03 21:38:14 +00:00
|
|
|
const zapFeed = useZapsFeed(id)
|
|
|
|
const zaps = useMemo(() => {
|
2023-02-04 12:07:57 +00:00
|
|
|
const profileZaps = zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id && !z.e && z.zapper !== id)
|
2023-02-04 10:16:08 +00:00
|
|
|
profileZaps.sort((a, b) => b.amount - a.amount)
|
|
|
|
return profileZaps
|
2023-02-04 22:43:04 +00:00
|
|
|
}, [zapFeed.store, id])
|
2023-02-03 21:38:14 +00:00
|
|
|
const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0)
|
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'}
|
|
|
|
<FollowsYou pubkey={id} />
|
|
|
|
</h2>
|
|
|
|
{user?.nip05 && <Nip05 nip05={user.nip05} pubkey={user.pubkey} />}
|
|
|
|
<Copy text={params.id || ""} />
|
|
|
|
{links()}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
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-03 13:01:09 +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-03 21:38:14 +00:00
|
|
|
<LNURLTip svc={lnurl} show={showLnQr} onClose={() => setShowLnQr(false)} author={id} />
|
2023-01-31 16:07:46 +00:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
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>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
2022-12-28 23:28:28 +00:00
|
|
|
|
2023-01-31 16:07:46 +00:00
|
|
|
function tabContent() {
|
|
|
|
switch (tab) {
|
|
|
|
case ProfileTab.Notes:
|
2023-02-06 22:33:01 +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-04 10:16:08 +00:00
|
|
|
case ProfileTab.Zaps: {
|
|
|
|
return (
|
|
|
|
<div className="main-content">
|
2023-02-05 11:25:35 +00:00
|
|
|
<h4 className="zaps-total">{formatShort(zapsTotal)} sats</h4>
|
2023-02-04 10:16:08 +00:00
|
|
|
{zaps.map(z => <ZapElement showZapped={false} zap={z} />)}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-01-31 16:07:46 +00:00
|
|
|
case ProfileTab.Follows: {
|
|
|
|
if (isMe) {
|
|
|
|
return (
|
|
|
|
<div className="main-content">
|
|
|
|
<h4>Following {follows.length}</h4>
|
|
|
|
{follows.map(a => <ProfilePreview key={a} pubkey={a.toLowerCase()} options={{ about: false }} />)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return <FollowsList pubkey={id} />;
|
2023-01-10 10:30:33 +00:00
|
|
|
}
|
2023-01-31 16:07:46 +00:00
|
|
|
}
|
|
|
|
case ProfileTab.Followers: {
|
|
|
|
return <FollowersList pubkey={id} />
|
|
|
|
}
|
|
|
|
case ProfileTab.Muted: {
|
|
|
|
return isMe ? <BlockList variant="muted" /> : <MutedList pubkey={id} />
|
|
|
|
}
|
|
|
|
case ProfileTab.Blocked: {
|
|
|
|
return isMe ? <BlockList variant="blocked" /> : null
|
|
|
|
}
|
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-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-01-31 16:07:46 +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")}>
|
|
|
|
Settings
|
|
|
|
</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 && (
|
|
|
|
<>
|
|
|
|
<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-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>
|
2022-12-18 14:51:32 +00:00
|
|
|
)
|
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">
|
2023-02-01 13:22:11 +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-06 21:42:47 +00:00
|
|
|
<div className="tabs main-content">
|
2023-02-04 10:16:08 +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-01-10 09:18:46 +00:00
|
|
|
}
|