Files
Compass/web/pages/notifications.tsx
2026-02-23 23:49:51 +01:00

139 lines
4.4 KiB
TypeScript

import {type Notification, NOTIFICATIONS_PER_PAGE} from 'common/notifications'
import {type User} from 'common/src/user'
import {Fragment, useEffect, useMemo, useState} from 'react'
import {Col} from 'web/components/layout/col'
import {UncontrolledTabs} from 'web/components/layout/tabs'
import {NoSEO} from 'web/components/NoSEO'
import {NotificationItem} from 'web/components/notification-items'
import {NotificationSettings} from 'web/components/notifications'
import {PageBase} from 'web/components/page-base'
import {CompassLoadingIndicator} from 'web/components/widgets/loading-indicator'
import {Pagination} from 'web/components/widgets/pagination'
import {Title} from 'web/components/widgets/title'
import {useGroupedNotifications} from 'web/hooks/use-notifications'
import {useRedirectIfSignedOut} from 'web/hooks/use-redirect-if-signed-out'
import {usePrivateUser, useUser} from 'web/hooks/use-user'
import {api} from 'web/lib/api'
import {useT} from 'web/lib/locale'
export default function NotificationsPage() {
useRedirectIfSignedOut()
const t = useT()
return (
<PageBase trackPageView={'notifications page'} className={'mx-4'}>
<NoSEO />
<Title>{t('notifications.title', 'Updates')}</Title>
<UncontrolledTabs
name={'notifications-page'}
tabs={[
{
title: t('notifications.tabs.notifications', 'Notifications'),
content: <NotificationsContent />,
},
{
title: t('notifications.tabs.settings', 'Settings'),
content: <NotificationSettings />,
},
]}
trackingName={'notifications page'}
/>
</PageBase>
)
}
const NotificationsContent = () => {
const user = useUser()
if (!user) return <CompassLoadingIndicator />
return <LoadedNotificationsContent user={user} />
}
function LoadedNotificationsContent(props: {user: User}) {
const {user} = props
const t = useT()
const privateUser = usePrivateUser()
const {groupedNotifications, mostRecentNotification} = useGroupedNotifications(
user,
// NOTIFICATION_TYPES_TO_SELECT
)
const [page, setPage] = useState(0)
const paginatedGroupedNotifications = useMemo(() => {
const start = page * NOTIFICATIONS_PER_PAGE
const end = start + NOTIFICATIONS_PER_PAGE
return groupedNotifications?.slice(start, end)
}, [groupedNotifications, page])
// Mark all notifications as seen. Rerun as new notifications come in.
useEffect(() => {
if (!privateUser) return
api('mark-all-notifs-read', {seen: true})
groupedNotifications
?.map((ng) => ng.notifications)
.flat()
.forEach((n) => (!n.isSeen ? (n.isSeen = true) : null))
}, [privateUser, mostRecentNotification?.id])
if (!privateUser) return null
return (
<div className="relative mt-2 h-full w-full">
<Col className={'min-h-[100dvh] gap-0 text-sm'}>
{groupedNotifications === undefined || paginatedGroupedNotifications === undefined ? (
<CompassLoadingIndicator />
) : paginatedGroupedNotifications.length === 0 ? (
<div className={'mt-2'}>
{t('notifications.empty', "You don't have any notifications, yet.")}
</div>
) : (
<RenderNotificationGroups
notificationGroups={paginatedGroupedNotifications}
totalItems={groupedNotifications.length}
page={page}
setPage={setPage}
/>
)}
</Col>
</div>
)
}
export type NotificationGroup = {
notifications: Notification[]
groupedById: string
isSeen: boolean
}
function RenderNotificationGroups(props: {
notificationGroups: NotificationGroup[]
totalItems: number
page: number
setPage: (page: number) => void
}) {
const {notificationGroups, page, setPage, totalItems} = props
return (
<>
{notificationGroups.map((notification) => {
return notification.notifications.map((notification: Notification) => (
<Fragment key={notification.id}>
<NotificationItem notification={notification} />
<div className="bg-ink-300 mx-2 box-border h-[1.5px]" />
</Fragment>
))
})}
{notificationGroups.length > 0 && totalItems > NOTIFICATIONS_PER_PAGE && (
<Pagination
page={page}
pageSize={NOTIFICATIONS_PER_PAGE}
totalItems={totalItems}
setPage={setPage}
savePageToQuery={true}
/>
)}
</>
)
}