Task #45: Tapping a chat notification opens the matching conversation

- notifications.tsx: When a notification is clicked, if it has
  relatedType === "conversation" and a relatedId, navigate to
  /chat?c=<conversationId> in addition to marking it read. Other
  notification types continue to behave as before (just mark read).
- chat.tsx: Read the optional `c` query parameter via wouter's
  useSearch hook. When present and valid, set it as the
  selectedConvId so the conversation opens automatically, then
  replace the URL back to /chat so refreshing or navigating away
  doesn't keep re-opening it.

No API or schema changes required; notification records already
carry relatedType/relatedId for chat notifications.

Replit-Task-Id: 2fda1864-99d6-4f07-a3d9-aa3021d5493f
This commit is contained in:
riyadhafraa
2026-04-21 11:07:55 +00:00
parent 1411486602
commit 8729cae01a
2 changed files with 28 additions and 2 deletions
+13 -1
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { useLocation } from "wouter";
import { useLocation, useSearch } from "wouter";
import {
useListConversations,
getListConversationsQueryKey,
@@ -135,6 +135,7 @@ type ConvMode = "direct" | "group";
export default function ChatPage() {
const { t, i18n } = useTranslation();
const [, setLocation] = useLocation();
const search = useSearch();
const { user } = useAuth();
const lang = i18n.language;
const isRtl = lang === "ar";
@@ -231,6 +232,17 @@ export default function ChatPage() {
};
}, [user, selectedConvId, queryClient]);
// Open conversation specified via ?c=<id> query param (e.g. from notifications)
useEffect(() => {
const params = new URLSearchParams(search);
const convParam = params.get("c");
if (!convParam) return;
const convId = Number(convParam);
if (!Number.isFinite(convId) || convId <= 0) return;
setSelectedConvId(convId);
setLocation("/chat", { replace: true });
}, [search, setLocation]);
// Mark conversation as read when selected
useEffect(() => {
if (selectedConvId) {
@@ -49,6 +49,20 @@ export default function NotificationsPage() {
);
};
const handleNotificationClick = (n: {
id: number;
isRead: boolean;
relatedType?: string | null;
relatedId?: number | null;
}) => {
if (!n.isRead) {
handleMarkOne(n.id);
}
if (n.relatedType === "conversation" && n.relatedId != null) {
setLocation(`/chat?c=${n.relatedId}`);
}
};
const unreadCount = notifications?.filter((n) => !n.isRead).length ?? 0;
return (
@@ -103,7 +117,7 @@ export default function NotificationsPage() {
return (
<div
key={n.id}
onClick={() => !n.isRead && handleMarkOne(n.id)}
onClick={() => handleNotificationClick(n)}
className={`glass-panel rounded-2xl p-4 cursor-pointer transition-opacity ${n.isRead ? "opacity-60" : ""}`}
>
<div className="flex items-start justify-between gap-2">