Skip to content

Commit a02ec5a

Browse files
committed
added guards
1 parent 6b0a95a commit a02ec5a

2 files changed

Lines changed: 64 additions & 18 deletions

File tree

‎src/components/NavBarLayout.tsx‎

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function NavLink({
3131
className: customClassName,
3232
liClassName,
3333
noWrapper = false,
34+
badge,
3435
}: {
3536
href?: string;
3637
onClick?: () => void;
@@ -40,6 +41,7 @@ function NavLink({
4041
className?: string;
4142
liClassName?: string;
4243
noWrapper?: boolean;
44+
badge?: number;
4345
}) {
4446
const path = usePathname();
4547
let className =
@@ -56,13 +58,17 @@ function NavLink({
5658
{label}
5759
</p>
5860
)}
61+
{badge != null && badge > 0 && (
62+
<span className="ml-auto rounded-full bg-red-500 px-2 py-0.5 text-xs text-white sm:hidden md:flex">
63+
{badge}
64+
</span>
65+
)}
5966
</>
6067
);
6168

62-
const liClass = [
63-
placeLast ? "mt-auto" : "",
64-
liClassName || "",
65-
].filter(Boolean).join(" ");
69+
const liClass = [placeLast ? "mt-auto" : "", liClassName || ""]
70+
.filter(Boolean)
71+
.join(" ");
6672

6773
const content = href ? (
6874
<Link href={href} className={className} onClick={onClick}>
@@ -78,11 +84,7 @@ function NavLink({
7884
return content;
7985
}
8086

81-
return (
82-
<li className={liClass || undefined}>
83-
{content}
84-
</li>
85-
);
87+
return <li className={liClass || undefined}>{content}</li>;
8688
}
8789

8890
function NavLinks() {
@@ -108,7 +110,12 @@ function NavLinks() {
108110
<>
109111
<NavLink href="/" label="Home" icon={<House size={22} />} />
110112
{canViewSupport && (
111-
<NavLink href="/support" label="Support" icon={<Chat size={22} />} />
113+
<NavLink
114+
href="/support"
115+
label="Support"
116+
icon={<Chat size={22} />}
117+
badge={10}
118+
/>
112119
)}
113120
{canViewAccounts && (
114121
<NavLink

‎src/components/NotificationHandler.tsx‎

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@ const NotificationContext = createContext<{
4444
notifications: UnifiedNotification[];
4545
refreshNotifications: () => Promise<void>;
4646
dismissNotification: (id: string | number) => Promise<void>;
47+
chatUnreadCount: number;
4748
}>({
4849
notifications: [],
4950
refreshNotifications: async () => {},
5051
dismissNotification: async () => {},
52+
chatUnreadCount: 0,
5153
});
5254

5355
export function useNotifications() {
@@ -65,6 +67,8 @@ export default function NotificationHandler({
6567
const [client, setClient] = useState<Ably.Realtime | null>(null);
6668
const [notifications, setNotifications] = useState<UnifiedNotification[]>([]);
6769

70+
const [chatUnreadCount, setChatUnreadCount] = useState(0);
71+
6872
// Ensure that mirroring doesn't overwrite sessionStorage before loading
6973
const isHydrated = useRef(false);
7074

@@ -112,7 +116,8 @@ export default function NotificationHandler({
112116

113117
const dismissNotification = useCallback(
114118
async (id: string | number) => {
115-
if (Number(id) > 0) {
119+
const notification = notifications.find((n) => n.id === id);
120+
if (!notification?.isChat && Number(id) > 0) {
116121
try {
117122
await apiClient.delete(`/api/notifications/${id}`);
118123
} catch (error) {
@@ -121,7 +126,7 @@ export default function NotificationHandler({
121126
}
122127
setNotifications((prev) => prev.filter((n) => n.id !== id));
123128
},
124-
[apiClient]
129+
[apiClient, notifications]
125130
);
126131

127132
useEffect(() => {
@@ -235,6 +240,26 @@ export default function NotificationHandler({
235240
);
236241
let didInterrupt = false;
237242

243+
const refreshChatUnreadCount = async () => {
244+
try {
245+
const channels = await streamClient.queryChannels(
246+
{
247+
type: "ticket",
248+
members: { $in: [user.streamUserId!] },
249+
},
250+
{},
251+
{ limit: 30 }
252+
);
253+
const total = channels.reduce(
254+
(sum, ch) => sum + (ch.state.unreadCount ?? 0),
255+
0
256+
);
257+
setChatUnreadCount(total);
258+
} catch (error) {
259+
console.error("Failed to query chat unread count:", error);
260+
}
261+
};
262+
238263
const handleTicketMessage = (event: Event) => {
239264
if (event.channel_type !== "ticket") {
240265
return;
@@ -276,6 +301,8 @@ export default function NotificationHandler({
276301
...prev,
277302
]);
278303

304+
setChatUnreadCount((prev) => prev + 1);
305+
279306
toast.custom(
280307
(t: Toast) => (
281308
<NotificationCard
@@ -292,6 +319,11 @@ export default function NotificationHandler({
292319
);
293320
};
294321

322+
const handleMarkRead = (event: Event) => {
323+
if (event.channel_type !== "ticket") return;
324+
refreshChatUnreadCount();
325+
};
326+
295327
streamClient
296328
.connectUser(
297329
{
@@ -300,10 +332,11 @@ export default function NotificationHandler({
300332
},
301333
user.streamUserToken
302334
)
303-
.then(() => {
304-
if (!didInterrupt) {
305-
streamClient.on("notification.message_new", handleTicketMessage);
306-
}
335+
.then(async () => {
336+
if (didInterrupt) return;
337+
await refreshChatUnreadCount();
338+
streamClient.on("notification.message_new", handleTicketMessage);
339+
streamClient.on("notification.mark_read", handleMarkRead);
307340
})
308341
.catch((error) => {
309342
console.error(
@@ -315,6 +348,7 @@ export default function NotificationHandler({
315348
return () => {
316349
didInterrupt = true;
317350
streamClient.off("notification.message_new", handleTicketMessage);
351+
streamClient.off("notification.mark_read", handleMarkRead);
318352
streamClient
319353
.disconnectUser()
320354
.catch((error) =>
@@ -334,8 +368,13 @@ export default function NotificationHandler({
334368
]);
335369

336370
const value = useMemo(
337-
() => ({ notifications, refreshNotifications, dismissNotification }),
338-
[notifications, refreshNotifications, dismissNotification]
371+
() => ({
372+
notifications,
373+
refreshNotifications,
374+
dismissNotification,
375+
chatUnreadCount,
376+
}),
377+
[notifications, refreshNotifications, dismissNotification, chatUnreadCount]
339378
);
340379

341380
return (

0 commit comments

Comments
 (0)