count in profile page

This commit is contained in:
Alejandro Gomez 2023-02-10 12:12:11 +01:00 committed by Kieran
parent fbd31526b7
commit 73957e6510
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
22 changed files with 205 additions and 219 deletions

View File

@ -1,42 +1,15 @@
import { FormattedMessage } from "react-intl";
import MuteButton from "Element/MuteButton";
import BlockButton from "Element/BlockButton";
import ProfilePreview from "Element/ProfilePreview";
import useModeration from "Hooks/useModeration";
import messages from "./messages";
interface BlockListProps {
variant: "muted" | "blocked";
}
export default function BlockList({ variant }: BlockListProps) {
const { blocked, muted } = useModeration();
export default function BlockList() {
const { blocked } = useModeration();
return (
<div className="main-content">
{variant === "muted" && (
<>
<h4>
<FormattedMessage {...messages.MuteCount} values={{ n: muted.length }} />
</h4>
{muted.map(a => {
return <ProfilePreview actions={<MuteButton pubkey={a} />} pubkey={a} options={{ about: false }} key={a} />;
})}
</>
)}
{variant === "blocked" && (
<>
<h4>
<FormattedMessage {...messages.BlockCount} values={{ n: blocked.length }} />
</h4>
{blocked.map(a => {
return (
<ProfilePreview actions={<BlockButton pubkey={a} />} pubkey={a} options={{ about: false }} key={a} />
);
})}
</>
)}
{blocked.map(a => {
return <ProfilePreview actions={<BlockButton pubkey={a} />} pubkey={a} options={{ about: false }} key={a} />;
})}
</div>
);
}

View File

@ -16,7 +16,7 @@ export default function Copy({ text, maxSize = 32 }: CopyProps) {
<div className="flex flex-row copy" onClick={() => copy(text)}>
<span className="body">{trimmed}</span>
<span className="icon" style={{ color: copied ? "var(--success)" : "var(--highlight)" }}>
{copied ? <Check width={13} height={13} /> : <CopyIcon width={13} height={13} />}
{copied ? <Check width={14} height={14} /> : <CopyIcon width={14} height={14} />}
</span>
</div>
);

View File

@ -1,27 +0,0 @@
import { useMemo } from "react";
import { useIntl } from "react-intl";
import useFollowersFeed from "Feed/FollowersFeed";
import { HexKey } from "@snort/nostr";
import { EventKind } from "@snort/nostr";
import FollowListBase from "Element/FollowListBase";
import messages from "./messages";
export interface FollowersListProps {
pubkey: HexKey;
}
export default function FollowersList({ pubkey }: FollowersListProps) {
const { formatMessage } = useIntl();
const feed = useFollowersFeed(pubkey);
const pubkeys = useMemo(() => {
const contactLists = feed?.store.notes.filter(
a => a.kind === EventKind.ContactList && a.tags.some(b => b[0] === "p" && b[1] === pubkey)
);
return [...new Set(contactLists?.map(a => a.pubkey))];
}, [feed, pubkey]);
return <FollowListBase pubkeys={pubkeys} title={formatMessage(messages.FollowerCount, { n: pubkeys?.length })} />;
}

View File

@ -1,24 +0,0 @@
import { useMemo } from "react";
import { useIntl } from "react-intl";
import useFollowsFeed from "Feed/FollowsFeed";
import { HexKey } from "@snort/nostr";
import FollowListBase from "Element/FollowListBase";
import { getFollowers } from "Feed/FollowsFeed";
import messages from "./messages";
export interface FollowsListProps {
pubkey: HexKey;
}
export default function FollowsList({ pubkey }: FollowsListProps) {
const feed = useFollowsFeed(pubkey);
const { formatMessage } = useIntl();
const pubkeys = useMemo(() => {
return getFollowers(feed.store, pubkey);
}, [feed, pubkey]);
return <FollowListBase pubkeys={pubkeys} title={formatMessage(messages.FollowingCount, { n: pubkeys?.length })} />;
}

View File

@ -1,29 +1,13 @@
import "./FollowsYou.css";
import { useMemo } from "react";
import { useSelector } from "react-redux";
import { useIntl } from "react-intl";
import { HexKey } from "@snort/nostr";
import { RootState } from "State/Store";
import useFollowsFeed from "Feed/FollowsFeed";
import { getFollowers } from "Feed/FollowsFeed";
import messages from "./messages";
export interface FollowsYouProps {
pubkey: HexKey;
followsMe: boolean;
}
export default function FollowsYou({ pubkey }: FollowsYouProps) {
export default function FollowsYou({ followsMe }: FollowsYouProps) {
const { formatMessage } = useIntl();
const feed = useFollowsFeed(pubkey);
const loginPubKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const pubkeys = useMemo(() => {
return getFollowers(feed.store, pubkey);
}, [feed, pubkey]);
const followsMe = loginPubKey ? pubkeys.includes(loginPubKey) : false;
return followsMe ? <span className="follows-you">{formatMessage(messages.FollowsYou)}</span> : null;
}

View File

@ -1,23 +1,17 @@
import { useMemo } from "react";
import { FormattedMessage } from "react-intl";
import { HexKey } from "@snort/nostr";
import MuteButton from "Element/MuteButton";
import ProfilePreview from "Element/ProfilePreview";
import useMutedFeed, { getMuted } from "Feed/MuteList";
import useModeration from "Hooks/useModeration";
import messages from "./messages";
export interface MutedListProps {
pubkey: HexKey;
pubkeys: HexKey[];
}
export default function MutedList({ pubkey }: MutedListProps) {
export default function MutedList({ pubkeys }: MutedListProps) {
const { isMuted, muteAll } = useModeration();
const feed = useMutedFeed(pubkey);
const pubkeys = useMemo(() => {
return getMuted(feed.store, pubkey);
}, [feed, pubkey]);
const hasAllMuted = pubkeys.every(isMuted);
return (

View File

@ -43,5 +43,6 @@
}
.nip05 .badge {
color: var(--highlight);
margin: 0.1em 0.2em;
}

View File

@ -1,9 +1,6 @@
import { useQuery } from "react-query";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCircleCheck, faSpinner, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
import "./Nip05.css";
import { useQuery } from "react-query";
import Badge from "Icons/Badge";
import { HexKey } from "@snort/nostr";
interface NostrJson {
@ -59,15 +56,17 @@ const Nip05 = ({ nip05, pubkey, verifyNip = true }: Nip05Params) => {
return (
<div className={`flex nip05${couldNotVerify ? " failed" : ""}`} onClick={ev => ev.stopPropagation()}>
{!isDefaultUser && <div className="nick">{`${name}@`}</div>}
<span className="domain" data-domain={domain?.toLowerCase()}>
{domain}
</span>
<span className="badge">
{isVerified && <FontAwesomeIcon color={"var(--highlight)"} icon={faCircleCheck} size="xs" />}
{!isVerified && !couldNotVerify && <FontAwesomeIcon color={"var(--fg-color)"} icon={faSpinner} size="xs" />}
{couldNotVerify && <FontAwesomeIcon color={"var(--error)"} icon={faTriangleExclamation} size="xs" />}
</span>
{!isDefaultUser && isVerified && <div className="nick">{`${name}@`}</div>}
{isVerified && (
<>
<span className="domain" data-domain={domain?.toLowerCase()}>
{domain}
</span>
<span className="badge">
<Badge />
</span>
</>
)}
</div>
);
};

View File

@ -115,12 +115,12 @@
border-bottom-right-radius: 16px;
}
.light .note > .footer .ctx-menu li:hover {
.note > .footer .ctx-menu li:hover {
color: white;
background: #2a2a2a;
}
.note > .footer .ctx-menu li:hover {
.light .note > .footer .ctx-menu li:hover {
color: white;
background: var(--font-secondary-color);
}
@ -151,6 +151,7 @@
.reaction-pill .reaction-pill-number {
margin-left: 8px;
font-feature-settings: "tnum";
}
.reaction-pill.reacted {

View File

@ -13,6 +13,8 @@
.relay-settings {
margin-left: auto;
display: flex;
align-items: center;
}
.relay-settings svg:not(:last-child) {
@ -25,6 +27,13 @@
opacity: 0.3;
}
@media (max-width: 520px) {
.relay-settings svg {
width: 16px;
height: 16px;
}
}
.relay-url {
font-size: 14px;
}

View File

@ -14,16 +14,17 @@
.tab {
color: var(--font-tertiary-color);
border: 1px solid var(--font-tertiary-color);
border: 1px solid var(--border-color);
border-radius: 16px;
text-align: center;
font-weight: 600;
line-height: 19px;
padding: 8px 12px;
font-weight: 600;
font-size: 14px;
line-height: 17px;
margin-right: 12px;
padding: 6px 8px;
text-align: center;
font-feature-settings: "tnum";
}
.tab:not(:last-of-type) {
margin-right: 8px;
}
.tab.active {
@ -40,3 +41,7 @@
cursor: not-allowed;
pointer-events: none;
}
.tab:hover {
border-color: var(--font-color);
}

View File

@ -1,5 +1,6 @@
import "./Tabs.css";
import useHorizontalScroll from "Hooks/useHorizontalScroll";
import { CSSProperties } from "react";
export interface Tab {
text: string;
@ -18,9 +19,11 @@ interface TabElementProps extends Omit<TabsProps, "tabs"> {
}
export const TabElement = ({ t, tab, setTab }: TabElementProps) => {
const style = { minWidth: `${t.text.length * 0.6}em` } as CSSProperties;
return (
<div
className={`tab ${tab.value === t.value ? "active" : ""} ${t.disabled ? "disabled" : ""}`}
style={style}
onClick={() => !t.disabled && setTab(t)}>
{t.text}
</div>

View File

@ -13,5 +13,14 @@ export default function useFollowersFeed(pubkey: HexKey) {
return x;
}, [pubkey]);
return useSubscription(sub);
const followersFeed = useSubscription(sub, { leaveOpen: false, cache: true });
const followers = useMemo(() => {
const contactLists = followersFeed?.store.notes.filter(
a => a.kind === EventKind.ContactList && a.tags.some(b => b[0] === "p" && b[1] === pubkey)
);
return [...new Set(contactLists?.map(a => a.pubkey))];
}, [followersFeed, pubkey]);
return followers;
}

View File

@ -1,23 +1,32 @@
import { useMemo } from "react";
import { HexKey } from "@snort/nostr";
import { EventKind, Subscriptions } from "@snort/nostr";
import useSubscription, { NoteStore } from "Feed/Subscription";
import { useSelector } from "react-redux";
import { HexKey, TaggedRawEvent, EventKind, Subscriptions } from "@snort/nostr";
import useSubscription from "Feed/Subscription";
import { RootState } from "State/Store";
export default function useFollowsFeed(pubkey: HexKey) {
const { publicKey, follows } = useSelector((s: RootState) => s.login);
const isMe = publicKey === pubkey;
const sub = useMemo(() => {
if (isMe) return null;
const x = new Subscriptions();
x.Id = `follows:${pubkey.slice(0, 12)}`;
x.Kinds = new Set([EventKind.ContactList]);
x.Authors = new Set([pubkey]);
return x;
}, [pubkey]);
}, [isMe, pubkey]);
return useSubscription(sub);
const contactFeed = useSubscription(sub, { leaveOpen: false, cache: true });
const following = useMemo(() => {
return getFollowing(contactFeed.store.notes ?? [], pubkey);
}, [contactFeed.store.notes]);
return isMe ? follows : following;
}
export function getFollowers(feed: NoteStore, pubkey: HexKey) {
const contactLists = feed?.notes.filter(a => a.kind === EventKind.ContactList && a.pubkey === pubkey);
export function getFollowing(notes: TaggedRawEvent[], pubkey: HexKey) {
const contactLists = notes.filter(a => a.kind === EventKind.ContactList && a.pubkey === pubkey);
const pTags = contactLists?.map(a => a.tags.filter(b => b[0] === "p").map(c => c[1]));
return [...new Set(pTags?.flat())];
}

View File

@ -1,12 +1,18 @@
import { useMemo } from "react";
import { useSelector } from "react-redux";
import { getNewest } from "Util";
import { HexKey, TaggedRawEvent, Lists } from "@snort/nostr";
import { EventKind, Subscriptions } from "@snort/nostr";
import useSubscription, { NoteStore } from "Feed/Subscription";
import { RootState } from "State/Store";
export default function useMutedFeed(pubkey: HexKey) {
const { publicKey, muted } = useSelector((s: RootState) => s.login);
const isMe = publicKey === pubkey;
const sub = useMemo(() => {
if (isMe) return null;
const sub = new Subscriptions();
sub.Id = `muted:${pubkey.slice(0, 12)}`;
sub.Kinds = new Set([EventKind.PubkeyLists]);
@ -16,7 +22,13 @@ export default function useMutedFeed(pubkey: HexKey) {
return sub;
}, [pubkey]);
return useSubscription(sub);
const mutedFeed = useSubscription(sub, { leaveOpen: false, cache: true });
const mutedList = useMemo(() => {
return getMuted(mutedFeed.store, pubkey);
}, [mutedFeed.store]);
return isMe ? muted : mutedList;
}
export function getMutedKeys(rawNotes: TaggedRawEvent[]): {

View File

@ -1,6 +1,6 @@
import { useMemo } from "react";
import { HexKey } from "@snort/nostr";
import { EventKind, Subscriptions } from "@snort/nostr";
import { HexKey, EventKind, Subscriptions } from "@snort/nostr";
import { parseZap } from "Element/Zap";
import useSubscription from "./Subscription";
export default function useZapsFeed(pubkey: HexKey) {
@ -12,5 +12,15 @@ export default function useZapsFeed(pubkey: HexKey) {
return x;
}, [pubkey]);
return useSubscription(sub, { leaveOpen: true, cache: true });
const zapsFeed = useSubscription(sub, { leaveOpen: false, cache: true });
const zaps = useMemo(() => {
const profileZaps = zapsFeed.store.notes
.map(parseZap)
.filter(z => z.valid && z.p === pubkey && z.zapper !== pubkey && !z.e);
profileZaps.sort((a, b) => b.amount - a.amount);
return profileZaps;
}, [zapsFeed]);
return zaps;
}

View File

@ -0,0 +1,15 @@
const Badge = () => {
return (
<svg width="16" height="15" viewBox="0 0 16 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.00004 7.50065L7.33337 8.83398L10.3334 5.83398M11.9342 2.83299C12.0714 3.16501 12.3349 3.42892 12.6667 3.5667L13.8302 4.04864C14.1622 4.18617 14.426 4.44998 14.5636 4.78202C14.7011 5.11407 14.7011 5.48715 14.5636 5.81919L14.082 6.98185C13.9444 7.31404 13.9442 7.6875 14.0824 8.01953L14.5632 9.18185C14.6313 9.34631 14.6664 9.52259 14.6665 9.70062C14.6665 9.87865 14.6315 10.0549 14.5633 10.2194C14.4952 10.3839 14.3953 10.5333 14.2694 10.6592C14.1435 10.7851 13.9941 10.8849 13.8296 10.953L12.6669 11.4346C12.3349 11.5718 12.071 11.8354 11.9333 12.1672L11.4513 13.3307C11.3138 13.6627 11.05 13.9265 10.718 14.0641C10.3859 14.2016 10.0129 14.2016 9.68085 14.0641L8.51823 13.5825C8.18619 13.4453 7.81326 13.4455 7.48143 13.5832L6.31797 14.0645C5.98612 14.2017 5.61338 14.2016 5.28162 14.0642C4.94986 13.9267 4.68621 13.6632 4.54858 13.3316L4.06652 12.1677C3.92924 11.8357 3.66574 11.5718 3.33394 11.434L2.17048 10.9521C1.8386 10.8146 1.57488 10.5509 1.4373 10.2191C1.29971 9.88724 1.29953 9.51434 1.43678 9.18235L1.91835 8.01968C2.05554 7.68763 2.05526 7.31469 1.91757 6.98284L1.43669 5.81851C1.36851 5.65405 1.3334 5.47777 1.33337 5.29974C1.33335 5.12171 1.3684 4.94542 1.43652 4.78094C1.50465 4.61646 1.60452 4.46702 1.73042 4.34115C1.85632 4.21529 2.00579 4.11546 2.17028 4.04739L3.33291 3.5658C3.66462 3.42863 3.92836 3.16545 4.06624 2.83402L4.54816 1.67052C4.68569 1.33848 4.94949 1.07467 5.28152 0.937137C5.61355 0.7996 5.98662 0.7996 6.31865 0.937137L7.48127 1.41873C7.81331 1.55593 8.18624 1.55565 8.51808 1.41795L9.68202 0.937884C10.014 0.800424 10.387 0.800452 10.719 0.937962C11.0509 1.07547 11.3147 1.3392 11.4522 1.67116L11.9343 2.835L11.9342 2.83299Z"
stroke="currentColor"
strokeWidth="1.33333"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default Badge;

View File

@ -30,13 +30,18 @@ header .pfp .avatar-wrapper {
align-items: center;
}
.header-actions .avatar {
width: 40px;
height: 40px;
}
.header-actions .btn-rnd {
position: relative;
margin-right: 8px;
}
@media (min-width: 520px) {
.header-actions .btn-rnd {
.header-actions .btn-rnd:last-of-type {
margin-right: 16px;
}
}

View File

@ -231,23 +231,24 @@
margin-left: 4px;
}
.icon-title {
font-weight: 600;
font-size: 19px;
line-height: 23px;
display: flex;
align-items: center;
margin-bottom: 22px;
.banner-bg {
width: 1438px;
height: 758px;
position: absolute;
top: -120px;
left: calc(50% - 750px);
background: radial-gradient(circle at top center, rgba(0, 0, 0, 0.7) 0%, #000000 81.25%), var(--img-url);
filter: blur(10px);
z-index: -1;
display: none;
}
.icon-title svg {
margin-right: 8px;
.light .banner-bg {
background: radial-gradient(circle at top center, rgba(241, 241, 241, 0.7) 0%, var(--bg-color) 81.25%), var(--img-url);
}
.icon-title h3 {
margin: 0;
}
.icon-title select {
margin-left: auto;
@media (min-width: 1420px) {
.banner-bg {
display: unset;
}
}

View File

@ -1,5 +1,5 @@
import "./ProfilePage.css";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState, CSSProperties } from "react";
import { useIntl, FormattedMessage } from "react-intl";
import { useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
@ -14,12 +14,16 @@ import Link from "Icons/Link";
import Qr from "Icons/Qr";
import Zap from "Icons/Zap";
import Envelope from "Icons/Envelope";
import useMutedFeed from "Feed/MuteList";
import useRelaysFeed from "Feed/RelaysFeed";
import usePinnedFeed from "Feed/PinnedFeed";
import useBookmarkFeed from "Feed/BookmarkFeed";
import useFollowersFeed from "Feed/FollowersFeed";
import useFollowsFeed from "Feed/FollowsFeed";
import { useUserProfile } from "Feed/ProfileFeed";
import useModeration from "Hooks/useModeration";
import useZapsFeed from "Feed/ZapsFeed";
import { default as ZapElement, parseZap } from "Element/Zap";
import { default as ZapElement } from "Element/Zap";
import FollowButton from "Element/FollowButton";
import { extractLnAddress, parseId, hexToBech32 } from "Util";
import Avatar from "Element/Avatar";
@ -30,10 +34,9 @@ import Nip05 from "Element/Nip05";
import Copy from "Element/Copy";
import ProfilePreview from "Element/ProfilePreview";
import ProfileImage from "Element/ProfileImage";
import FollowersList from "Element/FollowersList";
import BlockList from "Element/BlockList";
import MutedList from "Element/MutedList";
import FollowsList from "Element/FollowsList";
import FollowsList from "Element/FollowListBase";
import IconButton from "Element/IconButton";
import { RootState } from "State/Store";
import { HexKey, NostrPrefix } from "@snort/nostr";
@ -62,12 +65,9 @@ export default function ProfilePage() {
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);
const [showProfileQr, setShowProfileQr] = useState<boolean>(false);
const { notes: pinned, related: pinRelated } = usePinnedFeed(id);
const { notes: bookmarks, related: bookmarkRelated } = useBookmarkFeed(id);
const aboutText = user?.about || "";
const about = Text({
content: aboutText,
@ -78,32 +78,36 @@ export default function ProfilePage() {
const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || "");
const website_url =
user?.website && !user.website.startsWith("http") ? "https://" + user.website : user?.website || "";
// feeds
const { blocked } = useModeration();
const { notes: pinned, related: pinRelated } = usePinnedFeed(id);
const { notes: bookmarks, related: bookmarkRelated } = useBookmarkFeed(id);
const relays = useRelaysFeed(id);
const zapFeed = useZapsFeed(id);
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 zaps = useZapsFeed(id);
const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0);
const horizontalScroll = useHorizontalScroll();
const followers = useFollowersFeed(id);
const follows = useFollowsFeed(id);
const muted = useMutedFeed(id);
// tabs
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 },
Relays: { text: formatMessage(messages.Relays), value: RELAYS },
Bookmarks: { text: formatMessage(messages.Bookmarks), value: BOOKMARKS },
Followers: { text: formatMessage(messages.FollowersCount, { n: followers.length }), value: FOLLOWERS },
Follows: { text: formatMessage(messages.FollowsCount, { n: follows.length }), value: FOLLOWS },
Zaps: { text: formatMessage(messages.ZapsCount, { n: zaps.length }), value: ZAPS },
Muted: { text: formatMessage(messages.MutedCount, { n: muted.length }), value: MUTED },
Blocked: { text: formatMessage(messages.BlockedCount, { n: blocked.length }), value: BLOCKED },
Relays: { text: formatMessage(messages.RelaysCount, { n: relays.length }), value: RELAYS },
Bookmarks: { text: formatMessage(messages.BookmarksCount, { n: bookmarks.length }), value: BOOKMARKS },
};
const [tab, setTab] = useState<Tab>(ProfileTab.Notes);
const optionalTabs = [
zapsTotal > 0 && ProfileTab.Zaps,
relays.length > 0 && ProfileTab.Relays,
bookmarks.length > 0 && ProfileTab.Bookmarks,
muted.length > 0 && ProfileTab.Muted,
].filter(a => unwrap(a)) as Tab[];
const horizontalScroll = useHorizontalScroll();
useEffect(() => {
setTab(ProfileTab.Notes);
@ -114,7 +118,7 @@ export default function ProfilePage() {
<div className="name">
<h2>
{user?.display_name || user?.name || "Nostrich"}
<FollowsYou pubkey={id} />
<FollowsYou followsMe={follows.includes(id)} />
</h2>
{user?.nip05 && <Nip05 nip05={user.nip05} pubkey={user.pubkey} />}
<Copy text={params.id || ""} />
@ -211,29 +215,22 @@ export default function ProfilePage() {
}
case FOLLOWS: {
if (isMe) {
return (
<div className="main-content">
<h4>
<FormattedMessage {...messages.Following} values={{ n: follows.length }} />
</h4>
{follows.map(a => (
<ProfilePreview key={a} pubkey={a.toLowerCase()} options={{ about: false }} />
))}
</div>
);
} else {
return <FollowsList pubkey={id} />;
}
return (
<div className="main-content">
{follows.map(a => (
<ProfilePreview key={a} pubkey={a.toLowerCase()} options={{ about: !isMe }} />
))}
</div>
);
}
case FOLLOWERS: {
return <FollowersList pubkey={id} />;
return <FollowsList pubkeys={followers} />;
}
case MUTED: {
return isMe ? <BlockList variant="muted" /> : <MutedList pubkey={id} />;
return <MutedList pubkeys={muted} />;
}
case BLOCKED: {
return isMe ? <BlockList variant="blocked" /> : null;
return <BlockList />;
}
case RELAYS: {
return <RelaysMetadata relays={relays} />;
@ -308,20 +305,23 @@ export default function ProfilePage() {
}
const w = window.document.querySelector(".page")?.clientWidth;
const bannerStyle = user?.banner ? ({ "--img-url": `url(${user.banner})` } as CSSProperties) : {};
return (
<>
<div className="profile flex">
{user?.banner && <div className="banner-bg" style={bannerStyle} />}
{user?.banner && <ProxyImg alt="banner" className="banner" src={user.banner} size={w} />}
<div className="profile-wrapper flex">
{avatar()}
{userDetails()}
</div>
</div>
<div className="tabs main-content" ref={horizontalScroll}>
{[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows, ProfileTab.Muted].map(renderTab)}
{optionalTabs.map(renderTab)}
{isMe && renderTab(ProfileTab.Blocked)}
<div className="main-content">
<div className="tabs" ref={horizontalScroll}>
{[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows].map(renderTab)}
{optionalTabs.map(renderTab)}
{isMe && blocked.length > 0 && renderTab(ProfileTab.Blocked)}
</div>
</div>
{tabContent()}
</>

View File

@ -10,10 +10,15 @@ export default defineMessages({
Notes: { defaultMessage: "Notes" },
Reactions: { defaultMessage: "Reactions" },
Followers: { defaultMessage: "Followers" },
Follows: { defaultMessage: "Follows" },
FollowersCount: { defaultMessage: "{n} Followers" },
Follows: { defaultMessage: "Following" },
FollowsCount: { defaultMessage: "{n} Following" },
Zaps: { defaultMessage: "Zaps" },
ZapsCount: { defaultMessage: "{n} Zaps" },
Muted: { defaultMessage: "Muted" },
MutedCount: { defaultMessage: "{n} Muted" },
Blocked: { defaultMessage: "Blocked" },
BlockedCount: { defaultMessage: "{n} Blocked" },
Sats: { defaultMessage: "{n} {n, plural, =1 {sat} other {sats}}" },
Following: { defaultMessage: "Following {n}" },
Settings: { defaultMessage: "Settings" },
@ -36,6 +41,9 @@ export default defineMessages({
Relays: {
defaultMessage: "Relays",
},
RelaysCount: {
defaultMessage: "{n} Relays",
},
Bookmarks: { defaultMessage: "Bookmarks" },
BookmarksCount: { defaultMessage: "Bookmarks ({n})" },
BookmarksCount: { defaultMessage: "{n} Bookmarks" },
});

View File

@ -3,6 +3,7 @@
--font-color: #fff;
--font-secondary-color: #a7a7a7;
--font-tertiary-color: #a3a3a3;
--border-color: rgba(163, 163, 163, 0.3);
--font-size: 16px;
--font-size-small: 14px;
--font-size-tiny: 12px;
@ -39,10 +40,11 @@
}
html.light {
--bg-color: #f1f1f1;
--font-color: #57534e;
--font-secondary-color: #7b7b7b;
--font-tertiary-color: #a7a7a7;
--bg-color: #f8f8f8;
--font-color: #27272a;
--font-secondary-color: #71717a;
--font-tertiary-color: #71717a;
--border-color: rgba(167, 167, 167, 0.3);
--highlight-light: #16aac1;
--highlight: #0284c7;
@ -215,9 +217,8 @@ button.icon:hover {
cursor: pointer;
color: var(--font-color);
user-select: none;
background-color: var(--bg-color);
color: var(--font-color);
border: 1px solid;
background: none;
border: none;
display: inline-block;
}
@ -230,18 +231,16 @@ button.icon:hover {
}
.btn.active {
border: 2px solid;
background-color: var(--gray-secondary);
color: var(--font-color);
font-weight: 700;
}
.btn.disabled {
color: var(--gray-light);
opacity: 0.3;
}
.btn:hover {
background-color: var(--gray);
color: var(--highlight);
}
.btn-sm {