@ -10,7 +10,7 @@ import Icon from "@/Components/Icons/Icon";
|
|||||||
import Modal from "@/Components/Modal/Modal";
|
import Modal from "@/Components/Modal/Modal";
|
||||||
import TabSelectors, { Tab } from "@/Components/TabSelectors/TabSelectors";
|
import TabSelectors, { Tab } from "@/Components/TabSelectors/TabSelectors";
|
||||||
import ProfileImage from "@/Components/User/ProfileImage";
|
import ProfileImage from "@/Components/User/ProfileImage";
|
||||||
import { formatShort } from "@/Utils/Number";
|
import ZapAmount from "@/Components/zap-amount";
|
||||||
|
|
||||||
import messages from "../../messages";
|
import messages from "../../messages";
|
||||||
|
|
||||||
@ -84,10 +84,7 @@ const ReactionsModal = ({ onClose, event, initialTab = 0 }: ReactionsModalProps)
|
|||||||
z =>
|
z =>
|
||||||
z.sender && (
|
z.sender && (
|
||||||
<div key={z.id} className="reactions-item">
|
<div key={z.id} className="reactions-item">
|
||||||
<div className="zap-reaction-icon">
|
<ZapAmount n={z.amount} />
|
||||||
<Icon name="zap-solid" size={20} className="text-zap" />
|
|
||||||
<span className="zap-amount">{formatShort(z.amount)}</span>
|
|
||||||
</div>
|
|
||||||
<ProfileImage
|
<ProfileImage
|
||||||
showProfileCard={true}
|
showProfileCard={true}
|
||||||
pubkey={z.anonZap ? "" : z.sender}
|
pubkey={z.anonZap ? "" : z.sender}
|
||||||
|
@ -16,7 +16,7 @@ import { ProfileLink } from "./ProfileLink";
|
|||||||
|
|
||||||
export interface ProfileImageProps {
|
export interface ProfileImageProps {
|
||||||
pubkey: HexKey;
|
pubkey: HexKey;
|
||||||
subHeader?: JSX.Element;
|
subHeader?: ReactNode;
|
||||||
showUsername?: boolean;
|
showUsername?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
|
@ -15,6 +15,7 @@ export interface ProfilePreviewProps {
|
|||||||
linkToProfile?: boolean;
|
linkToProfile?: boolean;
|
||||||
profileCards?: boolean;
|
profileCards?: boolean;
|
||||||
};
|
};
|
||||||
|
subHeader?: ReactNode;
|
||||||
profile?: UserMetadata;
|
profile?: UserMetadata;
|
||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -50,7 +51,7 @@ export default memo(function ProfilePreview(props: ProfilePreviewProps) {
|
|||||||
pubkey={pubkey}
|
pubkey={pubkey}
|
||||||
profile={props.profile}
|
profile={props.profile}
|
||||||
link={options.linkToProfile ?? true ? undefined : ""}
|
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}
|
showProfileCard={options.profileCards}
|
||||||
/>
|
/>
|
||||||
{props.actions ?? (
|
{props.actions ?? (
|
||||||
|
12
packages/app/src/Components/zap-amount.tsx
Normal file
12
packages/app/src/Components/zap-amount.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -15,9 +15,8 @@ export default function useZapsFeed(link?: NostrLink) {
|
|||||||
|
|
||||||
const zaps = useMemo(() => {
|
const zaps = useMemo(() => {
|
||||||
if (zapsFeed) {
|
if (zapsFeed) {
|
||||||
const profileZaps = zapsFeed.map(a => parseZap(a)).filter(z => z.valid);
|
const parsedZaps = zapsFeed.map(a => parseZap(a)).filter(z => z.valid);
|
||||||
profileZaps.sort((a, b) => b.amount - a.amount);
|
return parsedZaps.sort((a, b) => b.amount - a.amount);
|
||||||
return profileZaps;
|
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}, [zapsFeed]);
|
}, [zapsFeed]);
|
||||||
|
@ -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 { useMemo } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import { Note } from "@/Components/Event/Note/Note";
|
import { Note } from "@/Components/Event/Note/Note";
|
||||||
import Zap from "@/Components/Event/Zap";
|
|
||||||
import Timeline from "@/Components/Feed/Timeline";
|
import Timeline from "@/Components/Feed/Timeline";
|
||||||
import RelaysMetadata from "@/Components/Relay/RelaysMetadata";
|
import RelaysMetadata from "@/Components/Relay/RelaysMetadata";
|
||||||
import Bookmarks from "@/Components/User/Bookmarks";
|
import Bookmarks from "@/Components/User/Bookmarks";
|
||||||
import FollowsList from "@/Components/User/FollowListBase";
|
import FollowsList from "@/Components/User/FollowListBase";
|
||||||
|
import ProfilePreview from "@/Components/User/ProfilePreview";
|
||||||
|
import ZapAmount from "@/Components/zap-amount";
|
||||||
import useFollowersFeed from "@/Feed/FollowersFeed";
|
import useFollowersFeed from "@/Feed/FollowersFeed";
|
||||||
import useFollowsFeed from "@/Feed/FollowsFeed";
|
import useFollowsFeed from "@/Feed/FollowsFeed";
|
||||||
import useRelaysFeed from "@/Feed/RelaysFeed";
|
import useRelaysFeed from "@/Feed/RelaysFeed";
|
||||||
import { TimelineSubject } from "@/Feed/TimelineFeed";
|
import { TimelineSubject } from "@/Feed/TimelineFeed";
|
||||||
import useZapsFeed from "@/Feed/ZapsFeed";
|
import useZapsFeed from "@/Feed/ZapsFeed";
|
||||||
import { useBookmarkList, usePinList } from "@/Hooks/useLists";
|
import { useBookmarkList, usePinList } from "@/Hooks/useLists";
|
||||||
import messages from "@/Pages/messages";
|
|
||||||
import { formatShort } from "@/Utils/Number";
|
|
||||||
|
|
||||||
export function ZapsProfileTab({ id }: { id: HexKey }) {
|
export function ZapsProfileTab({ id }: { id: HexKey }) {
|
||||||
const zaps = useZapsFeed(new NostrLink(NostrPrefix.PublicKey, id));
|
const zaps = useZapsFeed(new NostrLink(NostrPrefix.PublicKey, id));
|
||||||
const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2 className="p">
|
<div className="p text-2xl font-medium flex justify-between">
|
||||||
<FormattedMessage {...messages.Sats} values={{ n: formatShort(zapsTotal) }} />
|
<div>
|
||||||
</h2>
|
<FormattedMessage defaultMessage="Profile Zaps" />
|
||||||
{zaps.map(z => (
|
</div>
|
||||||
<Zap key={z.id} showZapped={false} zap={z} />
|
<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">"{a.topZap.content}"</div> : undefined}
|
||||||
|
options={{
|
||||||
|
about: false,
|
||||||
|
}}
|
||||||
|
actions={<></>}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<ZapAmount n={a.total} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -485,6 +485,9 @@
|
|||||||
"CHTbO3": {
|
"CHTbO3": {
|
||||||
"defaultMessage": "Failed to load invoice"
|
"defaultMessage": "Failed to load invoice"
|
||||||
},
|
},
|
||||||
|
"CJx5Nd": {
|
||||||
|
"defaultMessage": "Profile Zaps"
|
||||||
|
},
|
||||||
"CM+Cfj": {
|
"CM+Cfj": {
|
||||||
"defaultMessage": "Follow List"
|
"defaultMessage": "Follow List"
|
||||||
},
|
},
|
||||||
|
@ -160,6 +160,7 @@
|
|||||||
"C8FsOr": "Popular Servers",
|
"C8FsOr": "Popular Servers",
|
||||||
"C8HhVE": "Suggested Follows",
|
"C8HhVE": "Suggested Follows",
|
||||||
"CHTbO3": "Failed to load invoice",
|
"CHTbO3": "Failed to load invoice",
|
||||||
|
"CJx5Nd": "Profile Zaps",
|
||||||
"CM+Cfj": "Follow List",
|
"CM+Cfj": "Follow List",
|
||||||
"CM0k0d": "Prune follow list",
|
"CM0k0d": "Prune follow list",
|
||||||
"CVWeJ6": "Trending People",
|
"CVWeJ6": "Trending People",
|
||||||
|
Reference in New Issue
Block a user