Use list view to render accounts
This commit is contained in:
parent
0e04daebfb
commit
47dc05d285
|
@ -28,7 +28,7 @@ import { clearNotifications } from '../lib/badge'
|
|||
import { useServiceWorker } from './serviceworker'
|
||||
import SubSelect from './sub-select'
|
||||
import { useShowModal } from './modal'
|
||||
import SwitchAccountDialog, { useAccounts } from './switch-account'
|
||||
import SwitchAccountList, { useAccounts } from './switch-account'
|
||||
|
||||
function WalletSummary ({ me }) {
|
||||
if (!me) return null
|
||||
|
@ -128,7 +128,7 @@ function NavProfileMenu ({ me, dropNavKey }) {
|
|||
</Link>
|
||||
</div>
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item onClick={() => showModal(onClose => <SwitchAccountDialog onClose={onClose} />)}>switch account</Dropdown.Item>
|
||||
<Dropdown.Item onClick={() => showModal(onClose => <SwitchAccountList onClose={onClose} />)}>switch account</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
onClick={async () => {
|
||||
const status = await multiAuthSignout()
|
||||
|
@ -209,7 +209,7 @@ function LurkerCorner ({ path }) {
|
|||
</Nav.Link>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item onClick={() => showModal(onClose => <SwitchAccountDialog onClose={onClose} />)}>switch account</Dropdown.Item>
|
||||
<Dropdown.Item onClick={() => showModal(onClose => <SwitchAccountList onClose={onClose} />)}>switch account</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
||||
import AnonIcon from '../svgs/spy-fill.svg'
|
||||
import { useRouter } from 'next/router'
|
||||
import cookie from 'cookie'
|
||||
import { useMe } from './me'
|
||||
import Image from 'react-bootstrap/Image'
|
||||
import Link from 'next/link'
|
||||
import { SSR } from '../lib/constants'
|
||||
import { ANON_USER_ID, SSR } from '../lib/constants'
|
||||
import { USER } from '../fragments/users'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import { UserListRow } from './user-list'
|
||||
|
||||
const AccountContext = createContext()
|
||||
|
||||
|
@ -62,92 +60,67 @@ export const AccountProvider = ({ children }) => {
|
|||
|
||||
export const useAccounts = () => useContext(AccountContext)
|
||||
|
||||
const AnonAccount = ({ selected, onClick }) => {
|
||||
const AccountListRow = ({ account, ...props }) => {
|
||||
const { isAnon, setIsAnon } = useAccounts()
|
||||
const { refreshMe } = useMe()
|
||||
return (
|
||||
<div
|
||||
className='d-flex flex-column me-2 my-1 text-center'
|
||||
>
|
||||
<AnonIcon
|
||||
className='fill-muted'
|
||||
width='135' height='135' style={{ cursor: 'pointer' }} onClick={async () => {
|
||||
document.cookie = 'multi_auth.user-id=anonymous; Path=/; Secure'
|
||||
// order is important to prevent flashes of no session
|
||||
setIsAnon(true)
|
||||
await refreshMe()
|
||||
}}
|
||||
/>
|
||||
<div className='fst-italic'>anonymous</div>
|
||||
{isAnon && <div className='text-muted fst-italic'>selected</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const { me, refreshMe } = useMe()
|
||||
const anonRow = account.id === ANON_USER_ID
|
||||
const selected = (isAnon && anonRow) || Number(me?.id) === Number(account.id)
|
||||
|
||||
const Account = ({ account, className }) => {
|
||||
const { me } = useMe()
|
||||
// fetch updated names and photo ids since they might have changed since we were issued the JWTs
|
||||
const [name, setName] = useState(account.name)
|
||||
const [src, setSrc] = useState(account.photoId || '/dorian400.jpg')
|
||||
const { refreshMe } = useMe()
|
||||
const { setIsAnon } = useAccounts()
|
||||
const [photoId, setPhotoId] = useState(account.photoId)
|
||||
useQuery(USER,
|
||||
{
|
||||
variables: { id: account.id },
|
||||
onCompleted ({ user: { name, photoId } }) {
|
||||
if (photoId) {
|
||||
const src = `https://${process.env.NEXT_PUBLIC_MEDIA_DOMAIN}/${photoId}`
|
||||
setSrc(src)
|
||||
}
|
||||
setName(name)
|
||||
if (photoId) setPhotoId(photoId)
|
||||
if (name) setName(name)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const onClick = async () => {
|
||||
document.cookie = `multi_auth.user-id=${anonRow ? 'anonymous' : account.id}; Path=/; Secure`
|
||||
if (anonRow) {
|
||||
// order is important to prevent flashes of no session
|
||||
setIsAnon(true)
|
||||
await refreshMe()
|
||||
} else {
|
||||
await refreshMe()
|
||||
// order is important to prevent flashes of inconsistent data in switch account dialog
|
||||
setIsAnon(account.id === ANON_USER_ID)
|
||||
}
|
||||
}
|
||||
// can't show hat since we don't have access to the streak from the data available in the cookies
|
||||
return (
|
||||
<div
|
||||
className='d-flex flex-column me-2 my-1 text-center'
|
||||
>
|
||||
<Image
|
||||
width='135' height='135' src={src} style={{ cursor: 'pointer' }} onClick={async () => {
|
||||
document.cookie = `multi_auth.user-id=${account.id}; Path=/; Secure`
|
||||
await refreshMe()
|
||||
// order is important to prevent flashes of inconsistent data in switch account dialog
|
||||
setIsAnon(false)
|
||||
}}
|
||||
/>
|
||||
<Link href={`/${account.name}`}>@{name}</Link>
|
||||
{Number(me?.id) === Number(account.id) && <div className='text-muted fst-italic'>selected</div>}
|
||||
<div className='d-flex flex-row'>
|
||||
<UserListRow user={{ ...account, photoId, name }} className='d-flex align-items-center me-2' {...props} />
|
||||
<div className='me-2' style={{ cursor: 'pointer' }} onClick={onClick}>switch</div>
|
||||
{selected && <div className='text-muted fst-italic text-muted'>selected</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const AddAccount = () => {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div className='d-flex flex-column me-2 my-1 text-center'>
|
||||
<Image
|
||||
width='135' height='135' src='/Portrait_Placeholder.webp' style={{ cursor: 'pointer' }} onClick={() => {
|
||||
router.push({
|
||||
pathname: '/login',
|
||||
query: { callbackUrl: window.location.origin + router.asPath, multiAuth: true }
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<div className='fst-italic'>+ add account</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function SwitchAccountDialog () {
|
||||
export default function SwitchAccountList () {
|
||||
const { accounts } = useAccounts()
|
||||
const router = useRouter()
|
||||
const addAccount = () => {
|
||||
router.push({
|
||||
pathname: '/login',
|
||||
query: { callbackUrl: window.location.origin + router.asPath, multiAuth: true }
|
||||
})
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className='my-2'>
|
||||
<div className='d-flex flex-row flex-wrap'>
|
||||
<AnonAccount />
|
||||
<div className='d-flex flex-column flex-wrap'>
|
||||
|
||||
<AccountListRow account={{ id: ANON_USER_ID, name: 'anon' }} showHat={false} />
|
||||
{
|
||||
accounts.map((account) => <Account key={account.id} account={account} />)
|
||||
|
||||
accounts.map((account) => <AccountListRow key={account.id} account={account} showHat={false} />)
|
||||
}
|
||||
<AddAccount />
|
||||
<div style={{ cursor: 'pointer' }} onClick={addAccount}>+ add account</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -36,6 +36,29 @@ function seperate (arr, seperator) {
|
|||
return arr.flatMap((x, i) => i < arr.length - 1 ? [x, seperator] : [x])
|
||||
}
|
||||
|
||||
export const UserListRow = ({ user, stats, className, showHat = true }) => {
|
||||
return (
|
||||
<div className={`${styles.item} mb-2`} key={user.name}>
|
||||
<Link href={`/${user.name}`}>
|
||||
<Image
|
||||
src={user.photoId ? `https://${process.env.NEXT_PUBLIC_MEDIA_DOMAIN}/${user.photoId}` : '/dorian400.jpg'} width='32' height='32'
|
||||
className={`${userStyles.userimg} me-2`}
|
||||
/>
|
||||
</Link>
|
||||
<div className={`${styles.hunk} ${className}`}>
|
||||
<Link href={`/${user.name}`} className={`${styles.title} d-inline-flex align-items-center text-reset`}>
|
||||
@{user.name}{showHat && <Hat className='ms-1 fill-grey' height={14} width={14} user={user} />}
|
||||
</Link>
|
||||
{stats && (
|
||||
<div className={styles.other}>
|
||||
{stats.map((Comp, i) => <Comp key={i} user={user} />)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function UserList ({ ssrData, query, variables, destructureData }) {
|
||||
const { data, fetchMore } = useQuery(query, { variables })
|
||||
const dat = useData(data, ssrData)
|
||||
|
@ -62,24 +85,7 @@ export default function UserList ({ ssrData, query, variables, destructureData }
|
|||
|
||||
return (
|
||||
<>
|
||||
{users?.map(user => (
|
||||
<div className={`${styles.item} mb-2`} key={user.name}>
|
||||
<Link href={`/${user.name}`}>
|
||||
<Image
|
||||
src={user.photoId ? `https://${process.env.NEXT_PUBLIC_MEDIA_DOMAIN}/${user.photoId}` : '/dorian400.jpg'} width='32' height='32'
|
||||
className={`${userStyles.userimg} me-2`}
|
||||
/>
|
||||
</Link>
|
||||
<div className={styles.hunk}>
|
||||
<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>
|
||||
<div className={styles.other}>
|
||||
{statComps.map((Comp, i) => <Comp key={i} user={user} />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{users?.map(user => <UserListRow key={user.id} user={user} stats={statComps} />)}
|
||||
<MoreFooter cursor={cursor} count={users?.length} fetchMore={fetchMore} Skeleton={UsersSkeleton} noMoreText='NO MORE' />
|
||||
</>
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue