feat(P5C.T2): per-conv 'mentions only' toggle + notification gate
This commit is contained in:
@@ -35,7 +35,7 @@ export async function listConversations(client: AppSupabaseClient): Promise<Conv
|
||||
// cast the select to bypass typing.
|
||||
const { data: myMembers, error: mErr } = await client
|
||||
.from('conversation_members')
|
||||
.select('conversation_id, role, accepted, archived, muted_until' as '*')
|
||||
.select('conversation_id, role, accepted, archived, muted_until, mentions_only' as '*')
|
||||
.eq('user_id', myId);
|
||||
if (mErr) throw mErr;
|
||||
const myMembersList = (myMembers ?? []) as unknown as Array<{
|
||||
@@ -44,6 +44,7 @@ export async function listConversations(client: AppSupabaseClient): Promise<Conv
|
||||
accepted: boolean;
|
||||
archived: boolean | null;
|
||||
muted_until: string | null;
|
||||
mentions_only: boolean | null;
|
||||
}>;
|
||||
if (myMembersList.length === 0) return [];
|
||||
|
||||
@@ -114,7 +115,13 @@ export async function listConversations(client: AppSupabaseClient): Promise<Conv
|
||||
? (members.find((m) => m.userId !== myId)?.profile ?? null)
|
||||
: null;
|
||||
const mineRow = mine as
|
||||
| { accepted: boolean; role: string; archived?: boolean; muted_until?: string | null }
|
||||
| {
|
||||
accepted: boolean;
|
||||
role: string;
|
||||
archived?: boolean;
|
||||
muted_until?: string | null;
|
||||
mentions_only?: boolean | null;
|
||||
}
|
||||
| undefined;
|
||||
return {
|
||||
id: c.id,
|
||||
@@ -129,6 +136,7 @@ export async function listConversations(client: AppSupabaseClient): Promise<Conv
|
||||
lastMessageAt: lastSeen.get(c.id) ?? null,
|
||||
archived: mineRow?.archived ?? false,
|
||||
mutedUntil: mineRow?.muted_until ?? null,
|
||||
mentionsOnly: mineRow?.mentions_only ?? false,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -164,6 +172,24 @@ export async function setConversationMutedUntil(
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
// Toggle "mentions only" — when true the renderer's notification gate
|
||||
// suppresses non-mention alerts for this conversation. Mentions still fire
|
||||
// via the independent useMentionNotifications subscription on
|
||||
// message_mentions, so the @-alerts are never lost.
|
||||
export async function setConversationMentionsOnly(
|
||||
client: AppSupabaseClient,
|
||||
params: { conversationId: string; mentionsOnly: boolean },
|
||||
): Promise<void> {
|
||||
const { data: session } = await client.auth.getUser();
|
||||
if (!session.user) throw new Error('not authenticated');
|
||||
const { error } = await client
|
||||
.from('conversation_members')
|
||||
.update({ mentions_only: params.mentionsOnly } as never)
|
||||
.eq('conversation_id', params.conversationId)
|
||||
.eq('user_id', session.user.id);
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
// Convenience: `null` unmutes, number means minutes from now. For "forever"
|
||||
// pass a very large number (e.g. 100 years worth of minutes).
|
||||
export function muteDurationToIso(minutes: number | null): string | null {
|
||||
|
||||
@@ -28,6 +28,10 @@ export interface ConversationSummary {
|
||||
// ISO timestamp. null = not muted. Past timestamp = expired mute (treat as
|
||||
// not muted — the server row is kept for history until the next toggle).
|
||||
mutedUntil: string | null;
|
||||
// When true the renderer suppresses non-mention notifications. Mentions
|
||||
// still fire via the independent useMentionNotifications subscription on
|
||||
// message_mentions, so this flag never silences @-alerts.
|
||||
mentionsOnly: boolean;
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
|
||||
Reference in New Issue
Block a user