From 42c2eeca2fbd88682beb6d21dc5691e92d68639c Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Tue, 7 Feb 2023 17:04:18 -0600 Subject: [PATCH] add identicons --- README.md | 3 +++ package.json | 2 ++ src/components/post.tsx | 16 ++++++------- src/components/user-avatar-link.tsx | 28 +++++++++++++++++++++++ src/components/user-avatar.tsx | 28 +++++++++++++++++++++++ src/helpers/user-metadata.ts | 9 ++++++++ src/views/home.tsx | 35 +++++++++++++---------------- src/views/user/index.tsx | 12 +++++----- src/views/user/posts.tsx | 3 +++ yarn.lock | 10 +++++++++ 10 files changed, 112 insertions(+), 34 deletions(-) create mode 100644 README.md create mode 100644 src/components/user-avatar-link.tsx create mode 100644 src/components/user-avatar.tsx create mode 100644 src/helpers/user-metadata.ts diff --git a/README.md b/README.md new file mode 100644 index 000000000..d8311065e --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# TODO + +- Adding loading state to `useUserMetadata` so views can show loading state diff --git a/package.json b/package.json index cd5e166cd..4096cd8f8 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@emotion/styled": "^11.10.5", "framer-motion": "^7.10.3", "idb": "^7.1.1", + "identicon.js": "^2.3.3", "moment": "^2.29.4", "noble-secp256k1": "^1.2.14", "react": "^18.2.0", @@ -27,6 +28,7 @@ }, "devDependencies": { "@changesets/cli": "^2.26.0", + "@types/identicon.js": "^2.3.1", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.9", "@vitejs/plugin-react": "^3.0.0", diff --git a/src/components/post.tsx b/src/components/post.tsx index 4043aee74..d4004e20c 100644 --- a/src/components/post.tsx +++ b/src/components/post.tsx @@ -1,6 +1,5 @@ import React from "react"; import { - Avatar, Box, Button, Card, @@ -19,30 +18,31 @@ import moment from "moment"; import { PostModal } from "./post-modal"; import { NostrEvent } from "../types/nostr-event"; import { useUserMetadata } from "../hooks/use-user-metadata"; -import useSubject from "../hooks/use-subject"; -import settings from "../services/settings"; +import { UserAvatarLink } from "./user-avatar-link"; +import { getUserFullName } from "../helpers/user-metadata"; export type PostProps = { event: NostrEvent; }; export const Post = React.memo(({ event }: PostProps) => { const { isOpen, onClose, onOpen } = useDisclosure(); - const userMetadata = useUserMetadata(event.pubkey); + const metadata = useUserMetadata(event.pubkey); const isLong = event.content.length > 800; + const username = metadata + ? getUserFullName(metadata) || event.pubkey + : event.pubkey; return ( - + - - {userMetadata?.name ?? event.pubkey} - + {username} {moment(event.created_at * 1000).fromNow()} diff --git a/src/components/user-avatar-link.tsx b/src/components/user-avatar-link.tsx new file mode 100644 index 000000000..afec2fa07 --- /dev/null +++ b/src/components/user-avatar-link.tsx @@ -0,0 +1,28 @@ +import { Tooltip } from "@chakra-ui/react"; +import { Link } from "react-router-dom"; +import { useUserMetadata } from "../hooks/use-user-metadata"; +import { UserAvatar } from "./user-avatar"; + +export type UserAvatarProps = { + pubkey: string; +}; +export const UserAvatarLink = ({ pubkey }: UserAvatarProps) => { + const metadata = useUserMetadata(pubkey); + + let label = "Loading..."; + if (metadata?.display_name && metadata?.name) { + label = `${metadata.display_name} (${metadata.name})`; + } else if (metadata?.name) { + label = metadata.name; + } else { + label = pubkey; + } + + return ( + + + + + + ); +}; diff --git a/src/components/user-avatar.tsx b/src/components/user-avatar.tsx new file mode 100644 index 000000000..393328dfe --- /dev/null +++ b/src/components/user-avatar.tsx @@ -0,0 +1,28 @@ +import React, { useMemo } from "react"; +import { Avatar } from "@chakra-ui/react"; +import Identicon from "identicon.js"; +import { useUserMetadata } from "../hooks/use-user-metadata"; + +const cache: Record = {}; +function getIdenticon(pubkey: string) { + if (!cache[pubkey]) { + cache[pubkey] = new Identicon(pubkey, { format: "svg" }); + } + return cache[pubkey]; +} + +export type UserAvatarProps = { + pubkey: string; +}; +export const UserAvatar = React.memo(({ pubkey }: UserAvatarProps) => { + const metadata = useUserMetadata(pubkey); + + const url = useMemo(() => { + return ( + metadata?.picture ?? + `data:image/svg+xml;base64,${getIdenticon(pubkey).toString()}` + ); + }, [metadata]); + + return ; +}); diff --git a/src/helpers/user-metadata.ts b/src/helpers/user-metadata.ts new file mode 100644 index 000000000..a3f634896 --- /dev/null +++ b/src/helpers/user-metadata.ts @@ -0,0 +1,9 @@ +import { Kind0ParsedContent } from "../types/nostr-event"; + +export function getUserFullName(metadata: Kind0ParsedContent) { + if (metadata?.display_name && metadata?.name) { + return `${metadata.display_name} (${metadata.name})`; + } else if (metadata?.name) { + return metadata.name; + } +} diff --git a/src/views/home.tsx b/src/views/home.tsx index 63d06ae3c..85b838b8d 100644 --- a/src/views/home.tsx +++ b/src/views/home.tsx @@ -1,29 +1,24 @@ -import { Avatar, HStack } from "@chakra-ui/react"; +import { HStack } from "@chakra-ui/react"; import { Link } from "react-router-dom"; -import { useUserMetadata } from "../hooks/use-user-metadata"; - -const UserAvatar = ({ pubkey }: { pubkey: string }) => { - const metadata = useUserMetadata(pubkey); - - return ( - - - - ); -}; +import { UserAvatar } from "../components/user-avatar"; +import { UserAvatarLink } from "../components/user-avatar-link"; export const HomeView = () => { return ( <> - - - - - - - - + + + + + + + + + + + + ); diff --git a/src/views/user/index.tsx b/src/views/user/index.tsx index 68bf6c062..833e9a95c 100644 --- a/src/views/user/index.tsx +++ b/src/views/user/index.tsx @@ -1,6 +1,4 @@ -import React from "react"; import { - Avatar, Box, Heading, HStack, @@ -10,13 +8,14 @@ import { TabPanel, TabPanels, Tabs, - Text, VStack, } from "@chakra-ui/react"; import { useParams } from "react-router-dom"; import { UserPostsTab } from "./posts"; import { useUserMetadata } from "../../hooks/use-user-metadata"; import ReactMarkdown from "react-markdown"; +import { UserAvatar } from "../../components/user-avatar"; +import { getUserFullName } from "../../helpers/user-metadata"; export const UserView = () => { const { pubkey } = useParams(); @@ -26,15 +25,16 @@ export const UserView = () => { } const metadata = useUserMetadata(pubkey); + const label = metadata ? getUserFullName(metadata) || pubkey : pubkey; return ( {" "} - + - {metadata?.name ?? pubkey} - {metadata?.display_name} + {label} + {/* {metadata?.name} */} {metadata?.about ? ( diff --git a/src/views/user/posts.tsx b/src/views/user/posts.tsx index cf313fe0c..8a2616b4a 100644 --- a/src/views/user/posts.tsx +++ b/src/views/user/posts.tsx @@ -29,6 +29,9 @@ export const UserPostsTab = ({ pubkey }: { pubkey: string }) => { return () => s.unsubscribe(); }, [sub]); + // clear events when pubkey changes + useEffect(() => setEvents({}), [pubkey]); + const timeline = Object.values(events).sort( (a, b) => b.created_at - a.created_at ); diff --git a/yarn.lock b/yarn.lock index 13adf0221..e0f81f581 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2465,6 +2465,11 @@ dependencies: "@types/unist" "*" +"@types/identicon.js@^2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@types/identicon.js/-/identicon.js-2.3.1.tgz#bbfe440a3229d6930c12d933ebcbe8603957ea75" + integrity sha512-QyPIfllzfVTHVJ/xX5+cOKpWuX7Zv0EKQbzTCbIn6QjMOg4bn1j73Av1LIIvRqkDV+TErJuonwZg/IOl4tbPDQ== + "@types/is-ci@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/is-ci/-/is-ci-3.0.0.tgz#7e8910af6857601315592436f030aaa3ed9783c3" @@ -3616,6 +3621,11 @@ idb@^7.0.1, idb@^7.1.1: resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== +identicon.js@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/identicon.js/-/identicon.js-2.3.3.tgz#c505b8d60ecc6ea13bbd991a33964c44c1ad60a1" + integrity sha512-/qgOkXKZ7YbeCYbawJ9uQQ3XJ3uBg9VDpvHjabCAPp6aRMhjLaFAxG90+1TxzrhKaj6AYpVGrx6UXQfQA41UEA== + ignore@^5.2.0: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"