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/Onboarding/Layout.tsx2
-rw-r--r--src/screens/Onboarding/StepAlgoFeeds/index.tsx20
-rw-r--r--src/screens/Onboarding/StepFinished.tsx2
-rw-r--r--src/screens/Onboarding/StepFollowingFeed.tsx2
-rw-r--r--src/screens/Onboarding/StepInterests/InterestButton.tsx6
-rw-r--r--src/screens/Onboarding/StepInterests/data.ts36
-rw-r--r--src/screens/Onboarding/StepInterests/index.tsx13
-rw-r--r--src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx125
-rw-r--r--src/screens/Onboarding/StepModeration/ModerationOption.tsx49
-rw-r--r--src/screens/Onboarding/StepModeration/index.tsx52
-rw-r--r--src/screens/Onboarding/StepSuggestedAccounts/index.tsx16
-rw-r--r--src/screens/Onboarding/StepTopicalFeeds.tsx10
-rw-r--r--src/screens/Onboarding/index.tsx35
-rw-r--r--src/screens/Onboarding/state.ts40
-rw-r--r--src/screens/Onboarding/util.ts31
15 files changed, 258 insertions, 181 deletions
diff --git a/src/screens/Onboarding/Layout.tsx b/src/screens/Onboarding/Layout.tsx
index 50487c189..b9683999f 100644
--- a/src/screens/Onboarding/Layout.tsx
+++ b/src/screens/Onboarding/Layout.tsx
@@ -173,7 +173,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
             ? a.py_2xl
             : {
                 paddingTop: a.pt_lg.paddingTop,
-                paddingBottom: insets.bottom,
+                paddingBottom: insets.bottom + 10,
               },
         ]}>
         <View
diff --git a/src/screens/Onboarding/StepAlgoFeeds/index.tsx b/src/screens/Onboarding/StepAlgoFeeds/index.tsx
index 4920c5ad7..7a87318e8 100644
--- a/src/screens/Onboarding/StepAlgoFeeds/index.tsx
+++ b/src/screens/Onboarding/StepAlgoFeeds/index.tsx
@@ -3,6 +3,7 @@ import {View} from 'react-native'
 import {useLingui} from '@lingui/react'
 import {msg, Trans} from '@lingui/macro'
 
+import {IS_PROD} from '#/env'
 import {atoms as a, tokens, useTheme} from '#/alf'
 import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
@@ -27,15 +28,15 @@ export type FeedConfig = {
   gradient?: typeof tokens.gradients.midnight | typeof tokens.gradients.nordic
 }
 
-const PRIMARY_FEEDS: FeedConfig[] = [
+export const PRIMARY_FEEDS: FeedConfig[] = [
   {
-    default: true,
-    uri: 'at://did:plc:wqowuobffl66jv3kpsvo7ak4/app.bsky.feed.generator/the-algorithm',
+    default: IS_PROD, // these feeds are only available in prod
+    uri: 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot',
     gradient: tokens.gradients.midnight,
   },
   {
-    default: false,
-    uri: 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot',
+    default: IS_PROD, // these feeds are only available in prod
+    uri: 'at://did:plc:wqowuobffl66jv3kpsvo7ak4/app.bsky.feed.generator/the-algorithm',
     gradient: tokens.gradients.midnight,
   },
 ]
@@ -99,11 +100,12 @@ export function StepAlgoFeeds() {
       <IconCircle icon={ListSparkle} style={[a.mb_2xl]} />
 
       <Title>
-        <Trans>Choose your algorithmic feeds</Trans>
+        <Trans>Choose your main feeds</Trans>
       </Title>
       <Description>
         <Trans>
-          Feeds are created by users and can give you entirely new experiences.
+          Custom feeds built by the community bring you new experiences and help
+          you find the content you love.
         </Trans>
       </Description>
 
@@ -114,12 +116,12 @@ export function StepAlgoFeeds() {
           label={_(msg`Select your primary algorithmic feeds`)}>
           <Text
             style={[a.text_md, a.pt_4xl, a.pb_md, t.atoms.text_contrast_700]}>
-            <Trans>We recommend "For You" by Skygaze:</Trans>
+            <Trans>We recommend our "Discover" feed:</Trans>
           </Text>
           <FeedCard config={PRIMARY_FEEDS[0]} />
           <Text
             style={[a.text_md, a.pt_4xl, a.pb_lg, t.atoms.text_contrast_700]}>
-            <Trans>Or you can try our "Discover" algorithm:</Trans>
+            <Trans>We also think you'll like "For You" by Skygaze:</Trans>
           </Text>
           <FeedCard config={PRIMARY_FEEDS[1]} />
         </Toggle.Group>
diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx
index 02c45f590..af73c6fc1 100644
--- a/src/screens/Onboarding/StepFinished.tsx
+++ b/src/screens/Onboarding/StepFinished.tsx
@@ -116,7 +116,7 @@ export function StepFinished() {
             </Text>
             <Text
               style={[t.atoms.text_contrast_500, a.text_md, a.leading_snug]}>
-              <Trans>Never lose access to your followers and data.</Trans>
+              <Trans>Never lose access to your followers or data.</Trans>
             </Text>
           </View>
         </View>
diff --git a/src/screens/Onboarding/StepFollowingFeed.tsx b/src/screens/Onboarding/StepFollowingFeed.tsx
index 4b3c62889..114e274b6 100644
--- a/src/screens/Onboarding/StepFollowingFeed.tsx
+++ b/src/screens/Onboarding/StepFollowingFeed.tsx
@@ -61,7 +61,7 @@ export function StepFollowingFeed() {
         <Trans>Your default feed is "Following"</Trans>
       </Title>
       <Description style={[a.mb_md]}>
-        <Trans>It show posts from the people your follow as they happen.</Trans>
+        <Trans>It shows posts from the people you follow as they happen.</Trans>
       </Description>
 
       <View style={[a.w_full]}>
diff --git a/src/screens/Onboarding/StepInterests/InterestButton.tsx b/src/screens/Onboarding/StepInterests/InterestButton.tsx
index 02413b18d..cc692dafd 100644
--- a/src/screens/Onboarding/StepInterests/InterestButton.tsx
+++ b/src/screens/Onboarding/StepInterests/InterestButton.tsx
@@ -4,11 +4,13 @@ import {View, ViewStyle, TextStyle} from 'react-native'
 import {useTheme, atoms as a, native} from '#/alf'
 import * as Toggle from '#/components/forms/Toggle'
 import {Text} from '#/components/Typography'
+import {capitalize} from '#/lib/strings/capitalize'
 
-import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data'
+import {Context} from '#/screens/Onboarding/state'
 
 export function InterestButton({interest}: {interest: string}) {
   const t = useTheme()
+  const {interestsDisplayNames} = React.useContext(Context)
   const ctx = Toggle.useItemContext()
 
   const styles = React.useMemo(() => {
@@ -72,7 +74,7 @@ export function InterestButton({interest}: {interest: string}) {
           native({paddingTop: 2}),
           ctx.selected ? styles.textSelected : {},
         ]}>
-        {INTEREST_TO_DISPLAY_NAME[interest]}
+        {interestsDisplayNames[interest] || capitalize(interest)}
       </Text>
     </View>
   )
diff --git a/src/screens/Onboarding/StepInterests/data.ts b/src/screens/Onboarding/StepInterests/data.ts
deleted file mode 100644
index 00a25331c..000000000
--- a/src/screens/Onboarding/StepInterests/data.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-export const INTEREST_TO_DISPLAY_NAME: {
-  [key: string]: string
-} = {
-  news: 'News',
-  journalism: 'Journalism',
-  nature: 'Nature',
-  art: 'Art',
-  comics: 'Comics',
-  writers: 'Writers',
-  culture: 'Culture',
-  sports: 'Sports',
-  pets: 'Pets',
-  animals: 'Animals',
-  books: 'Books',
-  education: 'Education',
-  climate: 'Climate',
-  science: 'Science',
-  politics: 'Politics',
-  fitness: 'Fitness',
-  tech: 'Tech',
-  dev: 'Software Dev',
-  comedy: 'Comedy',
-  gaming: 'Video Games',
-  food: 'Food',
-  cooking: 'Cooking',
-}
-
-export type ApiResponseMap = {
-  interests: string[]
-  suggestedAccountDids: {
-    [key: string]: string[]
-  }
-  suggestedFeedUris: {
-    [key: string]: string[]
-  }
-}
diff --git a/src/screens/Onboarding/StepInterests/index.tsx b/src/screens/Onboarding/StepInterests/index.tsx
index 6f60991d5..5440dcd2b 100644
--- a/src/screens/Onboarding/StepInterests/index.tsx
+++ b/src/screens/Onboarding/StepInterests/index.tsx
@@ -17,17 +17,14 @@ import {getAgent} from '#/state/session'
 import {useAnalytics} from '#/lib/analytics/analytics'
 import {Text} from '#/components/Typography'
 import {useOnboardingDispatch} from '#/state/shell'
+import {capitalize} from '#/lib/strings/capitalize'
 
-import {Context} from '#/screens/Onboarding/state'
+import {Context, ApiResponseMap} from '#/screens/Onboarding/state'
 import {
   Title,
   Description,
   OnboardingControls,
 } from '#/screens/Onboarding/Layout'
-import {
-  ApiResponseMap,
-  INTEREST_TO_DISPLAY_NAME,
-} from '#/screens/Onboarding/StepInterests/data'
 import {InterestButton} from '#/screens/Onboarding/StepInterests/InterestButton'
 import {IconCircle} from '#/screens/Onboarding/IconCircle'
 
@@ -36,7 +33,7 @@ export function StepInterests() {
   const t = useTheme()
   const {track} = useAnalytics()
   const {gtMobile} = useBreakpoints()
-  const {state, dispatch} = React.useContext(Context)
+  const {state, dispatch, interestsDisplayNames} = React.useContext(Context)
   const [saving, setSaving] = React.useState(false)
   const [interests, setInterests] = React.useState<string[]>(
     state.interestsStepResults.selectedInterests.map(i => i),
@@ -202,7 +199,9 @@ export function StepInterests() {
                 <Toggle.Item
                   key={interest}
                   name={interest}
-                  label={INTEREST_TO_DISPLAY_NAME[interest]}>
+                  label={
+                    interestsDisplayNames[interest] || capitalize(interest)
+                  }>
                   <InterestButton interest={interest} />
                 </Toggle.Item>
               ))}
diff --git a/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx b/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx
index bc4c0387f..6b456de80 100644
--- a/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx
+++ b/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx
@@ -2,19 +2,17 @@ import React from 'react'
 import {View} from 'react-native'
 import {useLingui} from '@lingui/react'
 import {msg, Trans} from '@lingui/macro'
+import {UseMutateFunction} from '@tanstack/react-query'
 
-import {isIOS} from '#/platform/detection'
 import * as Toast from '#/view/com/util/Toast'
 import {atoms as a, useTheme} from '#/alf'
-import {
-  usePreferencesQuery,
-  usePreferencesSetAdultContentMutation,
-} from '#/state/queries/preferences'
+import {usePreferencesQuery} from '#/state/queries/preferences'
 import {logger} from '#/logger'
 import {Text} from '#/components/Typography'
-import {InlineLink} from '#/components/Link'
 import * as Toggle from '#/components/forms/Toggle'
 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
+import * as Prompt from '#/components/Prompt'
+import {isIOS} from '#/platform/detection'
 
 function Card({children}: React.PropsWithChildren<{}>) {
   const t = useTheme()
@@ -36,16 +34,25 @@ function Card({children}: React.PropsWithChildren<{}>) {
   )
 }
 
-export function AdultContentEnabledPref() {
+export function AdultContentEnabledPref({
+  mutate,
+  variables,
+}: {
+  mutate: UseMutateFunction<void, unknown, {enabled: boolean}, unknown>
+  variables: {enabled: boolean} | undefined
+}) {
   const {_} = useLingui()
   const t = useTheme()
+  const prompt = Prompt.usePromptControl()
 
   // Reuse logic here form ContentFilteringSettings.tsx
   const {data: preferences} = usePreferencesQuery()
-  const {mutate, variables} = usePreferencesSetAdultContentMutation()
 
   const onToggleAdultContent = React.useCallback(async () => {
-    if (isIOS) return
+    if (isIOS) {
+      prompt.open()
+      return
+    }
 
     try {
       mutate({
@@ -57,15 +64,33 @@ export function AdultContentEnabledPref() {
       )
       logger.error('Failed to update preferences with server', {error: e})
     }
-  }, [variables, preferences, mutate, _])
+  }, [variables, preferences, mutate, _, prompt])
 
   if (!preferences) return null
 
-  if (isIOS) {
-    if (preferences?.adultContentEnabled === true) {
-      return null
-    } else {
-      return (
+  return (
+    <>
+      {preferences.userAge && preferences.userAge >= 18 ? (
+        <View style={[a.w_full, a.px_xs]}>
+          <Toggle.Item
+            name={_(msg`Enable adult content in your feeds`)}
+            label={_(msg`Enable adult content in your feeds`)}
+            value={variables?.enabled ?? preferences?.adultContentEnabled}
+            onChange={onToggleAdultContent}>
+            <View
+              style={[
+                a.flex_row,
+                a.w_full,
+                a.justify_between,
+                a.align_center,
+                a.py_md,
+              ]}>
+              <Text style={[a.font_bold]}>Enable Adult Content</Text>
+              <Toggle.Switch />
+            </View>
+          </Toggle.Item>
+        </View>
+      ) : (
         <Card>
           <CircleInfo size="sm" fill={t.palette.contrast_500} />
           <Text
@@ -75,61 +100,23 @@ export function AdultContentEnabledPref() {
               a.leading_snug,
               {paddingTop: 1},
             ]}>
-            <Trans>
-              Adult content can only be enabled via the Web at{' '}
-              <InlineLink style={[a.leading_snug]} to="https://bsky.app">
-                bsky.app
-              </InlineLink>
-              .
-            </Trans>
+            <Trans>You must be 18 years or older to enable adult content</Trans>
           </Text>
         </Card>
-      )
-    }
-  } else {
-    if (preferences?.userAge) {
-      if (preferences.userAge >= 18) {
-        return (
-          <View style={[a.w_full]}>
-            <Toggle.Item
-              name={_(msg`Enable adult content in your feeds`)}
-              label={_(msg`Enable adult content in your feeds`)}
-              value={variables?.enabled ?? preferences?.adultContentEnabled}
-              onChange={onToggleAdultContent}>
-              <View
-                style={[
-                  a.flex_row,
-                  a.w_full,
-                  a.justify_between,
-                  a.align_center,
-                  a.py_md,
-                ]}>
-                <Text style={[a.font_bold]}>Enable Adult Content</Text>
-                <Toggle.Switch />
-              </View>
-            </Toggle.Item>
-          </View>
-        )
-      } else {
-        return (
-          <Card>
-            <CircleInfo size="sm" fill={t.palette.contrast_500} />
-            <Text
-              style={[
-                a.flex_1,
-                t.atoms.text_contrast_700,
-                a.leading_snug,
-                {paddingTop: 1},
-              ]}>
-              <Trans>
-                You must be 18 years or older to enable adult content
-              </Trans>
-            </Text>
-          </Card>
-        )
-      }
-    }
+      )}
 
-    return null
-  }
+      <Prompt.Outer control={prompt}>
+        <Prompt.Title>Adult Content</Prompt.Title>
+        <Prompt.Description>
+          <Trans>
+            Due to Apple policies, adult content can only be enabled on the web
+            after completing sign up.
+          </Trans>
+        </Prompt.Description>
+        <Prompt.Actions>
+          <Prompt.Action onPress={prompt.close}>OK</Prompt.Action>
+        </Prompt.Actions>
+      </Prompt.Outer>
+    </>
+  )
 }
diff --git a/src/screens/Onboarding/StepModeration/ModerationOption.tsx b/src/screens/Onboarding/StepModeration/ModerationOption.tsx
index 904c47299..d216692d0 100644
--- a/src/screens/Onboarding/StepModeration/ModerationOption.tsx
+++ b/src/screens/Onboarding/StepModeration/ModerationOption.tsx
@@ -3,6 +3,7 @@ import {View} from 'react-native'
 import {LabelPreference} from '@atproto/api'
 import {useLingui} from '@lingui/react'
 import {msg} from '@lingui/macro'
+import Animated, {Easing, Layout, FadeIn} from 'react-native-reanimated'
 
 import {
   CONFIGURABLE_LABEL_GROUPS,
@@ -16,8 +17,10 @@ import * as ToggleButton from '#/components/forms/ToggleButton'
 
 export function ModerationOption({
   labelGroup,
+  isMounted,
 }: {
   labelGroup: ConfigurableLabelGroup
+  isMounted: React.MutableRefObject<boolean>
 }) {
   const {_} = useLingui()
   const t = useTheme()
@@ -41,7 +44,7 @@ export function ModerationOption({
   }
 
   return (
-    <View
+    <Animated.View
       style={[
         a.flex_row,
         a.justify_between,
@@ -49,7 +52,9 @@ export function ModerationOption({
         a.py_xs,
         a.px_xs,
         a.align_center,
-      ]}>
+      ]}
+      layout={Layout.easing(Easing.ease).duration(200)}
+      entering={isMounted.current ? FadeIn : undefined}>
       <View style={[a.gap_xs, {width: '50%'}]}>
         <Text style={[a.font_bold]}>{groupInfo.title}</Text>
         <Text style={[t.atoms.text_contrast_700, a.leading_snug]}>
@@ -57,29 +62,23 @@ export function ModerationOption({
         </Text>
       </View>
       <View style={[a.justify_center, {minHeight: 35}]}>
-        {!preferences?.adultContentEnabled && groupInfo.isAdultImagery ? (
-          <View style={[a.justify_center, {minHeight: 40}]}>
-            <Text style={[a.font_bold]}>{labels.hide}</Text>
-          </View>
-        ) : (
-          <ToggleButton.Group
-            label={_(
-              msg`Configure content filtering setting for category: ${groupInfo.title.toLowerCase()}`,
-            )}
-            values={[visibility ?? 'hide']}
-            onChange={onChange}>
-            <ToggleButton.Button name="hide" label={labels.hide}>
-              {labels.hide}
-            </ToggleButton.Button>
-            <ToggleButton.Button name="warn" label={labels.warn}>
-              {labels.warn}
-            </ToggleButton.Button>
-            <ToggleButton.Button name="ignore" label={labels.show}>
-              {labels.show}
-            </ToggleButton.Button>
-          </ToggleButton.Group>
-        )}
+        <ToggleButton.Group
+          label={_(
+            msg`Configure content filtering setting for category: ${groupInfo.title.toLowerCase()}`,
+          )}
+          values={[visibility ?? 'hide']}
+          onChange={onChange}>
+          <ToggleButton.Button name="hide" label={labels.hide}>
+            {labels.hide}
+          </ToggleButton.Button>
+          <ToggleButton.Button name="warn" label={labels.warn}>
+            {labels.warn}
+          </ToggleButton.Button>
+          <ToggleButton.Button name="ignore" label={labels.show}>
+            {labels.show}
+          </ToggleButton.Button>
+        </ToggleButton.Group>
       </View>
-    </View>
+    </Animated.View>
   )
 }
diff --git a/src/screens/Onboarding/StepModeration/index.tsx b/src/screens/Onboarding/StepModeration/index.tsx
index be605e407..c831b6880 100644
--- a/src/screens/Onboarding/StepModeration/index.tsx
+++ b/src/screens/Onboarding/StepModeration/index.tsx
@@ -2,9 +2,14 @@ import React from 'react'
 import {View} from 'react-native'
 import {useLingui} from '@lingui/react'
 import {msg, Trans} from '@lingui/macro'
+import Animated, {Easing, Layout} from 'react-native-reanimated'
 
 import {atoms as a} from '#/alf'
-import {configurableLabelGroups} from 'state/queries/preferences'
+import {
+  configurableAdultLabelGroups,
+  configurableOtherLabelGroups,
+  usePreferencesSetAdultContentMutation,
+} from 'state/queries/preferences'
 import {Divider} from '#/components/Divider'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
@@ -23,11 +28,32 @@ import {AdultContentEnabledPref} from '#/screens/Onboarding/StepModeration/Adult
 import {Context} from '#/screens/Onboarding/state'
 import {IconCircle} from '#/screens/Onboarding/IconCircle'
 
+function AnimatedDivider() {
+  return (
+    <Animated.View layout={Layout.easing(Easing.ease).duration(200)}>
+      <Divider />
+    </Animated.View>
+  )
+}
+
 export function StepModeration() {
   const {_} = useLingui()
   const {track} = useAnalytics()
   const {state, dispatch} = React.useContext(Context)
   const {data: preferences} = usePreferencesQuery()
+  const {mutate, variables} = usePreferencesSetAdultContentMutation()
+
+  // We need to know if the screen is mounted so we know if we want to run entering animations
+  // https://github.com/software-mansion/react-native-reanimated/discussions/2513
+  const isMounted = React.useRef(false)
+  React.useLayoutEffect(() => {
+    isMounted.current = true
+  }, [])
+
+  const adultContentEnabled = !!(
+    (variables && variables.enabled) ||
+    (!variables && preferences?.adultContentEnabled)
+  )
 
   const onContinue = React.useCallback(() => {
     dispatch({type: 'next'})
@@ -43,12 +69,11 @@ export function StepModeration() {
       <IconCircle icon={EyeSlash} style={[a.mb_2xl]} />
 
       <Title>
-        <Trans>You are in control</Trans>
+        <Trans>You're in control</Trans>
       </Title>
       <Description style={[a.mb_xl]}>
         <Trans>
-          Select the types of content that you want to see (or not see), and
-          we'll handle the rest.
+          Select what you want to see (or not see), and we’ll handle the rest.
         </Trans>
       </Description>
 
@@ -58,14 +83,23 @@ export function StepModeration() {
         </View>
       ) : (
         <>
-          <AdultContentEnabledPref />
+          <AdultContentEnabledPref mutate={mutate} variables={variables} />
 
           <View style={[a.gap_sm, a.w_full]}>
-            {configurableLabelGroups.map((g, index) => (
+            {adultContentEnabled &&
+              configurableAdultLabelGroups.map((g, index) => (
+                <React.Fragment key={index}>
+                  {index === 0 && <AnimatedDivider />}
+                  <ModerationOption labelGroup={g} isMounted={isMounted} />
+                  <AnimatedDivider />
+                </React.Fragment>
+              ))}
+
+            {configurableOtherLabelGroups.map((g, index) => (
               <React.Fragment key={index}>
-                {index === 0 && <Divider />}
-                <ModerationOption labelGroup={g} />
-                <Divider />
+                {!adultContentEnabled && index === 0 && <AnimatedDivider />}
+                <ModerationOption labelGroup={g} isMounted={isMounted} />
+                <AnimatedDivider />
               </React.Fragment>
             ))}
           </View>
diff --git a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx
index 723e53a98..965dae334 100644
--- a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx
+++ b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx
@@ -14,6 +14,7 @@ import {Loader} from '#/components/Loader'
 import * as Toggle from '#/components/forms/Toggle'
 import {useModerationOpts} from '#/state/queries/preferences'
 import {useAnalytics} from '#/lib/analytics/analytics'
+import {capitalize} from '#/lib/strings/capitalize'
 
 import {Context} from '#/screens/Onboarding/state'
 import {
@@ -25,7 +26,6 @@ import {
   SuggestedAccountCard,
   SuggestedAccountCardPlaceholder,
 } from '#/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard'
-import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data'
 import {aggregateInterestItems} from '#/screens/Onboarding/util'
 import {IconCircle} from '#/screens/Onboarding/IconCircle'
 
@@ -70,7 +70,7 @@ export function Inner({
 export function StepSuggestedAccounts() {
   const {_} = useLingui()
   const {track} = useAnalytics()
-  const {state, dispatch} = React.useContext(Context)
+  const {state, dispatch, interestsDisplayNames} = React.useContext(Context)
   const {gtMobile} = useBreakpoints()
   const suggestedDids = React.useMemo(() => {
     return aggregateInterestItems(
@@ -93,10 +93,10 @@ export function StepSuggestedAccounts() {
 
   const interestsText = React.useMemo(() => {
     const i = state.interestsStepResults.selectedInterests.map(
-      i => INTEREST_TO_DISPLAY_NAME[i],
+      i => interestsDisplayNames[i] || capitalize(i),
     )
     return i.join(', ')
-  }, [state.interestsStepResults.selectedInterests])
+  }, [state.interestsStepResults.selectedInterests, interestsDisplayNames])
 
   const handleContinue = React.useCallback(async () => {
     setSaving(true)
@@ -107,7 +107,7 @@ export function StepSuggestedAccounts() {
 
     setSaving(false)
     dispatch({type: 'next'})
-    track('OnboardingV2:StepSuggestedAccounts:Start', {
+    track('OnboardingV2:StepSuggestedAccounts:End', {
       selectedAccountsLength: dids.length,
     })
   }, [dids, setSaving, dispatch, track])
@@ -129,13 +129,13 @@ export function StepSuggestedAccounts() {
       <IconCircle icon={At} style={[a.mb_2xl]} />
 
       <Title>
-        <Trans>Here are some accounts for your to follow</Trans>
+        <Trans>Here are some accounts for you to follow</Trans>
       </Title>
       <Description>
         {state.interestsStepResults.selectedInterests.length ? (
           <Trans>Based on your interest in {interestsText}</Trans>
         ) : (
-          <Trans>These are popular accounts you might like.</Trans>
+          <Trans>These are popular accounts you might like:</Trans>
         )}
       </Description>
 
@@ -171,7 +171,7 @@ export function StepSuggestedAccounts() {
             color="gradient_sky"
             size="large"
             label={_(
-              msg`Follow selected accounts and continue to then next step`,
+              msg`Follow selected accounts and continue to the next step`,
             )}
             onPress={handleContinue}>
             <ButtonText>
diff --git a/src/screens/Onboarding/StepTopicalFeeds.tsx b/src/screens/Onboarding/StepTopicalFeeds.tsx
index 516c18e6e..3640b764d 100644
--- a/src/screens/Onboarding/StepTopicalFeeds.tsx
+++ b/src/screens/Onboarding/StepTopicalFeeds.tsx
@@ -3,6 +3,7 @@ import {View} from 'react-native'
 import {useLingui} from '@lingui/react'
 import {msg, Trans} from '@lingui/macro'
 
+import {IS_PROD} from '#/env'
 import {atoms as a} from '#/alf'
 import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
 import {ListMagnifyingGlass_Stroke2_Corner0_Rounded as ListMagnifyingGlass} from '#/components/icons/ListMagnifyingGlass'
@@ -10,6 +11,7 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import * as Toggle from '#/components/forms/Toggle'
 import {Loader} from '#/components/Loader'
 import {useAnalytics} from '#/lib/analytics/analytics'
+import {capitalize} from '#/lib/strings/capitalize'
 
 import {Context} from '#/screens/Onboarding/state'
 import {
@@ -18,17 +20,17 @@ import {
   OnboardingControls,
 } from '#/screens/Onboarding/Layout'
 import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard'
-import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data'
 import {aggregateInterestItems} from '#/screens/Onboarding/util'
 import {IconCircle} from '#/screens/Onboarding/IconCircle'
 
 export function StepTopicalFeeds() {
   const {_} = useLingui()
   const {track} = useAnalytics()
-  const {state, dispatch} = React.useContext(Context)
+  const {state, dispatch, interestsDisplayNames} = React.useContext(Context)
   const [selectedFeedUris, setSelectedFeedUris] = React.useState<string[]>([])
   const [saving, setSaving] = React.useState(false)
   const suggestedFeedUris = React.useMemo(() => {
+    if (!IS_PROD) return []
     return aggregateInterestItems(
       state.interestsStepResults.selectedInterests,
       state.interestsStepResults.apiResponse.suggestedFeedUris,
@@ -38,10 +40,10 @@ export function StepTopicalFeeds() {
 
   const interestsText = React.useMemo(() => {
     const i = state.interestsStepResults.selectedInterests.map(
-      i => INTEREST_TO_DISPLAY_NAME[i],
+      i => interestsDisplayNames[i] || capitalize(i),
     )
     return i.join(', ')
-  }, [state.interestsStepResults.selectedInterests])
+  }, [state.interestsStepResults.selectedInterests, interestsDisplayNames])
 
   const saveFeeds = React.useCallback(async () => {
     setSaving(true)
diff --git a/src/screens/Onboarding/index.tsx b/src/screens/Onboarding/index.tsx
index a4eb04012..9e5029e87 100644
--- a/src/screens/Onboarding/index.tsx
+++ b/src/screens/Onboarding/index.tsx
@@ -1,4 +1,6 @@
 import React from 'react'
+import {useLingui} from '@lingui/react'
+import {msg} from '@lingui/macro'
 
 import {Portal} from '#/components/Portal'
 
@@ -13,13 +15,44 @@ import {StepFinished} from '#/screens/Onboarding/StepFinished'
 import {StepModeration} from '#/screens/Onboarding/StepModeration'
 
 export function Onboarding() {
+  const {_} = useLingui()
   const [state, dispatch] = React.useReducer(reducer, {...initialState})
 
+  const interestsDisplayNames = React.useMemo(() => {
+    return {
+      news: _(msg`News`),
+      journalism: _(msg`Journalism`),
+      nature: _(msg`Nature`),
+      art: _(msg`Art`),
+      comics: _(msg`Comics`),
+      writers: _(msg`Writers`),
+      culture: _(msg`Culture`),
+      sports: _(msg`Sports`),
+      pets: _(msg`Pets`),
+      animals: _(msg`Animals`),
+      books: _(msg`Books`),
+      education: _(msg`Education`),
+      climate: _(msg`Climate`),
+      science: _(msg`Science`),
+      politics: _(msg`Politics`),
+      fitness: _(msg`Fitness`),
+      tech: _(msg`Tech`),
+      dev: _(msg`Software Dev`),
+      comedy: _(msg`Comedy`),
+      gaming: _(msg`Video Games`),
+      food: _(msg`Food`),
+      cooking: _(msg`Cooking`),
+    }
+  }, [_])
+
   return (
     <Portal>
       <OnboardingControls.Provider>
         <Context.Provider
-          value={React.useMemo(() => ({state, dispatch}), [state, dispatch])}>
+          value={React.useMemo(
+            () => ({state, dispatch, interestsDisplayNames}),
+            [state, dispatch, interestsDisplayNames],
+          )}>
           <Layout>
             {state.activeStep === 'interests' && <StepInterests />}
             {state.activeStep === 'suggestedAccounts' && (
diff --git a/src/screens/Onboarding/state.ts b/src/screens/Onboarding/state.ts
index 164c2f5f3..bd8205ca2 100644
--- a/src/screens/Onboarding/state.ts
+++ b/src/screens/Onboarding/state.ts
@@ -1,6 +1,5 @@
 import React from 'react'
 
-import {ApiResponseMap} from '#/screens/Onboarding/StepInterests/data'
 import {logger} from '#/logger'
 
 export type OnboardingState = {
@@ -59,6 +58,16 @@ export type OnboardingAction =
       feedUris: string[]
     }
 
+export type ApiResponseMap = {
+  interests: string[]
+  suggestedAccountDids: {
+    [key: string]: string[]
+  }
+  suggestedFeedUris: {
+    [key: string]: string[]
+  }
+}
+
 export const initialState: OnboardingState = {
   hasPrev: false,
   totalSteps: 7,
@@ -84,12 +93,41 @@ export const initialState: OnboardingState = {
   },
 }
 
+export const INTEREST_TO_DISPLAY_NAME_DEFAULTS: {
+  [key: string]: string
+} = {
+  news: 'News',
+  journalism: 'Journalism',
+  nature: 'Nature',
+  art: 'Art',
+  comics: 'Comics',
+  writers: 'Writers',
+  culture: 'Culture',
+  sports: 'Sports',
+  pets: 'Pets',
+  animals: 'Animals',
+  books: 'Books',
+  education: 'Education',
+  climate: 'Climate',
+  science: 'Science',
+  politics: 'Politics',
+  fitness: 'Fitness',
+  tech: 'Tech',
+  dev: 'Software Dev',
+  comedy: 'Comedy',
+  gaming: 'Video Games',
+  food: 'Food',
+  cooking: 'Cooking',
+}
+
 export const Context = React.createContext<{
   state: OnboardingState
   dispatch: React.Dispatch<OnboardingAction>
+  interestsDisplayNames: {[key: string]: string}
 }>({
   state: {...initialState},
   dispatch: () => {},
+  interestsDisplayNames: INTEREST_TO_DISPLAY_NAME_DEFAULTS,
 })
 
 export function reducer(
diff --git a/src/screens/Onboarding/util.ts b/src/screens/Onboarding/util.ts
index 2a709a67b..1a0b8d21b 100644
--- a/src/screens/Onboarding/util.ts
+++ b/src/screens/Onboarding/util.ts
@@ -2,6 +2,7 @@ import {AppBskyGraphFollow, AppBskyGraphGetFollows} from '@atproto/api'
 
 import {until} from '#/lib/async/until'
 import {getAgent} from '#/state/session'
+import {PRIMARY_FEEDS} from './StepAlgoFeeds'
 
 function shuffle(array: any) {
   let currentIndex = array.length,
@@ -31,7 +32,15 @@ export function aggregateInterestItems(
   const selected = interests.length
   const all = interests
     .map(i => {
-      const suggestions = shuffle(map[i])
+      // suggestions from server
+      const rawSuggestions = map[i]
+
+      // safeguard against a missing interest->suggestion mapping
+      if (!rawSuggestions || !rawSuggestions.length) {
+        return []
+      }
+
+      const suggestions = shuffle(rawSuggestions)
 
       if (selected === 1) {
         return suggestions // return all
@@ -102,11 +111,19 @@ async function whenFollowsIndexed(
  * feed after Following
  */
 export function sortPrimaryAlgorithmFeeds(uris: string[]) {
-  return uris.sort(uri => {
-    return uri.includes('the-algorithm')
-      ? -1
-      : uri.includes('whats-hot')
-      ? 0
-      : 1
+  return uris.sort((a, b) => {
+    if (a === PRIMARY_FEEDS[0].uri) {
+      return -1
+    }
+    if (b === PRIMARY_FEEDS[0].uri) {
+      return 1
+    }
+    if (a === PRIMARY_FEEDS[1].uri) {
+      return -1
+    }
+    if (b === PRIMARY_FEEDS[1].uri) {
+      return 1
+    }
+    return a.localeCompare(b)
   })
 }