Files
supabase/apps/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopoverV2/NotificationRow.tsx
2024-03-15 15:16:50 +08:00

230 lines
8.4 KiB
TypeScript

import * as Tooltip from '@radix-ui/react-tooltip'
import dayjs from 'dayjs'
import { ArchiveRestoreIcon } from 'lucide-react'
import Link from 'next/link'
import { useEffect, useRef } from 'react'
import { useInView } from 'react-intersection-observer'
import { Button, IconArchive, IconExternalLink, cn } from 'ui'
import { Markdown } from 'components/interfaces/Markdown'
import { CriticalIcon, WarningIcon } from 'ui-patterns/Icons/StatusIcons'
import type { ItemRenderer } from 'components/ui/InfiniteList'
import { Notification, NotificationData } from 'data/notifications/notifications-v2-query'
import { ProjectInfo } from 'data/projects/projects-query'
import type { Organization } from 'types'
interface NotificationRowProps {
setRowHeight: (idx: number, height: number) => void
getProject: (ref: string) => ProjectInfo
getOrganizationById: (id: number) => Organization
getOrganizationBySlug: (slug: string) => Organization
onUpdateNotificationStatus: (id: string, status: 'archived' | 'seen') => void
queueMarkRead: (id: string) => void
}
const NotificationRow: ItemRenderer<Notification, NotificationRowProps> = ({
index,
listRef,
item: notification,
setRowHeight,
getProject,
getOrganizationById,
getOrganizationBySlug,
onUpdateNotificationStatus,
queueMarkRead,
}) => {
const ref = useRef<HTMLDivElement>(null)
const { ref: viewRef, inView } = useInView()
const { status, priority } = notification
const data = notification.data as NotificationData
const project = data.project_ref !== undefined ? getProject(data.project_ref) : undefined
const organization =
data.org_slug !== undefined
? getOrganizationBySlug(data.org_slug)
: project !== undefined
? getOrganizationById(project.organization_id)
: undefined
const daysFromNow = dayjs().diff(dayjs(notification.inserted_at), 'day')
const formattedTimeFromNow = dayjs(notification.inserted_at).fromNow()
const formattedInsertedAt = dayjs(notification.inserted_at).format('MMM DD, YYYY')
const onButtonAction = (type?: string) => {
// [Joshen] Implement accordingly - BE team will need to give us a heads up on this
console.log('Action', type)
}
useEffect(() => {
if (ref.current) {
listRef?.current?.resetAfterIndex(0)
setRowHeight(index, ref.current.clientHeight)
}
}, [ref])
useEffect(() => {
if (inView && notification.status === 'new') {
queueMarkRead(notification.id)
}
}, [inView])
return (
<div
ref={ref}
className={cn(
`p-4 flex justify-between gap-x-3 group`,
index !== 0 ? 'border-t border-overlay' : '',
status === 'new' ? 'bg-surface-100/50' : 'bg-background'
)}
>
<div ref={viewRef} className="flex flex-col gap-y-2.5 w-full py-0.5">
{(project !== undefined || organization !== undefined) && (
<div className="flex items-center max-w-[350px]">
{organization !== undefined && (
<Link
title={organization.name}
href={`/org/${organization.slug}/general`}
className="text-xs transition text-foreground-light hover:text-foreground underline truncate"
>
{organization.name}
</Link>
)}
{organization !== undefined && project !== undefined && (
<span className="text-foreground-lighter">
<svg
viewBox="0 0 24 24"
width="16"
height="16"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
shapeRendering="geometricPrecision"
>
<path d="M16 3.549L7.12 20.600"></path>
</svg>
</span>
)}
{project !== undefined && (
<Link
title={project.name}
href={`/project/${project.ref}`}
className="text-xs transition text-foreground-light hover:text-foreground underline truncate"
>
{project.name}
</Link>
)}
</div>
)}
<div className="flex items-center gap-x-2">
<p className="text-sm break-word">
{data.title}{' '}
<span className="ml-1 text-xs text-foreground-light capitalize-sentence">
{daysFromNow > 1 ? formattedInsertedAt : formattedTimeFromNow}
</span>
</p>
</div>
{data.message !== undefined && (
<Markdown className="text-foreground-light text-xs" content={data.message} />
)}
{(data.actions ?? []).length > 0 && (
<div className="flex items-center gap-x-2">
{data.actions.map((action, idx) => {
const key = `${notification.id}-action-${idx}`
if (action.url !== undefined) {
const url = action.url.includes('[ref]')
? action.url.replace('[ref]', project?.ref ?? '_')
: action.url.includes('[slug]')
? action.url.replace('[slug]', organization?.slug ?? '_')
: action.url
return (
<Button
key={key}
type="default"
icon={<IconExternalLink strokeWidth={1.5} size={14} />}
asChild
>
<Link href={url} target="_blank" rel="noreferrer">
{action.label}
</Link>
</Button>
)
} else if (action.action_type !== undefined) {
return (
<Button
key={key}
type="default"
onClick={() => onButtonAction(action.action_type)}
>
{action.label}
</Button>
)
} else {
return null
}
})}
</div>
)}
</div>
<div className="flex flex-col items-center gap-y-2">
{priority === 'Warning' && <WarningIcon className="w-5 h-5" />}
{priority === 'Critical' && <CriticalIcon className="w-5 h-5" />}
{notification.status === 'archived' ? (
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger asChild>
<Button
type="outline"
icon={
<ArchiveRestoreIcon size={13} strokeWidth={2} className="text-foreground-light" />
}
className="p-1.5 group-hover:opacity-100 opacity-0 transition rounded-full"
onClick={() => onUpdateNotificationStatus(notification.id, 'seen')}
/>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-alternative py-1 px-2 leading-none shadow',
'border border-background',
].join(' ')}
>
<span className="text-xs text-foreground">Unarchive</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
) : (
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger asChild>
<Button
type="outline"
icon={<IconArchive size={13} strokeWidth={2} className="text-foreground-light" />}
className="p-1.5 group-hover:opacity-100 opacity-0 transition rounded-full"
onClick={() => onUpdateNotificationStatus(notification.id, 'archived')}
/>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-alternative py-1 px-2 leading-none shadow',
'border border-background',
].join(' ')}
>
<span className="text-xs text-foreground">Archive</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
</div>
</div>
)
}
export default NotificationRow