about summary refs log tree commit diff
path: root/src/screens
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens')
-rw-r--r--src/screens/Feeds/NoFollowingFeed.tsx50
-rw-r--r--src/screens/Feeds/NoSavedFeedsOfAnyType.tsx57
-rw-r--r--src/screens/Home/NoFeedsPinned.tsx129
-rw-r--r--src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx2
-rw-r--r--src/screens/Onboarding/StepFinished.tsx57
5 files changed, 286 insertions, 9 deletions
diff --git a/src/screens/Feeds/NoFollowingFeed.tsx b/src/screens/Feeds/NoFollowingFeed.tsx
new file mode 100644
index 000000000..03ced8ebd
--- /dev/null
+++ b/src/screens/Feeds/NoFollowingFeed.tsx
@@ -0,0 +1,50 @@
+import React from 'react'
+import {View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {TIMELINE_SAVED_FEED} from '#/lib/constants'
+import {useAddSavedFeedsMutation} from '#/state/queries/preferences'
+import {atoms as a, useTheme} from '#/alf'
+import {InlineLinkText} from '#/components/Link'
+import {Text} from '#/components/Typography'
+
+export function NoFollowingFeed() {
+  const t = useTheme()
+  const {_} = useLingui()
+  const {mutateAsync: addSavedFeeds} = useAddSavedFeedsMutation()
+
+  const addRecommendedFeeds = React.useCallback(
+    (e: any) => {
+      e.preventDefault()
+
+      addSavedFeeds([
+        {
+          ...TIMELINE_SAVED_FEED,
+          pinned: true,
+        },
+      ])
+
+      // prevent navigation
+      return false
+    },
+    [addSavedFeeds],
+  )
+
+  return (
+    <View style={[a.flex_row, a.flex_wrap, a.align_center, a.py_md, a.px_lg]}>
+      <Text
+        style={[a.leading_snug, t.atoms.text_contrast_medium, {maxWidth: 310}]}>
+        <Trans>Looks like you're missing a following feed.</Trans>{' '}
+      </Text>
+
+      <InlineLinkText
+        to="/"
+        label={_(msg`Add the default feed of only people you follow`)}
+        onPress={addRecommendedFeeds}
+        style={[a.leading_snug]}>
+        <Trans>Click here to add one.</Trans>
+      </InlineLinkText>
+    </View>
+  )
+}
diff --git a/src/screens/Feeds/NoSavedFeedsOfAnyType.tsx b/src/screens/Feeds/NoSavedFeedsOfAnyType.tsx
new file mode 100644
index 000000000..8f6bd9d2e
--- /dev/null
+++ b/src/screens/Feeds/NoSavedFeedsOfAnyType.tsx
@@ -0,0 +1,57 @@
+import React from 'react'
+import {View} from 'react-native'
+import {TID} from '@atproto/common-web'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {RECOMMENDED_SAVED_FEEDS} from '#/lib/constants'
+import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences'
+import {atoms as a, useTheme} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
+import {Text} from '#/components/Typography'
+
+/**
+ * Explicitly named, since the CTA in this component will overwrite all saved
+ * feeds if pressed. It should only be presented to the user if they actually
+ * have no other feeds saved.
+ */
+export function NoSavedFeedsOfAnyType() {
+  const t = useTheme()
+  const {_} = useLingui()
+  const {isPending, mutateAsync: overwriteSavedFeeds} =
+    useOverwriteSavedFeedsMutation()
+
+  const addRecommendedFeeds = React.useCallback(async () => {
+    await overwriteSavedFeeds(
+      RECOMMENDED_SAVED_FEEDS.map(f => ({
+        ...f,
+        id: TID.nextStr(),
+      })),
+    )
+  }, [overwriteSavedFeeds])
+
+  return (
+    <View
+      style={[a.flex_row, a.flex_wrap, a.justify_between, a.p_xl, a.gap_md]}>
+      <Text
+        style={[a.leading_snug, t.atoms.text_contrast_medium, {maxWidth: 310}]}>
+        <Trans>
+          Looks like you haven't saved any feeds! Use our recommendations or
+          browse more below.
+        </Trans>
+      </Text>
+
+      <Button
+        disabled={isPending}
+        label={_(msg`Apply default recommended feeds`)}
+        size="small"
+        variant="solid"
+        color="primary"
+        onPress={addRecommendedFeeds}>
+        <ButtonIcon icon={Plus} position="left" />
+        <ButtonText>{_(msg`Use recommended`)}</ButtonText>
+      </Button>
+    </View>
+  )
+}
diff --git a/src/screens/Home/NoFeedsPinned.tsx b/src/screens/Home/NoFeedsPinned.tsx
new file mode 100644
index 000000000..e804e3e09
--- /dev/null
+++ b/src/screens/Home/NoFeedsPinned.tsx
@@ -0,0 +1,129 @@
+import React from 'react'
+import {View} from 'react-native'
+import {TID} from '@atproto/common-web'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useNavigation} from '@react-navigation/native'
+
+import {DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED} from '#/lib/constants'
+import {isNative} from '#/platform/detection'
+import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences'
+import {UsePreferencesQueryResponse} from '#/state/queries/preferences'
+import {NavigationProp} from 'lib/routes/types'
+import {CenteredView} from '#/view/com/util/Views'
+import {atoms as a} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+import {useHeaderOffset} from '#/components/hooks/useHeaderOffset'
+import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle'
+import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
+import {Link} from '#/components/Link'
+import {Text} from '#/components/Typography'
+
+export function NoFeedsPinned({
+  preferences,
+}: {
+  preferences: UsePreferencesQueryResponse
+}) {
+  const {_} = useLingui()
+  const headerOffset = useHeaderOffset()
+  const navigation = useNavigation<NavigationProp>()
+  const {isPending, mutateAsync: overwriteSavedFeeds} =
+    useOverwriteSavedFeedsMutation()
+
+  const addRecommendedFeeds = React.useCallback(async () => {
+    let skippedTimeline = false
+    let skippedDiscover = false
+    let remainingSavedFeeds = []
+
+    // remove first instance of both timeline and discover, since we're going to overwrite them
+    for (const savedFeed of preferences.savedFeeds) {
+      if (savedFeed.type === 'timeline' && !skippedTimeline) {
+        skippedTimeline = true
+      } else if (
+        savedFeed.value === DISCOVER_SAVED_FEED.value &&
+        !skippedDiscover
+      ) {
+        skippedDiscover = true
+      } else {
+        remainingSavedFeeds.push(savedFeed)
+      }
+    }
+
+    const toSave = [
+      {
+        ...DISCOVER_SAVED_FEED,
+        pinned: true,
+        id: TID.nextStr(),
+      },
+      {
+        ...TIMELINE_SAVED_FEED,
+        pinned: true,
+        id: TID.nextStr(),
+      },
+      ...remainingSavedFeeds,
+    ]
+
+    await overwriteSavedFeeds(toSave)
+  }, [overwriteSavedFeeds, preferences.savedFeeds])
+
+  const onPressFeedsLink = React.useCallback(() => {
+    if (isNative) {
+      // Hack that's necessary due to how our navigators are set up.
+      navigation.navigate('FeedsTab')
+      navigation.popToTop()
+      return false
+    }
+  }, [navigation])
+
+  return (
+    <CenteredView sideBorders style={[a.h_full_vh]}>
+      <View
+        style={[
+          a.align_center,
+          a.h_full_vh,
+          a.py_3xl,
+          a.px_xl,
+          {
+            paddingTop: headerOffset + a.py_3xl.paddingTop,
+          },
+        ]}>
+        <View style={[a.align_center, a.gap_sm, a.pb_xl]}>
+          <Text style={[a.text_xl, a.font_bold]}>
+            <Trans>Whoops!</Trans>
+          </Text>
+          <Text
+            style={[a.text_md, a.text_center, a.leading_snug, {maxWidth: 340}]}>
+            <Trans>
+              Looks like you unpinned all your feeds. But don't worry, you can
+              add some below 😄
+            </Trans>
+          </Text>
+        </View>
+
+        <View style={[a.flex_row, a.gap_md, a.justify_center, a.flex_wrap]}>
+          <Button
+            disabled={isPending}
+            label={_(msg`Apply default recommended feeds`)}
+            size="medium"
+            variant="solid"
+            color="primary"
+            onPress={addRecommendedFeeds}>
+            <ButtonIcon icon={Plus} position="left" />
+            <ButtonText>{_(msg`Add recommended feeds`)}</ButtonText>
+          </Button>
+
+          <Link
+            label={_(msg`Browse other feeds`)}
+            to="/feeds"
+            onPress={onPressFeedsLink}
+            size="medium"
+            variant="solid"
+            color="secondary">
+            <ButtonIcon icon={ListSparkle} position="left" />
+            <ButtonText>{_(msg`Browse other feeds`)}</ButtonText>
+          </Link>
+        </View>
+      </View>
+    </CenteredView>
+  )
+}
diff --git a/src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx b/src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx
index d2b2a5f39..0aa063faa 100644
--- a/src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx
+++ b/src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx
@@ -2,7 +2,7 @@ import React from 'react'
 import {View} from 'react-native'
 import {Image} from 'expo-image'
 import {LinearGradient} from 'expo-linear-gradient'
-import {Trans, msg} from '@lingui/macro'
+import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {FeedSourceInfo, useFeedSourceInfoQuery} from '#/state/queries/feed'
diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx
index e7054fb1f..4cc611ef4 100644
--- a/src/screens/Onboarding/StepFinished.tsx
+++ b/src/screens/Onboarding/StepFinished.tsx
@@ -1,13 +1,15 @@
 import React from 'react'
 import {View} from 'react-native'
+import {TID} from '@atproto/common-web'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {useAnalytics} from '#/lib/analytics/analytics'
-import {BSKY_APP_ACCOUNT_DID} from '#/lib/constants'
+import {BSKY_APP_ACCOUNT_DID, IS_PROD_SERVICE} from '#/lib/constants'
+import {DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED} from '#/lib/constants'
 import {logEvent} from '#/lib/statsig/statsig'
 import {logger} from '#/logger'
-import {useSetSaveFeedsMutation} from '#/state/queries/preferences'
+import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences'
 import {useAgent} from '#/state/session'
 import {useOnboardingDispatch} from '#/state/shell'
 import {
@@ -37,7 +39,7 @@ export function StepFinished() {
   const {state, dispatch} = React.useContext(Context)
   const onboardDispatch = useOnboardingDispatch()
   const [saving, setSaving] = React.useState(false)
-  const {mutateAsync: saveFeeds} = useSetSaveFeedsMutation()
+  const {mutateAsync: overwriteSavedFeeds} = useOverwriteSavedFeedsMutation()
   const {getAgent} = useAgent()
 
   const finishOnboarding = React.useCallback(async () => {
@@ -64,10 +66,41 @@ export function StepFinished() {
         // these must be serial
         (async () => {
           await getAgent().setInterestsPref({tags: selectedInterests})
-          await saveFeeds({
-            saved: selectedFeeds,
-            pinned: selectedFeeds,
-          })
+
+          // TODO: In the reduced onboarding, we'll want to exit early here.
+
+          const otherFeeds = selectedFeeds.length
+            ? selectedFeeds.map(f => ({
+                type: 'feed',
+                value: f,
+                pinned: true,
+                id: TID.nextStr(),
+              }))
+            : []
+
+          /*
+           * If no selected feeds and we're in prod, add the discover feed
+           * (mimics old behavior)
+           */
+          if (
+            IS_PROD_SERVICE(getAgent().service.toString()) &&
+            !otherFeeds.length
+          ) {
+            otherFeeds.push({
+              ...DISCOVER_SAVED_FEED,
+              pinned: true,
+              id: TID.nextStr(),
+            })
+          }
+
+          await overwriteSavedFeeds([
+            {
+              ...TIMELINE_SAVED_FEED,
+              pinned: true,
+              id: TID.nextStr(),
+            },
+            ...otherFeeds,
+          ])
         })(),
       ])
     } catch (e: any) {
@@ -82,7 +115,15 @@ export function StepFinished() {
     track('OnboardingV2:StepFinished:End')
     track('OnboardingV2:Complete')
     logEvent('onboarding:finished:nextPressed', {})
-  }, [state, dispatch, onboardDispatch, setSaving, saveFeeds, track, getAgent])
+  }, [
+    state,
+    dispatch,
+    onboardDispatch,
+    setSaving,
+    overwriteSavedFeeds,
+    track,
+    getAgent,
+  ])
 
   React.useEffect(() => {
     track('OnboardingV2:StepFinished:Start')