Skip to content

Commit 32ac0eb

Browse files
authored
Merge pull request #95 from techdiary-dev/copilot/add-in-app-notifications
v1.6.0 — 2026-04-01
2 parents cd29f2c + 5067a5f commit 32ac0eb

7 files changed

Lines changed: 148 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ This project adheres to [Semantic Versioning](https://semver.org/).
66

77
---
88

9+
## v1.6.0 — 2026-04-01
10+
11+
### ✨ Features
12+
- feat: add in-app notifications with Inngest queue integration (d70acd2)
13+
- feat: enhance notification system with improved display and payload handling (1f2b650)
14+
- feat: integrate unread notification count in sidebars and navbar (b21f7c0)
15+
16+
### 🐛 Bug Fixes
17+
- fix: address code review — actor guard, article author username in payload, correct deep links (1f5fe25)
18+
19+
### 🔧 Other Changes
20+
- refactor: clean up and standardize WorkOS skill documentation (b49acae)
21+
22+
---
23+
924
## v1.5.0 — 2026-04-01
1025

1126
### ✨ Features

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "techdiary.dev-next",
3-
"version": "1.5.0",
3+
"version": "1.6.0",
44
"private": true,
55
"scripts": {
66
"dev": "next dev --turbo",

src/app/(home)/_components/HomeLeftSidebar.tsx

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
TagsIcon,
1919
} from "lucide-react";
2020
import Link from "next/link";
21+
import { useUnreadNotificationCount } from "@/components/notifications/use-unread-notification-count";
2122

2223
const HomeLeftSidebar = () => {
2324
const [open, setOpen] = useAtom(homeSidebarOpenAtom);
@@ -94,11 +95,7 @@ const Sidebar = () => {
9495
icon={<BookmarkIcon size={17} />}
9596
label={_t("Bookmarks")}
9697
/>
97-
<NavLink
98-
href="/dashboard/notifications"
99-
icon={<BellIcon size={17} />}
100-
label={_t("Notifications")}
101-
/>
98+
<NotificationsNavLink />
10299
</div>
103100
)}
104101

@@ -139,16 +136,36 @@ const NavLink = ({
139136
href,
140137
icon,
141138
label,
139+
badge,
142140
}: {
143141
href: string;
144142
icon: React.ReactNode;
145143
label: string;
144+
badge?: number;
146145
}) => (
147146
<Link
148147
href={href}
149-
className="flex items-center gap-2.5 px-2 py-1.5 rounded-md text-sm text-foreground hover:bg-muted transition-colors"
148+
className="flex min-w-0 items-center gap-2.5 px-2 py-1.5 rounded-md text-sm text-foreground hover:bg-muted transition-colors"
150149
>
151-
<span className="text-muted-foreground">{icon}</span>
152-
{label}
150+
<span className="text-muted-foreground shrink-0">{icon}</span>
151+
<span className="min-w-0 flex-1 truncate">{label}</span>
152+
{badge != null && badge > 0 ? (
153+
<span className="ml-auto flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground tabular-nums">
154+
{badge > 99 ? "99+" : badge}
155+
</span>
156+
) : null}
153157
</Link>
154158
);
159+
160+
const NotificationsNavLink = () => {
161+
const { _t } = useTranslation();
162+
const { data: unread = 0 } = useUnreadNotificationCount();
163+
return (
164+
<NavLink
165+
href="/dashboard/notifications"
166+
icon={<BellIcon size={17} />}
167+
label={_t("Notifications")}
168+
badge={unread}
169+
/>
170+
);
171+
};

src/app/dashboard/_components/DashboardSidebar.tsx

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ import {
1616
Bookmark,
1717
Home,
1818
KeySquareIcon,
19-
Settings,
2019
Settings2,
2120
} from "lucide-react";
2221
import Link from "next/link";
2322
import { usePathname } from "next/navigation";
23+
import { useUnreadNotificationCount } from "@/components/notifications/use-unread-notification-count";
2424

2525
const DashboardSidebar = () => {
2626
const { _t } = useTranslation();
@@ -36,11 +36,6 @@ const DashboardSidebar = () => {
3636
// url: "/series",
3737
// icon: Home,
3838
// },
39-
// {
40-
// title: _t("Notifications"),
41-
// url: "/notifications",
42-
// icon: BellIcon,
43-
// },
4439
{
4540
title: _t("Bookmarks"),
4641
url: "/bookmarks",
@@ -64,7 +59,7 @@ const DashboardSidebar = () => {
6459
<SidebarGroupLabel>{_t("Dashboard")}</SidebarGroupLabel>
6560
<SidebarGroupContent>
6661
<SidebarMenu>
67-
{items.map((item, key) => (
62+
{items.slice(0, 1).map((item, key) => (
6863
<SidebarMenuItem key={key}>
6964
<SidebarMenuButton
7065
asChild
@@ -80,6 +75,26 @@ const DashboardSidebar = () => {
8075
</SidebarMenuButton>
8176
</SidebarMenuItem>
8277
))}
78+
<DashboardNotificationsMenuItem
79+
pathName={pathName}
80+
label={_t("Notifications")}
81+
/>
82+
{items.slice(1).map((item, key) => (
83+
<SidebarMenuItem key={`rest-${key}`}>
84+
<SidebarMenuButton
85+
asChild
86+
isActive={pathName === `/dashboard${item.url}`}
87+
>
88+
<Link
89+
className="text-muted-foreground"
90+
href={`/dashboard${item.url}`}
91+
>
92+
<item.icon />
93+
<span>{item.title}</span>
94+
</Link>
95+
</SidebarMenuButton>
96+
</SidebarMenuItem>
97+
))}
8398
</SidebarMenu>
8499
</SidebarGroupContent>
85100
</SidebarGroup>
@@ -88,4 +103,35 @@ const DashboardSidebar = () => {
88103
);
89104
};
90105

106+
function DashboardNotificationsMenuItem({
107+
pathName,
108+
label,
109+
}: {
110+
pathName: string;
111+
label: string;
112+
}) {
113+
const { data: unread = 0 } = useUnreadNotificationCount();
114+
return (
115+
<SidebarMenuItem>
116+
<SidebarMenuButton
117+
asChild
118+
isActive={pathName === "/dashboard/notifications"}
119+
>
120+
<Link
121+
className="text-muted-foreground flex w-full min-w-0 items-center gap-2"
122+
href="/dashboard/notifications"
123+
>
124+
<BellIcon className="shrink-0" />
125+
<span className="min-w-0 flex-1 truncate">{label}</span>
126+
{unread > 0 ? (
127+
<span className="flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground tabular-nums">
128+
{unread > 99 ? "99+" : unread}
129+
</span>
130+
) : null}
131+
</Link>
132+
</SidebarMenuButton>
133+
</SidebarMenuItem>
134+
);
135+
}
136+
91137
export default DashboardSidebar;

src/components/Navbar/NavbarActions.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import LanguageSwitcher from "./LanguageSwitcher";
1818
import Link from "next/link";
1919
import { useAtom } from "jotai";
2020
import { searchBarAtom } from "@/store/search-bar.atom";
21+
import { NavbarNotificationButton } from "@/components/notifications/NavbarNotificationButton";
2122

2223
const NavbarActions: React.FC = () => {
2324
const { _t } = useTranslation();
@@ -35,6 +36,7 @@ const NavbarActions: React.FC = () => {
3536
</Button>
3637
<LanguageSwitcher />
3738
<ThemeSwitcher />
39+
<NavbarNotificationButton />
3840

3941
{authSession?.session ? (
4042
<>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"use client";
2+
3+
import { Button } from "@/components/ui/button";
4+
import { useTranslation } from "@/i18n/use-translation";
5+
import { useSession } from "@/store/session.atom";
6+
import { Bell } from "lucide-react";
7+
import Link from "next/link";
8+
import { useUnreadNotificationCount } from "./use-unread-notification-count";
9+
10+
export function NavbarNotificationButton() {
11+
const { _t } = useTranslation();
12+
const authSession = useSession();
13+
const { data: unread = 0 } = useUnreadNotificationCount();
14+
15+
if (!authSession?.session) return null;
16+
17+
return (
18+
<Button variant="ghost" size="icon" className="relative shrink-0" asChild>
19+
<Link
20+
href="/dashboard/notifications"
21+
aria-label={_t("Notifications")}
22+
title={_t("Notifications")}
23+
>
24+
<Bell className="size-[1.15rem]" strokeWidth={2} />
25+
{unread > 0 ? (
26+
<span className="absolute right-0.5 top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-none text-primary-foreground tabular-nums">
27+
{unread > 99 ? "99+" : unread}
28+
</span>
29+
) : null}
30+
</Link>
31+
</Button>
32+
);
33+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"use client";
2+
3+
import * as notificationActions from "@/backend/services/notifications.actions";
4+
import { useSession } from "@/store/session.atom";
5+
import { useQuery } from "@tanstack/react-query";
6+
7+
export function useUnreadNotificationCount() {
8+
const session = useSession();
9+
return useQuery({
10+
queryKey: ["unread-notification-count"],
11+
queryFn: async () => {
12+
const r = await notificationActions.unreadNotificationCount();
13+
if (!r.success) return 0;
14+
return r.data.count;
15+
},
16+
enabled: Boolean(session?.session),
17+
staleTime: 60 * 1000,
18+
});
19+
}

0 commit comments

Comments
 (0)