2022-06-01 13:11:25 +00:00
|
|
|
import { Tabs } from 'web/components/layout/tabs'
|
|
|
|
import { useUser } from 'web/hooks/use-user'
|
|
|
|
import React, { useEffect, useState } from 'react'
|
2022-06-01 17:31:46 +00:00
|
|
|
import {
|
|
|
|
Notification,
|
|
|
|
notification_reason_types,
|
|
|
|
notification_source_types,
|
|
|
|
} from 'common/notification'
|
2022-06-01 13:11:25 +00:00
|
|
|
import { listenForNotifications } from 'web/lib/firebase/notifications'
|
|
|
|
import { Avatar } from 'web/components/avatar'
|
|
|
|
import { Row } from 'web/components/layout/row'
|
|
|
|
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 { Answer } from 'common/answer'
|
|
|
|
import { Comment } from 'web/lib/firebase/comments'
|
|
|
|
import { getValue } from 'web/lib/firebase/utils'
|
|
|
|
import Custom404 from 'web/pages/404'
|
|
|
|
import { UserLink } from 'web/components/user-page'
|
|
|
|
import { User } from 'common/user'
|
|
|
|
import { useContract } from 'web/hooks/use-contract'
|
2022-06-01 17:31:46 +00:00
|
|
|
import { Contract } from 'common/contract'
|
2022-06-01 13:11:25 +00:00
|
|
|
|
|
|
|
export default function Notifications() {
|
|
|
|
const user = useUser()
|
|
|
|
const [notifications, setNotifications] = useState<
|
|
|
|
Notification[] | undefined
|
|
|
|
>()
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (user) return listenForNotifications(user.id, setNotifications)
|
|
|
|
}, [user])
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
// TODO: return sign in page
|
|
|
|
return <Custom404 />
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: use infinite scroll
|
|
|
|
return (
|
|
|
|
<Page>
|
2022-06-01 17:31:46 +00:00
|
|
|
<div className={'p-2 sm:p-4'}>
|
|
|
|
<Title text={'Notifications'} className={'hidden sm:block'} />
|
2022-06-01 13:11:25 +00:00
|
|
|
<Tabs
|
|
|
|
className={'pb-2 pt-1 '}
|
|
|
|
defaultIndex={0}
|
|
|
|
tabs={[
|
|
|
|
{
|
|
|
|
title: 'All Notifications',
|
|
|
|
content: (
|
|
|
|
<div className={''}>
|
|
|
|
{notifications &&
|
|
|
|
notifications.map((notification) => (
|
|
|
|
<Notification
|
|
|
|
currentUser={user}
|
|
|
|
notification={notification}
|
|
|
|
key={notification.id}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
),
|
|
|
|
},
|
|
|
|
]}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</Page>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
function Notification(props: {
|
|
|
|
currentUser: User
|
|
|
|
notification: Notification
|
|
|
|
}) {
|
|
|
|
const { notification, currentUser } = props
|
|
|
|
const {
|
|
|
|
sourceType,
|
|
|
|
sourceContractId,
|
|
|
|
sourceId,
|
|
|
|
userId,
|
|
|
|
id,
|
|
|
|
sourceUserName,
|
|
|
|
sourceUserAvatarUrl,
|
|
|
|
reasonText,
|
2022-06-01 17:31:46 +00:00
|
|
|
reason,
|
2022-06-01 13:11:25 +00:00
|
|
|
sourceUserUsername,
|
|
|
|
createdTime,
|
|
|
|
} = notification
|
|
|
|
const [subText, setSubText] = useState<string>('')
|
|
|
|
const contract = useContract(sourceContractId ?? '')
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!contract) return
|
|
|
|
if (sourceType === 'contract') {
|
|
|
|
setSubText(contract.question)
|
|
|
|
}
|
|
|
|
}, [contract, sourceType])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!sourceContractId || !sourceId) return
|
|
|
|
|
|
|
|
if (sourceType === 'answer') {
|
|
|
|
getValue<Answer>(
|
|
|
|
doc(db, `contracts/${sourceContractId}/answers/`, sourceId)
|
|
|
|
).then((answer) => {
|
|
|
|
setSubText(answer?.text || '')
|
|
|
|
})
|
|
|
|
} else if (sourceType === 'comment') {
|
|
|
|
getValue<Comment>(
|
|
|
|
doc(db, `contracts/${sourceContractId}/comments/`, sourceId)
|
|
|
|
).then((comment) => {
|
|
|
|
setSubText(comment?.text || '')
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}, [sourceContractId, sourceId, sourceType])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!contract || !notification || notification.isSeen) return
|
|
|
|
updateDoc(doc(db, `users/${currentUser.id}/notifications/`, id), {
|
|
|
|
...notification,
|
|
|
|
isSeen: true,
|
|
|
|
viewTime: new Date(),
|
|
|
|
})
|
|
|
|
}, [notification, contract, currentUser, id, userId])
|
|
|
|
|
|
|
|
function getSourceUrl(sourceId?: string) {
|
|
|
|
if (!contract) return ''
|
|
|
|
return `/${contract.creatorUsername}/${
|
|
|
|
contract.slug
|
|
|
|
}#${getSourceIdForLinkComponent(sourceId ?? '')}`
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSourceIdForLinkComponent(sourceId: string) {
|
|
|
|
switch (sourceType) {
|
|
|
|
case 'answer':
|
|
|
|
return `answer-${sourceId}`
|
|
|
|
case 'comment':
|
|
|
|
return sourceId
|
|
|
|
case 'contract':
|
|
|
|
return ''
|
|
|
|
default:
|
|
|
|
return sourceId
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2022-06-01 17:31:46 +00:00
|
|
|
<div className={'bg-white px-1 pt-6 text-sm sm:px-4'}>
|
2022-06-01 13:11:25 +00:00
|
|
|
<Row className={'items-center text-gray-500 sm:justify-start'}>
|
|
|
|
<Avatar
|
|
|
|
avatarUrl={sourceUserAvatarUrl}
|
|
|
|
size={'sm'}
|
|
|
|
className={'mr-2'}
|
|
|
|
username={sourceUserName}
|
|
|
|
/>
|
2022-06-01 17:31:46 +00:00
|
|
|
<div className={'flex-1 overflow-hidden sm:flex'}>
|
|
|
|
<div
|
|
|
|
className={
|
|
|
|
'flex max-w-sm shrink overflow-hidden text-ellipsis sm:max-w-md'
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<UserLink
|
|
|
|
name={sourceUserName || ''}
|
|
|
|
username={sourceUserUsername || ''}
|
|
|
|
className={'mr-0 flex-shrink-0'}
|
|
|
|
/>
|
|
|
|
<a
|
|
|
|
href={getSourceUrl(sourceId)}
|
|
|
|
className={'inline-flex overflow-hidden text-ellipsis pl-1'}
|
|
|
|
>
|
|
|
|
{sourceType && reason ? (
|
|
|
|
<div className={'inline truncate'}>
|
|
|
|
{getReasonTextFromReason(sourceType, reason, contract)}
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
reasonText
|
|
|
|
)}
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
{contract && sourceId && (
|
|
|
|
<CopyLinkDateTimeComponent
|
|
|
|
contract={contract}
|
|
|
|
createdTime={createdTime}
|
|
|
|
elementId={getSourceIdForLinkComponent(sourceId)}
|
|
|
|
className={'-mx-1 inline-flex sm:inline-block'}
|
|
|
|
/>
|
|
|
|
)}
|
2022-06-01 13:11:25 +00:00
|
|
|
</div>
|
|
|
|
</Row>
|
|
|
|
<a href={getSourceUrl(sourceId)}>
|
2022-06-01 17:31:46 +00:00
|
|
|
<div className={'mt-1 md:text-base'}>
|
2022-06-01 13:11:25 +00:00
|
|
|
{' '}
|
|
|
|
{contract && subText === contract.question ? (
|
2022-06-01 17:31:46 +00:00
|
|
|
<div className={'text-indigo-700 hover:underline'}>{subText}</div>
|
2022-06-01 13:11:25 +00:00
|
|
|
) : (
|
2022-06-01 17:31:46 +00:00
|
|
|
<div className={'line-clamp-4 whitespace-pre-line'}>{subText}</div>
|
2022-06-01 13:11:25 +00:00
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className={'mt-6 border-b border-gray-300'} />
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
2022-06-01 17:31:46 +00:00
|
|
|
|
|
|
|
function getReasonTextFromReason(
|
|
|
|
source: notification_source_types,
|
|
|
|
reason: notification_reason_types,
|
|
|
|
contract: Contract | undefined
|
|
|
|
) {
|
|
|
|
switch (source) {
|
|
|
|
case 'comment':
|
|
|
|
return `commented on ${contract?.question}`
|
|
|
|
case 'contract':
|
|
|
|
return `${reason} ${contract?.question}`
|
|
|
|
case 'answer':
|
|
|
|
return `answered ${contract?.question}`
|
|
|
|
default:
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
}
|