Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
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}