about summary refs log tree commit diff
path: root/src/view/com/feeds/MissingFeed.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/feeds/MissingFeed.tsx')
-rw-r--r--src/view/com/feeds/MissingFeed.tsx222
1 files changed, 222 insertions, 0 deletions
diff --git a/src/view/com/feeds/MissingFeed.tsx b/src/view/com/feeds/MissingFeed.tsx
new file mode 100644
index 000000000..3d281a731
--- /dev/null
+++ b/src/view/com/feeds/MissingFeed.tsx
@@ -0,0 +1,222 @@
+import {type StyleProp, View, type ViewStyle} from 'react-native'
+import {AtUri} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {cleanError} from '#/lib/strings/errors'
+import {isNative, isWeb} from '#/platform/detection'
+import {useModerationOpts} from '#/state/preferences/moderation-opts'
+import {getFeedTypeFromUri} from '#/state/queries/feed'
+import {useProfileQuery} from '#/state/queries/profile'
+import {atoms as a, useTheme, web} from '#/alf'
+import {Button, ButtonText} from '#/components/Button'
+import * as Dialog from '#/components/Dialog'
+import {Divider} from '#/components/Divider'
+import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
+import * as ProfileCard from '#/components/ProfileCard'
+import {Text} from '#/components/Typography'
+
+export function MissingFeed({
+  style,
+  hideTopBorder,
+  uri,
+  error,
+}: {
+  style?: StyleProp<ViewStyle>
+  hideTopBorder?: boolean
+  uri: string
+  error?: unknown
+}) {
+  const t = useTheme()
+  const {_} = useLingui()
+  const control = Dialog.useDialogControl()
+
+  const type = getFeedTypeFromUri(uri)
+
+  return (
+    <>
+      <Button
+        label={
+          type === 'feed'
+            ? _(msg`Could not connect to custom feed`)
+            : _(msg`Deleted list`)
+        }
+        accessibilityHint={_(msg`Tap for more information`)}
+        onPress={control.open}
+        style={[
+          a.flex_1,
+          a.p_lg,
+          a.gap_md,
+          !hideTopBorder && !a.border_t,
+          t.atoms.border_contrast_low,
+          a.justify_start,
+          style,
+        ]}>
+        <View style={[a.flex_row, a.align_center]}>
+          <View
+            style={[
+              {width: 36, height: 36},
+              t.atoms.bg_contrast_25,
+              a.rounded_sm,
+              a.mr_md,
+              a.align_center,
+              a.justify_center,
+            ]}>
+            <WarningIcon size="lg" />
+          </View>
+          <View style={[a.flex_1]}>
+            <Text
+              emoji
+              style={[a.text_sm, a.font_bold, a.leading_snug, a.italic]}
+              numberOfLines={1}>
+              {type === 'feed' ? (
+                <Trans>Feed unavailable</Trans>
+              ) : (
+                <Trans>Deleted list</Trans>
+              )}
+            </Text>
+            <Text
+              style={[
+                a.text_sm,
+                t.atoms.text_contrast_medium,
+                a.leading_snug,
+                a.italic,
+              ]}
+              numberOfLines={1}>
+              {isWeb ? (
+                <Trans>Click for information</Trans>
+              ) : (
+                <Trans>Tap for information</Trans>
+              )}
+            </Text>
+          </View>
+        </View>
+      </Button>
+
+      <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
+        <Dialog.Handle />
+        <DialogInner uri={uri} type={type} error={error} />
+      </Dialog.Outer>
+    </>
+  )
+}
+
+function DialogInner({
+  uri,
+  type,
+  error,
+}: {
+  uri: string
+  type: 'feed' | 'list'
+  error: unknown
+}) {
+  const control = Dialog.useDialogContext()
+  const t = useTheme()
+  const {_} = useLingui()
+  const atUri = new AtUri(uri)
+  const {data: profile, isError: isProfileError} = useProfileQuery({
+    did: atUri.host,
+  })
+  const moderationOpts = useModerationOpts()
+
+  return (
+    <Dialog.ScrollableInner
+      label={
+        type === 'feed'
+          ? _(msg`Unavailable feed information`)
+          : _(msg`Deleted list`)
+      }
+      style={web({maxWidth: 500})}>
+      <View style={[a.gap_sm]}>
+        <Text style={[a.font_heavy, a.text_2xl]}>
+          {type === 'feed' ? (
+            <Trans>Could not connect to feed service</Trans>
+          ) : (
+            <Trans>Deleted list</Trans>
+          )}
+        </Text>
+        <Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
+          {type === 'feed' ? (
+            <Trans>
+              We could not connect to the service that provides this custom
+              feed. It may be temporarily unavailable and experiencing issues,
+              or permanently unavailable.
+            </Trans>
+          ) : (
+            <Trans>We could not find this list. It was probably deleted.</Trans>
+          )}
+        </Text>
+        <Divider style={[a.my_md]} />
+        <Text style={[a.font_bold, t.atoms.text_contrast_high]}>
+          {type === 'feed' ? (
+            <Trans>Feed creator</Trans>
+          ) : (
+            <Trans>List creator</Trans>
+          )}
+        </Text>
+        {profile && moderationOpts && (
+          <View style={[a.w_full, a.align_start]}>
+            <ProfileCard.Link profile={profile} onPress={() => control.close()}>
+              <ProfileCard.Header>
+                <ProfileCard.Avatar
+                  profile={profile}
+                  moderationOpts={moderationOpts}
+                  disabledPreview
+                />
+                <ProfileCard.NameAndHandle
+                  profile={profile}
+                  moderationOpts={moderationOpts}
+                />
+              </ProfileCard.Header>
+            </ProfileCard.Link>
+          </View>
+        )}
+        {isProfileError && (
+          <Text
+            style={[
+              t.atoms.text_contrast_high,
+              a.italic,
+              a.text_center,
+              a.w_full,
+            ]}>
+            <Trans>Could not find profile</Trans>
+          </Text>
+        )}
+        {type === 'feed' && (
+          <>
+            <Text style={[a.font_bold, t.atoms.text_contrast_high, a.mt_md]}>
+              <Trans>Feed identifier</Trans>
+            </Text>
+            <Text style={[a.text_md, t.atoms.text_contrast_high, a.italic]}>
+              {atUri.rkey}
+            </Text>
+          </>
+        )}
+        {error instanceof Error && (
+          <>
+            <Text style={[a.font_bold, t.atoms.text_contrast_high, a.mt_md]}>
+              <Trans>Error message</Trans>
+            </Text>
+            <Text style={[a.text_md, t.atoms.text_contrast_high, a.italic]}>
+              {cleanError(error.message)}
+            </Text>
+          </>
+        )}
+      </View>
+      {isNative && (
+        <Button
+          label={_(msg`Close`)}
+          onPress={() => control.close()}
+          size="small"
+          variant="solid"
+          color="secondary"
+          style={[a.mt_5xl]}>
+          <ButtonText>
+            <Trans>Close</Trans>
+          </ButtonText>
+        </Button>
+      )}
+      <Dialog.Close />
+    </Dialog.ScrollableInner>
+  )
+}