import {SparklesIcon} from '@heroicons/react/24/solid' import clsx from 'clsx' import {ENV_CONFIG} from 'common/envs/constants' import {Notification} from 'common/notifications' import {sortBy} from 'lodash' import Link from 'next/link' import {ReactNode, useState} from 'react' import {useIsMobile} from 'web/hooks/use-is-mobile' import {useT} from 'web/lib/locale' import {Col} from './layout/col' import {Row} from './layout/row' import {MultiUserReactionModal} from './multi-user-reaction-link' import {RelativeTimestampNoTooltip} from './relative-timestamp' import {Avatar} from './widgets/avatar' import {Linkify} from './widgets/linkify' import {UserLink} from './widgets/user-link' export function NotificationItem(props: {notification: Notification}) { const {notification} = props const {sourceType, reason} = notification const [highlighted, setHighlighted] = useState(!notification.isSeen) const params = { notification, highlighted, setHighlighted, } if (sourceType === 'comment_on_profile') { return } else if (sourceType === 'new_match') { return } else if (reason === 'new_profile_like') { return } else if (reason === 'new_profile_ship') { return } else if (reason === 'connection_interest_match') { return } else { return } } export function BaseNotification(props: { notification: Notification highlighted: boolean setHighlighted: (highlighted: boolean) => void }) { const {notification, highlighted, setHighlighted} = props return ( } subtitle={
} link={notification.sourceSlug} >
{notification.title}
) } export function CommentOnProfileNotification(props: { notification: Notification highlighted: boolean setHighlighted: (highlighted: boolean) => void isChildOfGroup?: boolean }) { const {notification, isChildOfGroup, highlighted, setHighlighted} = props const {sourceUserName, sourceUserUsername, sourceText} = notification const t = useT() const reasonText = t('notifications.comment.commented', `commented `) return ( } subtitle={
} link={notification.sourceSlug} >
{reasonText} {!isChildOfGroup && ( {t('notifications.comment.on_your_profile', 'on your profile')} )}
) } export function NewMatchNotification(props: { notification: Notification highlighted: boolean setHighlighted: (highlighted: boolean) => void isChildOfGroup?: boolean }) { const {notification, isChildOfGroup, highlighted, setHighlighted} = props const {sourceContractTitle, sourceText, sourceUserName, sourceUserUsername} = notification const t = useT() return ( } link={getSourceUrl(notification)} subtitle={
} >
{' '} {t('notifications.match.proposed_new_match', 'proposed a new match:')}
) } function ProfileLikeNotification(props: { notification: Notification highlighted: boolean setHighlighted: (highlighted: boolean) => void isChildOfGroup?: boolean }) { const {notification, highlighted, setHighlighted, isChildOfGroup} = props const [open, setOpen] = useState(false) const t = useT() const {sourceUserName, sourceUserUsername} = notification const relatedNotifications: Notification[] = notification.data?.relatedNotifications ?? [ notification, ] const reactorsText = relatedNotifications.length > 1 ? `${sourceUserName} & ${relatedNotifications.length - 1} other${ relatedNotifications.length > 2 ? 's' : '' }` : sourceUserName return ( } link={`https://${ENV_CONFIG.domain}/${sourceUserUsername}`} subtitle={<>} > {reactorsText && }{' '} {t('notifications.profile.liked_you', 'liked you!')} ) } function ProfileShipNotification(props: { notification: Notification highlighted: boolean setHighlighted: (highlighted: boolean) => void isChildOfGroup?: boolean }) { const {notification, highlighted, setHighlighted, isChildOfGroup} = props const [open, setOpen] = useState(false) const t = useT() const {sourceUserName, sourceUserUsername} = notification const relatedNotifications: Notification[] = notification.data?.relatedNotifications ?? [ notification, ] const reactorsText = relatedNotifications.length > 1 ? `${sourceUserName} & ${relatedNotifications.length - 1} other${ relatedNotifications.length > 2 ? 's' : '' }` : sourceUserName const {creatorId, creatorName, creatorUsername} = notification.data ?? {} return ( } link={`https://${ENV_CONFIG.domain}/${sourceUserUsername}`} subtitle={<>} > You and {reactorsText && }{' '} {t('notifications.profile.are_being_shipped_by', 'are being shipped by')}{' '} ! ) } export function ConnectionInterestMatchNotification(props: { notification: Notification highlighted: boolean setHighlighted: (highlighted: boolean) => void isChildOfGroup?: boolean }) { const {notification, highlighted, setHighlighted, isChildOfGroup} = props const {sourceUserName, sourceUserUsername, sourceText} = notification const t = useT() const connectionType = notification.data?.connectionType || sourceText const type = t(`profile.relationship.${connectionType}`, connectionType).toLowerCase() return ( } link={`/${sourceUserUsername}`} subtitle={<>} > {' '} {t('notifications.connection.interested_in_you', 'is interested in a {type} with you', { type, })} ! ) } const getSourceUrl = (notification: Notification) => { const {sourceSlug, sourceId} = notification if (sourceSlug) { return `${sourceSlug.startsWith('/') ? sourceSlug : '/' + sourceSlug}#${sourceId}` } return '' } // TODO: fix badges (id based) export function NotificationUserLink(props: { userId?: string name?: string username?: string className?: string hideBadge?: boolean }) { const {userId, name, username, className, hideBadge} = props return ( ) } export function AvatarNotificationIcon(props: { notification: Notification symbol?: string | ReactNode }) { const {notification, symbol} = props const {sourceUserName, sourceUserAvatarUrl, sourceUserUsername, sourceSlug} = notification const href = sourceUserUsername ? `/${sourceUserUsername}` : (sourceSlug ?? '/') return (
) => e.stopPropagation} >
{symbol}
) } export function MultipleAvatarIcons(props: { notification: Notification symbol: string setOpen: (open: boolean) => void }) { const {notification, symbol, setOpen} = props const relatedNotifications: Notification[] = sortBy( notification.data?.relatedNotifications ?? [notification], (n) => n.createdTime, ) const combineAvatars = (notifications: Notification[]) => { const totalAvatars = notifications.length const maxToShow = Math.min(totalAvatars, 3) const avatarsToCombine = notifications.slice(totalAvatars - maxToShow, totalAvatars) const max = avatarsToCombine.length const startLeft = -0.35 * (max - 1) return avatarsToCombine.map((n, index) => (
)) } return (
{ if (relatedNotifications.length === 1) return event.preventDefault() setOpen(true) }} > {relatedNotifications.length > 1 ? ( {/* placeholder avatar to set the proper size*/} {combineAvatars(relatedNotifications)} ) : ( )}
) } export function PrimaryNotificationLink(props: {text: string | undefined}) { const {text} = props if (!text) { return <> } return {text} } // the primary skeleton for notifications export function NotificationFrame(props: { notification: Notification highlighted: boolean setHighlighted: (highlighted: boolean) => void children: React.ReactNode icon: ReactNode link?: string onClick?: () => void subtitle?: string | ReactNode isChildOfGroup?: boolean customBackground?: ReactNode }) { const { notification, highlighted, setHighlighted, children, icon, subtitle, onClick, link, customBackground, } = props const isMobile = useIsMobile() const frameObject = ( {icon} {children}
{subtitle}
{highlighted && !isMobile && }
) return ( {customBackground} {link && ( { if (highlighted) { setHighlighted(false) } }} > {frameObject} )} {!link && ( { if (highlighted) { setHighlighted(false) } if (onClick) { onClick() } }} > {frameObject} )} ) }