Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 0 additions & 64 deletions app/(dashboard)/[tenant]/notifications/page.tsx

This file was deleted.

122 changes: 76 additions & 46 deletions components/core/notifications.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
"use client";

import { useTRPC } from "@/trpc/client";
import { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery } from "@tanstack/react-query";
import { TurboWire } from "@turbowire/web";
import { Bell } from "lucide-react";
import Link from "next/link";
import { useParams } from "next/navigation";
import { useEffect } from "react";
import { Bell, X } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { displayMutationError } from "@/lib/utils/error";
import { useTRPC } from "@/trpc/client";
import { Button } from "../ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { SpinnerWithSpacing } from "./loaders";
import { Spinner, SpinnerWithSpacing } from "./loaders";
import { NotificationItem } from "./notification-item";
import { Panel } from "./panel";

export function Notifications({
notificationsWire,
}: {
notificationsWire: string;
}) {
const params = useParams();
const tenant = params.tenant as string;
const [open, setOpen] = useState(false);
const trpc = useTRPC();
const {
data: notifications,
Expand All @@ -30,6 +28,15 @@ export function Notifications({
trpc.settings.getTimezone.queryOptions(),
);

const markNotificationsAsRead = useMutation(
trpc.user.markNotificationsAsRead.mutationOptions({
onError: displayMutationError,
onSuccess: () => {
refetchNotifications();
},
}),
);

const unreadCount = notifications?.filter((x) => !x.read).length;

useEffect(() => {
Expand All @@ -54,49 +61,72 @@ export function Notifications({
}, [notificationsWire, refetchNotifications]);

return (
<Popover>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon" className="relative">
<Bell className="h-5 w-5" />
{unreadCount ? (
<span className="absolute top-1 right-1 h-2 w-2 rounded-full bg-red-500" />
) : null}
<span className="sr-only">Notifications</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-80 sm:w-96 p-0" align="end">
<>
<Button
variant="ghost"
size="icon"
className="relative"
onClick={() => setOpen(true)}
>
<Bell className="h-5 w-5" />
{unreadCount ? (
<span className="absolute top-1 right-1 h-2 w-2 rounded-full bg-red-500" />
) : null}
<span className="sr-only">Notifications</span>
</Button>

<Panel open={open} setOpen={setOpen}>
{notificationsLoading || timezoneLoading ? (
<SpinnerWithSpacing />
) : (
<div className="flex flex-col">
<div className="flex items-center justify-between px-4 py-2 border-b dark:border-white/10">
<h3 className="font-medium">Notifications</h3>
{tenant && (
<Link href={`/${tenant}/notifications`}>
<Button variant="ghost" size="sm" className="text-xs">
View all
<div className="flex flex-col h-full">
<div className="flex items-center justify-between px-4 py-3 border-b dark:border-white/10">
<h3 className="font-semibold text-lg">Notifications</h3>
<div className="flex items-center gap-2">
{unreadCount ? (
<Button
variant="outline"
size="sm"
onClick={() => markNotificationsAsRead.mutate()}
disabled={markNotificationsAsRead.isPending}
>
{markNotificationsAsRead.isPending ? (
<Spinner className="h-4 w-4" />
) : (
"Mark all as read"
)}
</Button>
</Link>
) : null}
<Button
variant="ghost"
size="icon"
onClick={() => setOpen(false)}
>
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</Button>
</div>
</div>
<div className="flex-1 overflow-y-auto">
{notifications?.length && timezone ? (
<div className="flex flex-col divide-y dark:divide-white/10">
{notifications.map((notification) => (
<NotificationItem
key={notification.id}
notification={notification}
timezone={timezone}
/>
))}
</div>
) : (
<div className="text-sm text-muted-foreground px-4 py-8 text-center">
You have no new notifications.
</div>
)}
</div>
{notifications?.length && timezone ? (
<div className="flex flex-col divide-y dark:divide-white/10">
{notifications.map((notification) => (
<NotificationItem
key={notification.id}
notification={notification}
timezone={timezone}
/>
))}
</div>
) : (
<div className="text-sm text-muted-foreground px-4 py-2">
You have no new notifications.
</div>
)}
</div>
)}
</PopoverContent>
</Popover>
</Panel>
</>
);
}
9 changes: 1 addition & 8 deletions components/layout/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,7 @@ export function Navbar({ notificationsWire }: { notificationsWire: string }) {
{
href: `/${tenant}/projects/${projectId}/posts`,
label: "Posts",
active: pathname.startsWith(
`/${tenant}/projects/${projectId}/posts`,
),
active: pathname.startsWith(`/${tenant}/projects/${projectId}/posts`),
},
{
href: `/${tenant}/projects/${projectId}/activity`,
Expand All @@ -120,11 +118,6 @@ export function Navbar({ notificationsWire }: { notificationsWire: string }) {
label: "Today",
active: pathname === `/${tenant}/today`,
},
{
href: `/${tenant}/notifications`,
label: "Notifications",
active: pathname === `/${tenant}/notifications`,
},
{
href: `/${tenant}/settings`,
label: "Settings",
Expand Down