feat(P5C.T2): per-conv 'mentions only' toggle + notification gate

This commit is contained in:
byGalax
2026-05-16 22:22:04 +02:00
parent b8b451ef4f
commit 7ebc5f6c9d
6 changed files with 71 additions and 5 deletions
@@ -1,6 +1,7 @@
import {
muteDurationToIso,
setConversationArchived,
setConversationMentionsOnly,
setConversationMutedUntil,
} from '@chat-app/shared/chat';
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
@@ -8,12 +9,13 @@ import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { supabase } from '../lib/supabase';
import { ArchiveIcon, BellIcon, BellOffIcon, MoreVerticalIcon } from './icons';
import { ArchiveIcon, AtIcon, BellIcon, BellOffIcon, MoreVerticalIcon } from './icons';
interface Props {
conversationId: string;
archived: boolean;
mutedUntil: string | null;
mentionsOnly: boolean;
}
interface MuteOption {
@@ -50,7 +52,7 @@ interface MenuPos {
// escape the sidebar's `overflow-y-auto` clipping context. Position is
// computed from the trigger's bounding rect — menu anchors right-aligned
// under the trigger so it doesn't push off-screen on narrow windows.
export function ConversationRowMenu({ conversationId, archived, mutedUntil }: Props) {
export function ConversationRowMenu({ conversationId, archived, mutedUntil, mentionsOnly }: Props) {
const { t } = useTranslation(['app']);
const [open, setOpen] = useState(false);
const [submenuOpen, setSubmenuOpen] = useState<'mute' | null>(null);
@@ -145,6 +147,21 @@ export function ConversationRowMenu({ conversationId, archived, mutedUntil }: Pr
[conversationId],
);
const handleMentionsOnly = useCallback(
async (next: boolean) => {
setOpen(false);
try {
await setConversationMentionsOnly(supabase, {
conversationId,
mentionsOnly: next,
});
} catch (err: unknown) {
console.warn('mentions-only toggle failed', err);
}
},
[conversationId],
);
return (
<>
<button
@@ -196,6 +213,16 @@ export function ConversationRowMenu({ conversationId, archived, mutedUntil }: Pr
}}
hasSubmenu={!isMuted}
/>
<MenuItem
icon={<AtIcon className="h-4 w-4" />}
label={
(mentionsOnly ? '✓ ' : '') +
t('app:chats.mentions_only', {
defaultValue: 'Nur bei @Mentions benachrichtigen',
})
}
onClick={() => void handleMentionsOnly(!mentionsOnly)}
/>
</div>,
document.body,
)}
@@ -225,7 +225,12 @@ export function ConversationsProvider({ children }: { children: ReactNode }) {
(c) => c.id === row.conversation_id,
);
const muted = isConversationMuted(convForMute?.mutedUntil ?? null);
if (presenceRef.current !== 'dnd' && !muted) {
// "Mentions only" silences non-mention messages here. Mentions
// still fire via the independent useMentionNotifications
// subscription on message_mentions, so this branch doesn't
// lose the @-alerts.
const mentionsOnly = convForMute?.mentionsOnly ?? false;
if (presenceRef.current !== 'dnd' && !muted && !mentionsOnly) {
playNotificationTone();
const conv = conversationsRef.current.find(
(c) => c.id === row.conversation_id,
+1
View File
@@ -425,6 +425,7 @@ function ConversationRow({
conversationId={item.id}
archived={item.archived}
mutedUntil={item.mutedUntil}
mentionsOnly={item.mentionsOnly}
/>
</NavLink>
);