Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 676 lines 26 kB view raw
1import React, {memo} from 'react' 2import {type AppBskyActorDefs} from '@atproto/api' 3import {msg} from '@lingui/core/macro' 4import {useLingui} from '@lingui/react' 5import {Trans} from '@lingui/react/macro' 6import {useNavigation} from '@react-navigation/native' 7import {useQueryClient} from '@tanstack/react-query' 8 9import {HITSLOP_20} from '#/lib/constants' 10import {makeProfileLink} from '#/lib/routes/links' 11import {type NavigationProp} from '#/lib/routes/types' 12import {shareText, shareUrl} from '#/lib/sharing' 13import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' 14import {type Shadow} from '#/state/cache/types' 15import {useModalControls} from '#/state/modals' 16import { 17 useDeerVerificationEnabled, 18 useDeerVerificationTrusted, 19 useSetDeerVerificationTrust, 20} from '#/state/preferences/deer-verification' 21import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 22import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs' 23import { 24 RQKEY as profileQueryKey, 25 useProfileBlockMutationQueue, 26 useProfileFollowMutationQueue, 27 useProfileMuteMutationQueue, 28} from '#/state/queries/profile' 29import {useSession} from '#/state/session' 30import {EventStopper} from '#/view/com/util/EventStopper' 31import * as Toast from '#/view/com/util/Toast' 32import {atoms as a, useTheme} from '#/alf' 33import {Button, ButtonIcon} from '#/components/Button' 34import {useDialogControl} from '#/components/Dialog' 35import {StarterPackDialog} from '#/components/dialogs/StarterPackDialog' 36import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox' 37import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' 38import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheckIcon} from '#/components/icons/CircleCheck' 39import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX' 40import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' 41import {DotGrid3x1_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid' 42import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' 43import {ListSparkle_Stroke2_Corner0_Rounded as List} from '#/components/icons/ListSparkle' 44import {Live_Stroke2_Corner0_Rounded as LiveIcon} from '#/components/icons/Live' 45import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass' 46import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' 47import {PeopleRemove2_Stroke2_Corner0_Rounded as UserMinus} from '#/components/icons/PeopleRemove2' 48import { 49 PersonCheck_Stroke2_Corner0_Rounded as PersonCheck, 50 PersonX_Stroke2_Corner0_Rounded as PersonX, 51} from '#/components/icons/Person' 52import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 53import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' 54import {StarterPack} from '#/components/icons/StarterPack' 55import * as Menu from '#/components/Menu' 56import { 57 ReportDialog, 58 useReportDialogControl, 59} from '#/components/moderation/ReportDialog' 60import * as Prompt from '#/components/Prompt' 61import {useFullVerificationState} from '#/components/verification' 62import {VerificationCreatePrompt} from '#/components/verification/VerificationCreatePrompt' 63import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' 64import {useAnalytics} from '#/analytics' 65import {IS_WEB} from '#/env' 66import {useActorStatus, useLiveNowConfig} from '#/features/liveNow' 67import {EditLiveDialog} from '#/features/liveNow/components/EditLiveDialog' 68import {GoLiveDialog} from '#/features/liveNow/components/GoLiveDialog' 69import {GoLiveDisabledDialog} from '#/features/liveNow/components/GoLiveDisabledDialog' 70import {Dot} from '#/features/nuxs/components/Dot' 71import {Gradient} from '#/features/nuxs/components/Gradient' 72import {useDevMode} from '#/storage/hooks/dev-mode' 73 74let ProfileMenu = ({ 75 profile, 76}: { 77 profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> 78}): React.ReactNode => { 79 const t = useTheme() 80 const ax = useAnalytics() 81 const {_} = useLingui() 82 const {currentAccount, hasSession} = useSession() 83 const {openModal} = useModalControls() 84 const reportDialogControl = useReportDialogControl() 85 const queryClient = useQueryClient() 86 const navigation = useNavigation<NavigationProp>() 87 const isSelf = currentAccount?.did === profile.did 88 const isFollowedBy = profile.viewer?.followedBy 89 const isFollowing = profile.viewer?.following 90 const isBlocked = profile.viewer?.blocking || profile.viewer?.blockedBy 91 const isFollowingBlockedAccount = isFollowing && isBlocked 92 const isLabelerAndNotBlocked = !!profile.associated?.labeler && !isBlocked 93 const [devModeEnabled] = useDevMode() 94 const verification = useFullVerificationState({profile}) 95 const {canGoLive} = useLiveNowConfig() 96 const status = useActorStatus(profile) 97 const statusNudge = useNux(Nux.LiveNowBetaNudge) 98 const statusNudgeActive = 99 isSelf && 100 canGoLive && 101 statusNudge.status === 'ready' && 102 !statusNudge.nux?.completed 103 const {mutate: saveNux} = useSaveNux() 104 105 const deerVerificationEnabled = useDeerVerificationEnabled() 106 const deerVerificationTrusted = useDeerVerificationTrusted().has(profile.did) 107 const setDeerVerificationTrust = useSetDeerVerificationTrust() 108 109 const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile) 110 const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) 111 const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( 112 profile, 113 'ProfileMenu', 114 ) 115 116 const blockPromptControl = Prompt.usePromptControl() 117 const loggedOutWarningPromptControl = Prompt.usePromptControl() 118 const goLiveDialogControl = useDialogControl() 119 const goLiveDisabledDialogControl = useDialogControl() 120 const addToStarterPacksDialogControl = useDialogControl() 121 122 const showLoggedOutWarning = React.useMemo(() => { 123 return ( 124 profile.did !== currentAccount?.did && 125 !!profile.labels?.find(label => label.val === '!no-unauthenticated') 126 ) 127 }, [currentAccount, profile]) 128 129 const invalidateProfileQuery = React.useCallback(() => { 130 queryClient.invalidateQueries({ 131 queryKey: profileQueryKey(profile.did), 132 }) 133 }, [queryClient, profile.did]) 134 135 const onPressAddToStarterPacks = React.useCallback(() => { 136 ax.metric('profile:addToStarterPack', {}) 137 addToStarterPacksDialogControl.open() 138 }, [addToStarterPacksDialogControl]) 139 140 const onPressShare = React.useCallback(() => { 141 shareUrl(toShareUrl(makeProfileLink(profile))) 142 }, [profile]) 143 144 const onPressShareBsky = React.useCallback(() => { 145 shareUrl(toShareUrlBsky(makeProfileLink(profile))) 146 }, [profile]) 147 148 const onPressAddRemoveLists = React.useCallback(() => { 149 openModal({ 150 name: 'user-add-remove-lists', 151 subject: profile.did, 152 handle: profile.handle, 153 displayName: profile.displayName || profile.handle, 154 onAdd: invalidateProfileQuery, 155 onRemove: invalidateProfileQuery, 156 }) 157 }, [profile, openModal, invalidateProfileQuery]) 158 159 const onPressMuteAccount = React.useCallback(async () => { 160 if (profile.viewer?.muted) { 161 try { 162 await queueUnmute() 163 Toast.show(_(msg({message: 'Account unmuted', context: 'toast'}))) 164 } catch (e: any) { 165 if (e?.name !== 'AbortError') { 166 ax.logger.error('Failed to unmute account', {message: e}) 167 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 168 } 169 } 170 } else { 171 try { 172 await queueMute() 173 Toast.show(_(msg({message: 'Account muted', context: 'toast'}))) 174 } catch (e: any) { 175 if (e?.name !== 'AbortError') { 176 ax.logger.error('Failed to mute account', {message: e}) 177 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 178 } 179 } 180 } 181 }, [ax, profile.viewer?.muted, queueUnmute, _, queueMute]) 182 183 const blockAccount = React.useCallback(async () => { 184 if (profile.viewer?.blocking) { 185 try { 186 await queueUnblock() 187 Toast.show(_(msg({message: 'Account unblocked', context: 'toast'}))) 188 } catch (e: any) { 189 if (e?.name !== 'AbortError') { 190 ax.logger.error('Failed to unblock account', {message: e}) 191 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 192 } 193 } 194 } else { 195 try { 196 await queueBlock() 197 Toast.show(_(msg({message: 'Account blocked', context: 'toast'}))) 198 } catch (e: any) { 199 if (e?.name !== 'AbortError') { 200 ax.logger.error('Failed to block account', {message: e}) 201 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 202 } 203 } 204 } 205 }, [ax, profile.viewer?.blocking, _, queueUnblock, queueBlock]) 206 207 const onPressFollowAccount = React.useCallback(async () => { 208 try { 209 await queueFollow() 210 Toast.show(_(msg({message: 'Account followed', context: 'toast'}))) 211 } catch (e: any) { 212 if (e?.name !== 'AbortError') { 213 ax.logger.error('Failed to follow account', {message: e}) 214 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 215 } 216 } 217 }, [_, ax, queueFollow]) 218 219 const onPressUnfollowAccount = React.useCallback(async () => { 220 try { 221 await queueUnfollow() 222 Toast.show(_(msg({message: 'Account unfollowed', context: 'toast'}))) 223 } catch (e: any) { 224 if (e?.name !== 'AbortError') { 225 ax.logger.error('Failed to unfollow account', {message: e}) 226 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') 227 } 228 } 229 }, [_, ax, queueUnfollow]) 230 231 const onPressReportAccount = React.useCallback(() => { 232 reportDialogControl.open() 233 }, [reportDialogControl]) 234 235 const onPressShareATUri = React.useCallback(() => { 236 shareText(`at://${profile.did}`) 237 }, [profile.did]) 238 239 const onPressShareDID = React.useCallback(() => { 240 shareText(profile.did) 241 }, [profile.did]) 242 243 const onPressSearch = React.useCallback(() => { 244 navigation.navigate('ProfileSearch', {name: profile.handle}) 245 }, [navigation, profile.handle]) 246 247 const verificationCreatePromptControl = Prompt.usePromptControl() 248 const verificationRemovePromptControl = Prompt.usePromptControl() 249 const currentAccountVerifications = 250 profile.verification?.verifications?.filter(v => { 251 return v.issuer === currentAccount?.did 252 }) ?? [] 253 254 const enableSquareButtons = useEnableSquareButtons() 255 256 return ( 257 <EventStopper onKeyDown={false}> 258 <Menu.Root> 259 <Menu.Trigger label={_(msg`More options`)}> 260 {({props}) => { 261 return ( 262 <> 263 <Button 264 {...props} 265 testID="profileHeaderDropdownBtn" 266 label={_(msg`More options`)} 267 hitSlop={HITSLOP_20} 268 variant="solid" 269 color="secondary" 270 size="small" 271 shape={enableSquareButtons ? 'square' : 'round'}> 272 {statusNudgeActive && ( 273 <Gradient 274 style={[ 275 enableSquareButtons ? a.rounded_sm : a.rounded_full, 276 ]} 277 /> 278 )} 279 <ButtonIcon icon={Ellipsis} size="sm" /> 280 </Button> 281 282 {statusNudgeActive && <Dot top={1} right={1} />} 283 </> 284 ) 285 }} 286 </Menu.Trigger> 287 288 <Menu.Outer style={{minWidth: 170}}> 289 <Menu.Group> 290 <Menu.Item 291 testID="profileHeaderDropdownShareBtn" 292 label={ 293 IS_WEB ? _(msg`Copy link to profile`) : _(msg`Share via...`) 294 } 295 onPress={() => { 296 if (showLoggedOutWarning) { 297 loggedOutWarningPromptControl.open() 298 } else { 299 onPressShare() 300 } 301 }}> 302 <Menu.ItemText> 303 {IS_WEB ? ( 304 <Trans>Copy link to profile</Trans> 305 ) : ( 306 <Trans>Share via...</Trans> 307 )} 308 </Menu.ItemText> 309 <Menu.ItemIcon 310 icon={IS_WEB ? ChainLinkIcon : ArrowOutOfBoxIcon} 311 /> 312 </Menu.Item> 313 <Menu.Item 314 testID="profileHeaderDropdownShareBtn" 315 label={ 316 IS_WEB 317 ? _(msg`Copy via bsky.app`) 318 : _(msg`Share via bsky.app...`) 319 } 320 onPress={() => { 321 if (showLoggedOutWarning) { 322 loggedOutWarningPromptControl.open() 323 } else { 324 onPressShareBsky() 325 } 326 }}> 327 <Menu.ItemText> 328 {IS_WEB ? ( 329 <Trans>Copy via bsky.app</Trans> 330 ) : ( 331 <Trans>Share via bsky.app...</Trans> 332 )} 333 </Menu.ItemText> 334 <Menu.ItemIcon 335 icon={IS_WEB ? ChainLinkIcon : ArrowOutOfBoxIcon} 336 /> 337 </Menu.Item> 338 <Menu.Item 339 testID="profileHeaderDropdownSearchBtn" 340 label={_(msg`Search posts`)} 341 onPress={onPressSearch}> 342 <Menu.ItemText> 343 <Trans>Search posts</Trans> 344 </Menu.ItemText> 345 <Menu.ItemIcon icon={SearchIcon} /> 346 </Menu.Item> 347 </Menu.Group> 348 349 {hasSession && ( 350 <> 351 <Menu.Divider /> 352 <Menu.Group> 353 {!isSelf && ( 354 <> 355 {(isLabelerAndNotBlocked || isFollowingBlockedAccount) && ( 356 <Menu.Item 357 testID="profileHeaderDropdownFollowBtn" 358 label={ 359 isFollowing 360 ? isFollowedBy 361 ? _(msg`Divorce mutual`) 362 : _(msg`Unfollow account`) 363 : _(msg`Follow account`) 364 } 365 onPress={ 366 isFollowing 367 ? onPressUnfollowAccount 368 : onPressFollowAccount 369 }> 370 <Menu.ItemText> 371 {isFollowing ? ( 372 isFollowedBy ? ( 373 <Trans>Divorce mutual</Trans> 374 ) : ( 375 <Trans>Unfollow account</Trans> 376 ) 377 ) : ( 378 <Trans>Follow account</Trans> 379 )} 380 </Menu.ItemText> 381 <Menu.ItemIcon icon={isFollowing ? UserMinus : Plus} /> 382 </Menu.Item> 383 )} 384 </> 385 )} 386 {!isSelf && ( 387 <Menu.Item 388 testID="profileHeaderDropdownStarterPackAddRemoveBtn" 389 label={_(msg`Add to starter packs`)} 390 onPress={onPressAddToStarterPacks}> 391 <Menu.ItemText> 392 <Trans>Add to starter packs</Trans> 393 </Menu.ItemText> 394 <Menu.ItemIcon icon={StarterPack} /> 395 </Menu.Item> 396 )} 397 <Menu.Item 398 testID="profileHeaderDropdownListAddRemoveBtn" 399 label={_(msg`Add to lists`)} 400 onPress={onPressAddRemoveLists}> 401 <Menu.ItemText> 402 <Trans>Add to lists</Trans> 403 </Menu.ItemText> 404 <Menu.ItemIcon icon={List} /> 405 </Menu.Item> 406 {!isSelf && 407 deerVerificationEnabled && 408 (deerVerificationTrusted ? ( 409 <Menu.Item 410 testID="profileHeaderDropdownVerificationTrustRemoveButton" 411 label={_(msg`Remove trust`)} 412 onPress={() => 413 setDeerVerificationTrust.remove(profile.did) 414 }> 415 <Menu.ItemText> 416 <Trans>Remove trust</Trans> 417 </Menu.ItemText> 418 <Menu.ItemIcon icon={CircleXIcon} /> 419 </Menu.Item> 420 ) : ( 421 <Menu.Item 422 testID="profileHeaderDropdownVerificationTrustAddButton" 423 label={_(msg`Trust verifier`)} 424 onPress={() => setDeerVerificationTrust.add(profile.did)}> 425 <Menu.ItemText> 426 <Trans>Trust verifier</Trans> 427 </Menu.ItemText> 428 <Menu.ItemIcon icon={CircleCheckIcon} /> 429 </Menu.Item> 430 ))} 431 {isSelf && canGoLive && ( 432 <Menu.Item 433 testID="profileHeaderDropdownListAddRemoveBtn" 434 label={ 435 status.isDisabled 436 ? _(msg`Go live (disabled)`) 437 : status.isActive 438 ? _(msg`Edit live status`) 439 : _(msg`Go live`) 440 } 441 onPress={() => { 442 if (status.isDisabled) { 443 goLiveDisabledDialogControl.open() 444 } else { 445 goLiveDialogControl.open() 446 } 447 saveNux({ 448 id: Nux.LiveNowBetaNudge, 449 data: undefined, 450 completed: true, 451 }) 452 }}> 453 {statusNudgeActive && <Gradient />} 454 <Menu.ItemText> 455 {status.isDisabled ? ( 456 <Trans>Go live (disabled)</Trans> 457 ) : status.isActive ? ( 458 <Trans>Edit live status</Trans> 459 ) : ( 460 <Trans>Go live</Trans> 461 )} 462 </Menu.ItemText> 463 {statusNudgeActive && ( 464 <Menu.ItemText 465 style={[ 466 a.flex_0, 467 { 468 color: t.palette.primary_500, 469 right: IS_WEB ? -8 : -4, 470 }, 471 ]}> 472 <Trans>New</Trans> 473 </Menu.ItemText> 474 )} 475 <Menu.ItemIcon 476 icon={LiveIcon} 477 fill={ 478 statusNudgeActive 479 ? () => t.palette.primary_500 480 : undefined 481 } 482 /> 483 </Menu.Item> 484 )} 485 {verification.viewer.role === 'verifier' && 486 !verification.profile.isViewer && 487 (verification.viewer.hasIssuedVerification ? ( 488 <Menu.Item 489 testID="profileHeaderDropdownVerificationRemoveButton" 490 label={_(msg`Remove verification`)} 491 onPress={() => verificationRemovePromptControl.open()}> 492 <Menu.ItemText> 493 <Trans>Remove verification</Trans> 494 </Menu.ItemText> 495 <Menu.ItemIcon icon={CircleXIcon} /> 496 </Menu.Item> 497 ) : ( 498 <Menu.Item 499 testID="profileHeaderDropdownVerificationCreateButton" 500 label={_(msg`Verify account`)} 501 onPress={() => verificationCreatePromptControl.open()}> 502 <Menu.ItemText> 503 <Trans>Verify account</Trans> 504 </Menu.ItemText> 505 <Menu.ItemIcon icon={CircleCheckIcon} /> 506 </Menu.Item> 507 ))} 508 {!isSelf && ( 509 <> 510 {!profile.viewer?.blocking && 511 !profile.viewer?.mutedByList && ( 512 <Menu.Item 513 testID="profileHeaderDropdownMuteBtn" 514 label={ 515 profile.viewer?.muted 516 ? _(msg`Unmute account`) 517 : _(msg`Mute account`) 518 } 519 onPress={onPressMuteAccount}> 520 <Menu.ItemText> 521 {profile.viewer?.muted ? ( 522 <Trans>Unmute account</Trans> 523 ) : ( 524 <Trans>Mute account</Trans> 525 )} 526 </Menu.ItemText> 527 <Menu.ItemIcon 528 icon={profile.viewer?.muted ? Unmute : Mute} 529 /> 530 </Menu.Item> 531 )} 532 {!profile.viewer?.blockingByList && ( 533 <Menu.Item 534 testID="profileHeaderDropdownBlockBtn" 535 label={ 536 profile.viewer 537 ? _(msg`Unblock account`) 538 : _(msg`Block account`) 539 } 540 onPress={() => blockPromptControl.open()}> 541 <Menu.ItemText> 542 {profile.viewer?.blocking ? ( 543 <Trans>Unblock account</Trans> 544 ) : ( 545 <Trans>Block account</Trans> 546 )} 547 </Menu.ItemText> 548 <Menu.ItemIcon 549 icon={ 550 profile.viewer?.blocking ? PersonCheck : PersonX 551 } 552 /> 553 </Menu.Item> 554 )} 555 <Menu.Item 556 testID="profileHeaderDropdownReportBtn" 557 label={_(msg`Report account`)} 558 onPress={onPressReportAccount}> 559 <Menu.ItemText> 560 <Trans>Report account</Trans> 561 </Menu.ItemText> 562 <Menu.ItemIcon icon={Flag} /> 563 </Menu.Item> 564 </> 565 )} 566 </Menu.Group> 567 </> 568 )} 569 {devModeEnabled ? ( 570 <> 571 <Menu.Divider /> 572 <Menu.Group> 573 <Menu.Item 574 testID="profileHeaderDropdownShareATURIBtn" 575 label={_(msg`Copy at:// URI`)} 576 onPress={onPressShareATUri}> 577 <Menu.ItemText> 578 <Trans>Copy at:// URI</Trans> 579 </Menu.ItemText> 580 <Menu.ItemIcon icon={ClipboardIcon} /> 581 </Menu.Item> 582 <Menu.Item 583 testID="profileHeaderDropdownShareDIDBtn" 584 label={_(msg`Copy DID`)} 585 onPress={onPressShareDID}> 586 <Menu.ItemText> 587 <Trans>Copy DID</Trans> 588 </Menu.ItemText> 589 <Menu.ItemIcon icon={ClipboardIcon} /> 590 </Menu.Item> 591 </Menu.Group> 592 </> 593 ) : null} 594 </Menu.Outer> 595 </Menu.Root> 596 597 <StarterPackDialog 598 control={addToStarterPacksDialogControl} 599 targetDid={profile.did} 600 /> 601 602 <ReportDialog 603 control={reportDialogControl} 604 subject={{ 605 ...profile, 606 $type: 'app.bsky.actor.defs#profileViewDetailed', 607 }} 608 /> 609 610 <Prompt.Basic 611 control={blockPromptControl} 612 title={ 613 profile.viewer?.blocking 614 ? _(msg`Unblock Account?`) 615 : _(msg`Block Account?`) 616 } 617 description={ 618 profile.viewer?.blocking 619 ? _( 620 msg`The account will be able to interact with you after unblocking.`, 621 ) 622 : profile.associated?.labeler 623 ? _( 624 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.`, 625 ) 626 : _( 627 msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, 628 ) 629 } 630 onConfirm={blockAccount} 631 confirmButtonCta={ 632 profile.viewer?.blocking ? _(msg`Unblock`) : _(msg`Block`) 633 } 634 confirmButtonColor={profile.viewer?.blocking ? undefined : 'negative'} 635 /> 636 637 <Prompt.Basic 638 control={loggedOutWarningPromptControl} 639 title={_(msg`Note about sharing`)} 640 description={_( 641 msg`This profile is only visible to logged-in users. It won't be visible to people who aren't signed in.`, 642 )} 643 onConfirm={onPressShare} 644 confirmButtonCta={_(msg`Share anyway`)} 645 /> 646 647 <VerificationCreatePrompt 648 control={verificationCreatePromptControl} 649 profile={profile} 650 /> 651 <VerificationRemovePrompt 652 control={verificationRemovePromptControl} 653 profile={profile} 654 verifications={currentAccountVerifications} 655 /> 656 657 {status.isDisabled ? ( 658 <GoLiveDisabledDialog 659 control={goLiveDisabledDialogControl} 660 status={status} 661 /> 662 ) : status.isActive ? ( 663 <EditLiveDialog 664 control={goLiveDialogControl} 665 status={status} 666 embed={status.embed} 667 /> 668 ) : ( 669 <GoLiveDialog control={goLiveDialogControl} profile={profile} /> 670 )} 671 </EventStopper> 672 ) 673} 674 675ProfileMenu = memo(ProfileMenu) 676export {ProfileMenu}