Use react-query to cache notifications (#625)

* Use react-query to cache notifications

* Fix imports

* Cleanup

* Limit unseen notifs query

* Catch the bounced query

* Don't use interval

* Unused var

* Avoid flash of page nav

* Give notification question priority & 2 lines

* Right justify timestamps

* Rewording

* Margin

* Simplify error msg

* Be explicit about limit for unseen notifs

* Pass limit > 0
This commit is contained in:
Ian Philips 2022-07-07 14:41:50 -06:00 committed by GitHub
parent a22b29ad6d
commit cfbb78af48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 358 additions and 322 deletions

View File

@ -12,7 +12,7 @@ import {
import clsx from 'clsx'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useUser } from 'web/hooks/use-user'
import { usePrivateUser, useUser } from 'web/hooks/use-user'
import { firebaseLogout, User } from 'web/lib/firebase/users'
import { ManifoldLogo } from './manifold-logo'
import { MenuButton } from './menu'
@ -26,8 +26,9 @@ import { groupPath } from 'web/lib/firebase/groups'
import { trackCallback, withTracking } from 'web/lib/service/analytics'
import { Group } from 'common/group'
import { Spacer } from '../layout/spacer'
import { usePreferredNotifications } from 'web/hooks/use-notifications'
import { useUnseenPreferredNotifications } from 'web/hooks/use-notifications'
import { setNotificationsAsSeen } from 'web/pages/notifications'
import { PrivateUser } from 'common/user'
function getNavigation() {
return [
@ -186,6 +187,7 @@ export default function Sidebar(props: { className?: string }) {
const currentPage = router.pathname
const user = useUser()
const privateUser = usePrivateUser(user?.id)
const navigationOptions = !user ? signedOutNavigation : getNavigation()
const mobileNavigationOptions = !user
? signedOutMobileNavigation
@ -220,11 +222,13 @@ export default function Sidebar(props: { className?: string }) {
/>
)}
<GroupsList
currentPage={router.asPath}
memberItems={memberItems}
user={user}
/>
{privateUser && (
<GroupsList
currentPage={router.asPath}
memberItems={memberItems}
privateUser={privateUser}
/>
)}
</div>
{/* Desktop navigation */}
@ -243,11 +247,13 @@ export default function Sidebar(props: { className?: string }) {
<div className="h-[1px] bg-gray-300" />
</div>
)}
<GroupsList
currentPage={router.asPath}
memberItems={memberItems}
user={user}
/>
{privateUser && (
<GroupsList
currentPage={router.asPath}
memberItems={memberItems}
privateUser={privateUser}
/>
)}
</div>
</nav>
)
@ -256,13 +262,16 @@ export default function Sidebar(props: { className?: string }) {
function GroupsList(props: {
currentPage: string
memberItems: Item[]
user: User | null | undefined
privateUser: PrivateUser
}) {
const { currentPage, memberItems, user } = props
const preferredNotifications = usePreferredNotifications(user?.id, {
unseenOnly: true,
customHref: '/group/',
})
const { currentPage, memberItems, privateUser } = props
const preferredNotifications = useUnseenPreferredNotifications(
privateUser,
{
customHref: '/group/',
},
memberItems.length > 0 ? memberItems.length : undefined
)
// Set notification as seen if our current page is equal to the isSeenOnHref property
useEffect(() => {

View File

@ -4,45 +4,53 @@ import { Row } from 'web/components/layout/row'
import { useEffect, useState } from 'react'
import { usePrivateUser, useUser } from 'web/hooks/use-user'
import { useRouter } from 'next/router'
import { usePreferredGroupedNotifications } from 'web/hooks/use-notifications'
import { useUnseenPreferredNotificationGroups } from 'web/hooks/use-notifications'
import { NOTIFICATIONS_PER_PAGE } from 'web/pages/notifications'
import { requestBonuses } from 'web/lib/firebase/api-call'
import { PrivateUser } from 'common/user'
export default function NotificationsIcon(props: { className?: string }) {
const user = useUser()
const privateUser = usePrivateUser(user?.id)
const notifications = usePreferredGroupedNotifications(privateUser?.id, {
unseenOnly: true,
})
const [seen, setSeen] = useState(false)
useEffect(() => {
if (!privateUser) return
if (Date.now() - (privateUser.lastTimeCheckedBonuses ?? 0) > 65 * 1000)
requestBonuses({}).catch((error) => {
console.log("couldn't get bonuses:", error.message)
})
if (
privateUser &&
privateUser.lastTimeCheckedBonuses &&
Date.now() - privateUser.lastTimeCheckedBonuses > 1000 * 70
)
requestBonuses({}).catch(() => console.log('no bonuses for you (yet)'))
}, [privateUser])
const router = useRouter()
useEffect(() => {
if (router.pathname.endsWith('notifications')) return setSeen(true)
else setSeen(false)
}, [router.pathname])
return (
<Row className={clsx('justify-center')}>
<div className={'relative'}>
{!seen && notifications && notifications.length > 0 && (
<div className="-mt-0.75 absolute ml-3.5 min-w-[15px] rounded-full bg-indigo-500 p-[2px] text-center text-[10px] leading-3 text-white lg:-mt-1 lg:ml-2">
{notifications.length > NOTIFICATIONS_PER_PAGE
? `${NOTIFICATIONS_PER_PAGE}+`
: notifications.length}
</div>
)}
{privateUser && <UnseenNotificationsBubble privateUser={privateUser} />}
<BellIcon className={clsx(props.className)} />
</div>
</Row>
)
}
function UnseenNotificationsBubble(props: { privateUser: PrivateUser }) {
const router = useRouter()
const { privateUser } = props
const [seen, setSeen] = useState(false)
useEffect(() => {
if (router.pathname.endsWith('notifications')) return setSeen(true)
else setSeen(false)
}, [router.pathname])
const notifications = useUnseenPreferredNotificationGroups(privateUser)
if (!notifications || notifications.length === 0 || seen) {
return <div />
}
return (
<div className="-mt-0.75 absolute ml-3.5 min-w-[15px] rounded-full bg-indigo-500 p-[2px] text-center text-[10px] leading-3 text-white lg:-mt-1 lg:ml-2">
{notifications.length > NOTIFICATIONS_PER_PAGE
? `${NOTIFICATIONS_PER_PAGE}+`
: notifications.length}
</div>
)
}

View File

@ -1,9 +1,13 @@
import { useEffect, useState } from 'react'
import { listenForPrivateUser } from 'web/lib/firebase/users'
import { notification_subscribe_types, PrivateUser } from 'common/user'
import { Notification } from 'common/notification'
import { listenForNotifications } from 'web/lib/firebase/notifications'
import {
getNotificationsQuery,
listenForNotifications,
} from 'web/lib/firebase/notifications'
import { groupBy, map } from 'lodash'
import { useFirestoreQuery } from '@react-query-firebase/firestore'
import { NOTIFICATIONS_PER_PAGE } from 'web/pages/notifications'
export type NotificationGroup = {
notifications: Notification[]
@ -13,15 +17,30 @@ export type NotificationGroup = {
type: 'income' | 'normal'
}
export function usePreferredGroupedNotifications(
userId: string | undefined,
options: { unseenOnly: boolean }
) {
// For some reason react-query subscriptions don't actually listen for notifications
// Use useUnseenPreferredNotificationGroups to listen for new notifications
export function usePreferredGroupedNotifications(privateUser: PrivateUser) {
const [notificationGroups, setNotificationGroups] = useState<
NotificationGroup[] | undefined
>(undefined)
const [notifications, setNotifications] = useState<Notification[]>([])
const key = `notifications-${privateUser.id}-all`
const result = useFirestoreQuery([key], getNotificationsQuery(privateUser.id))
useEffect(() => {
if (result.isLoading) return
if (!result.data) return setNotifications([])
const notifications = result.data.docs.map(
(doc) => doc.data() as Notification
)
const notificationsToShow = getAppropriateNotifications(
notifications,
privateUser.notificationPreferences
).filter((n) => !n.isSeenOnHref)
setNotifications(notificationsToShow)
}, [privateUser.notificationPreferences, result.data, result.isLoading])
const notifications = usePreferredNotifications(userId, options)
useEffect(() => {
if (!notifications) return
@ -32,6 +51,20 @@ export function usePreferredGroupedNotifications(
return notificationGroups
}
export function useUnseenPreferredNotificationGroups(privateUser: PrivateUser) {
const notifications = useUnseenPreferredNotifications(privateUser, {})
const [notificationGroups, setNotificationGroups] = useState<
NotificationGroup[] | undefined
>(undefined)
useEffect(() => {
if (!notifications) return
const groupedNotifications = groupNotifications(notifications)
setNotificationGroups(groupedNotifications)
}, [notifications])
return notificationGroups
}
export function groupNotifications(notifications: Notification[]) {
let notificationGroups: NotificationGroup[] = []
const notificationGroupsByDay = groupBy(notifications, (notification) =>
@ -85,32 +118,24 @@ export function groupNotifications(notifications: Notification[]) {
return notificationGroups
}
export function usePreferredNotifications(
userId: string | undefined,
options: { unseenOnly: boolean; customHref?: string }
export function useUnseenPreferredNotifications(
privateUser: PrivateUser,
options: { customHref?: string },
limit: number = NOTIFICATIONS_PER_PAGE
) {
const { unseenOnly, customHref } = options
const [privateUser, setPrivateUser] = useState<PrivateUser | null>(null)
const { customHref } = options
const [notifications, setNotifications] = useState<Notification[]>([])
const [userAppropriateNotifications, setUserAppropriateNotifications] =
useState<Notification[]>([])
useEffect(() => {
if (userId) listenForPrivateUser(userId, setPrivateUser)
}, [userId])
return listenForNotifications(privateUser.id, setNotifications, {
unseenOnly: true,
limit,
})
}, [limit, privateUser.id])
useEffect(() => {
if (privateUser)
return listenForNotifications(
privateUser.id,
setNotifications,
unseenOnly
)
}, [privateUser, unseenOnly])
useEffect(() => {
if (!privateUser) return
const notificationsToShow = getAppropriateNotifications(
notifications,
privateUser.notificationPreferences
@ -118,7 +143,7 @@ export function usePreferredNotifications(
customHref ? n.isSeenOnHref?.includes(customHref) : !n.isSeenOnHref
)
setUserAppropriateNotifications(notificationsToShow)
}, [privateUser, notifications, customHref])
}, [notifications, customHref, privateUser.notificationPreferences])
return userAppropriateNotifications
}

View File

@ -1,21 +1,36 @@
import { collection, query, where } from 'firebase/firestore'
import { collection, limit, orderBy, query, where } from 'firebase/firestore'
import { Notification } from 'common/notification'
import { db } from 'web/lib/firebase/init'
import { listenForValues } from 'web/lib/firebase/utils'
import { NOTIFICATIONS_PER_PAGE } from 'web/pages/notifications'
function getNotificationsQuery(userId: string, unseenOnly?: boolean) {
export function getNotificationsQuery(
userId: string,
unseenOnlyOptions?: { unseenOnly: boolean; limit: number }
) {
const notifsCollection = collection(db, `/users/${userId}/notifications`)
if (unseenOnly) return query(notifsCollection, where('isSeen', '==', false))
return query(notifsCollection)
if (unseenOnlyOptions?.unseenOnly)
return query(
notifsCollection,
where('isSeen', '==', false),
orderBy('createdTime', 'desc'),
limit(unseenOnlyOptions.limit)
)
return query(
notifsCollection,
orderBy('createdTime', 'desc'),
// Nobody's going through 10 pages of notifications, right?
limit(NOTIFICATIONS_PER_PAGE * 10)
)
}
export function listenForNotifications(
userId: string,
setNotifications: (notifs: Notification[]) => void,
unseenOnly?: boolean
unseenOnlyOptions?: { unseenOnly: boolean; limit: number }
) {
return listenForValues<Notification>(
getNotificationsQuery(userId, unseenOnly),
getNotificationsQuery(userId, unseenOnlyOptions),
(notifs) => {
notifs.sort((n1, n2) => n2.createdTime - n1.createdTime)
setNotifications(notifs)

View File

@ -1,5 +1,5 @@
import { Tabs } from 'web/components/layout/tabs'
import { useUser } from 'web/hooks/use-user'
import { usePrivateUser, useUser } from 'web/hooks/use-user'
import React, { useEffect, useState } from 'react'
import { Notification, notification_source_types } from 'common/notification'
import { Avatar, EmptyAvatar } from 'web/components/avatar'
@ -8,8 +8,6 @@ import { Page } from 'web/components/page'
import { Title } from 'web/components/title'
import { doc, updateDoc } from 'firebase/firestore'
import { db } from 'web/lib/firebase/init'
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
import Custom404 from 'web/pages/404'
import { UserLink } from 'web/components/user-page'
import { notification_subscribe_types, PrivateUser } from 'common/user'
import { Contract } from 'common/contract'
@ -35,137 +33,149 @@ import { formatMoney } from 'common/util/format'
import { groupPath } from 'web/lib/firebase/groups'
import { UNIQUE_BETTOR_BONUS_AMOUNT } from 'common/numeric-constants'
import { groupBy, sum, uniq } from 'lodash'
import Custom404 from 'web/pages/404'
export const NOTIFICATIONS_PER_PAGE = 30
const MULTIPLE_USERS_KEY = 'multipleUsers'
export default function Notifications() {
const user = useUser()
const [page, setPage] = useState(1)
const groupedNotifications = usePreferredGroupedNotifications(user?.id, {
unseenOnly: false,
})
const [paginatedNotificationGroups, setPaginatedNotificationGroups] =
useState<NotificationGroup[]>([])
useEffect(() => {
if (!groupedNotifications) return
const start = (page - 1) * NOTIFICATIONS_PER_PAGE
const end = start + NOTIFICATIONS_PER_PAGE
const maxNotificationsToShow = groupedNotifications.slice(start, end)
const remainingNotification = groupedNotifications.slice(end)
for (const notification of remainingNotification) {
if (notification.isSeen) break
else setNotificationsAsSeen(notification.notifications)
}
setPaginatedNotificationGroups(maxNotificationsToShow)
}, [groupedNotifications, page])
if (user === undefined) {
return <LoadingIndicator />
}
if (user === null) {
return <Custom404 />
}
const privateUser = usePrivateUser(user?.id)
if (!user) return <Custom404 />
return (
<Page>
<div className={'p-2 sm:p-4'}>
<Title text={'Notifications'} className={'hidden md:block'} />
<Tabs
labelClassName={'pb-2 pt-1 '}
defaultIndex={0}
tabs={[
{
title: 'Notifications',
content: groupedNotifications ? (
<div className={''}>
{paginatedNotificationGroups.length === 0 &&
"You don't have any notifications. Try changing your settings to see more."}
{paginatedNotificationGroups.map((notification) =>
notification.type === 'income' ? (
<IncomeNotificationGroupItem
notificationGroup={notification}
key={notification.groupedById + notification.timePeriod}
/>
) : notification.notifications.length === 1 ? (
<NotificationItem
notification={notification.notifications[0]}
key={notification.notifications[0].id}
/>
) : (
<NotificationGroupItem
notificationGroup={notification}
key={notification.groupedById + notification.timePeriod}
/>
)
)}
{groupedNotifications.length > NOTIFICATIONS_PER_PAGE && (
<nav
className="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6"
aria-label="Pagination"
>
<div className="hidden sm:block">
<p className="text-sm text-gray-700">
Showing{' '}
<span className="font-medium">
{page === 1
? page
: (page - 1) * NOTIFICATIONS_PER_PAGE}
</span>{' '}
to{' '}
<span className="font-medium">
{page * NOTIFICATIONS_PER_PAGE}
</span>{' '}
of{' '}
<span className="font-medium">
{groupedNotifications.length}
</span>{' '}
results
</p>
</div>
<div className="flex flex-1 justify-between sm:justify-end">
<a
href="#"
className="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
onClick={() => page > 1 && setPage(page - 1)}
>
Previous
</a>
<a
href="#"
className="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
onClick={() =>
page <
groupedNotifications?.length /
NOTIFICATIONS_PER_PAGE && setPage(page + 1)
}
>
Next
</a>
</div>
</nav>
)}
</div>
) : (
<LoadingIndicator />
),
},
{
title: 'Settings',
content: (
<div className={''}>
<NotificationSettings />
</div>
),
},
]}
/>
<div>
<Tabs
labelClassName={'pb-2 pt-1 '}
className={'mb-0 sm:mb-2'}
defaultIndex={0}
tabs={[
{
title: 'Notifications',
content: privateUser ? (
<NotificationsList privateUser={privateUser} />
) : (
<LoadingIndicator />
),
},
{
title: 'Settings',
content: (
<div className={''}>
<NotificationSettings />
</div>
),
},
]}
/>
</div>
</div>
</Page>
)
}
function NotificationsList(props: { privateUser: PrivateUser }) {
const { privateUser } = props
const [page, setPage] = useState(1)
const allGroupedNotifications = usePreferredGroupedNotifications(privateUser)
const [paginatedGroupedNotifications, setPaginatedGroupedNotifications] =
useState<NotificationGroup[] | undefined>(undefined)
useEffect(() => {
if (!allGroupedNotifications) return
const start = (page - 1) * NOTIFICATIONS_PER_PAGE
const end = start + NOTIFICATIONS_PER_PAGE
const maxNotificationsToShow = allGroupedNotifications.slice(start, end)
const remainingNotification = allGroupedNotifications.slice(end)
for (const notification of remainingNotification) {
if (notification.isSeen) break
else setNotificationsAsSeen(notification.notifications)
}
setPaginatedGroupedNotifications(maxNotificationsToShow)
}, [allGroupedNotifications, page])
if (!paginatedGroupedNotifications || !allGroupedNotifications)
return <LoadingIndicator />
return (
<div className={'min-h-[100vh]'}>
{paginatedGroupedNotifications.length === 0 && (
<div className={'mt-2'}>
You don't have any notifications. Try changing your settings to see
more.
</div>
)}
{paginatedGroupedNotifications.map((notification) =>
notification.type === 'income' ? (
<IncomeNotificationGroupItem
notificationGroup={notification}
key={notification.groupedById + notification.timePeriod}
/>
) : notification.notifications.length === 1 ? (
<NotificationItem
notification={notification.notifications[0]}
key={notification.notifications[0].id}
/>
) : (
<NotificationGroupItem
notificationGroup={notification}
key={notification.groupedById + notification.timePeriod}
/>
)
)}
{paginatedGroupedNotifications.length > 0 &&
allGroupedNotifications.length > NOTIFICATIONS_PER_PAGE && (
<nav
className="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6"
aria-label="Pagination"
>
<div className="hidden sm:block">
<p className="text-sm text-gray-700">
Showing{' '}
<span className="font-medium">
{page === 1 ? page : (page - 1) * NOTIFICATIONS_PER_PAGE}
</span>{' '}
to{' '}
<span className="font-medium">
{page * NOTIFICATIONS_PER_PAGE}
</span>{' '}
of{' '}
<span className="font-medium">
{allGroupedNotifications.length}
</span>{' '}
results
</p>
</div>
<div className="flex flex-1 justify-between sm:justify-end">
<a
href="#"
className="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
onClick={() => page > 1 && setPage(page - 1)}
>
Previous
</a>
<a
href="#"
className="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
onClick={() =>
page <
allGroupedNotifications?.length / NOTIFICATIONS_PER_PAGE &&
setPage(page + 1)
}
>
Next
</a>
</div>
</nav>
)}
</div>
)
}
function IncomeNotificationGroupItem(props: {
notificationGroup: NotificationGroup
className?: string
@ -261,18 +271,20 @@ function IncomeNotificationGroupItem(props: {
)}
<Row className={'items-center text-gray-500 sm:justify-start'}>
<TrendingUpIcon className={'text-primary h-7 w-7'} />
<div className={'flex truncate'}>
<div
onClick={() => setExpanded(!expanded)}
className={'line-clamp-1 cursor-pointer pl-1 sm:pl-0'}
>
<span>
<div
className={'flex w-full flex-row flex-wrap pl-1 sm:pl-0'}
onClick={() => setExpanded(!expanded)}
>
<div className={'flex w-full flex-row justify-between'}>
<div>
{'Daily Income Summary: '}
<span className={'text-primary'}>
{'+' + formatMoney(totalIncome)}
</span>
</span>
<RelativeTimestamp time={notifications[0].createdTime} />
</div>
<div className={'inline-block'}>
<RelativeTimestamp time={notifications[0].createdTime} />
</div>
</div>
</div>
</Row>
@ -329,13 +341,7 @@ function IncomeNotificationItem(props: {
justSummary?: boolean
}) {
const { notification, justSummary } = props
const {
sourceType,
sourceUserName,
reason,
sourceUserUsername,
createdTime,
} = notification
const { sourceType, sourceUserName, sourceUserUsername } = notification
const [highlighted] = useState(!notification.isSeen)
useEffect(() => {
@ -354,7 +360,7 @@ function IncomeNotificationItem(props: {
} else if (sourceType === 'tip') {
reasonText = !simple ? `tipped you` : `in tips on`
}
return <span className={'flex-shrink-0'}>{reasonText}</span>
return reasonText
}
if (justSummary) {
@ -374,7 +380,7 @@ function IncomeNotificationItem(props: {
</div>
<span className={'flex truncate'}>
{getReasonForShowingIncomeNotification(true)}
<NotificationLink notification={notification} />
<NotificationLink notification={notification} noClick={true} />
</span>
</div>
</div>
@ -392,42 +398,33 @@ function IncomeNotificationItem(props: {
>
<a href={getSourceUrl(notification)}>
<Row className={'items-center text-gray-500 sm:justify-start'}>
<div className={'flex max-w-xl shrink '}>
{sourceType && reason && (
<div className={'inline'}>
<span className={'mr-1'}>
<NotificationTextLabel
contract={null}
defaultText={notification.sourceText ?? ''}
notification={notification}
<div className={'line-clamp-2 flex max-w-xl shrink '}>
<div className={'inline'}>
<span className={'mr-1'}>
<NotificationTextLabel
contract={null}
defaultText={notification.sourceText ?? ''}
notification={notification}
/>
</span>
</div>
<span>
{sourceType != 'bonus' &&
(sourceUserUsername === MULTIPLE_USERS_KEY ? (
<span className={'mr-1 truncate'}>Multiple users</span>
) : (
<UserLink
name={sourceUserName || ''}
username={sourceUserUsername || ''}
className={'mr-1 flex-shrink-0'}
justFirstName={true}
/>
</span>
{sourceType != 'bonus' &&
(sourceUserUsername === MULTIPLE_USERS_KEY ? (
<span className={'mr-1 truncate'}>Multiple users</span>
) : (
<UserLink
name={sourceUserName || ''}
username={sourceUserUsername || ''}
className={'mr-1 flex-shrink-0'}
justFirstName={true}
/>
))}
</div>
)}
{getReasonForShowingIncomeNotification(false)}
<span className={'ml-1 flex hidden sm:inline-block'}>
on
))}
{getReasonForShowingIncomeNotification(false)} {' on'}
<NotificationLink notification={notification} />
</span>
<RelativeTimestamp time={createdTime} />
</div>
</Row>
<span className={'flex truncate text-gray-500 sm:hidden'}>
on
<NotificationLink notification={notification} />
</span>
<div className={'mt-4 border-b border-gray-300'} />
</a>
</div>
@ -473,23 +470,25 @@ function NotificationGroupItem(props: {
)}
<Row className={'items-center text-gray-500 sm:justify-start'}>
<EmptyAvatar multi />
<div className={'flex truncate pl-2'}>
<div
onClick={() => setExpanded(!expanded)}
className={' flex cursor-pointer truncate pl-1 sm:pl-0'}
>
{sourceContractTitle ? (
<>
<span className={'flex-shrink-0'}>{'Activity on '}</span>
<span className={'truncate'}>
<NotificationLink notification={notifications[0]} />
</span>
</>
) : (
'Other activity'
)}
</div>
<RelativeTimestamp time={notifications[0].createdTime} />
<div
className={'line-clamp-2 flex w-full flex-row flex-wrap pl-1 sm:pl-0'}
>
{sourceContractTitle ? (
<div className={'flex w-full flex-row justify-between'}>
<div className={'ml-2'}>
Activity on
<NotificationLink notification={notifications[0]} />
</div>
<div className={'hidden sm:inline-block'}>
<RelativeTimestamp time={notifications[0].createdTime} />
</div>
</div>
) : (
<span>
Other activity
<RelativeTimestamp time={notifications[0].createdTime} />
</span>
)}
</div>
</Row>
<div>
@ -528,7 +527,7 @@ function NotificationGroupItem(props: {
notification={notification}
key={notification.id}
justSummary={false}
hideTitle={true}
isChildOfGroup={true}
/>
))}
</>
@ -545,22 +544,18 @@ function NotificationGroupItem(props: {
function NotificationItem(props: {
notification: Notification
justSummary?: boolean
hideTitle?: boolean
isChildOfGroup?: boolean
}) {
const { notification, justSummary, hideTitle } = props
const { notification, justSummary, isChildOfGroup } = props
const {
sourceType,
sourceId,
sourceUserName,
sourceUserAvatarUrl,
sourceUpdateType,
reasonText,
reason,
sourceUserUsername,
createdTime,
sourceText,
sourceContractCreatorUsername,
sourceContractSlug,
} = notification
const [defaultNotificationText, setDefaultNotificationText] =
@ -629,44 +624,38 @@ function NotificationItem(props: {
className={'mr-2'}
username={sourceUserName}
/>
<div className={'flex-1 overflow-hidden sm:flex'}>
<div className={'flex w-full flex-row pl-1 sm:pl-0'}>
<div
className={
'flex max-w-xl shrink overflow-hidden text-ellipsis pl-1 sm:pl-0'
'line-clamp-2 sm:line-clamp-none flex w-full flex-row justify-between'
}
>
{sourceUpdateType != 'closed' && (
<UserLink
name={sourceUserName || ''}
username={sourceUserUsername || ''}
className={'mr-0 flex-shrink-0'}
justFirstName={true}
/>
)}
{sourceType && reason && (
<div className={'inline flex truncate'}>
<span className={'ml-1 flex-shrink-0'}>
{getReasonForShowingNotification(notification, false, true)}
</span>
{!hideTitle && (
<NotificationLink notification={notification} />
)}
</div>
)}
{sourceId &&
sourceContractSlug &&
sourceContractCreatorUsername ? (
<CopyLinkDateTimeComponent
prefix={sourceContractCreatorUsername}
slug={sourceContractSlug}
createdTime={createdTime}
elementId={getSourceIdForLinkComponent(sourceId)}
className={'-mx-1 inline-flex sm:inline-block'}
/>
) : (
<RelativeTimestamp time={createdTime} />
)}
<div>
{sourceUpdateType != 'closed' && (
<UserLink
name={sourceUserName || ''}
username={sourceUserUsername || ''}
className={'mr-1 flex-shrink-0'}
justFirstName={true}
/>
)}
{getReasonForShowingNotification(
notification,
false,
isChildOfGroup
)}
{isChildOfGroup ? (
<RelativeTimestamp time={notification.createdTime} />
) : (
<NotificationLink notification={notification} />
)}
</div>
</div>
{!isChildOfGroup && (
<div className={'hidden sm:inline-block'}>
<RelativeTimestamp time={notification.createdTime} />
</div>
)}
</div>
</Row>
<div className={'mt-1 ml-1 md:text-base'}>
@ -697,8 +686,11 @@ export const setNotificationsAsSeen = (notifications: Notification[]) => {
return notifications
}
function NotificationLink(props: { notification: Notification }) {
const { notification } = props
function NotificationLink(props: {
notification: Notification
noClick?: boolean
}) {
const { notification, noClick } = props
const {
sourceType,
sourceContractTitle,
@ -707,8 +699,17 @@ function NotificationLink(props: { notification: Notification }) {
sourceSlug,
sourceTitle,
} = notification
if (noClick)
return (
<span className={'ml-1 font-bold '}>
{sourceContractTitle || sourceTitle}
</span>
)
return (
<a
className={
'ml-1 font-bold hover:underline hover:decoration-indigo-400 hover:decoration-2 '
}
href={
sourceContractCreatorUsername
? `/${sourceContractCreatorUsername}/${sourceContractSlug}`
@ -716,9 +717,6 @@ function NotificationLink(props: { notification: Notification }) {
? `${groupPath(sourceSlug)}`
: ''
}
className={
'ml-1 inline max-w-xs truncate font-bold text-gray-500 hover:underline hover:decoration-indigo-400 hover:decoration-2 sm:max-w-sm'
}
>
{sourceContractTitle || sourceTitle}
</a>
@ -852,14 +850,6 @@ function getReasonForShowingNotification(
reasonText = !simple ? 'tagged you on' : 'tagged you'
else if (reason === 'reply_to_users_comment')
reasonText = !simple ? 'replied to you on' : 'replied'
else if (reason === 'on_users_contract')
reasonText = !simple ? `commented on your question` : 'commented'
else if (reason === 'on_contract_with_users_comment')
reasonText = `commented on`
else if (reason === 'on_contract_with_users_answer')
reasonText = `commented on`
else if (reason === 'on_contract_with_users_shares_in')
reasonText = `commented on`
else reasonText = `commented on`
break
case 'contract':
@ -871,12 +861,6 @@ function getReasonForShowingNotification(
break
case 'answer':
if (reason === 'on_users_contract') reasonText = `answered your question `
else if (reason === 'on_contract_with_users_comment')
reasonText = `answered`
else if (reason === 'on_contract_with_users_answer')
reasonText = `answered`
else if (reason === 'on_contract_with_users_shares_in')
reasonText = `answered`
else reasonText = `answered`
break
case 'follow':
@ -897,12 +881,7 @@ function getReasonForShowingNotification(
default:
reasonText = ''
}
return (
<span className={'flex-shrink-0'}>
{replaceOn ? reasonText.replace(' on', '') : reasonText}
</span>
)
return replaceOn ? reasonText.replace(' on', '') : reasonText
}
// TODO: where should we put referral bonus notifications?