Various notifications bugfixes/improvements (#442)
* Various notifications bugfixes/improvements * eslint
This commit is contained in:
parent
849e7d03a8
commit
a7a482eecd
|
@ -4,11 +4,13 @@ import { Row } from 'web/components/layout/row'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useNotifications } from 'web/hooks/use-notifications'
|
import { usePreferredGroupedNotifications } from 'web/hooks/use-notifications'
|
||||||
|
|
||||||
export default function NotificationsIcon(props: { className?: string }) {
|
export default function NotificationsIcon(props: { className?: string }) {
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const notifications = useNotifications(user?.id, { unseenOnly: true })
|
const notifications = usePreferredGroupedNotifications(user?.id, {
|
||||||
|
unseenOnly: true,
|
||||||
|
})
|
||||||
const [seen, setSeen] = useState(false)
|
const [seen, setSeen] = useState(false)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
@ -21,7 +23,9 @@ export default function NotificationsIcon(props: { className?: string }) {
|
||||||
<Row className={clsx('justify-center')}>
|
<Row className={clsx('justify-center')}>
|
||||||
<div className={'relative'}>
|
<div className={'relative'}>
|
||||||
{!seen && notifications && notifications.length > 0 && (
|
{!seen && notifications && notifications.length > 0 && (
|
||||||
<div className="absolute mt-0.5 ml-3.5 min-h-[10px] min-w-[10px] rounded-full bg-indigo-500 p-[2px] text-center text-[10px] leading-3 text-white lg:-mt-0 lg:ml-2.5"></div>
|
<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}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<BellIcon className={clsx(props.className)} />
|
<BellIcon className={clsx(props.className)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,8 +3,68 @@ import { listenForPrivateUser } from 'web/lib/firebase/users'
|
||||||
import { notification_subscribe_types, PrivateUser } from 'common/user'
|
import { notification_subscribe_types, PrivateUser } from 'common/user'
|
||||||
import { Notification } from 'common/notification'
|
import { Notification } from 'common/notification'
|
||||||
import { listenForNotifications } from 'web/lib/firebase/notifications'
|
import { listenForNotifications } from 'web/lib/firebase/notifications'
|
||||||
|
import { groupBy, map } from 'lodash'
|
||||||
|
|
||||||
export function useNotifications(
|
export type NotificationGroup = {
|
||||||
|
notifications: Notification[]
|
||||||
|
sourceContractId: string
|
||||||
|
isSeen: boolean
|
||||||
|
timePeriod: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePreferredGroupedNotifications(
|
||||||
|
userId: string | undefined,
|
||||||
|
options: { unseenOnly: boolean }
|
||||||
|
) {
|
||||||
|
const [notificationGroups, setNotificationGroups] = useState<
|
||||||
|
NotificationGroup[]
|
||||||
|
>([])
|
||||||
|
|
||||||
|
const notifications = usePreferredNotifications(userId, options)
|
||||||
|
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) =>
|
||||||
|
new Date(notification.createdTime).toDateString()
|
||||||
|
)
|
||||||
|
Object.keys(notificationGroupsByDay).forEach((day) => {
|
||||||
|
// Group notifications by contract:
|
||||||
|
const groupedNotificationsByContractId = groupBy(
|
||||||
|
notificationGroupsByDay[day],
|
||||||
|
(notification) => {
|
||||||
|
return notification.sourceContractId
|
||||||
|
}
|
||||||
|
)
|
||||||
|
notificationGroups = notificationGroups.concat(
|
||||||
|
map(groupedNotificationsByContractId, (notifications, contractId) => {
|
||||||
|
// Create a notification group for each contract within each day
|
||||||
|
const notificationGroup: NotificationGroup = {
|
||||||
|
notifications: groupedNotificationsByContractId[contractId].sort(
|
||||||
|
(a, b) => {
|
||||||
|
return b.createdTime - a.createdTime
|
||||||
|
}
|
||||||
|
),
|
||||||
|
sourceContractId: contractId,
|
||||||
|
isSeen: groupedNotificationsByContractId[contractId][0].isSeen,
|
||||||
|
timePeriod: day,
|
||||||
|
}
|
||||||
|
return notificationGroup
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return notificationGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
function usePreferredNotifications(
|
||||||
userId: string | undefined,
|
userId: string | undefined,
|
||||||
options: { unseenOnly: boolean }
|
options: { unseenOnly: boolean }
|
||||||
) {
|
) {
|
||||||
|
@ -25,7 +85,7 @@ export function useNotifications(
|
||||||
setNotifications,
|
setNotifications,
|
||||||
unseenOnly
|
unseenOnly
|
||||||
)
|
)
|
||||||
}, [privateUser])
|
}, [privateUser, unseenOnly])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!privateUser) return
|
if (!privateUser) return
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
Notification,
|
Notification,
|
||||||
notification_reason_types,
|
notification_reason_types,
|
||||||
notification_source_types,
|
notification_source_types,
|
||||||
|
notification_source_update_types,
|
||||||
} from 'common/notification'
|
} from 'common/notification'
|
||||||
import { Avatar } from 'web/components/avatar'
|
import { Avatar } from 'web/components/avatar'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
|
@ -25,7 +26,6 @@ import { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
|
||||||
import { listenForPrivateUser, updatePrivateUser } from 'web/lib/firebase/users'
|
import { listenForPrivateUser, updatePrivateUser } from 'web/lib/firebase/users'
|
||||||
import { LoadingIndicator } from 'web/components/loading-indicator'
|
import { LoadingIndicator } from 'web/components/loading-indicator'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { groupBy, map } from 'lodash'
|
|
||||||
import { UsersIcon } from '@heroicons/react/solid'
|
import { UsersIcon } from '@heroicons/react/solid'
|
||||||
import { RelativeTimestamp } from 'web/components/relative-timestamp'
|
import { RelativeTimestamp } from 'web/components/relative-timestamp'
|
||||||
import { Linkify } from 'web/components/linkify'
|
import { Linkify } from 'web/components/linkify'
|
||||||
|
@ -33,70 +33,46 @@ import {
|
||||||
FreeResponseOutcomeLabel,
|
FreeResponseOutcomeLabel,
|
||||||
OutcomeLabel,
|
OutcomeLabel,
|
||||||
} from 'web/components/outcome-label'
|
} from 'web/components/outcome-label'
|
||||||
import { useNotifications } from 'web/hooks/use-notifications'
|
import {
|
||||||
|
groupNotifications,
|
||||||
|
NotificationGroup,
|
||||||
|
usePreferredGroupedNotifications,
|
||||||
|
} from 'web/hooks/use-notifications'
|
||||||
import { getContractFromId } from 'web/lib/firebase/contracts'
|
import { getContractFromId } from 'web/lib/firebase/contracts'
|
||||||
import { CheckIcon, XIcon } from '@heroicons/react/outline'
|
import { CheckIcon, XIcon } from '@heroicons/react/outline'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
type NotificationGroup = {
|
|
||||||
notifications: Notification[]
|
|
||||||
sourceContractId: string
|
|
||||||
isSeen: boolean
|
|
||||||
timePeriod: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Notifications() {
|
export default function Notifications() {
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const [allNotificationGroups, setAllNotificationsGroups] = useState<
|
|
||||||
NotificationGroup[]
|
|
||||||
>([])
|
|
||||||
const [unseenNotificationGroups, setUnseenNotificationGroups] = useState<
|
const [unseenNotificationGroups, setUnseenNotificationGroups] = useState<
|
||||||
NotificationGroup[]
|
NotificationGroup[]
|
||||||
>([])
|
>([])
|
||||||
const notifications = useNotifications(user?.id, { unseenOnly: false })
|
const allNotificationGroups = usePreferredGroupedNotifications(user?.id, {
|
||||||
|
unseenOnly: false,
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const notificationIdsToShow = notifications.map(
|
// Don't re-add notifications that are visible right now or have been seen already.
|
||||||
(notification) => notification.id
|
|
||||||
)
|
|
||||||
// Hide notifications the user doesn't want to see.
|
|
||||||
const notificationIdsToHide = notifications
|
|
||||||
.filter(
|
|
||||||
(notification) => !notificationIdsToShow.includes(notification.id)
|
|
||||||
)
|
|
||||||
.map((notification) => notification.id)
|
|
||||||
|
|
||||||
// Because hidden notifications won't be rendered, set them to seen here
|
|
||||||
setNotificationsAsSeen(
|
|
||||||
notifications.filter((n) => notificationIdsToHide.includes(n.id))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Group notifications by contract and 24-hour time period.
|
|
||||||
const allGroupedNotifications = groupNotifications(
|
|
||||||
notifications,
|
|
||||||
notificationIdsToHide
|
|
||||||
)
|
|
||||||
|
|
||||||
// Don't add notifications that are already visible or have been seen.
|
|
||||||
const currentlyVisibleUnseenNotificationIds = Object.values(
|
const currentlyVisibleUnseenNotificationIds = Object.values(
|
||||||
unseenNotificationGroups
|
unseenNotificationGroups
|
||||||
)
|
)
|
||||||
.map((n) => n.notifications.map((n) => n.id))
|
.map((n) => n.notifications.map((n) => n.id))
|
||||||
.flat()
|
.flat()
|
||||||
const unseenGroupedNotifications = groupNotifications(
|
const unseenGroupedNotifications = groupNotifications(
|
||||||
notifications.filter(
|
allNotificationGroups
|
||||||
(notification) =>
|
.map((notification: NotificationGroup) => notification.notifications)
|
||||||
|
.flat()
|
||||||
|
.filter(
|
||||||
|
(notification: Notification) =>
|
||||||
!notification.isSeen ||
|
!notification.isSeen ||
|
||||||
currentlyVisibleUnseenNotificationIds.includes(notification.id)
|
currentlyVisibleUnseenNotificationIds.includes(notification.id)
|
||||||
),
|
|
||||||
notificationIdsToHide
|
|
||||||
)
|
)
|
||||||
setAllNotificationsGroups(allGroupedNotifications)
|
)
|
||||||
setUnseenNotificationGroups(unseenGroupedNotifications)
|
setUnseenNotificationGroups(unseenGroupedNotifications)
|
||||||
|
|
||||||
// We don't want unseenNotificationsGroup to be in the dependencies as we update it here.
|
// We don't want unseenNotificationsGroup to be in the dependencies as we update it here.
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [notifications])
|
}, [allNotificationGroups])
|
||||||
|
|
||||||
if (user === undefined) {
|
if (user === undefined) {
|
||||||
return <LoadingIndicator />
|
return <LoadingIndicator />
|
||||||
|
@ -129,7 +105,10 @@ export default function Notifications() {
|
||||||
) : (
|
) : (
|
||||||
<NotificationGroupItem
|
<NotificationGroupItem
|
||||||
notificationGroup={notification}
|
notificationGroup={notification}
|
||||||
key={notification.sourceContractId}
|
key={
|
||||||
|
notification.sourceContractId +
|
||||||
|
notification.timePeriod
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
@ -151,7 +130,10 @@ export default function Notifications() {
|
||||||
) : (
|
) : (
|
||||||
<NotificationGroupItem
|
<NotificationGroupItem
|
||||||
notificationGroup={notification}
|
notificationGroup={notification}
|
||||||
key={notification.sourceContractId}
|
key={
|
||||||
|
notification.sourceContractId +
|
||||||
|
notification.timePeriod
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
@ -188,47 +170,6 @@ const setNotificationsAsSeen = (notifications: Notification[]) => {
|
||||||
return notifications
|
return notifications
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupNotifications(
|
|
||||||
notifications: Notification[],
|
|
||||||
hideNotificationIds: string[]
|
|
||||||
) {
|
|
||||||
// Then remove them from the list of notifications to show
|
|
||||||
notifications = notifications.filter(
|
|
||||||
(notification) => !hideNotificationIds.includes(notification.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
let notificationGroups: NotificationGroup[] = []
|
|
||||||
const notificationGroupsByDay = groupBy(notifications, (notification) =>
|
|
||||||
new Date(notification.createdTime).toDateString()
|
|
||||||
)
|
|
||||||
Object.keys(notificationGroupsByDay).forEach((day) => {
|
|
||||||
// Group notifications by contract:
|
|
||||||
const groupedNotificationsByContractId = groupBy(
|
|
||||||
notificationGroupsByDay[day],
|
|
||||||
(notification) => {
|
|
||||||
return notification.sourceContractId
|
|
||||||
}
|
|
||||||
)
|
|
||||||
notificationGroups = notificationGroups.concat(
|
|
||||||
map(groupedNotificationsByContractId, (notifications, contractId) => {
|
|
||||||
// Create a notification group for each contract within each day
|
|
||||||
const notificationGroup: NotificationGroup = {
|
|
||||||
notifications: groupedNotificationsByContractId[contractId].sort(
|
|
||||||
(a, b) => {
|
|
||||||
return b.createdTime - a.createdTime
|
|
||||||
}
|
|
||||||
),
|
|
||||||
sourceContractId: contractId,
|
|
||||||
isSeen: groupedNotificationsByContractId[contractId][0].isSeen,
|
|
||||||
timePeriod: day,
|
|
||||||
}
|
|
||||||
return notificationGroup
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return notificationGroups
|
|
||||||
}
|
|
||||||
|
|
||||||
function NotificationGroupItem(props: {
|
function NotificationGroupItem(props: {
|
||||||
notificationGroup: NotificationGroup
|
notificationGroup: NotificationGroup
|
||||||
className?: string
|
className?: string
|
||||||
|
@ -280,13 +221,12 @@ function NotificationGroupItem(props: {
|
||||||
<div className={'line-clamp-4 mt-1 gap-1 whitespace-pre-line'}>
|
<div className={'line-clamp-4 mt-1 gap-1 whitespace-pre-line'}>
|
||||||
{!expanded ? (
|
{!expanded ? (
|
||||||
<>
|
<>
|
||||||
{notifications
|
{notifications.slice(0, numSummaryLines).map((notification) => {
|
||||||
.slice(0, numSummaryLines)
|
|
||||||
.map((notification, i) => {
|
|
||||||
return (
|
return (
|
||||||
<NotificationItem
|
<NotificationItem
|
||||||
notification={notification}
|
notification={notification}
|
||||||
justSummary={true}
|
justSummary={true}
|
||||||
|
key={notification.id}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
@ -300,7 +240,7 @@ function NotificationGroupItem(props: {
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{notifications.map((notification, i) => (
|
{notifications.map((notification) => (
|
||||||
<NotificationItem
|
<NotificationItem
|
||||||
notification={notification}
|
notification={notification}
|
||||||
key={notification.id}
|
key={notification.id}
|
||||||
|
@ -517,23 +457,17 @@ function NotificationSettings() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getNotificationSummaryText(
|
function isNotificationAboutContractResolution(
|
||||||
sourceId: string,
|
sourceType: notification_source_types | undefined,
|
||||||
sourceContractId: string,
|
sourceUpdateType: notification_source_update_types | undefined,
|
||||||
sourceType: 'answer' | 'comment',
|
contract: Contract | null | undefined
|
||||||
setText: (text: string) => void
|
|
||||||
) {
|
) {
|
||||||
if (sourceType === 'answer') {
|
return (
|
||||||
const answer = await getValue<Answer>(
|
(sourceType === 'contract' && !sourceUpdateType && contract?.resolution) ||
|
||||||
doc(db, `contracts/${sourceContractId}/answers/`, sourceId)
|
(sourceType === 'contract' &&
|
||||||
|
sourceUpdateType === 'resolved' &&
|
||||||
|
contract?.resolution)
|
||||||
)
|
)
|
||||||
setText(answer?.text ?? '')
|
|
||||||
} else {
|
|
||||||
const comment = await getValue<Comment>(
|
|
||||||
doc(db, `contracts/${sourceContractId}/comments/`, sourceId)
|
|
||||||
)
|
|
||||||
setText(comment?.text ?? '')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotificationItem(props: {
|
function NotificationItem(props: {
|
||||||
|
@ -547,6 +481,7 @@ function NotificationItem(props: {
|
||||||
sourceId,
|
sourceId,
|
||||||
sourceUserName,
|
sourceUserName,
|
||||||
sourceUserAvatarUrl,
|
sourceUserAvatarUrl,
|
||||||
|
sourceUpdateType,
|
||||||
reasonText,
|
reasonText,
|
||||||
reason,
|
reason,
|
||||||
sourceUserUsername,
|
sourceUserUsername,
|
||||||
|
@ -554,6 +489,7 @@ function NotificationItem(props: {
|
||||||
} = notification
|
} = notification
|
||||||
const [notificationText, setNotificationText] = useState<string>('')
|
const [notificationText, setNotificationText] = useState<string>('')
|
||||||
const [contract, setContract] = useState<Contract | null>(null)
|
const [contract, setContract] = useState<Contract | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!sourceContractId) return
|
if (!sourceContractId) return
|
||||||
getContractFromId(sourceContractId).then((contract) => {
|
getContractFromId(sourceContractId).then((contract) => {
|
||||||
|
@ -562,27 +498,32 @@ function NotificationItem(props: {
|
||||||
}, [sourceContractId])
|
}, [sourceContractId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!contract || !sourceContractId) return
|
if (!contract || !sourceContractId || !sourceId) return
|
||||||
if (sourceType === 'contract') {
|
if (
|
||||||
// We don't handle anything other than contract updates & resolution yet.
|
sourceType === 'answer' ||
|
||||||
if (contract.resolution) setNotificationText(contract.resolution)
|
sourceType === 'comment' ||
|
||||||
else setNotificationText(contract.question)
|
sourceType === 'contract'
|
||||||
return
|
) {
|
||||||
}
|
getNotificationText(
|
||||||
if (!sourceId) return
|
|
||||||
|
|
||||||
if (sourceType === 'answer' || sourceType === 'comment') {
|
|
||||||
getNotificationSummaryText(
|
|
||||||
sourceId,
|
sourceId,
|
||||||
sourceContractId,
|
sourceContractId,
|
||||||
sourceType,
|
sourceType,
|
||||||
setNotificationText
|
sourceUpdateType,
|
||||||
|
setNotificationText,
|
||||||
|
contract
|
||||||
)
|
)
|
||||||
} else if (reasonText) {
|
} else if (reasonText) {
|
||||||
// Handle arbitrary notifications with reason text here.
|
// Handle arbitrary notifications with reason text here.
|
||||||
setNotificationText(reasonText)
|
setNotificationText(reasonText)
|
||||||
}
|
}
|
||||||
}, [contract, reasonText, sourceContractId, sourceId, sourceType])
|
}, [
|
||||||
|
contract,
|
||||||
|
reasonText,
|
||||||
|
sourceContractId,
|
||||||
|
sourceId,
|
||||||
|
sourceType,
|
||||||
|
sourceUpdateType,
|
||||||
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNotificationsAsSeen([notification])
|
setNotificationsAsSeen([notification])
|
||||||
|
@ -608,8 +549,39 @@ function NotificationItem(props: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNotificationContractResolution() {
|
async function getNotificationText(
|
||||||
return sourceType === 'contract' && contract?.resolution
|
sourceId: string,
|
||||||
|
sourceContractId: string,
|
||||||
|
sourceType: 'answer' | 'comment' | 'contract',
|
||||||
|
sourceUpdateType: notification_source_update_types | undefined,
|
||||||
|
setText: (text: string) => void,
|
||||||
|
contract?: Contract
|
||||||
|
) {
|
||||||
|
if (sourceType === 'contract' && !contract)
|
||||||
|
contract = await getContractFromId(sourceContractId)
|
||||||
|
|
||||||
|
if (sourceType === 'contract' && contract) {
|
||||||
|
if (
|
||||||
|
isNotificationAboutContractResolution(
|
||||||
|
sourceType,
|
||||||
|
sourceUpdateType,
|
||||||
|
contract
|
||||||
|
) &&
|
||||||
|
contract?.resolution
|
||||||
|
)
|
||||||
|
setText(contract.resolution)
|
||||||
|
else setText(contract.question)
|
||||||
|
} else if (sourceType === 'answer') {
|
||||||
|
const answer = await getValue<Answer>(
|
||||||
|
doc(db, `contracts/${sourceContractId}/answers/`, sourceId)
|
||||||
|
)
|
||||||
|
setText(answer?.text ?? '')
|
||||||
|
} else {
|
||||||
|
const comment = await getValue<Comment>(
|
||||||
|
doc(db, `contracts/${sourceContractId}/comments/`, sourceId)
|
||||||
|
)
|
||||||
|
setText(comment?.text ?? '')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (justSummary) {
|
if (justSummary) {
|
||||||
|
@ -625,9 +597,10 @@ function NotificationItem(props: {
|
||||||
<div className={'inline-flex overflow-hidden text-ellipsis pl-1'}>
|
<div className={'inline-flex overflow-hidden text-ellipsis pl-1'}>
|
||||||
{sourceType &&
|
{sourceType &&
|
||||||
reason &&
|
reason &&
|
||||||
getReasonTextFromReason(
|
getReasonForShowingNotification(
|
||||||
sourceType,
|
sourceType,
|
||||||
reason,
|
reason,
|
||||||
|
sourceUpdateType,
|
||||||
contract,
|
contract,
|
||||||
true
|
true
|
||||||
).replace(' on', '')}
|
).replace(' on', '')}
|
||||||
|
@ -635,8 +608,10 @@ function NotificationItem(props: {
|
||||||
{contract ? (
|
{contract ? (
|
||||||
<NotificationTextLabel
|
<NotificationTextLabel
|
||||||
contract={contract}
|
contract={contract}
|
||||||
notificationText={notificationText}
|
defaultText={notificationText}
|
||||||
className={'line-clamp-1'}
|
className={'line-clamp-1'}
|
||||||
|
sourceUpdateType={sourceUpdateType}
|
||||||
|
sourceType={sourceType}
|
||||||
/>
|
/>
|
||||||
) : sourceType != 'follow' ? (
|
) : sourceType != 'follow' ? (
|
||||||
<LoadingIndicator
|
<LoadingIndicator
|
||||||
|
@ -655,6 +630,7 @@ function NotificationItem(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'bg-white px-2 pt-6 text-sm sm:px-4'}>
|
<div className={'bg-white px-2 pt-6 text-sm sm:px-4'}>
|
||||||
|
<a href={getSourceUrl(sourceId)}>
|
||||||
<Row className={'items-center text-gray-500 sm:justify-start'}>
|
<Row className={'items-center text-gray-500 sm:justify-start'}>
|
||||||
<Avatar
|
<Avatar
|
||||||
avatarUrl={sourceUserAvatarUrl}
|
avatarUrl={sourceUserAvatarUrl}
|
||||||
|
@ -673,17 +649,21 @@ function NotificationItem(props: {
|
||||||
username={sourceUserUsername || ''}
|
username={sourceUserUsername || ''}
|
||||||
className={'mr-0 flex-shrink-0'}
|
className={'mr-0 flex-shrink-0'}
|
||||||
/>
|
/>
|
||||||
<a
|
<div className={'inline-flex overflow-hidden text-ellipsis pl-1'}>
|
||||||
href={getSourceUrl(sourceId)}
|
|
||||||
className={'inline-flex overflow-hidden text-ellipsis pl-1'}
|
|
||||||
>
|
|
||||||
{sourceType && reason && (
|
{sourceType && reason && (
|
||||||
<div className={'inline truncate'}>
|
<div className={'inline truncate'}>
|
||||||
{getReasonTextFromReason(sourceType, reason, contract)}
|
{getReasonForShowingNotification(
|
||||||
<span className={'mx-1 font-bold'}>{contract?.question}</span>
|
sourceType,
|
||||||
|
reason,
|
||||||
|
sourceUpdateType,
|
||||||
|
contract
|
||||||
|
)}
|
||||||
|
<span className={'mx-1 font-bold'}>
|
||||||
|
{contract?.question}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{contract && sourceId && (
|
{contract && sourceId && (
|
||||||
<CopyLinkDateTimeComponent
|
<CopyLinkDateTimeComponent
|
||||||
|
@ -695,13 +675,13 @@ function NotificationItem(props: {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
<a href={getSourceUrl(sourceId)}>
|
|
||||||
<div className={'mt-1 md:text-base'}>
|
<div className={'mt-1 md:text-base'}>
|
||||||
{isNotificationContractResolution() && ' Resolved:'}{' '}
|
|
||||||
{contract ? (
|
{contract ? (
|
||||||
<NotificationTextLabel
|
<NotificationTextLabel
|
||||||
contract={contract}
|
contract={contract}
|
||||||
notificationText={notificationText}
|
defaultText={notificationText}
|
||||||
|
sourceType={sourceType}
|
||||||
|
sourceUpdateType={sourceUpdateType}
|
||||||
/>
|
/>
|
||||||
) : sourceType != 'follow' ? (
|
) : sourceType != 'follow' ? (
|
||||||
<LoadingIndicator spinnerClassName={'border-gray-500 h-4 w-4'} />
|
<LoadingIndicator spinnerClassName={'border-gray-500 h-4 w-4'} />
|
||||||
|
@ -718,17 +698,22 @@ function NotificationItem(props: {
|
||||||
|
|
||||||
function NotificationTextLabel(props: {
|
function NotificationTextLabel(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
notificationText: string
|
defaultText: string
|
||||||
|
sourceType?: notification_source_types
|
||||||
|
sourceUpdateType?: notification_source_update_types
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const { contract, notificationText, className } = props
|
const { contract, className, sourceUpdateType, sourceType, defaultText } =
|
||||||
if (notificationText === contract.question) {
|
props
|
||||||
return (
|
|
||||||
<div className={clsx('text-indigo-700 hover:underline', className)}>
|
if (
|
||||||
{notificationText}
|
isNotificationAboutContractResolution(
|
||||||
</div>
|
sourceType,
|
||||||
)
|
sourceUpdateType,
|
||||||
} else if (notificationText === contract.resolution) {
|
contract
|
||||||
|
) &&
|
||||||
|
contract.resolution
|
||||||
|
) {
|
||||||
if (contract.outcomeType === 'FREE_RESPONSE') {
|
if (contract.outcomeType === 'FREE_RESPONSE') {
|
||||||
return (
|
return (
|
||||||
<FreeResponseOutcomeLabel
|
<FreeResponseOutcomeLabel
|
||||||
|
@ -746,24 +731,31 @@ function NotificationTextLabel(props: {
|
||||||
truncate={'long'}
|
truncate={'long'}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
} else if (sourceType === 'contract') {
|
||||||
|
return (
|
||||||
|
<div className={clsx('text-indigo-700 hover:underline', className)}>
|
||||||
|
{defaultText}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className ? className : 'line-clamp-4 whitespace-pre-line'}
|
className={className ? className : 'line-clamp-4 whitespace-pre-line'}
|
||||||
>
|
>
|
||||||
<Linkify text={notificationText} />
|
<Linkify text={defaultText} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getReasonTextFromReason(
|
function getReasonForShowingNotification(
|
||||||
source: notification_source_types,
|
source: notification_source_types,
|
||||||
reason: notification_reason_types,
|
reason: notification_reason_types,
|
||||||
|
sourceUpdateType: notification_source_update_types | undefined,
|
||||||
contract: Contract | undefined | null,
|
contract: Contract | undefined | null,
|
||||||
simple?: boolean
|
simple?: boolean
|
||||||
) {
|
) {
|
||||||
let reasonText = ''
|
let reasonText: string
|
||||||
switch (source) {
|
switch (source) {
|
||||||
case 'comment':
|
case 'comment':
|
||||||
if (reason === 'reply_to_users_answer')
|
if (reason === 'reply_to_users_answer')
|
||||||
|
@ -783,7 +775,14 @@ function getReasonTextFromReason(
|
||||||
else reasonText = `commented on`
|
else reasonText = `commented on`
|
||||||
break
|
break
|
||||||
case 'contract':
|
case 'contract':
|
||||||
if (contract?.resolution) reasonText = `resolved`
|
if (
|
||||||
|
isNotificationAboutContractResolution(
|
||||||
|
source,
|
||||||
|
sourceUpdateType,
|
||||||
|
contract
|
||||||
|
)
|
||||||
|
)
|
||||||
|
reasonText = `resolved`
|
||||||
else reasonText = `updated`
|
else reasonText = `updated`
|
||||||
break
|
break
|
||||||
case 'answer':
|
case 'answer':
|
||||||
|
|
Loading…
Reference in New Issue
Block a user