diff --git a/web/components/nav/sidebar.tsx b/web/components/nav/sidebar.tsx index b9449ea0..6ab095ef 100644 --- a/web/components/nav/sidebar.tsx +++ b/web/components/nav/sidebar.tsx @@ -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 }) { /> )} - + {privateUser && ( + + )} {/* Desktop navigation */} @@ -243,11 +247,13 @@ export default function Sidebar(props: { className?: string }) {
)} - + {privateUser && ( + + )} ) @@ -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(() => { diff --git a/web/components/notifications-icon.tsx b/web/components/notifications-icon.tsx index 8f45a054..2938fd17 100644 --- a/web/components/notifications-icon.tsx +++ b/web/components/notifications-icon.tsx @@ -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 (
- {!seen && notifications && notifications.length > 0 && ( -
- {notifications.length > NOTIFICATIONS_PER_PAGE - ? `${NOTIFICATIONS_PER_PAGE}+` - : notifications.length} -
- )} + {privateUser && }
) } +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
+ } + + return ( +
+ {notifications.length > NOTIFICATIONS_PER_PAGE + ? `${NOTIFICATIONS_PER_PAGE}+` + : notifications.length} +
+ ) +} diff --git a/web/hooks/use-notifications.ts b/web/hooks/use-notifications.ts index 98b0f2fd..f5502b85 100644 --- a/web/hooks/use-notifications.ts +++ b/web/hooks/use-notifications.ts @@ -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([]) + 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(null) + const { customHref } = options const [notifications, setNotifications] = useState([]) const [userAppropriateNotifications, setUserAppropriateNotifications] = useState([]) 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 } diff --git a/web/lib/firebase/notifications.ts b/web/lib/firebase/notifications.ts index c0dca8be..d2db3665 100644 --- a/web/lib/firebase/notifications.ts +++ b/web/lib/firebase/notifications.ts @@ -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( - getNotificationsQuery(userId, unseenOnly), + getNotificationsQuery(userId, unseenOnlyOptions), (notifs) => { notifs.sort((n1, n2) => n2.createdTime - n1.createdTime) setNotifications(notifs) diff --git a/web/pages/notifications.tsx b/web/pages/notifications.tsx index 08ef9bb8..3f9b4eed 100644 --- a/web/pages/notifications.tsx +++ b/web/pages/notifications.tsx @@ -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([]) - 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 - } - if (user === null) { - return - } + const privateUser = usePrivateUser(user?.id) + if (!user) return return (
- <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?