UserPopover (#1094)
* WIP UserPopover * Add show delay on UserPopover * UserDetails -> StackingSince on UserPopover * Make UserPopover hoverable * Add felipe to contributors.txt * Remove export from SocialLink * Remove @ outside of UserPopover * userQuery -> useLazyQuery + Handling user not found * Move styles to user-popover.module.css * Update components/user-popover.module.css Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Remove poll + SSR check from useLazyQuery * USER_FULL -> USER (we are only using stacking since, for now) * refine user popover --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
This commit is contained in:
		
							parent
							
								
									cdeaa35ff4
								
							
						
					
					
						commit
						72c27e339c
					
				@ -21,6 +21,7 @@ import MuteDropdownItem from './mute'
 | 
			
		||||
import { DropdownItemUpVote } from './upvote'
 | 
			
		||||
import { useRoot } from './root'
 | 
			
		||||
import { MuteSubDropdownItem, PinSubDropdownItem } from './territory-header'
 | 
			
		||||
import UserPopover from './user-popover'
 | 
			
		||||
 | 
			
		||||
export default function ItemInfo ({
 | 
			
		||||
  item, full, commentsText = 'comments',
 | 
			
		||||
@ -101,10 +102,12 @@ export default function ItemInfo ({
 | 
			
		||||
      </Link>
 | 
			
		||||
      <span> \ </span>
 | 
			
		||||
      <span>
 | 
			
		||||
        <Link href={`/${item.user.name}`}>
 | 
			
		||||
          @{item.user.name}<span> </span><Hat className='fill-grey' user={item.user} height={12} width={12} />
 | 
			
		||||
          {embellishUser}
 | 
			
		||||
        </Link>
 | 
			
		||||
        <UserPopover name={item.user.name}>
 | 
			
		||||
          <Link href={`/${item.user.name}`}>
 | 
			
		||||
            @{item.user.name}<span> </span><Hat className='fill-grey' user={item.user} height={12} width={12} />
 | 
			
		||||
            {embellishUser}
 | 
			
		||||
          </Link>
 | 
			
		||||
        </UserPopover>
 | 
			
		||||
        <span> </span>
 | 
			
		||||
        <Link href={`/items/${item.id}`} title={item.createdAt} className='text-reset' suppressHydrationWarning>
 | 
			
		||||
          {timeSince(new Date(item.createdAt))}
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ import { useRouter } from 'next/router'
 | 
			
		||||
import Link from 'next/link'
 | 
			
		||||
import { UNKNOWN_LINK_REL } from '@/lib/constants'
 | 
			
		||||
import isEqual from 'lodash/isEqual'
 | 
			
		||||
import UserPopover from './user-popover'
 | 
			
		||||
 | 
			
		||||
export function SearchText ({ text }) {
 | 
			
		||||
  return (
 | 
			
		||||
@ -196,7 +197,18 @@ export default memo(function Text ({ rel, imgproxyUrls, children, tab, itemId, o
 | 
			
		||||
                  </Link>
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
              if (href.startsWith('/') || url?.origin === internalURL) {
 | 
			
		||||
              if (text.startsWith('@')) {
 | 
			
		||||
                return (
 | 
			
		||||
                  <UserPopover name={text.replace('@', '')}>
 | 
			
		||||
                    <Link
 | 
			
		||||
                      id={props.id}
 | 
			
		||||
                      href={href}
 | 
			
		||||
                    >
 | 
			
		||||
                      {text}
 | 
			
		||||
                    </Link>
 | 
			
		||||
                  </UserPopover>
 | 
			
		||||
                )
 | 
			
		||||
              } else if (href.startsWith('/') || url?.origin === internalURL) {
 | 
			
		||||
                return (
 | 
			
		||||
                  <Link
 | 
			
		||||
                    id={props.id}
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ import Hat from './hat'
 | 
			
		||||
import { useMe } from './me'
 | 
			
		||||
import { MEDIA_URL } from '@/lib/constants'
 | 
			
		||||
import { NymActionDropdown } from '@/components/user-header'
 | 
			
		||||
import classNames from 'classnames'
 | 
			
		||||
 | 
			
		||||
// all of this nonsense is to show the stat we are sorting by first
 | 
			
		||||
const Stacked = ({ user }) => (user.optional.stacked !== null && <span>{abbrNum(user.optional.stacked)} stacked</span>)
 | 
			
		||||
@ -39,7 +40,29 @@ function seperate (arr, seperator) {
 | 
			
		||||
  return arr.flatMap((x, i) => i < arr.length - 1 ? [x, seperator] : [x])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function User ({ user, rank, statComps, Embellish, nymActionDropdown = false }) {
 | 
			
		||||
export function UserBase ({ user, className, children, nymActionDropdown }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={classNames(styles.item, className)}>
 | 
			
		||||
      <Link href={`/${user.name}`}>
 | 
			
		||||
        <Image
 | 
			
		||||
          src={user.photoId ? `${MEDIA_URL}/${user.photoId}` : '/dorian400.jpg'} width='32' height='32'
 | 
			
		||||
          className={`${userStyles.userimg} me-2`}
 | 
			
		||||
        />
 | 
			
		||||
      </Link>
 | 
			
		||||
      <div className={styles.hunk}>
 | 
			
		||||
        <div className='d-flex'>
 | 
			
		||||
          <Link href={`/${user.name}`} className={`${styles.title} d-inline-flex align-items-center text-reset`}>
 | 
			
		||||
            @{user.name}<Hat className='ms-1 fill-grey' height={14} width={14} user={user} />
 | 
			
		||||
          </Link>
 | 
			
		||||
          {nymActionDropdown && <NymActionDropdown user={user} className='' />}
 | 
			
		||||
        </div>
 | 
			
		||||
        {children}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function User ({ user, rank, statComps, className = 'mb-2', Embellish, nymActionDropdown = false }) {
 | 
			
		||||
  const me = useMe()
 | 
			
		||||
  const showStatComps = statComps && statComps.length > 0
 | 
			
		||||
  return (
 | 
			
		||||
@ -50,27 +73,12 @@ function User ({ user, rank, statComps, Embellish, nymActionDropdown = false })
 | 
			
		||||
            {rank}
 | 
			
		||||
          </div>)
 | 
			
		||||
        : <div />}
 | 
			
		||||
      <div className={`${styles.item} ${me?.id === user.id && me.privates?.hideFromTopUsers ? userStyles.hidden : 'mb-2'}`}>
 | 
			
		||||
        <Link href={`/${user.name}`}>
 | 
			
		||||
          <Image
 | 
			
		||||
            src={user.photoId ? `${MEDIA_URL}/${user.photoId}` : '/dorian400.jpg'} width='32' height='32'
 | 
			
		||||
            className={`${userStyles.userimg} me-2`}
 | 
			
		||||
          />
 | 
			
		||||
        </Link>
 | 
			
		||||
        <div className={`${styles.hunk} ${!showStatComps && 'd-flex flex-column justify-content-around'}`}>
 | 
			
		||||
          <div className='d-flex'>
 | 
			
		||||
            <Link href={`/${user.name}`} className={`${styles.title} d-inline-flex align-items-center text-reset`}>
 | 
			
		||||
              @{user.name}<Hat className='ms-1 fill-grey' height={14} width={14} user={user} />
 | 
			
		||||
            </Link>
 | 
			
		||||
            {nymActionDropdown && <NymActionDropdown user={user} className='' />}
 | 
			
		||||
          </div>
 | 
			
		||||
          {showStatComps &&
 | 
			
		||||
            <div className={styles.other}>
 | 
			
		||||
              {statComps.map((Comp, i) => <Comp key={i} user={user} />)}
 | 
			
		||||
            </div>}
 | 
			
		||||
          {Embellish && <Embellish rank={rank} />}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <UserBase user={user} nymActionDropdown={nymActionDropdown} className={(me?.id === user.id && me.privates?.hideFromTopUsers) ? userStyles.hidden : 'mb-2'}>
 | 
			
		||||
        {showStatComps &&
 | 
			
		||||
          <div className={styles.other}>
 | 
			
		||||
            {statComps.map((Comp, i) => <Comp key={i} user={user} />)}
 | 
			
		||||
          </div>}
 | 
			
		||||
      </UserBase>
 | 
			
		||||
    </>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@ -152,23 +160,31 @@ export function UsersSkeleton () {
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>{users.map((_, i) => (
 | 
			
		||||
      <div className={`${styles.item} ${styles.skeleton} mb-2`} key={i}>
 | 
			
		||||
        <Image
 | 
			
		||||
          src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/clouds.jpeg`}
 | 
			
		||||
          width='32' height='32'
 | 
			
		||||
          className={`${userStyles.userimg} clouds me-2`}
 | 
			
		||||
        />
 | 
			
		||||
        <div className={styles.hunk}>
 | 
			
		||||
          <div className={`${styles.name} clouds text-reset`} />
 | 
			
		||||
          <div className={styles.other}>
 | 
			
		||||
            <span className={`${styles.otherItem} clouds`} />
 | 
			
		||||
            <span className={`${styles.otherItem} clouds`} />
 | 
			
		||||
            <span className={`${styles.otherItem} ${styles.otherItemLonger} clouds`} />
 | 
			
		||||
            <span className={`${styles.otherItem} ${styles.otherItemLonger} clouds`} />
 | 
			
		||||
          </div>
 | 
			
		||||
      <UserSkeleton key={i} className='mb-2'>
 | 
			
		||||
        <div className={styles.other}>
 | 
			
		||||
          <span className={`${styles.otherItem} clouds`} />
 | 
			
		||||
          <span className={`${styles.otherItem} clouds`} />
 | 
			
		||||
          <span className={`${styles.otherItem} ${styles.otherItemLonger} clouds`} />
 | 
			
		||||
          <span className={`${styles.otherItem} ${styles.otherItemLonger} clouds`} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      </UserSkeleton>
 | 
			
		||||
    ))}
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function UserSkeleton ({ children, className }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={`${styles.item} ${styles.skeleton} ${className}`}>
 | 
			
		||||
      <Image
 | 
			
		||||
        src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/clouds.jpeg`}
 | 
			
		||||
        width='32' height='32'
 | 
			
		||||
        className={`${userStyles.userimg} clouds me-2`}
 | 
			
		||||
      />
 | 
			
		||||
      <div className={styles.hunk}>
 | 
			
		||||
        <div className={`${styles.name} clouds text-reset`} />
 | 
			
		||||
        {children}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										82
									
								
								components/user-popover.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								components/user-popover.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
			
		||||
import { USER } from '@/fragments/users'
 | 
			
		||||
import errorStyles from '@/styles/error.module.css'
 | 
			
		||||
import { useLazyQuery } from '@apollo/client'
 | 
			
		||||
import Link from 'next/link'
 | 
			
		||||
import { useRef, useState } from 'react'
 | 
			
		||||
import { Popover } from 'react-bootstrap'
 | 
			
		||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
 | 
			
		||||
import { UserBase, UserSkeleton } from './user-list'
 | 
			
		||||
import styles from './user-popover.module.css'
 | 
			
		||||
import classNames from 'classnames'
 | 
			
		||||
 | 
			
		||||
function StackingSince ({ since }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <small className='text-muted d-flex-inline'>
 | 
			
		||||
      stacking since:{' '}
 | 
			
		||||
      {since
 | 
			
		||||
        ? <Link href={`/items/${since}`}>#{since}</Link>
 | 
			
		||||
        : <span>never</span>}
 | 
			
		||||
    </small>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function UserPopover ({ name, children }) {
 | 
			
		||||
  const [showOverlay, setShowOverlay] = useState(false)
 | 
			
		||||
 | 
			
		||||
  const [getUser, { loading, data }] = useLazyQuery(
 | 
			
		||||
    USER,
 | 
			
		||||
    {
 | 
			
		||||
      variables: { name },
 | 
			
		||||
      fetchPolicy: 'cache-first'
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const timeoutId = useRef(null)
 | 
			
		||||
 | 
			
		||||
  const handleMouseEnter = () => {
 | 
			
		||||
    clearTimeout(timeoutId.current)
 | 
			
		||||
    getUser()
 | 
			
		||||
    timeoutId.current = setTimeout(() => {
 | 
			
		||||
      setShowOverlay(true)
 | 
			
		||||
    }, 500)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleMouseLeave = () => {
 | 
			
		||||
    clearTimeout(timeoutId.current)
 | 
			
		||||
    timeoutId.current = setTimeout(() => setShowOverlay(false), 100)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <OverlayTrigger
 | 
			
		||||
      show={showOverlay}
 | 
			
		||||
      placement='bottom'
 | 
			
		||||
      onHide={handleMouseLeave}
 | 
			
		||||
      overlay={
 | 
			
		||||
        <Popover
 | 
			
		||||
          onMouseEnter={handleMouseEnter}
 | 
			
		||||
          onMouseLeave={handleMouseLeave}
 | 
			
		||||
          className={styles.userPopover}
 | 
			
		||||
        >
 | 
			
		||||
          <Popover.Body className={styles.userPopBody}>
 | 
			
		||||
            {!data || loading
 | 
			
		||||
              ? <UserSkeleton />
 | 
			
		||||
              : !data.user
 | 
			
		||||
                  ? <h1 className={classNames(errorStyles.status, errorStyles.describe)}>USER NOT FOUND</h1>
 | 
			
		||||
                  : (
 | 
			
		||||
                    <UserBase user={data.user} className='mb-0 pb-0'>
 | 
			
		||||
                      <StackingSince since={data.user.since} />
 | 
			
		||||
                    </UserBase>
 | 
			
		||||
                    )}
 | 
			
		||||
          </Popover.Body>
 | 
			
		||||
        </Popover>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <span
 | 
			
		||||
        onMouseEnter={handleMouseEnter}
 | 
			
		||||
        onMouseLeave={handleMouseLeave}
 | 
			
		||||
      >
 | 
			
		||||
        {children}
 | 
			
		||||
      </span>
 | 
			
		||||
    </OverlayTrigger>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								components/user-popover.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								components/user-popover.module.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
.userPopover {
 | 
			
		||||
    border: 1px solid var(--theme-toolbarActive)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.userPopBody {
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    font-size: 0.9rem;
 | 
			
		||||
}
 | 
			
		||||
@ -7,4 +7,5 @@ bitcoinplebdev
 | 
			
		||||
benthecarman
 | 
			
		||||
stargut
 | 
			
		||||
mz
 | 
			
		||||
btcbagehot
 | 
			
		||||
btcbagehot
 | 
			
		||||
felipe
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user