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>
|
||||
<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>
|
||||
<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>}
|
||||
{Embellish && <Embellish rank={rank} />}
|
||||
</div>
|
||||
</div>
|
||||
</UserBase>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -152,7 +160,22 @@ export function UsersSkeleton () {
|
|||
|
||||
return (
|
||||
<div>{users.map((_, i) => (
|
||||
<div className={`${styles.item} ${styles.skeleton} mb-2`} key={i}>
|
||||
<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>
|
||||
</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'
|
||||
|
@ -160,15 +183,8 @@ export function UsersSkeleton () {
|
|||
/>
|
||||
<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`} />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</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;
|
||||
}
|
|
@ -8,3 +8,4 @@ benthecarman
|
|||
stargut
|
||||
mz
|
||||
btcbagehot
|
||||
felipe
|
||||
|
|
Loading…
Reference in New Issue