about summary refs log tree commit diff
path: root/src/screens/ProfileList/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens/ProfileList/components')
-rw-r--r--src/screens/ProfileList/components/ErrorScreen.tsx46
-rw-r--r--src/screens/ProfileList/components/Header.tsx208
-rw-r--r--src/screens/ProfileList/components/MoreOptionsMenu.tsx298
-rw-r--r--src/screens/ProfileList/components/SubscribeMenu.tsx130
4 files changed, 682 insertions, 0 deletions
diff --git a/src/screens/ProfileList/components/ErrorScreen.tsx b/src/screens/ProfileList/components/ErrorScreen.tsx
new file mode 100644
index 000000000..7ce343def
--- /dev/null
+++ b/src/screens/ProfileList/components/ErrorScreen.tsx
@@ -0,0 +1,46 @@
+import {View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useNavigation} from '@react-navigation/native'
+
+import {type NavigationProp} from '#/lib/routes/types'
+import {atoms as a, useTheme} from '#/alf'
+import {Button, ButtonText} from '#/components/Button'
+import {Text} from '#/components/Typography'
+
+export function ErrorScreen({error}: {error: React.ReactNode}) {
+  const t = useTheme()
+  const navigation = useNavigation<NavigationProp>()
+  const {_} = useLingui()
+  const onPressBack = () => {
+    if (navigation.canGoBack()) {
+      navigation.goBack()
+    } else {
+      navigation.navigate('Home')
+    }
+  }
+
+  return (
+    <View style={[a.px_xl, a.py_md, a.gap_md]}>
+      <Text style={[a.text_4xl, a.font_heavy]}>
+        <Trans>Could not load list</Trans>
+      </Text>
+      <Text style={[a.text_md, t.atoms.text_contrast_high, a.leading_snug]}>
+        {error}
+      </Text>
+
+      <View style={[a.flex_row, a.mt_lg]}>
+        <Button
+          label={_(msg`Go back`)}
+          accessibilityHint={_(msg`Returns to previous page`)}
+          onPress={onPressBack}
+          size="small"
+          color="secondary">
+          <ButtonText>
+            <Trans>Go back</Trans>
+          </ButtonText>
+        </Button>
+      </View>
+    </View>
+  )
+}
diff --git a/src/screens/ProfileList/components/Header.tsx b/src/screens/ProfileList/components/Header.tsx
new file mode 100644
index 000000000..fe4b33c75
--- /dev/null
+++ b/src/screens/ProfileList/components/Header.tsx
@@ -0,0 +1,208 @@
+import {useMemo} from 'react'
+import {View} from 'react-native'
+import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {useHaptics} from '#/lib/haptics'
+import {makeListLink} from '#/lib/routes/links'
+import {logger} from '#/logger'
+import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list'
+import {
+  useAddSavedFeedsMutation,
+  type UsePreferencesQueryResponse,
+  useUpdateSavedFeedsMutation,
+} from '#/state/queries/preferences'
+import {useSession} from '#/state/session'
+import {ProfileSubpageHeader} from '#/view/com/profile/ProfileSubpageHeader'
+import {atoms as a} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin'
+import {Loader} from '#/components/Loader'
+import {RichText} from '#/components/RichText'
+import * as Toast from '#/components/Toast'
+import {MoreOptionsMenu} from './MoreOptionsMenu'
+import {SubscribeMenu} from './SubscribeMenu'
+
+export function Header({
+  rkey,
+  list,
+  preferences,
+}: {
+  rkey: string
+  list: AppBskyGraphDefs.ListView
+  preferences: UsePreferencesQueryResponse
+}) {
+  const {_} = useLingui()
+  const {currentAccount} = useSession()
+  const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST
+  const isModList = list.purpose === AppBskyGraphDefs.MODLIST
+  const isBlocking = !!list.viewer?.blocked
+  const isMuting = !!list.viewer?.muted
+  const playHaptic = useHaptics()
+
+  const {mutateAsync: muteList, isPending: isMutePending} =
+    useListMuteMutation()
+  const {mutateAsync: blockList, isPending: isBlockPending} =
+    useListBlockMutation()
+  const {mutateAsync: addSavedFeeds, isPending: isAddSavedFeedPending} =
+    useAddSavedFeedsMutation()
+  const {mutateAsync: updateSavedFeeds, isPending: isUpdatingSavedFeeds} =
+    useUpdateSavedFeedsMutation()
+
+  const isPending = isAddSavedFeedPending || isUpdatingSavedFeeds
+
+  const savedFeedConfig = preferences?.savedFeeds?.find(
+    f => f.value === list.uri,
+  )
+  const isPinned = Boolean(savedFeedConfig?.pinned)
+
+  const onTogglePinned = async () => {
+    playHaptic()
+
+    try {
+      if (savedFeedConfig) {
+        const pinned = !savedFeedConfig.pinned
+        await updateSavedFeeds([
+          {
+            ...savedFeedConfig,
+            pinned,
+          },
+        ])
+        Toast.show(
+          pinned
+            ? _(msg`Pinned to your feeds`)
+            : _(msg`Unpinned from your feeds`),
+        )
+      } else {
+        await addSavedFeeds([
+          {
+            type: 'list',
+            value: list.uri,
+            pinned: true,
+          },
+        ])
+        Toast.show(_(msg`Saved to your feeds`))
+      }
+    } catch (e) {
+      Toast.show(_(msg`There was an issue contacting the server`), {
+        type: 'error',
+      })
+      logger.error('Failed to toggle pinned feed', {message: e})
+    }
+  }
+
+  const onUnsubscribeMute = async () => {
+    try {
+      await muteList({uri: list.uri, mute: false})
+      Toast.show(_(msg({message: 'List unmuted', context: 'toast'})))
+      logger.metric(
+        'moderation:unsubscribedFromList',
+        {listType: 'mute'},
+        {statsig: true},
+      )
+    } catch {
+      Toast.show(
+        _(
+          msg`There was an issue. Please check your internet connection and try again.`,
+        ),
+      )
+    }
+  }
+
+  const onUnsubscribeBlock = async () => {
+    try {
+      await blockList({uri: list.uri, block: false})
+      Toast.show(_(msg({message: 'List unblocked', context: 'toast'})))
+      logger.metric(
+        'moderation:unsubscribedFromList',
+        {listType: 'block'},
+        {statsig: true},
+      )
+    } catch {
+      Toast.show(
+        _(
+          msg`There was an issue. Please check your internet connection and try again.`,
+        ),
+      )
+    }
+  }
+
+  const descriptionRT = useMemo(
+    () =>
+      list.description
+        ? new RichTextAPI({
+            text: list.description,
+            facets: list.descriptionFacets,
+          })
+        : undefined,
+    [list],
+  )
+
+  return (
+    <>
+      <ProfileSubpageHeader
+        href={makeListLink(list.creator.handle || list.creator.did || '', rkey)}
+        title={list.name}
+        avatar={list.avatar}
+        isOwner={list.creator.did === currentAccount?.did}
+        creator={list.creator}
+        purpose={list.purpose}
+        avatarType="list">
+        {isCurateList ? (
+          <Button
+            testID={isPinned ? 'unpinBtn' : 'pinBtn'}
+            color={isPinned ? 'secondary' : 'primary_subtle'}
+            label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)}
+            onPress={onTogglePinned}
+            disabled={isPending}
+            size="small"
+            style={[a.rounded_full]}>
+            {!isPinned && <ButtonIcon icon={isPending ? Loader : PinIcon} />}
+            <ButtonText>
+              {isPinned ? <Trans>Unpin</Trans> : <Trans>Pin to home</Trans>}
+            </ButtonText>
+          </Button>
+        ) : isModList ? (
+          isBlocking ? (
+            <Button
+              testID="unblockBtn"
+              color="secondary"
+              label={_(msg`Unblock`)}
+              onPress={onUnsubscribeBlock}
+              size="small"
+              style={[a.rounded_full]}
+              disabled={isBlockPending}>
+              {isBlockPending && <ButtonIcon icon={Loader} />}
+              <ButtonText>
+                <Trans>Unblock</Trans>
+              </ButtonText>
+            </Button>
+          ) : isMuting ? (
+            <Button
+              testID="unmuteBtn"
+              color="secondary"
+              label={_(msg`Unmute`)}
+              onPress={onUnsubscribeMute}
+              size="small"
+              style={[a.rounded_full]}
+              disabled={isMutePending}>
+              {isMutePending && <ButtonIcon icon={Loader} />}
+              <ButtonText>
+                <Trans>Unmute</Trans>
+              </ButtonText>
+            </Button>
+          ) : (
+            <SubscribeMenu list={list} />
+          )
+        ) : null}
+        <MoreOptionsMenu list={list} />
+      </ProfileSubpageHeader>
+      {descriptionRT ? (
+        <View style={[a.px_lg, a.pt_sm, a.pb_sm, a.gap_md]}>
+          <RichText value={descriptionRT} style={[a.text_md, a.leading_snug]} />
+        </View>
+      ) : null}
+    </>
+  )
+}
diff --git a/src/screens/ProfileList/components/MoreOptionsMenu.tsx b/src/screens/ProfileList/components/MoreOptionsMenu.tsx
new file mode 100644
index 000000000..17ca43a82
--- /dev/null
+++ b/src/screens/ProfileList/components/MoreOptionsMenu.tsx
@@ -0,0 +1,298 @@
+import {type AppBskyActorDefs, AppBskyGraphDefs, AtUri} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useNavigation} from '@react-navigation/native'
+
+import {type NavigationProp} from '#/lib/routes/types'
+import {shareUrl} from '#/lib/sharing'
+import {toShareUrl} from '#/lib/strings/url-helpers'
+import {logger} from '#/logger'
+import {isWeb} from '#/platform/detection'
+import {useModalControls} from '#/state/modals'
+import {
+  useListBlockMutation,
+  useListDeleteMutation,
+  useListMuteMutation,
+} from '#/state/queries/list'
+import {useRemoveFeedMutation} from '#/state/queries/preferences'
+import {useSession} from '#/state/session'
+import {Button, ButtonIcon} from '#/components/Button'
+import {useDialogControl} from '#/components/Dialog'
+import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ShareIcon} from '#/components/icons/ArrowOutOfBox'
+import {ChainLink_Stroke2_Corner0_Rounded as ChainLink} from '#/components/icons/ChainLink'
+import {DotGrid_Stroke2_Corner0_Rounded as DotGridIcon} from '#/components/icons/DotGrid'
+import {PencilLine_Stroke2_Corner0_Rounded as PencilLineIcon} from '#/components/icons/Pencil'
+import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheckIcon} from '#/components/icons/Person'
+import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin'
+import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
+import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
+import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
+import * as Menu from '#/components/Menu'
+import {
+  ReportDialog,
+  useReportDialogControl,
+} from '#/components/moderation/ReportDialog'
+import * as Prompt from '#/components/Prompt'
+import * as Toast from '#/components/Toast'
+
+export function MoreOptionsMenu({
+  list,
+  savedFeedConfig,
+}: {
+  list: AppBskyGraphDefs.ListView
+  savedFeedConfig?: AppBskyActorDefs.SavedFeed
+}) {
+  const {_} = useLingui()
+  const {currentAccount} = useSession()
+  const {openModal} = useModalControls()
+  const deleteListPromptControl = useDialogControl()
+  const reportDialogControl = useReportDialogControl()
+  const navigation = useNavigation<NavigationProp>()
+
+  const {mutateAsync: removeSavedFeed} = useRemoveFeedMutation()
+  const {mutateAsync: deleteList} = useListDeleteMutation()
+  const {mutateAsync: muteList} = useListMuteMutation()
+  const {mutateAsync: blockList} = useListBlockMutation()
+
+  const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST
+  const isModList = list.purpose === AppBskyGraphDefs.MODLIST
+  const isBlocking = !!list.viewer?.blocked
+  const isMuting = !!list.viewer?.muted
+  const isPinned = Boolean(savedFeedConfig?.pinned)
+  const isOwner = currentAccount?.did === list.creator.did
+
+  const onPressShare = () => {
+    const {rkey} = new AtUri(list.uri)
+    const url = toShareUrl(`/profile/${list.creator.did}/lists/${rkey}`)
+    shareUrl(url)
+  }
+
+  const onRemoveFromSavedFeeds = async () => {
+    if (!savedFeedConfig) return
+    try {
+      await removeSavedFeed(savedFeedConfig)
+      Toast.show(_(msg`Removed from your feeds`))
+    } catch (e) {
+      Toast.show(_(msg`There was an issue contacting the server`), {
+        type: 'error',
+      })
+      logger.error('Failed to remove pinned list', {message: e})
+    }
+  }
+
+  const onPressEdit = () => {
+    openModal({
+      name: 'create-or-edit-list',
+      list,
+    })
+  }
+
+  const onPressDelete = async () => {
+    await deleteList({uri: list.uri})
+
+    if (savedFeedConfig) {
+      await removeSavedFeed(savedFeedConfig)
+    }
+
+    Toast.show(_(msg({message: 'List deleted', context: 'toast'})))
+    if (navigation.canGoBack()) {
+      navigation.goBack()
+    } else {
+      navigation.navigate('Home')
+    }
+  }
+
+  const onUnpinModList = async () => {
+    try {
+      if (!savedFeedConfig) return
+      await removeSavedFeed(savedFeedConfig)
+      Toast.show(_(msg`Unpinned list`))
+    } catch {
+      Toast.show(_(msg`Failed to unpin list`), {
+        type: 'error',
+      })
+    }
+  }
+
+  const onUnsubscribeMute = async () => {
+    try {
+      await muteList({uri: list.uri, mute: false})
+      Toast.show(_(msg({message: 'List unmuted', context: 'toast'})))
+      logger.metric(
+        'moderation:unsubscribedFromList',
+        {listType: 'mute'},
+        {statsig: true},
+      )
+    } catch {
+      Toast.show(
+        _(
+          msg`There was an issue. Please check your internet connection and try again.`,
+        ),
+      )
+    }
+  }
+
+  const onUnsubscribeBlock = async () => {
+    try {
+      await blockList({uri: list.uri, block: false})
+      Toast.show(_(msg({message: 'List unblocked', context: 'toast'})))
+      logger.metric(
+        'moderation:unsubscribedFromList',
+        {listType: 'block'},
+        {statsig: true},
+      )
+    } catch {
+      Toast.show(
+        _(
+          msg`There was an issue. Please check your internet connection and try again.`,
+        ),
+      )
+    }
+  }
+
+  return (
+    <>
+      <Menu.Root>
+        <Menu.Trigger label={_(msg`More options`)}>
+          {({props}) => (
+            <Button
+              label={props.accessibilityLabel}
+              testID="moreOptionsBtn"
+              size="small"
+              color="secondary"
+              shape="round"
+              {...props}>
+              <ButtonIcon icon={DotGridIcon} />
+            </Button>
+          )}
+        </Menu.Trigger>
+        <Menu.Outer>
+          <Menu.Group>
+            <Menu.Item
+              label={isWeb ? _(msg`Copy link to list`) : _(msg`Share via...`)}
+              onPress={onPressShare}>
+              <Menu.ItemText>
+                {isWeb ? (
+                  <Trans>Copy link to list</Trans>
+                ) : (
+                  <Trans>Share via...</Trans>
+                )}
+              </Menu.ItemText>
+              <Menu.ItemIcon
+                position="right"
+                icon={isWeb ? ChainLink : ShareIcon}
+              />
+            </Menu.Item>
+            {savedFeedConfig && (
+              <Menu.Item
+                label={_(msg`Remove from my feeds`)}
+                onPress={onRemoveFromSavedFeeds}>
+                <Menu.ItemText>
+                  <Trans>Remove from my feeds</Trans>
+                </Menu.ItemText>
+                <Menu.ItemIcon position="right" icon={TrashIcon} />
+              </Menu.Item>
+            )}
+          </Menu.Group>
+
+          <Menu.Divider />
+
+          {isOwner ? (
+            <Menu.Group>
+              <Menu.Item
+                label={_(msg`Edit list details`)}
+                onPress={onPressEdit}>
+                <Menu.ItemText>
+                  <Trans>Edit list details</Trans>
+                </Menu.ItemText>
+                <Menu.ItemIcon position="right" icon={PencilLineIcon} />
+              </Menu.Item>
+              <Menu.Item
+                label={_(msg`Delete list`)}
+                onPress={deleteListPromptControl.open}>
+                <Menu.ItemText>
+                  <Trans>Delete list</Trans>
+                </Menu.ItemText>
+                <Menu.ItemIcon position="right" icon={TrashIcon} />
+              </Menu.Item>
+            </Menu.Group>
+          ) : (
+            <Menu.Group>
+              <Menu.Item
+                label={_(msg`Report list`)}
+                onPress={reportDialogControl.open}>
+                <Menu.ItemText>
+                  <Trans>Report list</Trans>
+                </Menu.ItemText>
+                <Menu.ItemIcon position="right" icon={WarningIcon} />
+              </Menu.Item>
+            </Menu.Group>
+          )}
+
+          {isModList && isPinned && (
+            <>
+              <Menu.Divider />
+              <Menu.Group>
+                <Menu.Item
+                  label={_(msg`Unpin moderation list`)}
+                  onPress={onUnpinModList}>
+                  <Menu.ItemText>
+                    <Trans>Unpin moderation list</Trans>
+                  </Menu.ItemText>
+                  <Menu.ItemIcon icon={PinIcon} />
+                </Menu.Item>
+              </Menu.Group>
+            </>
+          )}
+
+          {isCurateList && (isBlocking || isMuting) && (
+            <>
+              <Menu.Divider />
+              <Menu.Group>
+                {isBlocking && (
+                  <Menu.Item
+                    label={_(msg`Unblock list`)}
+                    onPress={onUnsubscribeBlock}>
+                    <Menu.ItemText>
+                      <Trans>Unblock list</Trans>
+                    </Menu.ItemText>
+                    <Menu.ItemIcon icon={PersonCheckIcon} />
+                  </Menu.Item>
+                )}
+                {isMuting && (
+                  <Menu.Item
+                    label={_(msg`Unmute list`)}
+                    onPress={onUnsubscribeMute}>
+                    <Menu.ItemText>
+                      <Trans>Unmute list</Trans>
+                    </Menu.ItemText>
+                    <Menu.ItemIcon icon={UnmuteIcon} />
+                  </Menu.Item>
+                )}
+              </Menu.Group>
+            </>
+          )}
+        </Menu.Outer>
+      </Menu.Root>
+
+      <Prompt.Basic
+        control={deleteListPromptControl}
+        title={_(msg`Delete this list?`)}
+        description={_(
+          msg`If you delete this list, you won't be able to recover it.`,
+        )}
+        onConfirm={onPressDelete}
+        confirmButtonCta={_(msg`Delete`)}
+        confirmButtonColor="negative"
+      />
+
+      <ReportDialog
+        control={reportDialogControl}
+        subject={{
+          ...list,
+          $type: 'app.bsky.graph.defs#listView',
+        }}
+      />
+    </>
+  )
+}
diff --git a/src/screens/ProfileList/components/SubscribeMenu.tsx b/src/screens/ProfileList/components/SubscribeMenu.tsx
new file mode 100644
index 000000000..5b6b9ba09
--- /dev/null
+++ b/src/screens/ProfileList/components/SubscribeMenu.tsx
@@ -0,0 +1,130 @@
+import {type AppBskyGraphDefs} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {logger} from '#/logger'
+import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list'
+import {atoms as a} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
+import {PersonX_Stroke2_Corner0_Rounded as PersonXIcon} from '#/components/icons/Person'
+import {Loader} from '#/components/Loader'
+import * as Menu from '#/components/Menu'
+import * as Prompt from '#/components/Prompt'
+import * as Toast from '#/components/Toast'
+
+export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) {
+  const {_} = useLingui()
+  const subscribeMutePromptControl = Prompt.usePromptControl()
+  const subscribeBlockPromptControl = Prompt.usePromptControl()
+
+  const {mutateAsync: muteList, isPending: isMutePending} =
+    useListMuteMutation()
+  const {mutateAsync: blockList, isPending: isBlockPending} =
+    useListBlockMutation()
+
+  const isPending = isMutePending || isBlockPending
+
+  const onSubscribeMute = async () => {
+    try {
+      await muteList({uri: list.uri, mute: true})
+      Toast.show(_(msg({message: 'List muted', context: 'toast'})))
+      logger.metric(
+        'moderation:subscribedToList',
+        {listType: 'mute'},
+        {statsig: true},
+      )
+    } catch {
+      Toast.show(
+        _(
+          msg`There was an issue. Please check your internet connection and try again.`,
+        ),
+        {type: 'error'},
+      )
+    }
+  }
+
+  const onSubscribeBlock = async () => {
+    try {
+      await blockList({uri: list.uri, block: true})
+      Toast.show(_(msg({message: 'List blocked', context: 'toast'})))
+      logger.metric(
+        'moderation:subscribedToList',
+        {listType: 'block'},
+        {statsig: true},
+      )
+    } catch {
+      Toast.show(
+        _(
+          msg`There was an issue. Please check your internet connection and try again.`,
+        ),
+        {type: 'error'},
+      )
+    }
+  }
+
+  return (
+    <>
+      <Menu.Root>
+        <Menu.Trigger label={_(msg`Subscribe to this list`)}>
+          {({props}) => (
+            <Button
+              label={props.accessibilityLabel}
+              testID="subscribeBtn"
+              size="small"
+              color="primary_subtle"
+              style={[a.rounded_full]}
+              disabled={isPending}
+              {...props}>
+              {isPending && <ButtonIcon icon={Loader} />}
+              <ButtonText>
+                <Trans>Subscribe</Trans>
+              </ButtonText>
+            </Button>
+          )}
+        </Menu.Trigger>
+        <Menu.Outer showCancel>
+          <Menu.Group>
+            <Menu.Item
+              label={_(msg`Mute accounts`)}
+              onPress={subscribeMutePromptControl.open}>
+              <Menu.ItemText>
+                <Trans>Mute accounts</Trans>
+              </Menu.ItemText>
+              <Menu.ItemIcon position="right" icon={MuteIcon} />
+            </Menu.Item>
+            <Menu.Item
+              label={_(msg`Block accounts`)}
+              onPress={subscribeBlockPromptControl.open}>
+              <Menu.ItemText>
+                <Trans>Block accounts</Trans>
+              </Menu.ItemText>
+              <Menu.ItemIcon position="right" icon={PersonXIcon} />
+            </Menu.Item>
+          </Menu.Group>
+        </Menu.Outer>
+      </Menu.Root>
+
+      <Prompt.Basic
+        control={subscribeMutePromptControl}
+        title={_(msg`Mute these accounts?`)}
+        description={_(
+          msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`,
+        )}
+        onConfirm={onSubscribeMute}
+        confirmButtonCta={_(msg`Mute list`)}
+      />
+
+      <Prompt.Basic
+        control={subscribeBlockPromptControl}
+        title={_(msg`Block these accounts?`)}
+        description={_(
+          msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`,
+        )}
+        onConfirm={onSubscribeBlock}
+        confirmButtonCta={_(msg`Block list`)}
+        confirmButtonColor="negative"
+      />
+    </>
+  )
+}