feat: track unread NIP-17 group conversations

This commit is contained in:
SkateSpots 2026-10-28 18:31:45 +07:00
parent 9aab761a08
commit 7f0524978c
2 changed files with 18 additions and 2 deletions

View file

@ -80,8 +80,10 @@ function GroupPreview({ conversation }: { conversation: Conversation }) {
const { t } = useTranslation();
const participants = conversation.participants ?? [];
const { user } = useCurrentUser();
const { getLastRead } = useDMRead();
const last = conversation.lastMessage;
const isFromMe = last?.pubkey === user?.pubkey;
const unread = !!last && !isFromMe && last.createdAt > getLastRead(conversation.pubkey);
const timeAgo = last
? formatDistanceToNow(new Date(last.createdAt * 1000), { addSuffix: true })
: '';
@ -90,6 +92,7 @@ function GroupPreview({ conversation }: { conversation: Conversation }) {
<Link to={`/messages/group/${participants.join(',')}`}>
<Card className={cn(
'hover:bg-gray-900/50 transition-colors cursor-pointer border-gray-800',
unread && 'border-[#ff0040]/40 bg-[#ff0040]/[0.04]',
'hover:border-[#ccff00]/50 active:scale-[0.99]',
)}>
<CardContent className="p-3">
@ -99,7 +102,10 @@ function GroupPreview({ conversation }: { conversation: Conversation }) {
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-2">
<h3 className="truncate font-bold text-white">{t('socialUi.dms.peopleCount', { count: participants.length })}</h3>
<h3 className={cn('truncate font-bold text-white', unread && 'font-black')}>
{t('socialUi.dms.peopleCount', { count: participants.length })}
{unread && <span className="ml-2 inline-block h-1.5 w-1.5 rounded-full bg-[#ff0040]" aria-label={t('socialUi.dms.unreadMessages')} />}
</h3>
<span className="shrink-0 text-xs text-gray-500">{timeAgo}</span>
</div>
<p className="mt-0.5 truncate text-xs text-gray-500">

View file

@ -11,7 +11,8 @@ import { useTranslation } from '@/contexts/language';
import { useToast } from '@/hooks/useToast';
import { useNostrLogin } from '@nostrify/react/login';
import { hasNip44 as checkNip44 } from '@/lib/signerCapabilities';
import { useGroupConversation, useSendDirectMessage } from '@/hooks/useDirectMessages';
import { useGroupConversation, useSendDirectMessage, getGroupId } from '@/hooks/useDirectMessages';
import { useDMRead } from '@/hooks/useDMRead';
import { useAuthor } from '@/hooks/useAuthor';
import { genUserName } from '@/lib/genUserName';
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
@ -54,11 +55,20 @@ export default function GroupConversationPage() {
useSeoMeta({ title: t('socialUi.groupConversation.seoTitle') });
const { data: messages, isLoading, refetch, isRefetching } = useGroupConversation(allParticipants);
const { markRead } = useDMRead();
const groupId = useMemo(() => getGroupId(allParticipants), [allParticipants]);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
// Opening a group marks it read up to its newest message, same as 1:1 DMs.
useEffect(() => {
if (!messages || messages.length === 0) return;
const newest = messages.reduce((max, message) => Math.max(max, message.createdAt), 0);
markRead(groupId, newest);
}, [groupId, markRead, messages]);
if (!participantsParam || otherPubkeys.length === 0) {
return (
<>