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