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