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 { useServiceWorker } from './serviceworker'
|
||||||
import SubSelect from './sub-select'
|
import SubSelect from './sub-select'
|
||||||
import { useShowModal } from './modal'
|
import { useShowModal } from './modal'
|
||||||
import SwitchAccountDialog, { useAccounts } from './switch-account'
|
import SwitchAccountList, { useAccounts } from './switch-account'
|
||||||
|
|
||||||
function WalletSummary ({ me }) {
|
function WalletSummary ({ me }) {
|
||||||
if (!me) return null
|
if (!me) return null
|
||||||
|
@ -128,7 +128,7 @@ function NavProfileMenu ({ me, dropNavKey }) {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Dropdown.Divider />
|
<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
|
<Dropdown.Item
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const status = await multiAuthSignout()
|
const status = await multiAuthSignout()
|
||||||
|
@ -209,7 +209,7 @@ function LurkerCorner ({ path }) {
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
</Dropdown.Toggle>
|
</Dropdown.Toggle>
|
||||||
<Dropdown.Menu>
|
<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.Menu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
||||||
import AnonIcon from '../svgs/spy-fill.svg'
|
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import cookie from 'cookie'
|
import cookie from 'cookie'
|
||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
import Image from 'react-bootstrap/Image'
|
import { ANON_USER_ID, SSR } from '../lib/constants'
|
||||||
import Link from 'next/link'
|
|
||||||
import { SSR } from '../lib/constants'
|
|
||||||
import { USER } from '../fragments/users'
|
import { USER } from '../fragments/users'
|
||||||
import { useQuery } from '@apollo/client'
|
import { useQuery } from '@apollo/client'
|
||||||
|
import { UserListRow } from './user-list'
|
||||||
|
|
||||||
const AccountContext = createContext()
|
const AccountContext = createContext()
|
||||||
|
|
||||||
|
@ -62,92 +60,67 @@ export const AccountProvider = ({ children }) => {
|
||||||
|
|
||||||
export const useAccounts = () => useContext(AccountContext)
|
export const useAccounts = () => useContext(AccountContext)
|
||||||
|
|
||||||
const AnonAccount = ({ selected, onClick }) => {
|
const AccountListRow = ({ account, ...props }) => {
|
||||||
const { isAnon, setIsAnon } = useAccounts()
|
const { isAnon, setIsAnon } = useAccounts()
|
||||||
const { refreshMe } = useMe()
|
const { me, refreshMe } = useMe()
|
||||||
return (
|
const anonRow = account.id === ANON_USER_ID
|
||||||
<div
|
const selected = (isAnon && anonRow) || Number(me?.id) === Number(account.id)
|
||||||
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 Account = ({ account, className }) => {
|
// fetch updated names and photo ids since they might have changed since we were issued the JWTs
|
||||||
const { me } = useMe()
|
|
||||||
const [name, setName] = useState(account.name)
|
const [name, setName] = useState(account.name)
|
||||||
const [src, setSrc] = useState(account.photoId || '/dorian400.jpg')
|
const [photoId, setPhotoId] = useState(account.photoId)
|
||||||
const { refreshMe } = useMe()
|
|
||||||
const { setIsAnon } = useAccounts()
|
|
||||||
useQuery(USER,
|
useQuery(USER,
|
||||||
{
|
{
|
||||||
variables: { id: account.id },
|
variables: { id: account.id },
|
||||||
onCompleted ({ user: { name, photoId } }) {
|
onCompleted ({ user: { name, photoId } }) {
|
||||||
if (photoId) {
|
if (photoId) setPhotoId(photoId)
|
||||||
const src = `https://${process.env.NEXT_PUBLIC_MEDIA_DOMAIN}/${photoId}`
|
if (name) setName(name)
|
||||||
setSrc(src)
|
|
||||||
}
|
|
||||||
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 (
|
return (
|
||||||
<div
|
<div className='d-flex flex-row'>
|
||||||
className='d-flex flex-column me-2 my-1 text-center'
|
<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>
|
||||||
<Image
|
{selected && <div className='text-muted fst-italic text-muted'>selected</div>}
|
||||||
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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddAccount = () => {
|
export default function SwitchAccountList () {
|
||||||
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 () {
|
|
||||||
const { accounts } = useAccounts()
|
const { accounts } = useAccounts()
|
||||||
|
const router = useRouter()
|
||||||
|
const addAccount = () => {
|
||||||
|
router.push({
|
||||||
|
pathname: '/login',
|
||||||
|
query: { callbackUrl: window.location.origin + router.asPath, multiAuth: true }
|
||||||
|
})
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='my-2'>
|
<div className='my-2'>
|
||||||
<div className='d-flex flex-row flex-wrap'>
|
<div className='d-flex flex-column flex-wrap'>
|
||||||
<AnonAccount />
|
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -36,6 +36,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])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 }) {
|
export default function UserList ({ ssrData, query, variables, destructureData }) {
|
||||||
const { data, fetchMore } = useQuery(query, { variables })
|
const { data, fetchMore } = useQuery(query, { variables })
|
||||||
const dat = useData(data, ssrData)
|
const dat = useData(data, ssrData)
|
||||||
|
@ -62,24 +85,7 @@ export default function UserList ({ ssrData, query, variables, destructureData }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{users?.map(user => (
|
{users?.map(user => <UserListRow key={user.id} user={user} stats={statComps} />)}
|
||||||
<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>
|
|
||||||
))}
|
|
||||||
<MoreFooter cursor={cursor} count={users?.length} fetchMore={fetchMore} Skeleton={UsersSkeleton} noMoreText='NO MORE' />
|
<MoreFooter cursor={cursor} count={users?.length} fetchMore={fetchMore} Skeleton={UsersSkeleton} noMoreText='NO MORE' />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue