feat: profile tab top zappers

closes #662
This commit is contained in:
kieran 2024-09-13 11:21:50 +01:00
parent 267ebe2ed9
commit f335d48e69
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
8 changed files with 69 additions and 20 deletions

View File

@ -10,7 +10,7 @@ import Icon from "@/Components/Icons/Icon";
import Modal from "@/Components/Modal/Modal";
import TabSelectors, { Tab } from "@/Components/TabSelectors/TabSelectors";
import ProfileImage from "@/Components/User/ProfileImage";
import { formatShort } from "@/Utils/Number";
import ZapAmount from "@/Components/zap-amount";
import messages from "../../messages";
@ -84,10 +84,7 @@ const ReactionsModal = ({ onClose, event, initialTab = 0 }: ReactionsModalProps)
z =>
z.sender && (
<div key={z.id} className="reactions-item">
<div className="zap-reaction-icon">
<Icon name="zap-solid" size={20} className="text-zap" />
<span className="zap-amount">{formatShort(z.amount)}</span>
</div>
<ZapAmount n={z.amount} />
<ProfileImage
showProfileCard={true}
pubkey={z.anonZap ? "" : z.sender}

View File

@ -16,7 +16,7 @@ import { ProfileLink } from "./ProfileLink";
export interface ProfileImageProps {
pubkey: HexKey;
subHeader?: JSX.Element;
subHeader?: ReactNode;
showUsername?: boolean;
className?: string;
link?: string;

View File

@ -15,6 +15,7 @@ export interface ProfilePreviewProps {
linkToProfile?: boolean;
profileCards?: boolean;
};
subHeader?: ReactNode;
profile?: UserMetadata;
actions?: ReactNode;
className?: string;
@ -50,7 +51,7 @@ export default memo(function ProfilePreview(props: ProfilePreviewProps) {
pubkey={pubkey}
profile={props.profile}
link={options.linkToProfile ?? true ? undefined : ""}
subHeader={options.about ? <div className="about">{user?.about}</div> : undefined}
subHeader={options.about ? <div className="about">{user?.about}</div> : props.subHeader}
showProfileCard={options.profileCards}
/>
{props.actions ?? (

View File

@ -0,0 +1,12 @@
import { formatShort } from "@/Utils/Number";
import Icon from "./Icons/Icon";
export default function ZapAmount({ n }: { n: number }) {
return (
<div className="flex gap-2 items-center text-xl font-bold">
<Icon name="zap-solid" size={20} className="text-zap" />
<span>{formatShort(n)}</span>
</div>
);
}

View File

@ -15,9 +15,8 @@ export default function useZapsFeed(link?: NostrLink) {
const zaps = useMemo(() => {
if (zapsFeed) {
const profileZaps = zapsFeed.map(a => parseZap(a)).filter(z => z.valid);
profileZaps.sort((a, b) => b.amount - a.amount);
return profileZaps;
const parsedZaps = zapsFeed.map(a => parseZap(a)).filter(z => z.valid);
return parsedZaps.sort((a, b) => b.amount - a.amount);
}
return [];
}, [zapsFeed]);

View File

@ -1,33 +1,69 @@
import { EventKind, HexKey, NostrLink, NostrPrefix } from "@snort/system";
import { EventKind, HexKey, NostrLink, NostrPrefix, ParsedZap } from "@snort/system";
import { useMemo } from "react";
import { FormattedMessage } from "react-intl";
import { Note } from "@/Components/Event/Note/Note";
import Zap from "@/Components/Event/Zap";
import Timeline from "@/Components/Feed/Timeline";
import RelaysMetadata from "@/Components/Relay/RelaysMetadata";
import Bookmarks from "@/Components/User/Bookmarks";
import FollowsList from "@/Components/User/FollowListBase";
import ProfilePreview from "@/Components/User/ProfilePreview";
import ZapAmount from "@/Components/zap-amount";
import useFollowersFeed from "@/Feed/FollowersFeed";
import useFollowsFeed from "@/Feed/FollowsFeed";
import useRelaysFeed from "@/Feed/RelaysFeed";
import { TimelineSubject } from "@/Feed/TimelineFeed";
import useZapsFeed from "@/Feed/ZapsFeed";
import { useBookmarkList, usePinList } from "@/Hooks/useLists";
import messages from "@/Pages/messages";
import { formatShort } from "@/Utils/Number";
export function ZapsProfileTab({ id }: { id: HexKey }) {
const zaps = useZapsFeed(new NostrLink(NostrPrefix.PublicKey, id));
const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0);
const fromGrouped = zaps.reduce(
(acc, v) => {
if (!v.sender) return acc;
acc[v.sender] ??= [];
acc[v.sender].push(v);
return acc;
},
{} as Record<string, Array<ParsedZap>>,
);
return (
<>
<h2 className="p">
<FormattedMessage {...messages.Sats} values={{ n: formatShort(zapsTotal) }} />
</h2>
{zaps.map(z => (
<Zap key={z.id} showZapped={false} zap={z} />
))}
<div className="p text-2xl font-medium flex justify-between">
<div>
<FormattedMessage defaultMessage="Profile Zaps" />
</div>
<ZapAmount n={zapsTotal} />
</div>
{Object.entries(fromGrouped)
.map(a => ({
pubkey: a[0],
total: a[1].reduce((acc, v) => acc + v.amount, 0),
topZap: a[1].reduce((acc, v) => (v.amount > acc.amount ? v : acc), a[1][0]),
zaps: a[1],
}))
.sort((a, b) => {
return b.total > a.total ? 1 : -1;
})
.map(a => (
<div
className="px-4 py-1 hover:bg-gray-dark cursor:pointer rounded-xl flex items-center justify-between"
key={a.pubkey}>
<ProfilePreview
pubkey={a.pubkey}
subHeader={a.topZap.content ? <div className="about">&quot;{a.topZap.content}&quot;</div> : undefined}
options={{
about: false,
}}
actions={<></>}
/>
<div>
<ZapAmount n={a.total} />
</div>
</div>
))}
</>
);
}

View File

@ -485,6 +485,9 @@
"CHTbO3": {
"defaultMessage": "Failed to load invoice"
},
"CJx5Nd": {
"defaultMessage": "Profile Zaps"
},
"CM+Cfj": {
"defaultMessage": "Follow List"
},

View File

@ -160,6 +160,7 @@
"C8FsOr": "Popular Servers",
"C8HhVE": "Suggested Follows",
"CHTbO3": "Failed to load invoice",
"CJx5Nd": "Profile Zaps",
"CM+Cfj": "Follow List",
"CM0k0d": "Prune follow list",
"CVWeJ6": "Trending People",