From c274c0a842a08d4a2326299ceebc822703a7fed4 Mon Sep 17 00:00:00 2001 From: kieran Date: Fri, 20 Sep 2024 22:15:12 +0100 Subject: [PATCH] refactor: polish --- .../src/Components/Event/Thread/Thread.css | 6 +- packages/app/src/Components/Text/Text.css | 1 + .../src/Components/Trending/TrendingUsers.tsx | 1 + packages/app/src/Components/User/Avatar.css | 38 -------- packages/app/src/Components/User/Avatar.tsx | 38 +++++--- .../app/src/Components/User/AvatarGroup.tsx | 3 +- .../User/FollowDistanceIndicator.tsx | 7 +- .../src/Components/User/FollowListBase.tsx | 6 +- .../app/src/Components/User/FollowedBy.tsx | 6 +- .../app/src/Components/User/ProfileImage.css | 41 --------- .../app/src/Components/User/ProfileImage.tsx | 56 ++++++------ .../app/src/Components/User/ProfileLink.tsx | 31 +------ .../src/Components/User/ProfilePreview.css | 17 ---- .../src/Components/User/ProfilePreview.tsx | 54 +++++------ .../app/src/Components/ZapModal/ZapModal.tsx | 6 +- packages/app/src/Feed/ArticlesFeed.ts | 2 +- packages/app/src/Feed/WorkerRelayView.ts | 10 +-- packages/app/src/Hooks/useFollowControls.ts | 48 +++++----- packages/app/src/Hooks/useProfileLink.ts | 25 ++++++ packages/app/src/Hooks/useWindowSize.ts | 20 +++++ packages/app/src/Hooks/useWoT.ts | 18 ++-- packages/app/src/Pages/Layout/Footer.tsx | 35 +++----- packages/app/src/Pages/Layout/NavSidebar.tsx | 38 +++----- packages/app/src/Pages/Layout/ProfileMenu.tsx | 90 +++++++++++++++++++ packages/app/src/Pages/Layout/RightColumn.tsx | 5 ++ .../Pages/Profile/ProfileTabComponents.tsx | 24 ++++- packages/app/src/Pages/settings/Accounts.tsx | 8 +- packages/app/src/index.css | 2 +- packages/app/src/lang.json | 9 +- packages/app/src/system.ts | 1 + packages/app/src/translations/en.json | 3 +- .../system/src/SocialGraph/SocialGraph.ts | 14 ++- packages/system/src/connection-pool.ts | 2 +- packages/system/src/connection.ts | 1 - packages/system/src/event-ext.ts | 11 ++- packages/system/src/note-collection.ts | 2 +- packages/system/src/query-optimizer/index.ts | 1 + packages/system/src/sync/connection.ts | 54 +++++++++-- packages/system/src/sync/safe-sync.ts | 5 +- 39 files changed, 419 insertions(+), 320 deletions(-) delete mode 100644 packages/app/src/Components/User/Avatar.css delete mode 100644 packages/app/src/Components/User/ProfileImage.css delete mode 100644 packages/app/src/Components/User/ProfilePreview.css create mode 100644 packages/app/src/Hooks/useProfileLink.ts create mode 100644 packages/app/src/Hooks/useWindowSize.ts create mode 100644 packages/app/src/Pages/Layout/ProfileMenu.tsx diff --git a/packages/app/src/Components/Event/Thread/Thread.css b/packages/app/src/Components/Event/Thread/Thread.css index 52d8df8d..2daa6012 100644 --- a/packages/app/src/Components/Event/Thread/Thread.css +++ b/packages/app/src/Components/Event/Thread/Thread.css @@ -42,7 +42,7 @@ top: 48px; border-left: 1px solid var(--border-color); height: 100%; - z-index: 1; + z-index: -1; } .subthread-container.subthread-mid:not(.subthread-last) .line-container:before { @@ -52,7 +52,7 @@ left: calc(48px / 2 + 16px); top: 0; height: 48px; - z-index: 1; + z-index: -1; } .subthread-container.subthread-last .line-container:before { @@ -62,7 +62,7 @@ left: calc(48px / 2 + 16px); top: 0; height: 48px; - z-index: 1; + z-index: -1; } .divider { diff --git a/packages/app/src/Components/Text/Text.css b/packages/app/src/Components/Text/Text.css index e0c3fc08..b27d6cb5 100644 --- a/packages/app/src/Components/Text/Text.css +++ b/packages/app/src/Components/Text/Text.css @@ -63,6 +63,7 @@ object-fit: cover; width: 100%; height: 100%; + max-height: 100%; display: block; border-radius: 0; } diff --git a/packages/app/src/Components/Trending/TrendingUsers.tsx b/packages/app/src/Components/Trending/TrendingUsers.tsx index f6a524d3..54b9bf6e 100644 --- a/packages/app/src/Components/Trending/TrendingUsers.tsx +++ b/packages/app/src/Components/Trending/TrendingUsers.tsx @@ -40,6 +40,7 @@ export default function TrendingUsers({ pubkeys={trendingUsersData.slice(0, count) as HexKey[]} title={title} showFollowAll={true} + className="flex flex-col gap-2" profilePreviewProps={{ options: { about: true, diff --git a/packages/app/src/Components/User/Avatar.css b/packages/app/src/Components/User/Avatar.css deleted file mode 100644 index d44a8bb7..00000000 --- a/packages/app/src/Components/User/Avatar.css +++ /dev/null @@ -1,38 +0,0 @@ -.avatar { - @apply rounded-full; - height: 210px; - width: 210px; - background-color: var(--gray); - z-index: 2; - background-size: cover; -} - -.avatar[data-domain="iris.to"], -.avatar[data-domain="snort.social"] { - background-image: var(--img-url), var(--snort-gradient); -} - -.avatar .overlay { - color: white; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - border-radius: 50%; - background-color: rgba(0, 0, 0, 0.4); - position: absolute; -} - -.avatar .icons { - position: absolute; - top: 0; - right: 0; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - transform-origin: center; - transform: rotate(-135deg) translateY(50%); -} diff --git a/packages/app/src/Components/User/Avatar.tsx b/packages/app/src/Components/User/Avatar.tsx index 68f205e9..e644e5aa 100644 --- a/packages/app/src/Components/User/Avatar.tsx +++ b/packages/app/src/Components/User/Avatar.tsx @@ -1,8 +1,6 @@ -import "./Avatar.css"; - import type { UserMetadata } from "@snort/system"; import classNames from "classnames"; -import { ReactNode, useMemo } from "react"; +import { HTMLProps, ReactNode, useMemo } from "react"; import { ProxyImg } from "@/Components/ProxyImg"; import { defaultAvatar, getDisplayName } from "@/Utils"; @@ -22,14 +20,15 @@ interface AvatarProps { const Avatar = ({ pubkey, user, - size, + size = 48, onClick, image, imageOverlay, icons, className, showTitle = true, -}: AvatarProps) => { + ...others +}: AvatarProps & Omit, "onClick" | "style" | "className">) => { const defaultImg = defaultAvatar(pubkey); const url = useMemo(() => { if ((image?.length ?? 0) > 0) return image; @@ -51,22 +50,39 @@ const Avatar = ({ onClick={onClick} style={style} className={classNames( - "avatar relative flex items-center justify-center", - { "with-overlay": imageOverlay }, + "relative rounded-full aspect-square flex items-center justify-center gap-2 bg-gray", { "outline outline-2 outline-nostr-purple m-[2px]": isDefault }, className, )} data-domain={domain?.toLowerCase()} - title={showTitle ? getDisplayName(user, "") : undefined}> + title={showTitle ? getDisplayName(user, "") : undefined} + {...others}> - {icons &&
{icons}
} - {imageOverlay &&
{imageOverlay}
} + {icons && ( +
+
+ {icons} +
+
+ )} + {imageOverlay && ( +
+ {imageOverlay} +
+ )} ); }; diff --git a/packages/app/src/Components/User/AvatarGroup.tsx b/packages/app/src/Components/User/AvatarGroup.tsx index 913e1752..cce16ea5 100644 --- a/packages/app/src/Components/User/AvatarGroup.tsx +++ b/packages/app/src/Components/User/AvatarGroup.tsx @@ -1,5 +1,4 @@ import { HexKey } from "@snort/system"; -import React from "react"; import ProfileImage from "@/Components/User/ProfileImage"; @@ -13,6 +12,8 @@ export function AvatarGroup({ ids, onClick, size }: { ids: HexKey[]; onClick?: ( pubkey={a} size={size ?? 24} showUsername={false} + showBadges={false} + showProfileCard={false} /> )); diff --git a/packages/app/src/Components/User/FollowDistanceIndicator.tsx b/packages/app/src/Components/User/FollowDistanceIndicator.tsx index 43967ff6..480493c6 100644 --- a/packages/app/src/Components/User/FollowDistanceIndicator.tsx +++ b/packages/app/src/Components/User/FollowDistanceIndicator.tsx @@ -1,6 +1,5 @@ import { HexKey, socialGraphInstance } from "@snort/system"; import classNames from "classnames"; -import React from "react"; import Icon from "@/Components/Icons/Icon"; @@ -31,8 +30,10 @@ export default function FollowDistanceIndicator({ pubkey, className }: FollowDis } return ( - +
- +
); } diff --git a/packages/app/src/Components/User/FollowListBase.tsx b/packages/app/src/Components/User/FollowListBase.tsx index ae41d6c7..fc125204 100644 --- a/packages/app/src/Components/User/FollowListBase.tsx +++ b/packages/app/src/Components/User/FollowListBase.tsx @@ -34,7 +34,7 @@ export default function FollowListBase({ } return ( -
+
{(showFollowAll ?? true) && (
{title}
@@ -45,9 +45,7 @@ export default function FollowListBase({
)}
- {pubkeys?.map((a, index) => ( - 10} {...profilePreviewProps} /> - ))} + {pubkeys?.slice(0, 20).map(a => )}
); diff --git a/packages/app/src/Components/User/FollowedBy.tsx b/packages/app/src/Components/User/FollowedBy.tsx index e3e8b2e8..68469b0f 100644 --- a/packages/app/src/Components/User/FollowedBy.tsx +++ b/packages/app/src/Components/User/FollowedBy.tsx @@ -4,7 +4,6 @@ import { FormattedMessage } from "react-intl"; import { AvatarGroup } from "@/Components/User/AvatarGroup"; import DisplayName from "@/Components/User/DisplayName"; -import FollowDistanceIndicator from "@/Components/User/FollowDistanceIndicator"; import { ProfileLink } from "@/Components/User/ProfileLink"; const MAX_FOLLOWED_BY_FRIENDS = 3; @@ -31,9 +30,8 @@ export default function FollowedBy({ pubkey }: { pubkey: HexKey }) { }; return ( -
-
- +
+
{totalFollowedByFriends > 0 && ( diff --git a/packages/app/src/Components/User/ProfileImage.css b/packages/app/src/Components/User/ProfileImage.css deleted file mode 100644 index 9e11ba91..00000000 --- a/packages/app/src/Components/User/ProfileImage.css +++ /dev/null @@ -1,41 +0,0 @@ -.pfp { - display: grid; - grid-template-columns: min-content auto; - align-items: center; - text-decoration: none; - user-select: none; - min-width: 0; - gap: 8px; -} - -.pfp .avatar { - width: 48px; - height: 48px; - cursor: pointer; - position: relative; -} - -a.pfp { - text-decoration: none; -} - -.pfp .profile-name { - max-width: stretch; - max-width: -webkit-fill-available; - max-width: -moz-available; -} - -.pfp a { - text-decoration: none; -} - -.pfp .icon-circle { - display: flex; - align-items: center; - justify-content: center; - transform-origin: center; - padding: 4px; - border-radius: 100%; - background-color: var(--gray-superdark); - transform: rotate(135deg); -} diff --git a/packages/app/src/Components/User/ProfileImage.tsx b/packages/app/src/Components/User/ProfileImage.tsx index 116a2801..c46c054f 100644 --- a/packages/app/src/Components/User/ProfileImage.tsx +++ b/packages/app/src/Components/User/ProfileImage.tsx @@ -1,5 +1,3 @@ -import "./ProfileImage.css"; - import { HexKey, UserMetadata } from "@snort/system"; import { useUserProfile } from "@snort/system-react"; import classNames from "classnames"; @@ -31,6 +29,7 @@ export interface ProfileImageProps { icons?: ReactNode; showProfileCard?: boolean; showBadges?: boolean; + displayNameClassName?: string; } export default function ProfileImage({ @@ -48,6 +47,7 @@ export default function ProfileImage({ icons, showProfileCard = false, showBadges = false, + displayNameClassName, }: ProfileImageProps) { const user = useUserProfile(profile ? "" : pubkey) ?? profile; const [isHovering, setIsHovering] = useState(false); @@ -75,30 +75,29 @@ export default function ProfileImage({ function inner() { return ( <> -
- - {icons} - {showFollowDistance && } - - ) : undefined - } - /> -
+ + {icons} + {showFollowDistance && } + + ) : undefined + } + /> {showUsername && ( -
+
{overrideUsername ? overrideUsername : } {leader && showBadges && CONFIG.features.communityLeaders && }
-
{subHeader}
+ {subHeader}
)} @@ -108,7 +107,7 @@ export default function ProfileImage({ function profileCard() { if (showProfileCard && user && isHovering) { return ( -
+
); @@ -116,10 +115,17 @@ export default function ProfileImage({ return null; } + const classNamesOverInner = classNames( + "min-w-0", + { + "grid grid-cols-[min-content_auto] gap-3 items-center": showUsername, + }, + className, + ); if (link === "") { return ( <> -
+
{inner()}
{profileCard()} @@ -127,12 +133,12 @@ export default function ProfileImage({ ); } else { return ( -
+
{inner()} diff --git a/packages/app/src/Components/User/ProfileLink.tsx b/packages/app/src/Components/User/ProfileLink.tsx index 510bf9e5..69b9b769 100644 --- a/packages/app/src/Components/User/ProfileLink.tsx +++ b/packages/app/src/Components/User/ProfileLink.tsx @@ -4,6 +4,7 @@ import { ReactNode, useContext } from "react"; import { Link, LinkProps } from "react-router-dom"; import { randomSample } from "@/Utils"; +import { useProfileLink } from "@/Hooks/useProfileLink"; export function ProfileLink({ pubkey, @@ -17,39 +18,13 @@ export function ProfileLink({ explicitLink?: string; children?: ReactNode; } & Omit) { - const system = useContext(SnortContext); - const relays = system.relayCache - .getFromCache(pubkey) - ?.relays?.filter(a => a.settings.write) - ?.map(a => a.url); - - function profileLink() { - if (explicitLink) { - return explicitLink; - } - if ( - user?.nip05 && - user.nip05.endsWith(`@${CONFIG.nip05Domain}`) && - (!("isNostrAddressValid" in user) || user.isNostrAddressValid) - ) { - const [username] = user.nip05.split("@"); - return `/${username}`; - } - return `/${new NostrLink( - NostrPrefix.Profile, - pubkey, - undefined, - undefined, - relays ? randomSample(relays, 3) : undefined, - ).encode(CONFIG.profileLinkPrefix)}`; - } - + const link = useProfileLink(pubkey, user); const oFiltered = others as Record; delete oFiltered["user"]; delete oFiltered["link"]; delete oFiltered["children"]; return ( - + {children} ); diff --git a/packages/app/src/Components/User/ProfilePreview.css b/packages/app/src/Components/User/ProfilePreview.css deleted file mode 100644 index 67f7cc3e..00000000 --- a/packages/app/src/Components/User/ProfilePreview.css +++ /dev/null @@ -1,17 +0,0 @@ -.profile-preview { - display: flex; - align-items: center; - min-height: 59px; -} - -.profile-preview .pfp { - flex: 1 1 auto; -} - -.profile-preview .about { - font-size: small; - color: var(--font-secondary-color); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} diff --git a/packages/app/src/Components/User/ProfilePreview.tsx b/packages/app/src/Components/User/ProfilePreview.tsx index 122592c8..3827be05 100644 --- a/packages/app/src/Components/User/ProfilePreview.tsx +++ b/packages/app/src/Components/User/ProfilePreview.tsx @@ -1,9 +1,7 @@ -import "./ProfilePreview.css"; - import { HexKey, UserMetadata } from "@snort/system"; import { useUserProfile } from "@snort/system-react"; -import { ReactNode } from "react"; -import { useInView } from "react-intersection-observer"; +import classNames from "classnames"; +import { forwardRef, ReactNode } from "react"; import FollowButton from "@/Components/User/FollowButton"; import ProfileImage, { ProfileImageProps } from "@/Components/User/ProfileImage"; @@ -17,13 +15,14 @@ export interface ProfilePreviewProps { actions?: ReactNode; className?: string; onClick?: (e: React.MouseEvent) => void; - waitUntilInView?: boolean; profileImageProps?: Omit; } -export default function ProfilePreview(props: ProfilePreviewProps) { +const ProfilePreview = forwardRef(function ProfilePreview( + props: ProfilePreviewProps, + ref, +) { const pubkey = props.pubkey; - const { ref, inView } = useInView({ triggerOnce: true, rootMargin: "500px" }); - const user = useUserProfile(inView ? pubkey : undefined); + const user = useUserProfile(pubkey); const options = { about: true, ...props.options, @@ -39,26 +38,29 @@ export default function ProfilePreview(props: ProfilePreviewProps) { return ( <> -
- {(!props.waitUntilInView || inView) && ( - <> - {user?.about}
} - {...props.profileImageProps} - /> - {props.actions ?? ( -
- +
+ + {user?.about}
- )} - + ) + } + {...props.profileImageProps} + /> + {props.actions ?? ( +
+ +
)}
); -} +}); + +export default ProfilePreview; diff --git a/packages/app/src/Components/ZapModal/ZapModal.tsx b/packages/app/src/Components/ZapModal/ZapModal.tsx index 83b06003..30df9270 100644 --- a/packages/app/src/Components/ZapModal/ZapModal.tsx +++ b/packages/app/src/Components/ZapModal/ZapModal.tsx @@ -86,9 +86,9 @@ export default function ZapModal(props: SendSatsProps) { if (!(props.show ?? false)) return null; return ( -
-
-
+
+
+
{props.title || }
diff --git a/packages/app/src/Feed/ArticlesFeed.ts b/packages/app/src/Feed/ArticlesFeed.ts index 213125bc..03b0f4e8 100644 --- a/packages/app/src/Feed/ArticlesFeed.ts +++ b/packages/app/src/Feed/ArticlesFeed.ts @@ -10,7 +10,7 @@ export function useArticles() { const sub = useMemo(() => { const rb = new RequestBuilder("articles"); if (followList.length > 0) { - rb.withFilter().kinds([EventKind.LongFormTextNote]).authors(followList).limit(20); + rb.withFilter().kinds([EventKind.LongFormTextNote]).authors(followList); } return rb; }, [followList]); diff --git a/packages/app/src/Feed/WorkerRelayView.ts b/packages/app/src/Feed/WorkerRelayView.ts index 32e219ec..740ce033 100644 --- a/packages/app/src/Feed/WorkerRelayView.ts +++ b/packages/app/src/Feed/WorkerRelayView.ts @@ -8,14 +8,14 @@ export function useNotificationsView() { const publicKey = useLogin(s => s.publicKey); const kinds = [EventKind.TextNote, EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt]; const req = useMemo(() => { + const rb = new RequestBuilder("notifications"); + rb.withOptions({ + leaveOpen: true, + }); if (publicKey) { - const rb = new RequestBuilder("notifications"); - rb.withOptions({ - leaveOpen: true, - }); rb.withFilter().kinds(kinds).tag("p", [publicKey]); - return rb; } + return rb; }, [publicKey]); return useRequestBuilder(req); } diff --git a/packages/app/src/Hooks/useFollowControls.ts b/packages/app/src/Hooks/useFollowControls.ts index ba45736d..055e754d 100644 --- a/packages/app/src/Hooks/useFollowControls.ts +++ b/packages/app/src/Hooks/useFollowControls.ts @@ -1,4 +1,5 @@ import { NostrLink } from "@snort/system"; +import { useMemo } from "react"; import useLogin from "./useLogin"; @@ -6,27 +7,30 @@ import useLogin from "./useLogin"; * Simple hook for adding / removing follows */ export default function useFollowsControls() { - const { state } = useLogin(s => ({ v: s.state.version, state: s.state })); + const { state, v } = useLogin(s => ({ v: s.state.version, state: s.state })); - return { - isFollowing: (pk: string) => { - return state.follows?.includes(pk); - }, - addFollow: async (pk: Array) => { - for (const p of pk) { - await state.follow(NostrLink.publicKey(p), false); - } - await state.saveContacts(); - }, - removeFollow: async (pk: Array) => { - for (const p of pk) { - await state.unfollow(NostrLink.publicKey(p), false); - } - await state.saveContacts(); - }, - setFollows: async (pk: Array) => { - await state.replaceFollows(pk.map(a => NostrLink.publicKey(a))); - }, - followList: state.follows ?? [], - }; + return useMemo(() => { + const follows = state.follows; + return { + isFollowing: (pk: string) => { + return follows?.includes(pk); + }, + addFollow: async (pk: Array) => { + for (const p of pk) { + await state.follow(NostrLink.publicKey(p), false); + } + await state.saveContacts(); + }, + removeFollow: async (pk: Array) => { + for (const p of pk) { + await state.unfollow(NostrLink.publicKey(p), false); + } + await state.saveContacts(); + }, + setFollows: async (pk: Array) => { + await state.replaceFollows(pk.map(a => NostrLink.publicKey(a))); + }, + followList: follows ?? [], + }; + }, [v]); } diff --git a/packages/app/src/Hooks/useProfileLink.ts b/packages/app/src/Hooks/useProfileLink.ts new file mode 100644 index 00000000..ed6485bd --- /dev/null +++ b/packages/app/src/Hooks/useProfileLink.ts @@ -0,0 +1,25 @@ +import { CachedMetadata, NostrLink, UserMetadata } from "@snort/system"; +import { SnortContext } from "@snort/system-react"; +import { useContext } from "react"; + +import { randomSample } from "@/Utils"; + +export function useProfileLink(pubkey?: string, user?: UserMetadata | CachedMetadata) { + const system = useContext(SnortContext); + if (!pubkey) return "#"; + const relays = system.relayCache + .getFromCache(pubkey) + ?.relays?.filter(a => a.settings.write) + ?.map(a => a.url); + + if ( + user?.nip05 && + user.nip05.endsWith(`@${CONFIG.nip05Domain}`) && + (!("isNostrAddressValid" in user) || user.isNostrAddressValid) + ) { + const [username] = user.nip05.split("@"); + return `/${username}`; + } + const link = NostrLink.profile(pubkey, relays ? randomSample(relays, 3) : undefined); + return `/${link.encode(CONFIG.profileLinkPrefix)}`; +} diff --git a/packages/app/src/Hooks/useWindowSize.ts b/packages/app/src/Hooks/useWindowSize.ts new file mode 100644 index 00000000..df4335d0 --- /dev/null +++ b/packages/app/src/Hooks/useWindowSize.ts @@ -0,0 +1,20 @@ +import { useEffect, useState } from "react"; + +export default function useWindowSize() { + const [dims, setDims] = useState({ + width: window.innerWidth, + height: window.innerHeight, + }); + + useEffect(() => { + const handler = () => { + setDims({ + width: window.innerWidth, + height: window.innerHeight, + }); + }; + window.addEventListener("resize", handler); + return () => window.removeEventListener("resize", handler); + }, []); + return dims; +} diff --git a/packages/app/src/Hooks/useWoT.ts b/packages/app/src/Hooks/useWoT.ts index 01bd66cb..11cdde2e 100644 --- a/packages/app/src/Hooks/useWoT.ts +++ b/packages/app/src/Hooks/useWoT.ts @@ -1,11 +1,15 @@ import { socialGraphInstance, TaggedNostrEvent } from "@snort/system"; +import { useMemo } from "react"; export default function useWoT() { - return { - sortEvents: (events: Array) => - events.sort( - (a, b) => socialGraphInstance.getFollowDistance(a.pubkey) - socialGraphInstance.getFollowDistance(b.pubkey), - ), - followDistance: (pk: string) => socialGraphInstance.getFollowDistance(pk), - }; + return useMemo( + () => ({ + sortEvents: (events: Array) => + events.sort( + (a, b) => socialGraphInstance.getFollowDistance(a.pubkey) - socialGraphInstance.getFollowDistance(b.pubkey), + ), + followDistance: (pk: string) => socialGraphInstance.getFollowDistance(pk), + }), + [socialGraphInstance.root], + ); } diff --git a/packages/app/src/Pages/Layout/Footer.tsx b/packages/app/src/Pages/Layout/Footer.tsx index fac8414e..a143a9a4 100644 --- a/packages/app/src/Pages/Layout/Footer.tsx +++ b/packages/app/src/Pages/Layout/Footer.tsx @@ -1,14 +1,13 @@ -import { useUserProfile } from "@snort/system-react"; import classNames from "classnames"; import React, { useState } from "react"; -import { useIntl } from "react-intl"; import NavLink from "@/Components/Button/NavLink"; import { NoteCreatorButton } from "@/Components/Event/Create/NoteCreatorButton"; import Icon from "@/Components/Icons/Icon"; -import Avatar from "@/Components/User/Avatar"; -import { ProfileLink } from "@/Components/User/ProfileLink"; import useLogin from "@/Hooks/useLogin"; +import useWindowSize from "@/Hooks/useWindowSize"; + +import ProfileMenu from "./ProfileMenu"; type MenuItem = { label?: string; @@ -34,33 +33,21 @@ const MENU_ITEMS: MenuItem[] = [ ]; const Footer = () => { - const { publicKey, readonly } = useLogin(s => ({ - publicKey: s.publicKey, + const { readonly } = useLogin(s => ({ readonly: s.readonly, })); - const profile = useUserProfile(publicKey); - const { formatMessage } = useIntl(); - - const readOnlyIcon = readonly && ( - - - - ); + const pageSize = useWindowSize(); + const isMobile = pageSize.width <= 768; //max-md + if (!isMobile) return; return (
-
+
{MENU_ITEMS.map((item, index) => ( ))} - {publicKey && ( - - - - )} + +
); @@ -83,7 +70,7 @@ const FooterNavItem = ({ item, readonly }: { item: MenuItem; readonly: boolean } onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} className={({ isActive }) => - classNames({ active: isActive || isHovered }, "flex flex-grow p-4 justify-center items-center cursor-pointer") + classNames({ active: isActive || isHovered }, "flex flex-1 p-4 justify-center items-center cursor-pointer") }> diff --git a/packages/app/src/Pages/Layout/NavSidebar.tsx b/packages/app/src/Pages/Layout/NavSidebar.tsx index 9a3e4f89..6c94c778 100644 --- a/packages/app/src/Pages/Layout/NavSidebar.tsx +++ b/packages/app/src/Pages/Layout/NavSidebar.tsx @@ -1,20 +1,19 @@ -import { useUserProfile } from "@snort/system-react"; import classNames from "classnames"; -import { FormattedMessage, useIntl } from "react-intl"; +import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; import NavLink from "@/Components/Button/NavLink"; import { NoteCreatorButton } from "@/Components/Event/Create/NoteCreatorButton"; import Icon from "@/Components/Icons/Icon"; -import Avatar from "@/Components/User/Avatar"; -import { ProfileLink } from "@/Components/User/ProfileLink"; import useEventPublisher from "@/Hooks/useEventPublisher"; import useLogin from "@/Hooks/useLogin"; +import useWindowSize from "@/Hooks/useWindowSize"; import { HasNotificationsMarker } from "@/Pages/Layout/HasNotificationsMarker"; import { WalletBalance } from "@/Pages/Layout/WalletBalance"; import { subscribeToNotifications } from "@/Utils/Notifications"; import { LogoHeader } from "./LogoHeader"; +import ProfileMenu from "./ProfileMenu"; const MENU_ITEMS = [ { @@ -79,20 +78,17 @@ export default function NavSidebar({ narrow = false }: { narrow?: boolean }) { publicKey: s.publicKey, readonly: s.readonly, })); - const profile = useUserProfile(publicKey); + const navigate = useNavigate(); const { publisher } = useEventPublisher(); - const { formatMessage } = useIntl(); + + const pageSize = useWindowSize(); + const isMobile = pageSize.width <= 768; //max-md + if (isMobile) return; const className = classNames( { "xl:w-56 xl:gap-2 xl:items-start": !narrow }, - "select-none overflow-y-auto hide-scrollbar sticky items-center border-r border-border-color top-0 z-20 h-screen max-h-screen hidden md:flex flex-col px-2 py-4 flex-shrink-0 gap-1", - ); - - const readOnlyIcon = readonly && ( - - - + "select-none overflow-y-auto hide-scrollbar sticky items-center border-r border-border-color top-0 z-20 h-screen max-h-screen flex flex-col px-2 py-4 flex-shrink-0 gap-1", ); return ( @@ -156,21 +152,7 @@ export default function NavSidebar({ narrow = false }: { narrow?: boolean }) { )}
- {publicKey && ( - <> - -
- - {!narrow && {profile?.name}} -
-
- {readonly && ( -
- -
- )} - - )} +
); } diff --git a/packages/app/src/Pages/Layout/ProfileMenu.tsx b/packages/app/src/Pages/Layout/ProfileMenu.tsx new file mode 100644 index 00000000..b7ce733d --- /dev/null +++ b/packages/app/src/Pages/Layout/ProfileMenu.tsx @@ -0,0 +1,90 @@ +import { Menu, MenuItem } from "@szhsin/react-menu"; +import classNames from "classnames"; +import { FormattedMessage } from "react-intl"; +import { useNavigate } from "react-router-dom"; + +import AsyncButton from "@/Components/Button/AsyncButton"; +import Icon from "@/Components/Icons/Icon"; +import ProfileImage from "@/Components/User/ProfileImage"; +import ProfilePreview from "@/Components/User/ProfilePreview"; +import useLogin from "@/Hooks/useLogin"; +import { useProfileLink } from "@/Hooks/useProfileLink"; +import useWindowSize from "@/Hooks/useWindowSize"; +import { LoginStore } from "@/Utils/Login"; + +export default function ProfileMenu({ className }: { className?: string }) { + const { publicKey, readonly } = useLogin(s => ({ + publicKey: s.publicKey, + readonly: s.readonly, + })); + const logins = LoginStore.getSessions(); + const navigate = useNavigate(); + const link = useProfileLink(publicKey); + + const pageSize = useWindowSize(); + const isNarrow = pageSize.width <= 1280; //xl + function profile() { + return ( + {!isNarrow && }} + profileImageProps={{ + size: 40, + link: "", + showBadges: false, + showProfileCard: false, + showFollowDistance: false, + displayNameClassName: "max-xl:hidden", + subHeader: readonly ? ( +
+ +
+ ) : undefined, + }} + /> + ); + } + + if (!publicKey) return; + return ( +
+ +
+ +
+ +
+ navigate(link)}> +
+ + +
+
+ + + + {logins + .filter(a => a.pubkey !== publicKey) + .map(a => ( + + LoginStore.switchAccount(a.id)} + /> + + ))} + + + + + +
+
+ ); +} diff --git a/packages/app/src/Pages/Layout/RightColumn.tsx b/packages/app/src/Pages/Layout/RightColumn.tsx index 188dc501..482206e0 100644 --- a/packages/app/src/Pages/Layout/RightColumn.tsx +++ b/packages/app/src/Pages/Layout/RightColumn.tsx @@ -12,12 +12,17 @@ import TrendingHashtags from "@/Components/Trending/TrendingHashtags"; import TrendingNotes from "@/Components/Trending/TrendingPosts"; import TrendingUsers from "@/Components/Trending/TrendingUsers"; import useLogin from "@/Hooks/useLogin"; +import useWindowSize from "@/Hooks/useWindowSize"; export default function RightColumn() { const { pubkey } = useLogin(s => ({ pubkey: s.publicKey })); const hideRightColumnPaths = ["/login", "/new", "/messages"]; const show = !hideRightColumnPaths.some(path => globalThis.location.pathname.startsWith(path)); + const pageSize = useWindowSize(); + const isDesktop = pageSize.width >= 1024; //max-xl + if (!isDesktop) return; + const widgets = pubkey ? [ RightColumnWidget.TaskList, diff --git a/packages/app/src/Pages/Profile/ProfileTabComponents.tsx b/packages/app/src/Pages/Profile/ProfileTabComponents.tsx index cd383689..10f7777a 100644 --- a/packages/app/src/Pages/Profile/ProfileTabComponents.tsx +++ b/packages/app/src/Pages/Profile/ProfileTabComponents.tsx @@ -72,12 +72,32 @@ export function ZapsProfileTab({ id }: { id: HexKey }) { export function FollowersTab({ id }: { id: HexKey }) { const followers = useFollowersFeed(id); - return ; + return ( + + ); } export function FollowsTab({ id }: { id: HexKey }) { const follows = useFollowsFeed(id); - return ; + return ( + + ); } export function RelaysTab({ id }: { id: HexKey }) { diff --git a/packages/app/src/Pages/settings/Accounts.tsx b/packages/app/src/Pages/settings/Accounts.tsx index 72a6ad8d..5249153f 100644 --- a/packages/app/src/Pages/settings/Accounts.tsx +++ b/packages/app/src/Pages/settings/Accounts.tsx @@ -10,20 +10,20 @@ export default function AccountsPage() { const sub = getActiveSubscriptions(LoginStore.allSubscriptions()); return ( -
+

{logins.map(a => ( -
+
-