Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-17 02:29:57 +00:00
co-authored by renee-png
parent 5158d11791
commit a723c5eecd
+57 -1
View File
@@ -46,10 +46,60 @@ const NAV: NavItem[] = [
{ to: "/settings", label: "Settings", icon: Settings, adminOnly: true },
];
function useUnreadMessagesCount() {
const { user } = useAuth();
const uid = user?.id;
const [count, setCount] = useState(0);
const refresh = useCallback(async () => {
if (!uid) {
setCount(0);
return;
}
const { data: mems } = await supabase
.from("conversation_members")
.select("conversation_id, last_read_at")
.eq("user_id", uid);
if (!mems || mems.length === 0) {
setCount(0);
return;
}
let total = 0;
await Promise.all(
mems.map(async (m) => {
const { count: c } = await supabase
.from("messages")
.select("id", { count: "exact", head: true })
.eq("conversation_id", m.conversation_id)
.gt("created_at", m.last_read_at)
.neq("sender_id", uid);
total += c ?? 0;
}),
);
setCount(total);
}, [uid]);
useEffect(() => {
refresh();
if (!uid) return;
const ch = supabase
.channel("sidebar-unread")
.on("postgres_changes", { event: "*", schema: "public", table: "messages" }, () => refresh())
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "conversation_members", filter: `user_id=eq.${uid}` }, () => refresh())
.subscribe();
return () => {
supabase.removeChannel(ch);
};
}, [uid, refresh]);
return count;
}
export function AppShell({ children }: { children: ReactNode }) {
const { user, signOut, isAdmin, roles } = useAuth();
const location = useLocation();
const navigate = useNavigate();
const unreadMessages = useUnreadMessagesCount();
const handleSignOut = async () => {
await signOut();
@@ -79,6 +129,7 @@ export function AppShell({ children }: { children: ReactNode }) {
? location.pathname === "/"
: location.pathname.startsWith(item.to);
const Icon = item.icon;
const showBadge = item.to === "/messages" && unreadMessages > 0;
return (
<Link
key={item.to}
@@ -91,7 +142,12 @@ export function AppShell({ children }: { children: ReactNode }) {
)}
>
<Icon className="h-4 w-4" />
{item.label}
<span className="flex-1">{item.label}</span>
{showBadge && (
<Badge variant="destructive" className="h-5 px-1.5 text-[10px]">
{unreadMessages}
</Badge>
)}
</Link>
);
})}