Use list view to render accounts

This commit is contained in:
ekzyis 2023-12-05 05:06:02 +01:00
parent 0e04daebfb
commit 47dc05d285
3 changed files with 69 additions and 90 deletions

View File

@ -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>

View File

@ -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>
</>

View File

@ -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' />
</>
)