forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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}