diff --git a/src/screens/Messages/ConversationSettings.tsx b/src/screens/Messages/ConversationSettings.tsx index 001b032a41..0b3bfa8b8e 100644 --- a/src/screens/Messages/ConversationSettings.tsx +++ b/src/screens/Messages/ConversationSettings.tsx @@ -1,5 +1,5 @@ import {useMemo, useState} from 'react' -import {Pressable, View} from 'react-native' +import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' import {moderateProfile} from '@atproto/api' import {plural} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro' @@ -7,10 +7,13 @@ import {useNavigation} from '@react-navigation/native' import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' +import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {type NavigationProp} from '#/lib/routes/types' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {useGetConvoAvailabilityQuery} from '#/state/queries/messages/get-convo-availability' +import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members' import {List} from '#/view/com/util/List' import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' @@ -39,9 +42,12 @@ import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/ import * as Layout from '#/components/Layout' import {InlineLinkText} from '#/components/Link' import * as Menu from '#/components/Menu' +import {type TriggerChildProps} from '#/components/Menu/types' import * as Prompt from '#/components/Prompt' import {SubtleHover} from '#/components/SubtleHover' +import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' import type * as bsky from '#/types/bsky' @@ -135,7 +141,7 @@ function SettingsInner() { desktopFixedHeight initialNumToRender={initialNumToRender} keyExtractor={keyExtractor} - ListHeaderComponent={} + ListHeaderComponent={} renderItem={renderItem} sideBorders={false} windowSize={11} @@ -277,7 +283,9 @@ function Member({ {l`Added by ${invitedByDisplayName}`} ) - let statusBadge: React.ReactNode | null = + let statusBadge: React.ReactNode | null = ( + + ) switch (status) { case 'admin': invitedBy = null @@ -289,7 +297,7 @@ function Member({ {l`Invited by ${invitedByDisplayName}`} ) - statusBadge = + statusBadge = break } @@ -367,10 +375,84 @@ function StatusBadge({label}: {label: string}) { ) } -function MemberMenu({profile}: {profile: bsky.profile.AnyProfileView}) { +function StatusButton({ + label, + style, + ...rest +}: { + label: string + style?: StyleProp +} & TriggerChildProps['props']) { + const t = useTheme() + + return ( + + + {label} + + + ) +} + +function MemberMenu({ + profile, + type, +}: { + profile: bsky.profile.AnyProfileView + type: 'member' | 'invited' +}) { const navigation = useNavigation() const t = useTheme() const {t: l} = useLingui() + const ax = useAnalytics() + const requireEmailVerification = useRequireEmailVerification() + + const {data: convoAvailability} = useGetConvoAvailabilityQuery(profile.did) + const {mutate: initiateConvo} = useGetConvoForMembers({ + onSuccess: ({convo}) => { + ax.metric('chat:open', {logContext: 'ProfileHeader'}) + navigation.navigate('MessagesConversation', {conversation: convo.id}) + }, + onError: () => { + Toast.show(l`Failed to create conversation`) + }, + }) + + const onPress = () => { + if (!convoAvailability?.canChat) { + return + } + + if (convoAvailability.convo) { + ax.metric('chat:open', {logContext: 'ProfileHeader'}) + navigation.navigate('MessagesConversation', { + conversation: convoAvailability.convo.id, + }) + } else { + ax.metric('chat:create', {logContext: 'ProfileHeader'}) + initiateConvo([profile.did]) + } + } + + const wrappedOnPress = requireEmailVerification(onPress, { + instructions: [ + + Before you can message another user, you must first verify your email. + , + ], + }) const moderationOpts = useModerationOpts() const moderation = useMemo( @@ -392,21 +474,35 @@ function MemberMenu({profile}: {profile: bsky.profile.AnyProfileView}) { return ( - {({props, state}) => ( - - - - )} + {({props, state}) => + type === 'invited' ? ( + + ) : ( + + + + ) + } @@ -420,7 +516,7 @@ function MemberMenu({profile}: {profile: bsky.profile.AnyProfileView}) { - {}}> + Message @@ -429,27 +525,41 @@ function MemberMenu({profile}: {profile: bsky.profile.AnyProfileView}) { - {}}> - - Block - - - - {}}> - - Remove from chat - - - + {type === 'member' ? ( + <> + {}}> + + Block + + + + {}}> + + Remove from chat + + + + + ) : null} + {type === 'invited' ? ( + {}}> + + Uninvite + + + + ) : null} ) } -function SettingsHeading() { +function SettingsHeader({profiles}: {profiles: bsky.profile.AnyProfileView[]}) { const t = useTheme() const {t: l} = useLingui() const editNamePrompt = Prompt.usePromptControl() @@ -457,7 +567,7 @@ function SettingsHeading() { const lockChatPrompt = Prompt.usePromptControl() const [groupName, setGroupName] = useState('Work in Progress') - const [newGroupName, setNewGroupName] = useState('Work in Progress') + const [newGroupName, setNewGroupName] = useState(groupName) const [isMuted, setIsMuted] = useState(false) const [isLocked, setIsLocked] = useState(false) @@ -500,7 +610,7 @@ function SettingsHeading() { - +