about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/lib/constants.ts107
-rw-r--r--src/state/models/feeds/custom-feed.ts13
-rw-r--r--src/view/com/auth/Onboarding.tsx23
-rw-r--r--src/view/com/auth/onboarding/RecommendedFeeds.tsx110
-rw-r--r--src/view/com/auth/onboarding/RecommendedFeeds.web.tsx214
-rw-r--r--src/view/com/auth/onboarding/Welcome.tsx8
-rw-r--r--src/view/com/auth/onboarding/Welcome.web.tsx123
-rw-r--r--src/view/com/util/layouts/TitleColumnLayout.tsx62
-rw-r--r--src/view/shell/index.web.tsx5
9 files changed, 539 insertions, 126 deletions
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 001cdf8c3..94551e6ef 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -148,3 +148,110 @@ export const HITSLOP_10 = createHitslop(10)
 export const HITSLOP_20 = createHitslop(20)
 export const HITSLOP_30 = createHitslop(30)
 export const BACK_HITSLOP = HITSLOP_30
+
+export const RECOMMENDED_FEEDS = [
+  {
+    did: 'did:plc:hsqwcidfez66lwm3gxhfv5in',
+    rkey: 'aaaf2pqeodmpy',
+  },
+  {
+    did: 'did:plc:gekdk2nd47gkk3utfz2xf7cn',
+    rkey: 'aaap4tbjcfe5y',
+  },
+  {
+    did: 'did:plc:5rw2on4i56btlcajojaxwcat',
+    rkey: 'aaao6g552b33o',
+  },
+  {
+    did: 'did:plc:jfhpnnst6flqway4eaeqzj2a',
+    rkey: 'for-science',
+  },
+  {
+    did: 'did:plc:7q4nnnxawajbfaq7to5dpbsy',
+    rkey: 'bsky-news',
+  },
+  {
+    did: 'did:plc:jcoy7v3a2t4rcfdh6i4kza25',
+    rkey: 'astro',
+  },
+  {
+    did: 'did:plc:tenurhgjptubkk5zf5qhi3og',
+    rkey: 'h-nba',
+  },
+  {
+    did: 'did:plc:vpkhqolt662uhesyj6nxm7ys',
+    rkey: 'devfeed',
+  },
+  {
+    did: 'did:plc:cndfx4udwgvpjaakvxvh7wm5',
+    rkey: 'flipboard-tech',
+  },
+  {
+    did: 'did:plc:w4xbfzo7kqfes5zb7r6qv3rw',
+    rkey: 'blacksky',
+  },
+  {
+    did: 'did:plc:lptjvw6ut224kwrj7ub3sqbe',
+    rkey: 'aaaotfjzjplna',
+  },
+  {
+    did: 'did:plc:gkvpokm7ec5j5yxls6xk4e3z',
+    rkey: 'formula-one',
+  },
+  {
+    did: 'did:plc:q6gjnaw2blty4crticxkmujt',
+    rkey: 'positivifeed',
+  },
+  {
+    did: 'did:plc:l72uci4styb4jucsgcrrj5ap',
+    rkey: 'aaao5dzfm36u4',
+  },
+  {
+    did: 'did:plc:k3jkadxv5kkjgs6boyon7m6n',
+    rkey: 'aaaavlyvqzst2',
+  },
+  {
+    did: 'did:plc:nkahctfdi6bxk72umytfwghw',
+    rkey: 'aaado2uvfsc6w',
+  },
+  {
+    did: 'did:plc:epihigio3d7un7u3gpqiy5gv',
+    rkey: 'aaaekwsc7zsvs',
+  },
+  {
+    did: 'did:plc:qiknc4t5rq7yngvz7g4aezq7',
+    rkey: 'aaaejxlobe474',
+  },
+  {
+    did: 'did:plc:mlq4aycufcuolr7ax6sezpc4',
+    rkey: 'aaaoudweck6uy',
+  },
+  {
+    did: 'did:plc:rcez5hcvq3vzlu5x7xrjyccg',
+    rkey: 'aaadzjxbcddzi',
+  },
+  {
+    did: 'did:plc:lnxbuzaenlwjrncx6sc4cfdr',
+    rkey: 'aaab2vesjtszc',
+  },
+  {
+    did: 'did:plc:x3cya3wkt4n6u4ihmvpsc5if',
+    rkey: 'aaacynbxwimok',
+  },
+  {
+    did: 'did:plc:abv47bjgzjgoh3yrygwoi36x',
+    rkey: 'aaagt6amuur5e',
+  },
+  {
+    did: 'did:plc:ffkgesg3jsv2j7aagkzrtcvt',
+    rkey: 'aaacjerk7gwek',
+  },
+  {
+    did: 'did:plc:geoqe3qls5mwezckxxsewys2',
+    rkey: 'aaai43yetqshu',
+  },
+  {
+    did: 'did:plc:2wqomm3tjqbgktbrfwgvrw34',
+    rkey: 'authors',
+  },
+]
diff --git a/src/state/models/feeds/custom-feed.ts b/src/state/models/feeds/custom-feed.ts
index 3c6d52755..2de4534e7 100644
--- a/src/state/models/feeds/custom-feed.ts
+++ b/src/state/models/feeds/custom-feed.ts
@@ -67,6 +67,19 @@ export class CustomFeedModel {
     }
   }
 
+  async pin() {
+    try {
+      await this.rootStore.preferences.addPinnedFeed(this.uri)
+    } catch (error) {
+      this.rootStore.log.error('Failed to pin feed', error)
+    } finally {
+      track('CustomFeed:Pin', {
+        name: this.data.displayName,
+        uri: this.uri,
+      })
+    }
+  }
+
   async unsave() {
     try {
       await this.rootStore.preferences.removeSavedFeed(this.uri)
diff --git a/src/view/com/auth/Onboarding.tsx b/src/view/com/auth/Onboarding.tsx
index f43d5d93a..2f0acfc47 100644
--- a/src/view/com/auth/Onboarding.tsx
+++ b/src/view/com/auth/Onboarding.tsx
@@ -6,7 +6,6 @@ import {s} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useStores} from 'state/index'
 import {useAnalytics} from 'lib/analytics/analytics'
-import {CenteredView} from '../util/Views'
 import {Welcome} from './onboarding/Welcome'
 import {RecommendedFeeds} from './onboarding/RecommendedFeeds'
 
@@ -24,17 +23,15 @@ export const Onboarding = observer(() => {
   const skip = () => store.onboarding.skip()
 
   return (
-    <CenteredView style={[s.hContentRegion, pal.view]}>
-      <SafeAreaView testID="noSessionView" style={s.hContentRegion}>
-        <ErrorBoundary>
-          {store.onboarding.step === 'Welcome' && (
-            <Welcome skip={skip} next={next} />
-          )}
-          {store.onboarding.step === 'RecommendedFeeds' && (
-            <RecommendedFeeds next={next} />
-          )}
-        </ErrorBoundary>
-      </SafeAreaView>
-    </CenteredView>
+    <SafeAreaView testID="onboardingView" style={[s.hContentRegion, pal.view]}>
+      <ErrorBoundary>
+        {store.onboarding.step === 'Welcome' && (
+          <Welcome skip={skip} next={next} />
+        )}
+        {store.onboarding.step === 'RecommendedFeeds' && (
+          <RecommendedFeeds next={next} />
+        )}
+      </ErrorBoundary>
+    </SafeAreaView>
   )
 })
diff --git a/src/view/com/auth/onboarding/RecommendedFeeds.tsx b/src/view/com/auth/onboarding/RecommendedFeeds.tsx
index fa9d69f98..f6b40b88d 100644
--- a/src/view/com/auth/onboarding/RecommendedFeeds.tsx
+++ b/src/view/com/auth/onboarding/RecommendedFeeds.tsx
@@ -9,113 +9,7 @@ import {useCustomFeed} from 'lib/hooks/useCustomFeed'
 import {makeRecordUri} from 'lib/strings/url-helpers'
 import {ViewHeader} from 'view/com/util/ViewHeader'
 import {isDesktopWeb} from 'platform/detection'
-
-const TEMPORARY_RECOMMENDED_FEEDS = [
-  {
-    did: 'did:plc:hsqwcidfez66lwm3gxhfv5in',
-    rkey: 'aaaf2pqeodmpy',
-  },
-  {
-    did: 'did:plc:gekdk2nd47gkk3utfz2xf7cn',
-    rkey: 'aaap4tbjcfe5y',
-  },
-  {
-    did: 'did:plc:5rw2on4i56btlcajojaxwcat',
-    rkey: 'aaao6g552b33o',
-  },
-  {
-    did: 'did:plc:jfhpnnst6flqway4eaeqzj2a',
-    rkey: 'for-science',
-  },
-  {
-    did: 'did:plc:7q4nnnxawajbfaq7to5dpbsy',
-    rkey: 'bsky-news',
-  },
-  {
-    did: 'did:plc:jcoy7v3a2t4rcfdh6i4kza25',
-    rkey: 'astro',
-  },
-  {
-    did: 'did:plc:tenurhgjptubkk5zf5qhi3og',
-    rkey: 'h-nba',
-  },
-  {
-    did: 'did:plc:vpkhqolt662uhesyj6nxm7ys',
-    rkey: 'devfeed',
-  },
-  {
-    did: 'did:plc:cndfx4udwgvpjaakvxvh7wm5',
-    rkey: 'flipboard-tech',
-  },
-  {
-    did: 'did:plc:w4xbfzo7kqfes5zb7r6qv3rw',
-    rkey: 'blacksky',
-  },
-  {
-    did: 'did:plc:lptjvw6ut224kwrj7ub3sqbe',
-    rkey: 'aaaotfjzjplna',
-  },
-  {
-    did: 'did:plc:gkvpokm7ec5j5yxls6xk4e3z',
-    rkey: 'formula-one',
-  },
-  {
-    did: 'did:plc:q6gjnaw2blty4crticxkmujt',
-    rkey: 'positivifeed',
-  },
-  {
-    did: 'did:plc:l72uci4styb4jucsgcrrj5ap',
-    rkey: 'aaao5dzfm36u4',
-  },
-  {
-    did: 'did:plc:k3jkadxv5kkjgs6boyon7m6n',
-    rkey: 'aaaavlyvqzst2',
-  },
-  {
-    did: 'did:plc:nkahctfdi6bxk72umytfwghw',
-    rkey: 'aaado2uvfsc6w',
-  },
-  {
-    did: 'did:plc:epihigio3d7un7u3gpqiy5gv',
-    rkey: 'aaaekwsc7zsvs',
-  },
-  {
-    did: 'did:plc:qiknc4t5rq7yngvz7g4aezq7',
-    rkey: 'aaaejxlobe474',
-  },
-  {
-    did: 'did:plc:mlq4aycufcuolr7ax6sezpc4',
-    rkey: 'aaaoudweck6uy',
-  },
-  {
-    did: 'did:plc:rcez5hcvq3vzlu5x7xrjyccg',
-    rkey: 'aaadzjxbcddzi',
-  },
-  {
-    did: 'did:plc:lnxbuzaenlwjrncx6sc4cfdr',
-    rkey: 'aaab2vesjtszc',
-  },
-  {
-    did: 'did:plc:x3cya3wkt4n6u4ihmvpsc5if',
-    rkey: 'aaacynbxwimok',
-  },
-  {
-    did: 'did:plc:abv47bjgzjgoh3yrygwoi36x',
-    rkey: 'aaagt6amuur5e',
-  },
-  {
-    did: 'did:plc:ffkgesg3jsv2j7aagkzrtcvt',
-    rkey: 'aaacjerk7gwek',
-  },
-  {
-    did: 'did:plc:geoqe3qls5mwezckxxsewys2',
-    rkey: 'aaai43yetqshu',
-  },
-  {
-    did: 'did:plc:2wqomm3tjqbgktbrfwgvrw34',
-    rkey: 'authors',
-  },
-]
+import {RECOMMENDED_FEEDS} from 'lib/constants'
 
 type Props = {
   next: () => void
@@ -132,7 +26,7 @@ export const RecommendedFeeds = observer(({next}: Props) => {
       </Text>
 
       <FlatList
-        data={TEMPORARY_RECOMMENDED_FEEDS}
+        data={RECOMMENDED_FEEDS}
         renderItem={({item}) => <Item item={item} />}
         keyExtractor={item => item.did + item.rkey}
         style={{flex: 1}}
diff --git a/src/view/com/auth/onboarding/RecommendedFeeds.web.tsx b/src/view/com/auth/onboarding/RecommendedFeeds.web.tsx
new file mode 100644
index 000000000..9038c7a5a
--- /dev/null
+++ b/src/view/com/auth/onboarding/RecommendedFeeds.web.tsx
@@ -0,0 +1,214 @@
+import React from 'react'
+import {FlatList, StyleSheet, View} from 'react-native'
+import {observer} from 'mobx-react-lite'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {Text} from 'view/com/util/text/Text'
+import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout'
+import {UserAvatar} from 'view/com/util/UserAvatar'
+import {Button} from 'view/com/util/forms/Button'
+import * as Toast from 'view/com/util/Toast'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useCustomFeed} from 'lib/hooks/useCustomFeed'
+import {makeRecordUri} from 'lib/strings/url-helpers'
+import {sanitizeHandle} from 'lib/strings/handles'
+import {HeartIcon} from 'lib/icons'
+import {RECOMMENDED_FEEDS} from 'lib/constants'
+
+type Props = {
+  next: () => void
+}
+export const RecommendedFeeds = observer(({next}: Props) => {
+  const pal = usePalette('default')
+
+  const title = (
+    <>
+      <Text style={[pal.textLight, styles.title1]}>Choose your</Text>
+      <Text style={[pal.link, styles.title2]}>Recomended</Text>
+      <Text style={[pal.link, styles.title2]}>Feeds</Text>
+      <Text type="2xl-medium" style={[pal.textLight, styles.description]}>
+        Feeds are created by users to curate content. Choose some feeds that you
+        find interesting.
+      </Text>
+      <View
+        style={{
+          flexDirection: 'row',
+          justifyContent: 'flex-end',
+          marginTop: 20,
+        }}>
+        <Button onPress={next} testID="continueBtn">
+          <View
+            style={{
+              flexDirection: 'row',
+              alignItems: 'center',
+              paddingLeft: 2,
+              gap: 6,
+            }}>
+            <Text
+              type="2xl-medium"
+              style={{color: '#fff', position: 'relative', top: -1}}>
+              Done
+            </Text>
+            <FontAwesomeIcon icon="angle-right" color="#fff" size={14} />
+          </View>
+        </Button>
+      </View>
+    </>
+  )
+
+  return (
+    <TitleColumnLayout
+      testID="recommendedFeedsScreen"
+      title={title}
+      horizontal
+      titleStyle={{minWidth: 470}}
+      contentStyle={{paddingHorizontal: 0}}>
+      <FlatList
+        data={RECOMMENDED_FEEDS}
+        renderItem={({item}) => <Item {...item} />}
+        keyExtractor={item => item.did + item.rkey}
+        style={{flex: 1}}
+      />
+    </TitleColumnLayout>
+  )
+})
+
+const Item = observer(({did, rkey}: {did: string; rkey: string}) => {
+  const pal = usePalette('default')
+  const uri = makeRecordUri(did, 'app.bsky.feed.generator', rkey)
+  const item = useCustomFeed(uri)
+  if (!item) return null
+  const onToggle = async () => {
+    if (item.isSaved) {
+      try {
+        await item.unsave()
+      } catch (e) {
+        Toast.show('There was an issue contacting your server')
+        console.error('Failed to unsave feed', {e})
+      }
+    } else {
+      try {
+        await item.save()
+        await item.pin()
+      } catch (e) {
+        Toast.show('There was an issue contacting your server')
+        console.error('Failed to pin feed', {e})
+      }
+    }
+  }
+  return (
+    <View testID={`feed-${item.displayName}`}>
+      <View
+        style={[
+          pal.border,
+          {
+            flexDirection: 'row',
+            gap: 18,
+            maxWidth: 670,
+            borderRightWidth: 1,
+            paddingHorizontal: 24,
+            paddingVertical: 24,
+            borderTopWidth: 1,
+          },
+        ]}>
+        <View style={{marginTop: 2}}>
+          <UserAvatar type="algo" size={42} avatar={item.data.avatar} />
+        </View>
+        <View>
+          <Text
+            type="2xl-bold"
+            numberOfLines={1}
+            style={[pal.text, {fontSize: 19}]}>
+            {item.displayName}
+          </Text>
+
+          <Text style={[pal.textLight, {marginBottom: 8}]} numberOfLines={1}>
+            by {sanitizeHandle(item.data.creator.handle, '@')}
+          </Text>
+
+          {item.data.description ? (
+            <Text
+              type="xl"
+              style={[pal.text, {maxWidth: 550, marginBottom: 18}]}
+              numberOfLines={6}>
+              {item.data.description}
+            </Text>
+          ) : null}
+
+          <View style={{flexDirection: 'row', alignItems: 'center', gap: 12}}>
+            <Button
+              type="inverted"
+              style={{paddingVertical: 6}}
+              onPress={onToggle}>
+              <View
+                style={{
+                  flexDirection: 'row',
+                  alignItems: 'center',
+                  paddingRight: 2,
+                  gap: 6,
+                }}>
+                {item.isSaved ? (
+                  <>
+                    <FontAwesomeIcon
+                      icon="check"
+                      size={16}
+                      color={pal.colors.textInverted}
+                    />
+                    <Text type="lg-medium" style={pal.textInverted}>
+                      Added
+                    </Text>
+                  </>
+                ) : (
+                  <>
+                    <FontAwesomeIcon
+                      icon="plus"
+                      size={16}
+                      color={pal.colors.textInverted}
+                    />
+                    <Text type="lg-medium" style={pal.textInverted}>
+                      Add
+                    </Text>
+                  </>
+                )}
+              </View>
+            </Button>
+
+            <View style={{flexDirection: 'row', gap: 4}}>
+              <HeartIcon
+                size={16}
+                strokeWidth={2.5}
+                style={[pal.textLight, {position: 'relative', top: 2}]}
+              />
+              <Text type="lg-medium" style={[pal.text, pal.textLight]}>
+                {item.data.likeCount || 0}
+              </Text>
+            </View>
+          </View>
+        </View>
+      </View>
+    </View>
+  )
+})
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    marginHorizontal: 16,
+    justifyContent: 'space-between',
+  },
+  title1: {
+    fontSize: 36,
+    fontWeight: '800',
+    textAlign: 'right',
+  },
+  title2: {
+    fontSize: 58,
+    fontWeight: '800',
+    textAlign: 'right',
+  },
+  description: {
+    maxWidth: 400,
+    marginTop: 10,
+    marginLeft: 'auto',
+    textAlign: 'right',
+  },
+})
diff --git a/src/view/com/auth/onboarding/Welcome.tsx b/src/view/com/auth/onboarding/Welcome.tsx
index a322e4d4f..6f95c0853 100644
--- a/src/view/com/auth/onboarding/Welcome.tsx
+++ b/src/view/com/auth/onboarding/Welcome.tsx
@@ -41,8 +41,10 @@ export const Welcome = observer(({next, skip}: Props) => {
         }}
       />
       <View>
-        <Text style={[pal.text, styles.title]}>Welcome to </Text>
-        <Text style={[pal.text, pal.link, styles.title]}>Bluesky</Text>
+        <Text style={[pal.text, styles.title]}>
+          Welcome to{' '}
+          <Text style={[pal.text, pal.link, styles.title]}>Bluesky</Text>
+        </Text>
         <View style={styles.spacer} />
         <View style={[styles.row]}>
           <FontAwesomeIcon icon={'globe'} size={36} color={pal.colors.link} />
@@ -98,7 +100,7 @@ const styles = StyleSheet.create({
     justifyContent: 'space-between',
   },
   title: {
-    fontSize: 48,
+    fontSize: 42,
     fontWeight: '800',
   },
   row: {
diff --git a/src/view/com/auth/onboarding/Welcome.web.tsx b/src/view/com/auth/onboarding/Welcome.web.tsx
new file mode 100644
index 000000000..af3ca7074
--- /dev/null
+++ b/src/view/com/auth/onboarding/Welcome.web.tsx
@@ -0,0 +1,123 @@
+import React from 'react'
+import {StyleSheet, View} from 'react-native'
+import {useMediaQuery} from 'react-responsive'
+import {Text} from 'view/com/util/text/Text'
+import {s} from 'lib/styles'
+import {usePalette} from 'lib/hooks/usePalette'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout'
+import {Button} from 'view/com/util/forms/Button'
+import {observer} from 'mobx-react-lite'
+
+type Props = {
+  next: () => void
+  skip: () => void
+}
+
+export const Welcome = observer(({next}: Props) => {
+  const pal = usePalette('default')
+  const horizontal = useMediaQuery({
+    query: '(min-width: 1230px)',
+  })
+  const title = (
+    <>
+      <Text
+        style={[
+          pal.textLight,
+          {
+            fontSize: 36,
+            fontWeight: '800',
+            textAlign: horizontal ? 'right' : 'left',
+          },
+        ]}>
+        Welcome to
+      </Text>
+      <Text
+        style={[
+          pal.link,
+          {
+            fontSize: 72,
+            fontWeight: '800',
+            textAlign: horizontal ? 'right' : 'left',
+          },
+        ]}>
+        Bluesky
+      </Text>
+    </>
+  )
+  return (
+    <TitleColumnLayout
+      testID="welcomeOnboarding"
+      title={title}
+      horizontal={horizontal}
+      titleStyle={horizontal ? {paddingBottom: 160} : undefined}>
+      <View style={[styles.row]}>
+        <FontAwesomeIcon icon={'globe'} size={36} color={pal.colors.link} />
+        <View style={[styles.rowText]}>
+          <Text type="xl-bold" style={[pal.text]}>
+            Bluesky is public.
+          </Text>
+          <Text type="xl" style={[pal.text, s.pt2]}>
+            Your posts, likes, and blocks are public. Mutes are private.
+          </Text>
+        </View>
+      </View>
+      <View style={[styles.row]}>
+        <FontAwesomeIcon icon={'at'} size={36} color={pal.colors.link} />
+        <View style={[styles.rowText]}>
+          <Text type="xl-bold" style={[pal.text]}>
+            Bluesky is open.
+          </Text>
+          <Text type="xl" style={[pal.text, s.pt2]}>
+            Never lose access to your followers and data.
+          </Text>
+        </View>
+      </View>
+      <View style={[styles.row]}>
+        <FontAwesomeIcon icon={'gear'} size={36} color={pal.colors.link} />
+        <View style={[styles.rowText]}>
+          <Text type="xl-bold" style={[pal.text]}>
+            Bluesky is flexible.
+          </Text>
+          <Text type="xl" style={[pal.text, s.pt2]}>
+            Choose the algorithms that power your experience with custom feeds.
+          </Text>
+        </View>
+      </View>
+      <View style={styles.spacer} />
+      <View style={{flexDirection: 'row'}}>
+        <Button onPress={next} testID="continueBtn">
+          <View
+            style={{
+              flexDirection: 'row',
+              alignItems: 'center',
+              paddingLeft: 2,
+              gap: 6,
+            }}>
+            <Text
+              type="2xl-medium"
+              style={{color: '#fff', position: 'relative', top: -1}}>
+              Next
+            </Text>
+            <FontAwesomeIcon icon="angle-right" color="#fff" size={14} />
+          </View>
+        </Button>
+      </View>
+    </TitleColumnLayout>
+  )
+})
+
+const styles = StyleSheet.create({
+  row: {
+    flexDirection: 'row',
+    columnGap: 20,
+    alignItems: 'center',
+    marginVertical: 20,
+  },
+  rowText: {
+    flex: 1,
+  },
+  spacer: {
+    height: 20,
+  },
+})
diff --git a/src/view/com/util/layouts/TitleColumnLayout.tsx b/src/view/com/util/layouts/TitleColumnLayout.tsx
new file mode 100644
index 000000000..3ca10868e
--- /dev/null
+++ b/src/view/com/util/layouts/TitleColumnLayout.tsx
@@ -0,0 +1,62 @@
+import React from 'react'
+import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
+import {usePalette} from 'lib/hooks/usePalette'
+
+interface Props {
+  testID?: string
+  title: React.Component
+  horizontal: boolean
+  titleStyle?: StyleProp<ViewStyle>
+  contentStyle?: StyleProp<ViewStyle>
+}
+
+export function TitleColumnLayout({
+  testID,
+  title,
+  horizontal,
+  children,
+  titleStyle,
+  contentStyle,
+}: React.PropsWithChildren<Props>) {
+  const pal = usePalette('default')
+
+  const layoutStyles = horizontal ? styles2Column : styles1Column
+  return (
+    <View testID={testID} style={layoutStyles.container}>
+      <View style={[layoutStyles.title, pal.viewLight, titleStyle]}>
+        {title}
+      </View>
+      <View style={[layoutStyles.content, contentStyle]}>{children}</View>
+    </View>
+  )
+}
+
+const styles2Column = StyleSheet.create({
+  container: {
+    flexDirection: 'row',
+    height: '100%',
+  },
+  title: {
+    flex: 1,
+    paddingHorizontal: 40,
+    paddingBottom: 80,
+    justifyContent: 'center',
+  },
+  content: {
+    flex: 2,
+    paddingHorizontal: 40,
+    justifyContent: 'center',
+  },
+})
+
+const styles1Column = StyleSheet.create({
+  container: {},
+  title: {
+    paddingHorizontal: 40,
+    paddingVertical: 40,
+  },
+  content: {
+    paddingHorizontal: 40,
+    paddingVertical: 40,
+  },
+})
diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx
index 16ed17a5b..9ad8007f6 100644
--- a/src/view/shell/index.web.tsx
+++ b/src/view/shell/index.web.tsx
@@ -20,7 +20,6 @@ import {NavigationProp} from 'lib/routes/types'
 const ShellInner = observer(() => {
   const store = useStores()
   const {isDesktop} = useWebMediaQueries()
-
   const navigator = useNavigation<NavigationProp>()
 
   useEffect(() => {
@@ -29,6 +28,8 @@ const ShellInner = observer(() => {
     })
   }, [navigator, store.shell])
 
+  const showSideNavs =
+    isDesktop && store.session.hasSession && !store.onboarding.isActive
   return (
     <>
       <View style={s.hContentRegion}>
@@ -36,7 +37,7 @@ const ShellInner = observer(() => {
           <FlatNavigator />
         </ErrorBoundary>
       </View>
-      {isDesktop && store.session.hasSession && (
+      {showSideNavs && (
         <>
           <DesktopLeftNav />
           <DesktopRightNav />