import { FALLBACK_PROFILE_IMAGE } from 'constants.ts' import { Event, Filter, kinds, nip19, UnsignedEvent } from 'nostr-tools' import { QRCodeSVG } from 'qrcode.react' import { Fragment, useMemo, useState } from 'react' import { Link } from 'react-router-dom' import { toast } from 'react-toastify' import { useAppSelector, useBodyScrollDisable, useDidMount, useNDKContext } from '../hooks' import { appRoutes, getProfilePageRoute } from '../routes' import '../styles/author.css' import '../styles/innerPage.css' import '../styles/socialPosts.css' import { UserRelaysType } from '../types' import { copyTextToClipboard, hexToNpub, log, LogType, now, npubToHex, truncate } from '../utils' import { LoadingSpinner } from './LoadingSpinner' import { ZapPopUp } from './Zap' import placeholder from '../assets/img/DEGMods Placeholder Img.png' import { NDKEvent, NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk' import { useProfile } from 'hooks/useProfile' import { createPortal } from 'react-dom' type Props = { pubkey: string } export const ProfileSection = ({ pubkey }: Props) => { return (
{posts.map((post, index) => (

{post.name}

{post.content}

{post.imageUrl && (
)}
))}
) } type ProfileProps = { pubkey: string } export const Profile = ({ pubkey }: ProfileProps) => { const profile = useProfile(pubkey, { cacheUsage: NDKSubscriptionCacheUsage.PARALLEL }) const displayName = profile?.displayName || profile?.name || '[name not set up]' const about = profile?.bio || profile?.about || '[bio not set up]' const image = profile?.image || FALLBACK_PROFILE_IMAGE const nip05 = profile?.nip05 const lud16 = profile?.lud16 const npub = hexToNpub(pubkey) const handleCopy = async () => { copyTextToClipboard(npub).then((isCopied) => { if (isCopied) { toast.success('Npub copied to clipboard!') } else { toast.error( 'Failed to copy, look into console for more details on error!' ) } }) } // Try to encode let profileRoute = appRoutes.home let nprofile: string | undefined try { const hexPubkey = npubToHex(pubkey) nprofile = hexPubkey ? nip19.nprofileEncode({ pubkey: hexPubkey }) : undefined profileRoute = nprofile ? getProfilePageRoute(nprofile) : appRoutes.home } catch (error) { // Silently ignore and redirect to home log(true, LogType.Error, 'Failed to encode profile.', error) } return (

{displayName}

{/* Nip05 can sometimes be an empty object '{}' which causes the error */} {typeof nip05 === 'string' && nip05 !== '' && (

{nip05}

)}

{npub}

{typeof nprofile !== 'undefined' && ( )} {typeof lud16 !== 'undefined' && lud16 !== '' && ( )}

{about}

) } interface Post { name: string link: string content: string imageUrl?: string } const posts: Post[] = [ { name: 'User name', link: `feed-note.html`, content: `user text, this is a long string of temporary text that would be replaced with the user post from their short posts` }, { name: 'User name', link: 'feed-note.html', content: `user text, this is a long string of temporary text that would be replaced with the user post from their short posts` }, { name: 'User name', link: `feed-note.html`, content: `user text, this is a long string of temporary text that would be replaced with the user post from their short posts`, imageUrl: placeholder } ] type QRButtonWithPopUpProps = { nprofile: string } export const ProfileQRButtonWithPopUp = ({ nprofile }: QRButtonWithPopUpProps) => { const [isOpen, setIsOpen] = useState(false) useBodyScrollDisable(isOpen) return ( <>
setIsOpen(true)} >
{isOpen && ( setIsOpen(false)} /> )} ) } interface ProfileQRPopupProps { nprofile: string handleClose: () => void } const ProfileQRPopup = ({ nprofile, handleClose }: ProfileQRPopupProps) => { const onQrCodeClicked = async () => { const href = `https://njump.me/${nprofile}` const a = document.createElement('a') a.href = href a.target = '_blank' // Open in a new tab a.rel = 'noopener noreferrer' // Recommended for security reasons a.click() } return createPortal(

Nostr Address

, document.body ) } type ZapButtonWithPopUpProps = { pubkey: string } const ZapButtonWithPopUp = ({ pubkey }: ZapButtonWithPopUpProps) => { const [isOpen, setIsOpen] = useState(false) useBodyScrollDisable(isOpen) return ( <>
setIsOpen(true)} >
{isOpen && ( setIsOpen(false)} /> )} ) } type FollowButtonProps = { pubkey: string } const FollowButton = ({ pubkey }: FollowButtonProps) => { const { ndk, fetchEventFromUserRelays, publish } = useNDKContext() const [isFollowing, setIsFollowing] = useState(false) const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') const userState = useAppSelector((state) => state.user) useDidMount(async () => { if (userState.auth && userState.user?.pubkey) { const userHexKey = userState.user.pubkey as string const { isFollowing: isAlreadyFollowing } = await checkIfFollowing( userHexKey, pubkey ) setIsFollowing(isAlreadyFollowing) } }) // Hide follow if own profile if ( userState.auth && userState.user?.pubkey && userState.user?.pubkey === pubkey ) { return null } const getUserPubKey = async (): Promise => { if (userState.auth && userState.user?.pubkey) { return userState.user.pubkey as string } else { try { return (await window.nostr?.getPublicKey()) as string } catch (error) { log(true, LogType.Error, `Could not get pubkey`, error) return null } } } const checkIfFollowing = async ( userHexKey: string, pubkey: string ): Promise<{ isFollowing: boolean tags: string[][] }> => { const filter: Filter = { kinds: [kinds.Contacts], authors: [userHexKey] } const contactListEvent = await fetchEventFromUserRelays( filter, userHexKey, UserRelaysType.Both ) if (!contactListEvent) return { isFollowing: false, tags: [] } return { isFollowing: contactListEvent.tags.some( (t) => t[0] === 'p' && t[1] === pubkey ), tags: contactListEvent.tags } } const signAndPublishEvent = async ( unsignedEvent: UnsignedEvent ): Promise => { const signedEvent = await window.nostr ?.signEvent(unsignedEvent) .then((event) => event as Event) .catch((err) => { toast.error('Failed to sign the event!') log(true, LogType.Error, 'Failed to sign the event!', err) return null }) if (!signedEvent) return false const ndkEvent = new NDKEvent(ndk, signedEvent) const publishedOnRelays = await publish(ndkEvent) if (publishedOnRelays.length === 0) { toast.error('Failed to publish event on any relay') return false } toast.success( `Event published successfully to the following relays\n\n${publishedOnRelays.join( '\n' )}` ) return true } const handleFollow = async () => { setIsLoading(true) setLoadingSpinnerDesc('Processing follow request') const userHexKey = await getUserPubKey() if (!userHexKey) { setIsLoading(false) toast.error('Could not get pubkey') return } const { isFollowing: isAlreadyFollowing, tags } = await checkIfFollowing( userHexKey, pubkey ) if (isAlreadyFollowing) { toast.info('Already following!') setIsFollowing(true) setIsLoading(false) return } const unsignedEvent: UnsignedEvent = { content: '', created_at: now(), kind: kinds.Contacts, pubkey: userHexKey, tags: [...tags, ['p', pubkey]] } setLoadingSpinnerDesc('Signing and publishing follow event') const success = await signAndPublishEvent(unsignedEvent) setIsFollowing(success) setIsLoading(false) } const handleUnFollow = async () => { setIsLoading(true) setLoadingSpinnerDesc('Processing unfollow request') const userHexKey = await getUserPubKey() if (!userHexKey) { setIsLoading(false) toast.error('Could not get pubkey') return } const filter: Filter = { kinds: [kinds.Contacts], authors: [userHexKey] } const contactListEvent = await fetchEventFromUserRelays( filter, userHexKey, UserRelaysType.Both ) if ( !contactListEvent || !contactListEvent.tags.some((t) => t[0] === 'p' && t[1] === pubkey) ) { // could not found target pubkey in user's follow list // so, just update the status and return setIsFollowing(false) setIsLoading(false) return } const unsignedEvent: UnsignedEvent = { content: '', created_at: now(), kind: kinds.Contacts, pubkey: userHexKey, tags: contactListEvent.tags.filter( (t) => !(t[0] === 'p' && t[1] === pubkey) ) } setLoadingSpinnerDesc('Signing and publishing unfollow event') const success = await signAndPublishEvent(unsignedEvent) setIsFollowing(!success) setIsLoading(false) } return ( <> {isLoading && } ) } type ProfileLinkProps = { pubkey?: string nip05?: string fallback?: string } export const ProfileLink = ({ pubkey, nip05, fallback }: ProfileLinkProps) => { const { ndk } = useNDKContext() const [hexPubkey, setHexPubkey] = useState() const profile = useProfile(hexPubkey, { cacheUsage: NDKSubscriptionCacheUsage.PARALLEL }) useDidMount(async () => { if (pubkey) { setHexPubkey(npubToHex(pubkey)!) } else if (nip05) { ndk.getUserFromNip05(nip05).then((user) => { if (user?.pubkey) { setHexPubkey(npubToHex(user.pubkey)!) } }) } }) const profileRoute = useMemo(() => { let nprofile: string | undefined try { if (hexPubkey) { nprofile = hexPubkey ? nip19.nprofileEncode({ pubkey: hexPubkey }) : undefined } } catch (error) { // Silently ignore log(true, LogType.Error, 'Failed to encode profile.', error) } return nprofile ? getProfilePageRoute(nprofile) : undefined }, [hexPubkey]) const displayName = useMemo(() => { const npub = hexPubkey ? hexToNpub(hexPubkey) : '' const displayName = profile?.displayName || profile?.name || truncate(npub) return displayName }, [hexPubkey, profile?.displayName, profile?.name]) if (!hexPubkey) return {fallback} if (!profileRoute) return @{displayName} return @{displayName} }