Add new analytics events for group chats (#10830)

This commit is contained in:
DS Boyce
2026-06-10 13:56:39 -07:00
committed by GitHub
parent 974d2b15d5
commit 871e9506df
11 changed files with 184 additions and 29 deletions
+65
View File
@@ -586,9 +586,74 @@ export type Events = {
| 'SendViaChatDialog' | 'SendViaChatDialog'
| 'ConvoSettings' | 'ConvoSettings'
} }
// Group chat adoption
'groupchat:create': { 'groupchat:create': {
logContext: 'NewChatDialog' logContext: 'NewChatDialog'
} }
'groupchat:landingPage:view': {
hasSession: boolean
}
'groupchat:inviteLink:redeem': {}
// Group chat user interactions
'groupchat:message:send': {
convoId: string
isOwner: boolean
}
'groupchat:mute': {
convoId: string
}
'groupchat:unmute': {
convoId: string
}
'groupchat:leave': {
convoId: string
isOwner: boolean
}
'groupchat:settings:view': {
convoId: string
isOwner: boolean
}
'groupchat:inviteLink:shareButton:press': {
convoId: string
method: 'post' | 'copy' | 'native'
}
// Group chat owner actions
'groupchat:owner:editName': {
convoId: string
}
'groupchat:owner:lock': {
convoId: string
}
'groupchat:owner:unlock': {
convoId: string
}
'groupchat:owner:kickMember': {
convoId: string
}
'groupchat:owner:inviteMember': {
convoId: string
}
'groupchat:owner:joinRequest:accept': {
convoId: string
}
'groupchat:owner:joinRequest:reject': {
convoId: string
}
'groupchat:owner:inviteLink:create': {
convoId: string
}
'groupchat:owner:inviteLink:disable': {
convoId: string
}
// Group chat problems
'groupchat:join:memberLimitReached': {
convoId: string
}
'starterPack:addUser': { 'starterPack:addUser': {
starterPack?: string starterPack?: string
} }
@@ -1,3 +1,4 @@
import {useEffect} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import { import {
ChatBskyGroupDefs, ChatBskyGroupDefs,
@@ -10,6 +11,7 @@ import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {useCallOnce} from '#/lib/once'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {isNetworkError} from '#/lib/strings/errors' import {isNetworkError} from '#/lib/strings/errors'
@@ -45,6 +47,7 @@ import {InlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {ProfileBadges} from '../ProfileBadges' import {ProfileBadges} from '../ProfileBadges'
export function GroupChatJoinDialog() { export function GroupChatJoinDialog() {
@@ -83,6 +86,14 @@ function GroupChatJoinDialogContent({code}: {code?: string}) {
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const ax = useAnalytics()
const logViewOnce = useCallOnce()
useEffect(() => {
logViewOnce(() => {
ax.metric('groupchat:landingPage:view', {hasSession})
})
}, [ax, hasSession, logViewOnce])
const {data, error, isLoading} = useJoinLinkPreviewsQuery({ const {data, error, isLoading} = useJoinLinkPreviewsQuery({
codes: code ? [code] : undefined, codes: code ? [code] : undefined,
@@ -96,6 +107,7 @@ function GroupChatJoinDialogContent({code}: {code?: string}) {
if (code) void invalidateJoinLinkPreviewsForCode(queryClient, code) if (code) void invalidateJoinLinkPreviewsForCode(queryClient, code)
switch (data.status) { switch (data.status) {
case 'pending': case 'pending':
ax.metric('groupchat:inviteLink:redeem', {})
control.close(() => { control.close(() => {
Toast.show( Toast.show(
l`Access requested! The group owner will review your request.`, l`Access requested! The group owner will review your request.`,
@@ -104,6 +116,7 @@ function GroupChatJoinDialogContent({code}: {code?: string}) {
break break
case 'joined': { case 'joined': {
if (data.convo && data.convo.id) { if (data.convo && data.convo.id) {
ax.metric('groupchat:inviteLink:redeem', {})
control.close(() => { control.close(() => {
Toast.show(l`Successfully joined the group chat!`) Toast.show(l`Successfully joined the group chat!`)
navigation.navigate('MessagesConversation', { navigation.navigate('MessagesConversation', {
@@ -140,6 +153,15 @@ function GroupChatJoinDialogContent({code}: {code?: string}) {
error instanceof ChatBskyGroupRequestJoin.MemberLimitReachedError error instanceof ChatBskyGroupRequestJoin.MemberLimitReachedError
) { ) {
errorMessage = l`The member limit has been reached.` errorMessage = l`The member limit has been reached.`
const preview = data?.joinLinkPreviews[0]
if (
ChatBskyGroupDefs.isJoinLinkPreviewView(preview) &&
preview.convo?.id
) {
ax.metric('groupchat:join:memberLimitReached', {
convoId: preview.convo.id,
})
}
} else if (error instanceof ChatBskyGroupRequestJoin.UserKickedError) { } else if (error instanceof ChatBskyGroupRequestJoin.UserKickedError) {
errorMessage = l`You have been previously removed from this group and cant join it using this link.` errorMessage = l`You have been previously removed from this group and cant join it using this link.`
} }
@@ -15,6 +15,7 @@ import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
export function AddMembersLink({ export function AddMembersLink({
convo, convo,
@@ -25,6 +26,7 @@ export function AddMembersLink({
}) { }) {
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
const ax = useAnalytics()
const addMembersControl = Dialog.useDialogControl() const addMembersControl = Dialog.useDialogControl()
@@ -33,6 +35,7 @@ export function AddMembersLink({
convoId, convoId,
{ {
onSuccess: data => { onSuccess: data => {
ax.metric('groupchat:owner:inviteMember', {convoId})
addMembersControl.close(() => { addMembersControl.close(() => {
const members = data.addedMembers ?? [] const members = data.addedMembers ?? []
@@ -68,6 +68,9 @@ export function MemberMenu({
}) })
const convoId = convo.view.id const convoId = convo.view.id
const {mutate: removeMembers} = useRemoveFromGroupChat(convoId, { const {mutate: removeMembers} = useRemoveFromGroupChat(convoId, {
onSuccess: () => {
ax.metric('groupchat:owner:kickMember', {convoId})
},
onError: e => { onError: e => {
logger.error('Failed to remove group chat member', {message: e}) logger.error('Failed to remove group chat member', {message: e})
Toast.show(l`Failed to remove group chat member`, {type: 'error'}) Toast.show(l`Failed to remove group chat member`, {type: 'error'})
@@ -1,4 +1,4 @@
import {useState} from 'react' import {useEffect, useState} from 'react'
import {Pressable, View} from 'react-native' import {Pressable, View} from 'react-native'
import { import {
ChatBskyActorDefs, ChatBskyActorDefs,
@@ -13,6 +13,7 @@ import {HITSLOP_10} from '#/lib/constants'
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted'
import {useCallOnce} from '#/lib/once'
import { import {
type CommonNavigatorParams, type CommonNavigatorParams,
type NativeStackScreenProps, type NativeStackScreenProps,
@@ -56,6 +57,7 @@ import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
import {InviteLinkDialog} from '../components/InviteLinkDialog' import {InviteLinkDialog} from '../components/InviteLinkDialog'
@@ -337,9 +339,12 @@ function SettingsHeader({
}) { }) {
const t = useTheme() const t = useTheme()
const {i18n, t: l} = useLingui() const {i18n, t: l} = useLingui()
const ax = useAnalytics()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const convoId = convo.view.id
const groupName = convo.details.name const groupName = convo.details.name
const [newGroupName, setNewGroupName] = useState(groupName) const [newGroupName, setNewGroupName] = useState(groupName)
@@ -350,9 +355,17 @@ function SettingsHeader({
const reportSubjectDid = convo.primaryMember?.did const reportSubjectDid = convo.primaryMember?.did
const logViewOnce = useCallOnce()
useEffect(() => {
logViewOnce(() => {
ax.metric('groupchat:settings:view', {convoId, isOwner})
})
}, [ax, convoId, isOwner, logViewOnce])
const {mutate: editGroupName, isPending: isEditingName} = const {mutate: editGroupName, isPending: isEditingName} =
useEditGroupChatName(convo.view.id, { useEditGroupChatName(convoId, {
onSuccess: () => { onSuccess: () => {
ax.metric('groupchat:owner:editName', {convoId})
Toast.show(l({message: 'Group chat name updated', context: 'toast'})) Toast.show(l({message: 'Group chat name updated', context: 'toast'}))
}, },
onError: e => { onError: e => {
@@ -362,11 +375,13 @@ function SettingsHeader({
}, },
}) })
const {mutate: muteConvo, isPending: isMuting} = useMuteConvo(convo.view.id, { const {mutate: muteConvo, isPending: isMuting} = useMuteConvo(convoId, {
onSuccess: data => { onSuccess: data => {
if (data.convo.muted) { if (data.convo.muted) {
ax.metric('groupchat:mute', {convoId})
Toast.show(l({message: 'Group chat muted', context: 'toast'})) Toast.show(l({message: 'Group chat muted', context: 'toast'}))
} else { } else {
ax.metric('groupchat:unmute', {convoId})
Toast.show(l({message: 'Group chat unmuted', context: 'toast'})) Toast.show(l({message: 'Group chat unmuted', context: 'toast'}))
} }
}, },
@@ -376,33 +391,32 @@ function SettingsHeader({
}, },
}) })
const {mutate: leaveConvo, isPending: isLeaving} = useLeaveConvo( const {mutate: leaveConvo, isPending: isLeaving} = useLeaveConvo(convoId, {
convo.view.id, onSuccess: () => {
{ ax.metric('groupchat:leave', {convoId, isOwner})
onSuccess: () => { navigation.replace('Messages', {animation: 'pop'})
navigation.replace('Messages', {animation: 'pop'})
},
onError: e => {
logger.error('Failed to leave group chat', {message: e})
Toast.show(
l({message: 'Failed to leave group chat', context: 'toast'}),
{type: 'error'},
)
},
}, },
) onError: e => {
logger.error('Failed to leave group chat', {message: e})
Toast.show(l({message: 'Failed to leave group chat', context: 'toast'}), {
type: 'error',
})
},
})
const { const {
mutate: lockConvo, mutate: lockConvo,
mutateAsync: lockConvoAsync, mutateAsync: lockConvoAsync,
isPending: isLocking, isPending: isLocking,
} = useLockConvo(convo.view.id, { } = useLockConvo(convoId, {
onSuccess: (data, {silent}) => { onSuccess: (data, {silent}) => {
if (!ChatBskyConvoDefs.isGroupConvo(data.convo.kind)) return if (!ChatBskyConvoDefs.isGroupConvo(data.convo.kind)) return
if (silent) return if (silent) return
if (data.convo.kind.lockStatus === 'locked') { if (data.convo.kind.lockStatus === 'locked') {
ax.metric('groupchat:owner:lock', {convoId})
Toast.show(l({message: 'Group chat locked', context: 'toast'})) Toast.show(l({message: 'Group chat locked', context: 'toast'}))
} else { } else {
ax.metric('groupchat:owner:unlock', {convoId})
Toast.show(l({message: 'Group chat unlocked', context: 'toast'})) Toast.show(l({message: 'Group chat unlocked', context: 'toast'}))
} }
}, },
+7
View File
@@ -1,3 +1,4 @@
import {useEffect} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {ImageBackground} from 'expo-image' import {ImageBackground} from 'expo-image'
import {ChatBskyGroupDefs, moderateProfile} from '@atproto/api' import {ChatBskyGroupDefs, moderateProfile} from '@atproto/api'
@@ -18,6 +19,7 @@ import {ChainLinkBroken_Stroke2_Corner0_Rounded as ChainLinkBrokenIcon} from '#/
import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/components/icons/Person' import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/components/icons/Person'
import {ProfileBadges} from '#/components/ProfileBadges' import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
const desktopDarkBg = require('../../../assets/images/chat-desktop-bg-dark.webp') const desktopDarkBg = require('../../../assets/images/chat-desktop-bg-dark.webp')
const desktopDimBg = require('../../../assets/images/chat-desktop-bg-dim.webp') const desktopDimBg = require('../../../assets/images/chat-desktop-bg-dim.webp')
@@ -44,10 +46,15 @@ type Props = {
export function JoinRequest({setScreenState}: Props) { export function JoinRequest({setScreenState}: Props) {
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
const ax = useAnalytics()
const {gtMobile, gtTablet} = useBreakpoints() const {gtMobile, gtTablet} = useBreakpoints()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
useEffect(() => {
ax.metric('groupchat:landingPage:view', {hasSession: false})
}, [ax])
// Get code from context (logged-out only) // Get code from context (logged-out only)
const contextJoinRequest = useActiveGroupChatJoinRequest() const contextJoinRequest = useActiveGroupChatJoinRequest()
const code = contextJoinRequest?.code const code = contextJoinRequest?.code
+11 -5
View File
@@ -42,6 +42,7 @@ import {Loader} from '#/components/Loader'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {InviteLinkDialog} from './components/InviteLinkDialog' import {InviteLinkDialog} from './components/InviteLinkDialog'
@@ -129,6 +130,7 @@ function JoinRequestsList({
}) { }) {
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
const ax = useAnalytics()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const bottomBarOffset = useBottomBarOffset() const bottomBarOffset = useBottomBarOffset()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -136,10 +138,12 @@ function JoinRequestsList({
const queryClient = useQueryClient() const queryClient = useQueryClient()
const inviteLinkControl = Dialog.useDialogControl() const inviteLinkControl = Dialog.useDialogControl()
const convoId = convo.view.id
const getRemainingRequestCount = () => { const getRemainingRequestCount = () => {
const data = queryClient.getQueryData< const data = queryClient.getQueryData<
InfiniteData<ChatBskyGroupListJoinRequests.OutputSchema> InfiniteData<ChatBskyGroupListJoinRequests.OutputSchema>
>(createListJoinRequestsQueryKey({convoId: convo.view.id})) >(createListJoinRequestsQueryKey({convoId}))
return data?.pages.reduce((sum, page) => sum + page.requests.length, 0) ?? 0 return data?.pages.reduce((sum, page) => sum + page.requests.length, 0) ?? 0
} }
@@ -172,12 +176,13 @@ function JoinRequestsList({
) ?? 0 ) ?? 0
const {mutate: approveJoinRequest, isPending: isApprovePending} = const {mutate: approveJoinRequest, isPending: isApprovePending} =
useJoinRequestMutation('approve', convo.view.id, { useJoinRequestMutation('approve', convoId, {
onSuccess: () => { onSuccess: () => {
ax.metric('groupchat:owner:joinRequest:accept', {convoId})
Toast.show(l`Request approved.`) Toast.show(l`Request approved.`)
if (getRemainingRequestCount() < 1) { if (getRemainingRequestCount() < 1) {
navigation.replace('MessagesConversationSettings', { navigation.replace('MessagesConversationSettings', {
conversation: convo.view.id, conversation: convoId,
}) })
} }
}, },
@@ -204,12 +209,13 @@ function JoinRequestsList({
}) })
const {mutate: rejectJoinRequest, isPending: isRejectPending} = const {mutate: rejectJoinRequest, isPending: isRejectPending} =
useJoinRequestMutation('reject', convo.view.id, { useJoinRequestMutation('reject', convoId, {
onSuccess: () => { onSuccess: () => {
ax.metric('groupchat:owner:joinRequest:reject', {convoId})
Toast.show(l`Request ignored.`) Toast.show(l`Request ignored.`)
if (getRemainingRequestCount() < 1) { if (getRemainingRequestCount() < 1) {
navigation.replace('MessagesConversationSettings', { navigation.replace('MessagesConversationSettings', {
conversation: convo.view.id, conversation: convoId,
}) })
} }
}, },
@@ -35,6 +35,7 @@ import {EditBig_Stroke2_Corner2_Rounded as EditIcon} from '#/components/icons/Ed
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {CopyTextButton} from './CopyTextButton' import {CopyTextButton} from './CopyTextButton'
import {EditTextButton} from './EditTextButton' import {EditTextButton} from './EditTextButton'
@@ -63,6 +64,9 @@ export function InviteLinkDialog({
}) { }) {
const t = useTheme() const t = useTheme()
const {t: l, i18n} = useLingui() const {t: l, i18n} = useLingui()
const ax = useAnalytics()
const convoId = convo.view.id
const ownerName = createSanitizedDisplayName( const ownerName = createSanitizedDisplayName(
owner, owner,
@@ -92,9 +96,10 @@ export function InviteLinkDialog({
const {openComposer} = useOpenComposer() const {openComposer} = useOpenComposer()
const {mutate: createJoinLink, isPending: isCreating} = useCreateJoinLink( const {mutate: createJoinLink, isPending: isCreating} = useCreateJoinLink(
convo.view.id, convoId,
{ {
onSuccess: () => { onSuccess: () => {
ax.metric('groupchat:owner:inviteLink:create', {convoId})
setStep(Step.MANAGE) setStep(Step.MANAGE)
}, },
onError: () => { onError: () => {
@@ -105,7 +110,7 @@ export function InviteLinkDialog({
}, },
) )
const {mutate: editJoinLink, isPending: isEditing} = useEditJoinLink( const {mutate: editJoinLink, isPending: isEditing} = useEditJoinLink(
convo.view.id, convoId,
{ {
onSuccess: () => { onSuccess: () => {
setStep(Step.MANAGE) setStep(Step.MANAGE)
@@ -118,8 +123,11 @@ export function InviteLinkDialog({
}, },
) )
const {mutate: disableJoinLink, isPending: isDisabling} = useDisableJoinLink( const {mutate: disableJoinLink, isPending: isDisabling} = useDisableJoinLink(
convo.view.id, convoId,
{ {
onSuccess: () => {
ax.metric('groupchat:owner:inviteLink:disable', {convoId})
},
onError: () => { onError: () => {
Toast.show(l`Failed to disable invite link`, { Toast.show(l`Failed to disable invite link`, {
type: 'error', type: 'error',
@@ -128,7 +136,7 @@ export function InviteLinkDialog({
}, },
) )
const {mutate: enableJoinLink, isPending: isEnabling} = useEnableJoinLink( const {mutate: enableJoinLink, isPending: isEnabling} = useEnableJoinLink(
convo.view.id, convoId,
{ {
onError: () => { onError: () => {
Toast.show(l`Failed to enable invite link`, { Toast.show(l`Failed to enable invite link`, {
@@ -325,7 +333,13 @@ export function InviteLinkDialog({
<CopyTextButton <CopyTextButton
disabled={linkDisabled || !joinLink?.code} disabled={linkDisabled || !joinLink?.code}
label={l`Invite link`} label={l`Invite link`}
value={joinLinkURI}> value={joinLinkURI}
onPress={() => {
ax.metric('groupchat:inviteLink:shareButton:press', {
convoId,
method: 'copy',
})
}}>
<Text <Text
numberOfLines={1} numberOfLines={1}
style={[ style={[
@@ -385,6 +399,10 @@ export function InviteLinkDialog({
color="primary_subtle" color="primary_subtle"
style={[a.flex_1, a.rounded_full]} style={[a.flex_1, a.rounded_full]}
onPress={() => { onPress={() => {
ax.metric('groupchat:inviteLink:shareButton:press', {
convoId,
method: 'post',
})
control.close(() => { control.close(() => {
openComposer({ openComposer({
text: joinLinkURI, text: joinLinkURI,
@@ -401,6 +419,10 @@ export function InviteLinkDialog({
color="primary_subtle" color="primary_subtle"
style={[a.flex_1, a.rounded_full]} style={[a.flex_1, a.rounded_full]}
onPress={() => { onPress={() => {
ax.metric('groupchat:inviteLink:shareButton:press', {
convoId,
method: 'native',
})
void shareUrl(joinLinkURI) void shareUrl(joinLinkURI)
}}> }}>
<Trans>Share</Trans> <Trans>Share</Trans>
@@ -135,7 +135,7 @@ export function MessagesList({
const ax = useAnalytics() const ax = useAnalytics()
const convoState = useConvoActive() const convoState = useConvoActive()
const agent = useAgent() const agent = useAgent()
const {hasSession} = useSession() const {hasSession, currentAccount} = useSession()
const getPost = useGetPost() const getPost = useGetPost()
const getJoinLinkPreview = useGetJoinLinkPreview() const getJoinLinkPreview = useGetJoinLinkPreview()
const {embed: messageEmbed, setEmbed} = useMessageEmbed() const {embed: messageEmbed, setEmbed} = useMessageEmbed()
@@ -471,6 +471,13 @@ export function MessagesList({
}, },
embedView, embedView,
) )
if (convoState.convo.kind === 'group') {
ax.metric('groupchat:message:send', {
convoId: convoState.convo.view.id,
isOwner: convoState.convo.primaryMember?.did === currentAccount?.did,
})
}
}, },
[ [
agent, agent,
@@ -481,6 +488,8 @@ export function MessagesList({
hasSession, hasSession,
hasScrolled, hasScrolled,
setHasScrolled, setHasScrolled,
ax,
currentAccount?.did,
], ],
) )
@@ -16,6 +16,7 @@ import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/i
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {InviteLinkDialog} from './InviteLinkDialog' import {InviteLinkDialog} from './InviteLinkDialog'
export function MessagesListGroupInfoPanel({ export function MessagesListGroupInfoPanel({
@@ -25,6 +26,7 @@ export function MessagesListGroupInfoPanel({
}) { }) {
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
const ax = useAnalytics()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const convoId = convo.view.id const convoId = convo.view.id
@@ -35,6 +37,7 @@ export function MessagesListGroupInfoPanel({
const {mutate: addGroupMembers} = useAddGroupMembers(convoId, { const {mutate: addGroupMembers} = useAddGroupMembers(convoId, {
onSuccess: () => { onSuccess: () => {
ax.metric('groupchat:owner:inviteMember', {convoId})
addMembersControl.close() addMembersControl.close()
}, },
onError: e => { onError: e => {
+1
View File
@@ -1164,6 +1164,7 @@ export const ComposePost = ({
loadedDraftCreatedAt, loadedDraftCreatedAt,
emptyPostsPromptControl, emptyPostsPromptControl,
getFilteredThread, getFilteredThread,
linkQueries,
]) ])
const handleConfirmSkipEmpty = () => { const handleConfirmSkipEmpty = () => {