Bluesky app fork with some witchin' additions 馃挮
at readme-update 674 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 {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 {useCanGoLive} from '#/state/service-config' 30import {useSession} from '#/state/session' 31import {EventStopper} from '#/view/com/util/EventStopper' 32import * as Toast from '#/view/com/util/Toast' 33import {atoms as a, useTheme} from '#/alf' 34import {Button, ButtonIcon} from '#/components/Button' 35import {useDialogControl} from '#/components/Dialog' 36import {StarterPackDialog} from '#/components/dialogs/StarterPackDialog' 37import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox' 38import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' 39import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheckIcon} from '#/components/icons/CircleCheck' 40import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX' 41import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' 42import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid' 43import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' 44import {ListSparkle_Stroke2_Corner0_Rounded as List} from '#/components/icons/ListSparkle' 45import {Live_Stroke2_Corner0_Rounded as LiveIcon} from '#/components/icons/Live' 46import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass' 47import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' 48import {PeopleRemove2_Stroke2_Corner0_Rounded as UserMinus} from '#/components/icons/PeopleRemove2' 49import { 50 PersonCheck_Stroke2_Corner0_Rounded as PersonCheck, 51 PersonX_Stroke2_Corner0_Rounded as PersonX, 52} from '#/components/icons/Person' 53import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 54import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' 55import {StarterPack} from '#/components/icons/StarterPack' 56import {EditLiveDialog} from '#/components/live/EditLiveDialog' 57import {GoLiveDialog} from '#/components/live/GoLiveDialog' 58import {GoLiveDisabledDialog} from '#/components/live/GoLiveDisabledDialog' 59import * as Menu from '#/components/Menu' 60import { 61 ReportDialog, 62 useReportDialogControl, 63} from '#/components/moderation/ReportDialog' 64import * as Prompt from '#/components/Prompt' 65import {useFullVerificationState} from '#/components/verification' 66import {VerificationCreatePrompt} from '#/components/verification/VerificationCreatePrompt' 67import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' 68import {useAnalytics} from '#/analytics' 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 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 = useCanGoLive() 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 skeets`)} 341 onPress={onPressSearch}> 342 <Menu.ItemText> 343 <Trans>Search skeets</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 <Menu.Item 387 testID="profileHeaderDropdownStarterPackAddRemoveBtn" 388 label={_(msg`Add to starter packs`)} 389 onPress={onPressAddToStarterPacks}> 390 <Menu.ItemText> 391 <Trans>Add to starter packs</Trans> 392 </Menu.ItemText> 393 <Menu.ItemIcon icon={StarterPack} /> 394 </Menu.Item> 395 <Menu.Item 396 testID="profileHeaderDropdownListAddRemoveBtn" 397 label={_(msg`Add to lists`)} 398 onPress={onPressAddRemoveLists}> 399 <Menu.ItemText> 400 <Trans>Add to lists</Trans> 401 </Menu.ItemText> 402 <Menu.ItemIcon icon={List} /> 403 </Menu.Item> 404 {!isSelf && 405 deerVerificationEnabled && 406 (deerVerificationTrusted ? ( 407 <Menu.Item 408 testID="profileHeaderDropdownVerificationTrustRemoveButton" 409 label={_(msg`Remove trust`)} 410 onPress={() => 411 setDeerVerificationTrust.remove(profile.did) 412 }> 413 <Menu.ItemText> 414 <Trans>Remove trust</Trans> 415 </Menu.ItemText> 416 <Menu.ItemIcon icon={CircleXIcon} /> 417 </Menu.Item> 418 ) : ( 419 <Menu.Item 420 testID="profileHeaderDropdownVerificationTrustAddButton" 421 label={_(msg`Trust verifier`)} 422 onPress={() => setDeerVerificationTrust.add(profile.did)}> 423 <Menu.ItemText> 424 <Trans>Trust verifier</Trans> 425 </Menu.ItemText> 426 <Menu.ItemIcon icon={CircleCheckIcon} /> 427 </Menu.Item> 428 ))} 429 {isSelf && canGoLive && ( 430 <Menu.Item 431 testID="profileHeaderDropdownListAddRemoveBtn" 432 label={ 433 status.isDisabled 434 ? _(msg`Go live (disabled)`) 435 : status.isActive 436 ? _(msg`Edit live status`) 437 : _(msg`Go live`) 438 } 439 onPress={() => { 440 if (status.isDisabled) { 441 goLiveDisabledDialogControl.open() 442 } else { 443 goLiveDialogControl.open() 444 } 445 saveNux({ 446 id: Nux.LiveNowBetaNudge, 447 data: undefined, 448 completed: true, 449 }) 450 }}> 451 {statusNudgeActive && <Gradient />} 452 <Menu.ItemText> 453 {status.isDisabled ? ( 454 <Trans>Go live (disabled)</Trans> 455 ) : status.isActive ? ( 456 <Trans>Edit live status</Trans> 457 ) : ( 458 <Trans>Go live</Trans> 459 )} 460 </Menu.ItemText> 461 {statusNudgeActive && ( 462 <Menu.ItemText 463 style={[ 464 a.flex_0, 465 { 466 color: t.palette.primary_500, 467 right: IS_WEB ? -8 : -4, 468 }, 469 ]}> 470 <Trans>New</Trans> 471 </Menu.ItemText> 472 )} 473 <Menu.ItemIcon 474 icon={LiveIcon} 475 fill={ 476 statusNudgeActive 477 ? () => t.palette.primary_500 478 : undefined 479 } 480 /> 481 </Menu.Item> 482 )} 483 {verification.viewer.role === 'verifier' && 484 !verification.profile.isViewer && 485 (verification.viewer.hasIssuedVerification ? ( 486 <Menu.Item 487 testID="profileHeaderDropdownVerificationRemoveButton" 488 label={_(msg`Remove verification`)} 489 onPress={() => verificationRemovePromptControl.open()}> 490 <Menu.ItemText> 491 <Trans>Remove verification</Trans> 492 </Menu.ItemText> 493 <Menu.ItemIcon icon={CircleXIcon} /> 494 </Menu.Item> 495 ) : ( 496 <Menu.Item 497 testID="profileHeaderDropdownVerificationCreateButton" 498 label={_(msg`Verify account`)} 499 onPress={() => verificationCreatePromptControl.open()}> 500 <Menu.ItemText> 501 <Trans>Verify account</Trans> 502 </Menu.ItemText> 503 <Menu.ItemIcon icon={CircleCheckIcon} /> 504 </Menu.Item> 505 ))} 506 {!isSelf && ( 507 <> 508 {!profile.viewer?.blocking && 509 !profile.viewer?.mutedByList && ( 510 <Menu.Item 511 testID="profileHeaderDropdownMuteBtn" 512 label={ 513 profile.viewer?.muted 514 ? _(msg`Unmute account`) 515 : _(msg`Mute account`) 516 } 517 onPress={onPressMuteAccount}> 518 <Menu.ItemText> 519 {profile.viewer?.muted ? ( 520 <Trans>Unmute account</Trans> 521 ) : ( 522 <Trans>Mute account</Trans> 523 )} 524 </Menu.ItemText> 525 <Menu.ItemIcon 526 icon={profile.viewer?.muted ? Unmute : Mute} 527 /> 528 </Menu.Item> 529 )} 530 {!profile.viewer?.blockingByList && ( 531 <Menu.Item 532 testID="profileHeaderDropdownBlockBtn" 533 label={ 534 profile.viewer 535 ? _(msg`Unblock account`) 536 : _(msg`Block account`) 537 } 538 onPress={() => blockPromptControl.open()}> 539 <Menu.ItemText> 540 {profile.viewer?.blocking ? ( 541 <Trans>Unblock account</Trans> 542 ) : ( 543 <Trans>Block account</Trans> 544 )} 545 </Menu.ItemText> 546 <Menu.ItemIcon 547 icon={ 548 profile.viewer?.blocking ? PersonCheck : PersonX 549 } 550 /> 551 </Menu.Item> 552 )} 553 <Menu.Item 554 testID="profileHeaderDropdownReportBtn" 555 label={_(msg`Report account`)} 556 onPress={onPressReportAccount}> 557 <Menu.ItemText> 558 <Trans>Report account</Trans> 559 </Menu.ItemText> 560 <Menu.ItemIcon icon={Flag} /> 561 </Menu.Item> 562 </> 563 )} 564 </Menu.Group> 565 </> 566 )} 567 {devModeEnabled ? ( 568 <> 569 <Menu.Divider /> 570 <Menu.Group> 571 <Menu.Item 572 testID="profileHeaderDropdownShareATURIBtn" 573 label={_(msg`Copy at:// URI`)} 574 onPress={onPressShareATUri}> 575 <Menu.ItemText> 576 <Trans>Copy at:// URI</Trans> 577 </Menu.ItemText> 578 <Menu.ItemIcon icon={ClipboardIcon} /> 579 </Menu.Item> 580 <Menu.Item 581 testID="profileHeaderDropdownShareDIDBtn" 582 label={_(msg`Copy DID`)} 583 onPress={onPressShareDID}> 584 <Menu.ItemText> 585 <Trans>Copy DID</Trans> 586 </Menu.ItemText> 587 <Menu.ItemIcon icon={ClipboardIcon} /> 588 </Menu.Item> 589 </Menu.Group> 590 </> 591 ) : null} 592 </Menu.Outer> 593 </Menu.Root> 594 595 <StarterPackDialog 596 control={addToStarterPacksDialogControl} 597 targetDid={profile.did} 598 /> 599 600 <ReportDialog 601 control={reportDialogControl} 602 subject={{ 603 ...profile, 604 $type: 'app.bsky.actor.defs#profileViewDetailed', 605 }} 606 /> 607 608 <Prompt.Basic 609 control={blockPromptControl} 610 title={ 611 profile.viewer?.blocking 612 ? _(msg`Unblock Account?`) 613 : _(msg`Block Account?`) 614 } 615 description={ 616 profile.viewer?.blocking 617 ? _( 618 msg`The account will be able to interact with you after unblocking.`, 619 ) 620 : profile.associated?.labeler 621 ? _( 622 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.`, 623 ) 624 : _( 625 msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, 626 ) 627 } 628 onConfirm={blockAccount} 629 confirmButtonCta={ 630 profile.viewer?.blocking ? _(msg`Unblock`) : _(msg`Block`) 631 } 632 confirmButtonColor={profile.viewer?.blocking ? undefined : 'negative'} 633 /> 634 635 <Prompt.Basic 636 control={loggedOutWarningPromptControl} 637 title={_(msg`Note about sharing`)} 638 description={_( 639 msg`This profile is only visible to logged-in users. It won't be visible to people who aren't signed in.`, 640 )} 641 onConfirm={onPressShare} 642 confirmButtonCta={_(msg`Share anyway`)} 643 /> 644 645 <VerificationCreatePrompt 646 control={verificationCreatePromptControl} 647 profile={profile} 648 /> 649 <VerificationRemovePrompt 650 control={verificationRemovePromptControl} 651 profile={profile} 652 verifications={currentAccountVerifications} 653 /> 654 655 {status.isDisabled ? ( 656 <GoLiveDisabledDialog 657 control={goLiveDisabledDialogControl} 658 status={status} 659 /> 660 ) : status.isActive ? ( 661 <EditLiveDialog 662 control={goLiveDialogControl} 663 status={status} 664 embed={status.embed} 665 /> 666 ) : ( 667 <GoLiveDialog control={goLiveDialogControl} profile={profile} /> 668 )} 669 </EventStopper> 670 ) 671} 672 673ProfileMenu = memo(ProfileMenu) 674export {ProfileMenu}