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 { DropdownItemUpVote } from './upvote'
|
||||||
import { useRoot } from './root'
|
import { useRoot } from './root'
|
||||||
import { MuteSubDropdownItem, PinSubDropdownItem } from './territory-header'
|
import { MuteSubDropdownItem, PinSubDropdownItem } from './territory-header'
|
||||||
|
import UserPopover from './user-popover'
|
||||||
|
|
||||||
export default function ItemInfo ({
|
export default function ItemInfo ({
|
||||||
item, full, commentsText = 'comments',
|
item, full, commentsText = 'comments',
|
||||||
|
@ -101,10 +102,12 @@ export default function ItemInfo ({
|
||||||
</Link>
|
</Link>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
<span>
|
<span>
|
||||||
<Link href={`/${item.user.name}`}>
|
<UserPopover name={item.user.name}>
|
||||||
@{item.user.name}<span> </span><Hat className='fill-grey' user={item.user} height={12} width={12} />
|
<Link href={`/${item.user.name}`}>
|
||||||
{embellishUser}
|
@{item.user.name}<span> </span><Hat className='fill-grey' user={item.user} height={12} width={12} />
|
||||||
</Link>
|
{embellishUser}
|
||||||
|
</Link>
|
||||||
|
</UserPopover>
|
||||||
<span> </span>
|
<span> </span>
|
||||||
<Link href={`/items/${item.id}`} title={item.createdAt} className='text-reset' suppressHydrationWarning>
|
<Link href={`/items/${item.id}`} title={item.createdAt} className='text-reset' suppressHydrationWarning>
|
||||||
{timeSince(new Date(item.createdAt))}
|
{timeSince(new Date(item.createdAt))}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { useRouter } from 'next/router'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { UNKNOWN_LINK_REL } from '@/lib/constants'
|
import { UNKNOWN_LINK_REL } from '@/lib/constants'
|
||||||
import isEqual from 'lodash/isEqual'
|
import isEqual from 'lodash/isEqual'
|
||||||
|
import UserPopover from './user-popover'
|
||||||
|
|
||||||
export function SearchText ({ text }) {
|
export function SearchText ({ text }) {
|
||||||
return (
|
return (
|
||||||
|
@ -196,7 +197,18 @@ export default memo(function Text ({ rel, imgproxyUrls, children, tab, itemId, o
|
||||||
</Link>
|
</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 (
|
return (
|
||||||
<Link
|
<Link
|
||||||
id={props.id}
|
id={props.id}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Hat from './hat'
|
||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
import { MEDIA_URL } from '@/lib/constants'
|
import { MEDIA_URL } from '@/lib/constants'
|
||||||
import { NymActionDropdown } from '@/components/user-header'
|
import { NymActionDropdown } from '@/components/user-header'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
// all of this nonsense is to show the stat we are sorting by first
|
// 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>)
|
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])
|
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 me = useMe()
|
||||||
const showStatComps = statComps && statComps.length > 0
|
const showStatComps = statComps && statComps.length > 0
|
||||||
return (
|
return (
|
||||||
|
@ -50,27 +73,12 @@ function User ({ user, rank, statComps, Embellish, nymActionDropdown = false })
|
||||||
{rank}
|
{rank}
|
||||||
</div>)
|
</div>)
|
||||||
: <div />}
|
: <div />}
|
||||||
<div className={`${styles.item} ${me?.id === user.id && me.privates?.hideFromTopUsers ? userStyles.hidden : 'mb-2'}`}>
|
<UserBase user={user} nymActionDropdown={nymActionDropdown} className={(me?.id === user.id && me.privates?.hideFromTopUsers) ? userStyles.hidden : 'mb-2'}>
|
||||||
<Link href={`/${user.name}`}>
|
{showStatComps &&
|
||||||
<Image
|
<div className={styles.other}>
|
||||||
src={user.photoId ? `${MEDIA_URL}/${user.photoId}` : '/dorian400.jpg'} width='32' height='32'
|
{statComps.map((Comp, i) => <Comp key={i} user={user} />)}
|
||||||
className={`${userStyles.userimg} me-2`}
|
</div>}
|
||||||
/>
|
</UserBase>
|
||||||
</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>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -152,23 +160,31 @@ export function UsersSkeleton () {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>{users.map((_, i) => (
|
<div>{users.map((_, i) => (
|
||||||
<div className={`${styles.item} ${styles.skeleton} mb-2`} key={i}>
|
<UserSkeleton key={i} className='mb-2'>
|
||||||
<Image
|
<div className={styles.other}>
|
||||||
src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/clouds.jpeg`}
|
<span className={`${styles.otherItem} clouds`} />
|
||||||
width='32' height='32'
|
<span className={`${styles.otherItem} clouds`} />
|
||||||
className={`${userStyles.userimg} clouds me-2`}
|
<span className={`${styles.otherItem} ${styles.otherItemLonger} clouds`} />
|
||||||
/>
|
<span className={`${styles.otherItem} ${styles.otherItemLonger} clouds`} />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</UserSkeleton>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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
|
benthecarman
|
||||||
stargut
|
stargut
|
||||||
mz
|
mz
|
||||||
btcbagehot
|
btcbagehot
|
||||||
|
felipe
|
||||||
|
|
Loading…
Reference in New Issue