stacker.news/components/user-header.js
SatsAllDay bc2363dfab
Verified contributors (#474)
* `isContributor`, `hideIsContributor` user fields and basic UI decoration on profile page

* Update verified contributor decoration on profile page

* Add contributors instructions

* update setting label

* Remove `isContributor` from DB, load contributors from file into memory

* fix merge error

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2023-09-18 13:57:02 -05:00

221 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Button from 'react-bootstrap/Button'
import InputGroup from 'react-bootstrap/InputGroup'
import Image from 'react-bootstrap/Image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import Nav from 'react-bootstrap/Nav'
import { useState } from 'react'
import { Form, Input, SubmitButton } from './form'
import { gql, useApolloClient, useMutation } from '@apollo/client'
import styles from './user-header.module.css'
import { useMe } from './me'
import { NAME_MUTATION } from '../fragments/users'
import QRCode from 'qrcode.react'
import LightningIcon from '../svgs/bolt.svg'
import { encodeLNUrl } from '../lib/lnurl'
import Avatar from './avatar'
import { userSchema } from '../lib/validate'
import { useShowModal } from './modal'
import { numWithUnits } from '../lib/format'
import Hat from './hat'
import SubscribeUserDropdownItem from './subscribeUser'
import ActionDropdown from './action-dropdown'
export default function UserHeader ({ user }) {
const router = useRouter()
return (
<>
<HeaderHeader user={user} />
<Nav
className={styles.nav}
activeKey={!!router.asPath.split('/')[2]}
>
<Nav.Item>
<Link href={'/' + user.name} passHref legacyBehavior>
<Nav.Link eventKey={false}>bio</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={'/' + user.name + '/all'} passHref legacyBehavior>
<Nav.Link eventKey>
{numWithUnits(user.nitems, {
abbreviate: false,
unitSingular: 'item',
unitPlural: 'items'
})}
</Nav.Link>
</Link>
</Nav.Item>
</Nav>
</>
)
}
function HeaderPhoto ({ user, isMe }) {
const [setPhoto] = useMutation(
gql`
mutation setPhoto($photoId: ID!) {
setPhoto(photoId: $photoId)
}`, {
update (cache, { data: { setPhoto } }) {
cache.modify({
id: `User:${user.id}`,
fields: {
photoId () {
return setPhoto
}
}
})
}
}
)
return (
<div className='position-relative align-self-start' style={{ width: 'fit-content' }}>
<Image
src={user.photoId ? `https://${process.env.NEXT_PUBLIC_AWS_UPLOAD_BUCKET}.s3.amazonaws.com/${user.photoId}` : '/dorian400.jpg'} width='135' height='135'
className={styles.userimg}
/>
{isMe &&
<Avatar onSuccess={async photoId => {
const { error } = await setPhoto({ variables: { photoId } })
if (error) {
console.log(error)
}
}}
/>}
</div>
)
}
function NymEdit ({ user, setEditting }) {
const router = useRouter()
const [setName] = useMutation(NAME_MUTATION, {
update (cache, { data: { setName } }) {
cache.modify({
id: `User:${user.id}`,
fields: {
name () {
return setName
}
}
})
}
})
const client = useApolloClient()
const schema = userSchema(client)
return (
<Form
schema={schema}
initial={{
name: user.name
}}
validateImmediately
validateOnChange={false}
onSubmit={async ({ name }) => {
if (name === user.name) {
setEditting(false)
return
}
const { error } = await setName({ variables: { name } })
if (error) {
throw new Error({ message: error.toString() })
}
setEditting(false)
// navigate to new name
const { nodata, ...query } = router.query
router.replace({
pathname: router.pathname,
query: { ...query, name }
}, undefined, { shallow: true })
}}
>
<div className='d-flex align-items-center mb-2'>
<Input
prepend=<InputGroup.Text>@</InputGroup.Text>
name='name'
autoFocus
groupClassName={styles.usernameForm}
showValid
debounce={500}
/>
<SubmitButton variant='link' onClick={() => setEditting(true)}>save</SubmitButton>
</div>
</Form>
)
}
function NymView ({ user, isMe, setEditting }) {
const me = useMe()
return (
<div className='d-flex align-items-center mb-2'>
<div className={styles.username}>@{user.name}<Hat className='' user={user} badge /></div>
{isMe &&
<Button className='py-0' style={{ lineHeight: '1.25' }} variant='link' onClick={() => setEditting(true)}>edit nym</Button>}
{!isMe &&
<div className='ms-2'>
<ActionDropdown>
{me && <SubscribeUserDropdownItem user={user} target='posts' />}
{me && <SubscribeUserDropdownItem user={user} target='comments' />}
</ActionDropdown>
</div>}
</div>
)
}
function HeaderNym ({ user, isMe }) {
const [editting, setEditting] = useState(false)
return editting
? <NymEdit user={user} setEditting={setEditting} />
: <NymView user={user} isMe={isMe} setEditting={setEditting} />
}
function HeaderHeader ({ user }) {
const me = useMe()
const showModal = useShowModal()
const isMe = me?.name === user.name
const Satistics = () => <div className={`mb-2 ms-0 ms-sm-1 ${styles.username} text-success`}>{user.stacked} stacked</div>
const lnurlp = encodeLNUrl(new URL(`https://stacker.news/.well-known/lnurlp/${user.name}`))
return (
<div className='d-flex mt-2 flex-wrap flex-column flex-sm-row'>
<HeaderPhoto user={user} isMe={isMe} />
<div className='ms-0 ms-sm-3 mt-3 mt-sm-0 justify-content-center align-self-sm-center'>
<HeaderNym user={user} isMe={isMe} />
<Satistics user={user} />
<Button
className='fw-bold ms-0' onClick={() => {
showModal(({ onClose }) => (
<>
<a className='d-flex m-auto p-3' style={{ background: 'white', width: 'fit-content' }} href={`lightning:${lnurlp}`}>
<QRCode className='d-flex m-auto' value={lnurlp} renderAs='svg' size={300} />
</a>
<div className='text-center fw-bold text-muted mt-3'>click or scan</div>
</>
))
}}
>
<LightningIcon
width={20}
height={20}
className='me-1'
/>{user.name}@stacker.news
</Button>
<div className='d-flex flex-column mt-1 ms-0'>
<small className='text-muted d-flex-inline'>stacking since: {user.since
? <Link href={`/items/${user.since}`} className='ms-1'>#{user.since}</Link>
: <span>never</span>}
</small>
<small className='text-muted d-flex-inline'>longest cowboy streak: {user.maxStreak !== null ? user.maxStreak : 'none'}</small>
{user.isContributor && <small className='text-muted'>🧑💻 verified stacker.news contributor</small>}
</div>
</div>
</div>
)
}