reduce rerenders in notifications
This commit is contained in:
parent
e4aaaac20f
commit
eeaf6e10e5
@ -1,9 +1,9 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react'
|
import { useState, useEffect, useMemo } from 'react'
|
||||||
import { useApolloClient, useQuery } from '@apollo/client'
|
import { useQuery } from '@apollo/client'
|
||||||
import Comment, { CommentSkeleton } from './comment'
|
import Comment, { CommentSkeleton } from './comment'
|
||||||
import Item from './item'
|
import Item from './item'
|
||||||
import ItemJob from './item-job'
|
import ItemJob from './item-job'
|
||||||
import { HAS_NOTIFICATIONS, NOTIFICATIONS } from '../fragments/notifications'
|
import { NOTIFICATIONS } from '../fragments/notifications'
|
||||||
import MoreFooter from './more-footer'
|
import MoreFooter from './more-footer'
|
||||||
import Invite from './invite'
|
import Invite from './invite'
|
||||||
import { ignoreClick } from '../lib/clicks'
|
import { ignoreClick } from '../lib/clicks'
|
||||||
@ -21,27 +21,34 @@ import { useServiceWorker } from './serviceworker'
|
|||||||
import { Checkbox, Form } from './form'
|
import { Checkbox, Form } from './form'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
function Notification ({ n }) {
|
function Notification ({ n, fresh }) {
|
||||||
switch (n.__typename) {
|
const type = n.__typename
|
||||||
case 'Earn': return <EarnNotification n={n} />
|
|
||||||
case 'Invitification': return <Invitification n={n} />
|
return (
|
||||||
case 'InvoicePaid': return <InvoicePaid n={n} />
|
<NotificationLayout nid={nid(n)} {...defaultOnClick(n)} fresh={fresh}>
|
||||||
case 'Referral': return <Referral n={n} />
|
{
|
||||||
case 'Streak': return <Streak n={n} />
|
(type === 'Earn' && <EarnNotification n={n} />) ||
|
||||||
case 'Votification': return <Votification n={n} />
|
(type === 'Invitification' && <Invitification n={n} />) ||
|
||||||
case 'Mention': return <Mention n={n} />
|
(type === 'InvoicePaid' && <InvoicePaid n={n} />) ||
|
||||||
case 'JobChanged': return <JobChanged n={n} />
|
(type === 'Referral' && <Referral n={n} />) ||
|
||||||
case 'Reply': return <Reply n={n} />
|
(type === 'Streak' && <Streak n={n} />) ||
|
||||||
}
|
(type === 'Votification' && <Votification n={n} />) ||
|
||||||
console.error('__typename not supported:', n.__typename)
|
(type === 'Mention' && <Mention n={n} />) ||
|
||||||
return null
|
(type === 'JobChanged' && <JobChanged n={n} />) ||
|
||||||
|
(type === 'Reply' && <Reply n={n} />)
|
||||||
|
}
|
||||||
|
</NotificationLayout>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotificationLayout ({ children, nid, href, as }) {
|
function NotificationLayout ({ children, nid, href, as, fresh }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
if (!href) return <div className={fresh ? styles.fresh : ''}>{children}</div>
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`clickToContext ${router?.query?.nid === nid ? 'outline-it' : ''}`}
|
className={
|
||||||
|
`clickToContext ${fresh ? styles.fresh : ''} ${router?.query?.nid === nid ? 'outline-it' : ''}`
|
||||||
|
}
|
||||||
onClick={async (e) => {
|
onClick={async (e) => {
|
||||||
if (ignoreClick(e)) return
|
if (ignoreClick(e)) return
|
||||||
nid && await router.replace({
|
nid && await router.replace({
|
||||||
@ -60,6 +67,14 @@ function NotificationLayout ({ children, nid, href, as }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultOnClick = n => {
|
const defaultOnClick = n => {
|
||||||
|
const type = n.__typename
|
||||||
|
if (type === 'Earn') return {}
|
||||||
|
if (type === 'Invitification') return { href: '/invites' }
|
||||||
|
if (type === 'InvoicePaid') return { href: `/invoices/${n.invoice.id}` }
|
||||||
|
if (type === 'Referral') return { href: '/referrals/month' }
|
||||||
|
if (type === 'Streak') return {}
|
||||||
|
|
||||||
|
// Votification, Mention, JobChanged, Reply all have item
|
||||||
if (!n.item.title) {
|
if (!n.item.title) {
|
||||||
const path = n.item.path.split('.')
|
const path = n.item.path.split('.')
|
||||||
if (path.length > COMMENT_DEPTH_LIMIT + 1) {
|
if (path.length > COMMENT_DEPTH_LIMIT + 1) {
|
||||||
@ -155,7 +170,7 @@ function EarnNotification ({ n }) {
|
|||||||
|
|
||||||
function Invitification ({ n }) {
|
function Invitification ({ n }) {
|
||||||
return (
|
return (
|
||||||
<NotificationLayout nid={nid(n)} href='/invites'>
|
<>
|
||||||
<small className='fw-bold text-secondary ms-2'>
|
<small className='fw-bold text-secondary ms-2'>
|
||||||
your invite has been redeemed by {n.invite.invitees.length} stackers
|
your invite has been redeemed by {n.invite.invitees.length} stackers
|
||||||
</small>
|
</small>
|
||||||
@ -167,35 +182,31 @@ function Invitification ({ n }) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</NotificationLayout>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function InvoicePaid ({ n }) {
|
function InvoicePaid ({ n }) {
|
||||||
return (
|
return (
|
||||||
<NotificationLayout nid={nid(n)} href={`/invoices/${n.invoice.id}`}>
|
<div className='fw-bold text-info ms-2 py-1'>
|
||||||
<div className='fw-bold text-info ms-2 py-1'>
|
<Check className='fill-info me-1' />{n.earnedSats} sats were deposited in your account
|
||||||
<Check className='fill-info me-1' />{n.earnedSats} sats were deposited in your account
|
<small className='text-muted ms-1 fw-normal' suppressHydrationWarning>{timeSince(new Date(n.sortTime))}</small>
|
||||||
<small className='text-muted ms-1 fw-normal' suppressHydrationWarning>{timeSince(new Date(n.sortTime))}</small>
|
</div>
|
||||||
</div>
|
|
||||||
</NotificationLayout>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Referral ({ n }) {
|
function Referral ({ n }) {
|
||||||
return (
|
return (
|
||||||
<NotificationLayout nid={nid(n)}>
|
<small className='fw-bold text-secondary ms-2'>
|
||||||
<small className='fw-bold text-secondary ms-2'>
|
someone joined via one of your referral links
|
||||||
someone joined via one of your <Link href='/referrals/month' className='text-reset'>referral links</Link>
|
<small className='text-muted ms-1 fw-normal' suppressHydrationWarning>{timeSince(new Date(n.sortTime))}</small>
|
||||||
<small className='text-muted ms-1 fw-normal' suppressHydrationWarning>{timeSince(new Date(n.sortTime))}</small>
|
</small>
|
||||||
</small>
|
|
||||||
</NotificationLayout>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Votification ({ n }) {
|
function Votification ({ n }) {
|
||||||
return (
|
return (
|
||||||
<NotificationLayout nid={nid(n)} {...defaultOnClick(n)}>
|
<>
|
||||||
<small className='fw-bold text-success ms-2'>
|
<small className='fw-bold text-success ms-2'>
|
||||||
your {n.item.title ? 'post' : 'reply'} {n.item.fwdUser ? 'forwarded' : 'stacked'} {n.earnedSats} sats{n.item.fwdUser && ` to @${n.item.fwdUser.name}`}
|
your {n.item.title ? 'post' : 'reply'} {n.item.fwdUser ? 'forwarded' : 'stacked'} {n.earnedSats} sats{n.item.fwdUser && ` to @${n.item.fwdUser.name}`}
|
||||||
</small>
|
</small>
|
||||||
@ -210,13 +221,13 @@ function Votification ({ n }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</NotificationLayout>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Mention ({ n }) {
|
function Mention ({ n }) {
|
||||||
return (
|
return (
|
||||||
<NotificationLayout nid={nid(n)} {...defaultOnClick(n)}>
|
<>
|
||||||
<small className='fw-bold text-info ms-2'>
|
<small className='fw-bold text-info ms-2'>
|
||||||
you were mentioned in
|
you were mentioned in
|
||||||
</small>
|
</small>
|
||||||
@ -230,13 +241,13 @@ function Mention ({ n }) {
|
|||||||
</RootProvider>
|
</RootProvider>
|
||||||
</div>)}
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
</NotificationLayout>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function JobChanged ({ n }) {
|
function JobChanged ({ n }) {
|
||||||
return (
|
return (
|
||||||
<NotificationLayout nid={nid(n)} {...defaultOnClick(n)}>
|
<>
|
||||||
<small className={`fw-bold text-${n.item.status === 'ACTIVE' ? 'success' : 'boost'} ms-1`}>
|
<small className={`fw-bold text-${n.item.status === 'ACTIVE' ? 'success' : 'boost'} ms-1`}>
|
||||||
{n.item.status === 'ACTIVE'
|
{n.item.status === 'ACTIVE'
|
||||||
? 'your job is active again'
|
? 'your job is active again'
|
||||||
@ -245,25 +256,23 @@ function JobChanged ({ n }) {
|
|||||||
: 'your job has been stopped')}
|
: 'your job has been stopped')}
|
||||||
</small>
|
</small>
|
||||||
<ItemJob item={n.item} />
|
<ItemJob item={n.item} />
|
||||||
</NotificationLayout>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Reply ({ n }) {
|
function Reply ({ n }) {
|
||||||
return (
|
return (
|
||||||
<NotificationLayout nid={nid(n)} {...defaultOnClick(n)} rootText='replying on:'>
|
<div className='py-2'>
|
||||||
<div className='py-2'>
|
{n.item.title
|
||||||
{n.item.title
|
? <Item item={n.item} />
|
||||||
? <Item item={n.item} />
|
: (
|
||||||
: (
|
<div className='pb-2'>
|
||||||
<div className='pb-2'>
|
<RootProvider root={n.item.root}>
|
||||||
<RootProvider root={n.item.root}>
|
<Comment item={n.item} noReply includeParent clickToContext rootText='replying on:' />
|
||||||
<Comment item={n.item} noReply includeParent clickToContext rootText='replying on:' />
|
</RootProvider>
|
||||||
</RootProvider>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
</NotificationLayout>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,17 +339,14 @@ const nid = n => n.__typename + n.id + n.sortTime
|
|||||||
|
|
||||||
export default function Notifications ({ ssrData }) {
|
export default function Notifications ({ ssrData }) {
|
||||||
const { data, fetchMore } = useQuery(NOTIFICATIONS)
|
const { data, fetchMore } = useQuery(NOTIFICATIONS)
|
||||||
const client = useApolloClient()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const { notifications: { notifications, lastChecked, cursor } } = useMemo(() => {
|
const { notifications: { notifications, lastChecked, cursor } } = useMemo(() => {
|
||||||
return data || ssrData || { notifications: {} }
|
return data || ssrData || { notifications: {} }
|
||||||
}, [data, ssrData])
|
}, [data, ssrData])
|
||||||
|
|
||||||
const checkedAt = router?.query?.checkedAt
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastChecked && !checkedAt) {
|
if (lastChecked && !router?.query?.checkedAt) {
|
||||||
router.replace({
|
router.replace({
|
||||||
pathname: router.pathname,
|
pathname: router.pathname,
|
||||||
query: {
|
query: {
|
||||||
@ -349,37 +355,18 @@ export default function Notifications ({ ssrData }) {
|
|||||||
checkedAt: lastChecked
|
checkedAt: lastChecked
|
||||||
}
|
}
|
||||||
}, router.asPath, { ...router.options, shallow: true })
|
}, router.asPath, { ...router.options, shallow: true })
|
||||||
client?.writeQuery({
|
|
||||||
query: HAS_NOTIFICATIONS,
|
|
||||||
data: {
|
|
||||||
hasNewNotes: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}, [lastChecked, checkedAt])
|
}, [router, lastChecked])
|
||||||
|
|
||||||
const [fresh, old] = useMemo(() => {
|
|
||||||
if (!notifications) return [[], []]
|
|
||||||
const freshTime = checkedAt || lastChecked
|
|
||||||
return notifications.reduce((result, n) => {
|
|
||||||
result[new Date(n.sortTime).getTime() > new Date(freshTime)?.getTime() ? 0 : 1].push(n)
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
[[], []])
|
|
||||||
}, [notifications, checkedAt])
|
|
||||||
|
|
||||||
if (!data && !ssrData) return <CommentsFlatSkeleton />
|
if (!data && !ssrData) return <CommentsFlatSkeleton />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='fresh'>
|
{notifications.map(n =>
|
||||||
{fresh.map((n, i) => (
|
<Notification
|
||||||
<Notification n={n} key={nid(n)} />
|
n={n} key={nid(n)}
|
||||||
))}
|
fresh={new Date(n.sortTime) > new Date(router?.query?.checkedAt)}
|
||||||
</div>
|
/>)}
|
||||||
{old.map((n, i) => (
|
|
||||||
<Notification n={n} key={nid(n)} />
|
|
||||||
))}
|
|
||||||
<MoreFooter cursor={cursor} count={notifications?.length} fetchMore={fetchMore} Skeleton={CommentsFlatSkeleton} noMoreText='NO MORE' />
|
<MoreFooter cursor={cursor} count={notifications?.length} fetchMore={fetchMore} Skeleton={CommentsFlatSkeleton} noMoreText='NO MORE' />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
.clickToContext {
|
|
||||||
border-radius: .4rem;
|
|
||||||
padding: .2rem 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clickToContext:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.03);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fresh {
|
.fresh {
|
||||||
background-color: rgba(0, 0, 0, 0.03);
|
background-color: rgba(0, 0, 0, 0.03);
|
||||||
border-radius: .4rem;
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fresh:not(.fresh ~ .fresh) {
|
||||||
|
border-top-left-radius: .4rem;
|
||||||
|
border-top-right-radius: .4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fresh:has(+ :not(.fresh)) {
|
||||||
|
border-bottom-left-radius: .4rem;
|
||||||
|
border-bottom-right-radius: .4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alertBtn {
|
.alertBtn {
|
||||||
|
@ -25,7 +25,8 @@ function writeQuery (client, apollo, data) {
|
|||||||
query: gql`${apollo.query}`,
|
query: gql`${apollo.query}`,
|
||||||
data,
|
data,
|
||||||
variables: apollo.variables,
|
variables: apollo.variables,
|
||||||
overwrite: SSR
|
overwrite: SSR,
|
||||||
|
broadcast: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,24 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
import { getGetServerSideProps } from '../api/ssrApollo'
|
import { getGetServerSideProps } from '../api/ssrApollo'
|
||||||
import Layout from '../components/layout'
|
import Layout from '../components/layout'
|
||||||
import Notifications, { NotificationAlert } from '../components/notifications'
|
import Notifications, { NotificationAlert } from '../components/notifications'
|
||||||
import { NOTIFICATIONS } from '../fragments/notifications'
|
import { HAS_NOTIFICATIONS, NOTIFICATIONS } from '../fragments/notifications'
|
||||||
|
import { useApolloClient } from '@apollo/client'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps(NOTIFICATIONS)
|
export const getServerSideProps = getGetServerSideProps(NOTIFICATIONS)
|
||||||
|
|
||||||
export default function NotificationPage ({ ssrData }) {
|
export default function NotificationPage ({ ssrData }) {
|
||||||
|
const client = useApolloClient()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
client?.writeQuery({
|
||||||
|
query: HAS_NOTIFICATIONS,
|
||||||
|
data: {
|
||||||
|
hasNewNotes: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<NotificationAlert />
|
<NotificationAlert />
|
||||||
|
@ -357,11 +357,6 @@ div[contenteditable]:disabled,
|
|||||||
fill: #212529;
|
fill: #212529;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fresh {
|
|
||||||
background-color: var(--theme-clickToContextColor);
|
|
||||||
border-radius: .4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
background-color: var(--theme-inputBg);
|
background-color: var(--theme-inputBg);
|
||||||
border-color: var(--theme-borderColor);
|
border-color: var(--theme-borderColor);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user