mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at static-click 360 lines 13 kB view raw
1import React, {memo} from 'react' 2import {AppBskyActorDefs} from '@atproto/api' 3import {msg, Trans} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5import {useQueryClient} from '@tanstack/react-query' 6 7import {HITSLOP_20} from '#/lib/constants' 8import {makeProfileLink} from '#/lib/routes/links' 9import {shareUrl} from '#/lib/sharing' 10import {toShareUrl} from '#/lib/strings/url-helpers' 11import {logger} from '#/logger' 12import {Shadow} from '#/state/cache/types' 13import {useModalControls} from '#/state/modals' 14import { 15 RQKEY as profileQueryKey, 16 useProfileBlockMutationQueue, 17 useProfileFollowMutationQueue, 18 useProfileMuteMutationQueue, 19} from '#/state/queries/profile' 20import {useSession} from '#/state/session' 21import {EventStopper} from '#/view/com/util/EventStopper' 22import * as Toast from '#/view/com/util/Toast' 23import {Button, ButtonIcon} from '#/components/Button' 24import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' 25import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid' 26import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' 27import {ListSparkle_Stroke2_Corner0_Rounded as List} from '#/components/icons/ListSparkle' 28import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' 29import {PeopleRemove2_Stroke2_Corner0_Rounded as UserMinus} from '#/components/icons/PeopleRemove2' 30import { 31 PersonCheck_Stroke2_Corner0_Rounded as PersonCheck, 32 PersonX_Stroke2_Corner0_Rounded as PersonX, 33} from '#/components/icons/Person' 34import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 35import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' 36import * as Menu from '#/components/Menu' 37import * as Prompt from '#/components/Prompt' 38import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' 39 40let ProfileMenu = ({ 41 profile, 42}: { 43 profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> 44}): React.ReactNode => { 45 const {_} = useLingui() 46 const {currentAccount, hasSession} = useSession() 47 const {openModal} = useModalControls() 48 const reportDialogControl = useReportDialogControl() 49 const queryClient = useQueryClient() 50 const isSelf = currentAccount?.did === profile.did 51 const isFollowing = profile.viewer?.following 52 const isBlocked = profile.viewer?.blocking || profile.viewer?.blockedBy 53 const isFollowingBlockedAccount = isFollowing && isBlocked 54 const isLabelerAndNotBlocked = !!profile.associated?.labeler && !isBlocked 55 56 const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile) 57 const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) 58 const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( 59 profile, 60 'ProfileMenu', 61 ) 62 63 const blockPromptControl = Prompt.usePromptControl() 64 const loggedOutWarningPromptControl = Prompt.usePromptControl() 65 66 const showLoggedOutWarning = React.useMemo(() => { 67 return ( 68 profile.did !== currentAccount?.did && 69 !!profile.labels?.find(label => label.val === '!no-unauthenticated') 70 ) 71 }, [currentAccount, profile]) 72 73 const invalidateProfileQuery = React.useCallback(() => { 74 queryClient.invalidateQueries({ 75 queryKey: profileQueryKey(profile.did), 76 }) 77 }, [queryClient, profile.did]) 78 79 const onPressShare = React.useCallback(() => { 80 shareUrl(toShareUrl(makeProfileLink(profile))) 81 }, [profile]) 82 83 const onPressAddRemoveLists = React.useCallback(() => { 84 openModal({ 85 name: 'user-add-remove-lists', 86 subject: profile.did, 87 handle: profile.handle, 88 displayName: profile.displayName || profile.handle, 89 onAdd: invalidateProfileQuery, 90 onRemove: invalidateProfileQuery, 91 }) 92 }, [profile, openModal, invalidateProfileQuery]) 93 94 const onPressMuteAccount = React.useCallback(async () => { 95 if (profile.viewer?.muted) { 96 try { 97 await queueUnmute() 98 Toast.show(_(msg`Account unmuted`)) 99 } catch (e: any) { 100 if (e?.name !== 'AbortError') { 101 logger.error('Failed to unmute account', {message: e}) 102 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 103 } 104 } 105 } else { 106 try { 107 await queueMute() 108 Toast.show(_(msg`Account muted`)) 109 } catch (e: any) { 110 if (e?.name !== 'AbortError') { 111 logger.error('Failed to mute account', {message: e}) 112 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 113 } 114 } 115 } 116 }, [profile.viewer?.muted, queueUnmute, _, queueMute]) 117 118 const blockAccount = React.useCallback(async () => { 119 if (profile.viewer?.blocking) { 120 try { 121 await queueUnblock() 122 Toast.show(_(msg`Account unblocked`)) 123 } catch (e: any) { 124 if (e?.name !== 'AbortError') { 125 logger.error('Failed to unblock account', {message: e}) 126 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 127 } 128 } 129 } else { 130 try { 131 await queueBlock() 132 Toast.show(_(msg`Account blocked`)) 133 } catch (e: any) { 134 if (e?.name !== 'AbortError') { 135 logger.error('Failed to block account', {message: e}) 136 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 137 } 138 } 139 } 140 }, [profile.viewer?.blocking, _, queueUnblock, queueBlock]) 141 142 const onPressFollowAccount = React.useCallback(async () => { 143 try { 144 await queueFollow() 145 Toast.show(_(msg`Account followed`)) 146 } catch (e: any) { 147 if (e?.name !== 'AbortError') { 148 logger.error('Failed to follow account', {message: e}) 149 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 150 } 151 } 152 }, [_, queueFollow]) 153 154 const onPressUnfollowAccount = React.useCallback(async () => { 155 try { 156 await queueUnfollow() 157 Toast.show(_(msg`Account unfollowed`)) 158 } catch (e: any) { 159 if (e?.name !== 'AbortError') { 160 logger.error('Failed to unfollow account', {message: e}) 161 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 162 } 163 } 164 }, [_, queueUnfollow]) 165 166 const onPressReportAccount = React.useCallback(() => { 167 reportDialogControl.open() 168 }, [reportDialogControl]) 169 170 return ( 171 <EventStopper onKeyDown={false}> 172 <Menu.Root> 173 <Menu.Trigger label={_(`More options`)}> 174 {({props}) => { 175 return ( 176 <Button 177 {...props} 178 testID="profileHeaderDropdownBtn" 179 label={_(msg`More options`)} 180 hitSlop={HITSLOP_20} 181 variant="solid" 182 color="secondary" 183 size="small" 184 shape="round"> 185 <ButtonIcon icon={Ellipsis} size="sm" /> 186 </Button> 187 ) 188 }} 189 </Menu.Trigger> 190 191 <Menu.Outer style={{minWidth: 170}}> 192 <Menu.Group> 193 <Menu.Item 194 testID="profileHeaderDropdownShareBtn" 195 label={_(msg`Share`)} 196 onPress={() => { 197 if (showLoggedOutWarning) { 198 loggedOutWarningPromptControl.open() 199 } else { 200 onPressShare() 201 } 202 }}> 203 <Menu.ItemText> 204 <Trans>Share</Trans> 205 </Menu.ItemText> 206 <Menu.ItemIcon icon={Share} /> 207 </Menu.Item> 208 </Menu.Group> 209 210 {hasSession && ( 211 <> 212 <Menu.Divider /> 213 <Menu.Group> 214 {!isSelf && ( 215 <> 216 {(isLabelerAndNotBlocked || isFollowingBlockedAccount) && ( 217 <Menu.Item 218 testID="profileHeaderDropdownFollowBtn" 219 label={ 220 isFollowing 221 ? _(msg`Unfollow Account`) 222 : _(msg`Follow Account`) 223 } 224 onPress={ 225 isFollowing 226 ? onPressUnfollowAccount 227 : onPressFollowAccount 228 }> 229 <Menu.ItemText> 230 {isFollowing ? ( 231 <Trans>Unfollow Account</Trans> 232 ) : ( 233 <Trans>Follow Account</Trans> 234 )} 235 </Menu.ItemText> 236 <Menu.ItemIcon icon={isFollowing ? UserMinus : Plus} /> 237 </Menu.Item> 238 )} 239 </> 240 )} 241 <Menu.Item 242 testID="profileHeaderDropdownListAddRemoveBtn" 243 label={_(msg`Add to Lists`)} 244 onPress={onPressAddRemoveLists}> 245 <Menu.ItemText> 246 <Trans>Add to Lists</Trans> 247 </Menu.ItemText> 248 <Menu.ItemIcon icon={List} /> 249 </Menu.Item> 250 {!isSelf && ( 251 <> 252 {!profile.viewer?.blocking && 253 !profile.viewer?.mutedByList && ( 254 <Menu.Item 255 testID="profileHeaderDropdownMuteBtn" 256 label={ 257 profile.viewer?.muted 258 ? _(msg`Unmute Account`) 259 : _(msg`Mute Account`) 260 } 261 onPress={onPressMuteAccount}> 262 <Menu.ItemText> 263 {profile.viewer?.muted ? ( 264 <Trans>Unmute Account</Trans> 265 ) : ( 266 <Trans>Mute Account</Trans> 267 )} 268 </Menu.ItemText> 269 <Menu.ItemIcon 270 icon={profile.viewer?.muted ? Unmute : Mute} 271 /> 272 </Menu.Item> 273 )} 274 {!profile.viewer?.blockingByList && ( 275 <Menu.Item 276 testID="profileHeaderDropdownBlockBtn" 277 label={ 278 profile.viewer 279 ? _(msg`Unblock Account`) 280 : _(msg`Block Account`) 281 } 282 onPress={() => blockPromptControl.open()}> 283 <Menu.ItemText> 284 {profile.viewer?.blocking ? ( 285 <Trans>Unblock Account</Trans> 286 ) : ( 287 <Trans>Block Account</Trans> 288 )} 289 </Menu.ItemText> 290 <Menu.ItemIcon 291 icon={ 292 profile.viewer?.blocking ? PersonCheck : PersonX 293 } 294 /> 295 </Menu.Item> 296 )} 297 <Menu.Item 298 testID="profileHeaderDropdownReportBtn" 299 label={_(msg`Report Account`)} 300 onPress={onPressReportAccount}> 301 <Menu.ItemText> 302 <Trans>Report Account</Trans> 303 </Menu.ItemText> 304 <Menu.ItemIcon icon={Flag} /> 305 </Menu.Item> 306 </> 307 )} 308 </Menu.Group> 309 </> 310 )} 311 </Menu.Outer> 312 </Menu.Root> 313 314 <ReportDialog 315 control={reportDialogControl} 316 params={{type: 'account', did: profile.did}} 317 /> 318 319 <Prompt.Basic 320 control={blockPromptControl} 321 title={ 322 profile.viewer?.blocking 323 ? _(msg`Unblock Account?`) 324 : _(msg`Block Account?`) 325 } 326 description={ 327 profile.viewer?.blocking 328 ? _( 329 msg`The account will be able to interact with you after unblocking.`, 330 ) 331 : profile.associated?.labeler 332 ? _( 333 msg`Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you.`, 334 ) 335 : _( 336 msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, 337 ) 338 } 339 onConfirm={blockAccount} 340 confirmButtonCta={ 341 profile.viewer?.blocking ? _(msg`Unblock`) : _(msg`Block`) 342 } 343 confirmButtonColor={profile.viewer?.blocking ? undefined : 'negative'} 344 /> 345 346 <Prompt.Basic 347 control={loggedOutWarningPromptControl} 348 title={_(msg`Note about sharing`)} 349 description={_( 350 msg`This profile is only visible to logged-in users. It won't be visible to people who aren't logged in.`, 351 )} 352 onConfirm={onPressShare} 353 confirmButtonCta={_(msg`Share anyway`)} 354 /> 355 </EventStopper> 356 ) 357} 358 359ProfileMenu = memo(ProfileMenu) 360export {ProfileMenu}