about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/view/com/onboard/FeatureExplainer.web.tsx203
-rw-r--r--src/view/com/onboard/Follows.web.tsx47
-rw-r--r--src/view/screens/Login.web.tsx163
-rw-r--r--src/view/shell/web/index.tsx16
4 files changed, 423 insertions, 6 deletions
diff --git a/src/view/com/onboard/FeatureExplainer.web.tsx b/src/view/com/onboard/FeatureExplainer.web.tsx
new file mode 100644
index 000000000..83d460808
--- /dev/null
+++ b/src/view/com/onboard/FeatureExplainer.web.tsx
@@ -0,0 +1,203 @@
+import React, {useState} from 'react'
+import {
+  Animated,
+  Image,
+  SafeAreaView,
+  StyleSheet,
+  TouchableOpacity,
+  useWindowDimensions,
+  View,
+} from 'react-native'
+import {TabView, SceneMap, Route, TabBarProps} from 'react-native-tab-view'
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
+import {CenteredView} from '../util/Views.web'
+import {Text} from '../util/text/Text'
+import {useStores} from '../../../state'
+import {s, colors} from '../../lib/styles'
+import {TABS_EXPLAINER} from '../../lib/assets'
+import {TABS_ENABLED} from '../../../build-flags'
+
+const ROUTES = TABS_ENABLED
+  ? [
+      {key: 'intro', title: 'Intro'},
+      {key: 'tabs', title: 'Tabs'},
+    ]
+  : [{key: 'intro', title: 'Intro'}]
+
+const Intro = () => (
+  <View style={styles.explainer}>
+    <Text
+      style={[styles.explainerHeading, s.normal, styles.explainerHeadingIntro]}>
+      Welcome to{' '}
+      <Text style={[s.bold, s.blue3, styles.explainerHeadingBrand]}>
+        Bluesky
+      </Text>
+    </Text>
+    <Text style={[styles.explainerDesc, styles.explainerDescIntro]}>
+      This is an early beta. Your feedback is appreciated!
+    </Text>
+  </View>
+)
+
+const Tabs = () => (
+  <View style={styles.explainer}>
+    <View style={styles.explainerIcon}>
+      <View style={s.flex1} />
+      <FontAwesomeIcon
+        icon={['far', 'clone']}
+        style={[s.black as FontAwesomeIconStyle, s.mb5]}
+        size={36}
+      />
+      <View style={s.flex1} />
+    </View>
+    <Text style={styles.explainerHeading}>Tabs</Text>
+    <Text style={styles.explainerDesc}>
+      Never lose your place! Long-press to open posts and profiles in a new tab.
+    </Text>
+    <Text style={styles.explainerDesc}>
+      <Image source={TABS_EXPLAINER} style={styles.explainerImg} />
+    </Text>
+  </View>
+)
+
+const SCENE_MAP = {
+  intro: Intro,
+  tabs: Tabs,
+}
+const renderScene = SceneMap(SCENE_MAP)
+
+export const FeatureExplainer = () => {
+  const layout = useWindowDimensions()
+  const store = useStores()
+  const [index, setIndex] = useState(0)
+
+  const onPressSkip = () => store.onboard.next()
+  const onPressNext = () => {
+    if (index >= ROUTES.length - 1) {
+      store.onboard.next()
+    } else {
+      setIndex(index + 1)
+    }
+  }
+
+  const renderTabBar = (props: TabBarProps<Route>) => {
+    const inputRange = props.navigationState.routes.map((x, i) => i)
+    return (
+      <View style={styles.tabBar}>
+        <View style={s.flex1} />
+        {props.navigationState.routes.map((route, i) => {
+          const opacity = props.position.interpolate({
+            inputRange,
+            outputRange: inputRange.map(inputIndex =>
+              inputIndex === i ? 1 : 0.5,
+            ),
+          })
+
+          return (
+            <TouchableOpacity
+              key={i}
+              style={styles.tabItem}
+              onPress={() => setIndex(i)}>
+              <Animated.Text style={{opacity}}>&deg;</Animated.Text>
+            </TouchableOpacity>
+          )
+        })}
+        <View style={s.flex1} />
+      </View>
+    )
+  }
+
+  const FirstExplainer = SCENE_MAP[ROUTES[0]?.key as keyof typeof SCENE_MAP]
+  return (
+    <CenteredView style={styles.container}>
+      {ROUTES.length > 1 ? (
+        <TabView
+          navigationState={{index, routes: ROUTES}}
+          renderScene={renderScene}
+          renderTabBar={renderTabBar}
+          onIndexChange={setIndex}
+          initialLayout={{width: layout.width}}
+          tabBarPosition="bottom"
+        />
+      ) : FirstExplainer ? (
+        <FirstExplainer />
+      ) : (
+        <View />
+      )}
+      <View style={styles.footer}>
+        <TouchableOpacity onPress={onPressSkip}>
+          <Text style={styles.footerBtn}>Skip</Text>
+        </TouchableOpacity>
+        <TouchableOpacity onPress={onPressNext}>
+          <Text style={[styles.footerBtn, styles.footerBtnNext]}>Next</Text>
+        </TouchableOpacity>
+      </View>
+    </CenteredView>
+  )
+}
+
+const styles = StyleSheet.create({
+  container: {
+    height: '100%',
+    justifyContent: 'center',
+    paddingBottom: '10%',
+  },
+
+  tabBar: {
+    flexDirection: 'row',
+  },
+  tabItem: {
+    alignItems: 'center',
+    padding: 16,
+  },
+
+  explainer: {
+    paddingHorizontal: 16,
+  },
+  explainerIcon: {
+    flexDirection: 'row',
+  },
+  explainerHeading: {
+    fontSize: 42,
+    fontWeight: 'bold',
+    textAlign: 'center',
+    marginBottom: 16,
+  },
+  explainerHeadingIntro: {
+    lineHeight: 40,
+  },
+  explainerHeadingBrand: {fontSize: 56},
+  explainerDesc: {
+    fontSize: 18,
+    textAlign: 'center',
+    marginBottom: 16,
+    color: colors.gray5,
+  },
+  explainerDescIntro: {fontSize: 24},
+  explainerImg: {
+    resizeMode: 'contain',
+    maxWidth: '100%',
+    maxHeight: 330,
+  },
+
+  footer: {
+    flexDirection: 'row',
+    justifyContent: 'center',
+    paddingTop: 24,
+  },
+  footerBtn: {
+    color: colors.blue3,
+    fontSize: 19,
+    paddingHorizontal: 36,
+    paddingVertical: 8,
+  },
+  footerBtnNext: {
+    marginLeft: 10,
+    borderWidth: 1,
+    borderColor: colors.blue3,
+    borderRadius: 6,
+  },
+})
diff --git a/src/view/com/onboard/Follows.web.tsx b/src/view/com/onboard/Follows.web.tsx
new file mode 100644
index 000000000..50e119e49
--- /dev/null
+++ b/src/view/com/onboard/Follows.web.tsx
@@ -0,0 +1,47 @@
+import React from 'react'
+import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native'
+import {observer} from 'mobx-react-lite'
+import {SuggestedFollows} from '../discover/SuggestedFollows'
+import {CenteredView} from '../util/Views.web'
+import {Text} from '../util/text/Text'
+import {useStores} from '../../../state'
+import {s, colors} from '../../lib/styles'
+
+export const Follows = observer(() => {
+  const store = useStores()
+
+  const onNoSuggestions = () => {
+    // no suggestions, bounce from this view
+    store.onboard.next()
+  }
+  const onPressNext = () => store.onboard.next()
+
+  return (
+    <SafeAreaView style={styles.container}>
+      <CenteredView style={styles.header}>
+        <Text type="title-lg">
+          Follow these people to see their posts in your feed
+        </Text>
+        <TouchableOpacity onPress={onPressNext}>
+          <Text style={[styles.title, s.blue3, s.pr10]}>Next &raquo;</Text>
+        </TouchableOpacity>
+      </CenteredView>
+      <SuggestedFollows onNoSuggestions={onNoSuggestions} />
+    </SafeAreaView>
+  )
+})
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+  },
+  title: {
+    fontSize: 24,
+    fontWeight: 'bold',
+  },
+
+  header: {
+    paddingTop: 30,
+    paddingBottom: 40,
+  },
+})
diff --git a/src/view/screens/Login.web.tsx b/src/view/screens/Login.web.tsx
new file mode 100644
index 000000000..77149090c
--- /dev/null
+++ b/src/view/screens/Login.web.tsx
@@ -0,0 +1,163 @@
+import React, {useState} from 'react'
+import {
+  Image,
+  SafeAreaView,
+  StyleSheet,
+  TouchableOpacity,
+  View,
+} from 'react-native'
+import {observer} from 'mobx-react-lite'
+import {CenteredView} from '../com/util/Views'
+import {Signin} from '../com/login/Signin'
+import {CreateAccount} from '../com/login/CreateAccount'
+import {Text} from '../com/util/text/Text'
+import {ErrorBoundary} from '../com/util/ErrorBoundary'
+import {colors} from '../lib/styles'
+import {usePalette} from '../lib/hooks/usePalette'
+import {CLOUD_SPLASH} from '../lib/assets'
+
+enum ScreenState {
+  S_SigninOrCreateAccount,
+  S_Signin,
+  S_CreateAccount,
+}
+
+const SigninOrCreateAccount = ({
+  onPressSignin,
+  onPressCreateAccount,
+}: {
+  onPressSignin: () => void
+  onPressCreateAccount: () => void
+}) => {
+  const pal = usePalette('default')
+  return (
+    <>
+      <View style={styles.hero}>
+        <View style={styles.heroText}>
+          <Text style={styles.title}>Bluesky</Text>
+          <Text style={styles.subtitle}>[ private beta ]</Text>
+        </View>
+      </View>
+      <View testID="signinOrCreateAccount" style={styles.btns}>
+        <TouchableOpacity
+          testID="createAccountButton"
+          style={[pal.view, styles.btn]}
+          onPress={onPressCreateAccount}>
+          <Text style={[pal.link, styles.btnLabel]}>New account</Text>
+        </TouchableOpacity>
+        <TouchableOpacity
+          testID="signInButton"
+          style={[pal.view, styles.btn]}
+          onPress={onPressSignin}>
+          <Text style={[pal.link, styles.btnLabel]}>Sign in</Text>
+        </TouchableOpacity>
+      </View>
+    </>
+  )
+}
+
+export const Login = observer(() => {
+  const pal = usePalette('default')
+  const [screenState, setScreenState] = useState<ScreenState>(
+    ScreenState.S_SigninOrCreateAccount,
+  )
+
+  if (screenState === ScreenState.S_SigninOrCreateAccount) {
+    return (
+      <CenteredView style={[styles.container, styles.vertCenter]}>
+        <ErrorBoundary>
+          <SigninOrCreateAccount
+            onPressSignin={() => setScreenState(ScreenState.S_Signin)}
+            onPressCreateAccount={() =>
+              setScreenState(ScreenState.S_CreateAccount)
+            }
+          />
+        </ErrorBoundary>
+      </CenteredView>
+    )
+  }
+
+  return (
+    <CenteredView
+      style={[
+        styles.container,
+        styles.containerBorder,
+        pal.view,
+        pal.borderDark,
+      ]}>
+      <SafeAreaView testID="noSessionView" style={styles.container}>
+        <ErrorBoundary>
+          {screenState === ScreenState.S_Signin ? (
+            <Signin
+              onPressBack={() =>
+                setScreenState(ScreenState.S_SigninOrCreateAccount)
+              }
+            />
+          ) : undefined}
+          {screenState === ScreenState.S_CreateAccount ? (
+            <CreateAccount
+              onPressBack={() =>
+                setScreenState(ScreenState.S_SigninOrCreateAccount)
+              }
+            />
+          ) : undefined}
+        </ErrorBoundary>
+      </SafeAreaView>
+    </CenteredView>
+  )
+})
+
+const styles = StyleSheet.create({
+  container: {
+    height: '100%',
+  },
+  containerBorder: {
+    borderLeftWidth: 1,
+    borderRightWidth: 1,
+  },
+  vertCenter: {
+    justifyContent: 'center',
+  },
+  bgImg: {
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    width: '100%',
+    height: '100%',
+  },
+  heroText: {
+    backgroundColor: colors.white,
+    paddingTop: 10,
+    paddingBottom: 20,
+  },
+  btns: {
+    flexDirection: 'row',
+    paddingTop: 40,
+  },
+  title: {
+    textAlign: 'center',
+    color: colors.blue3,
+    fontSize: 68,
+    fontWeight: 'bold',
+  },
+  subtitle: {
+    textAlign: 'center',
+    color: colors.blue3,
+    fontSize: 18,
+  },
+  btn: {
+    flex: 1,
+    borderRadius: 4,
+    paddingVertical: 16,
+    marginBottom: 20,
+    marginHorizontal: 20,
+    borderWidth: 1,
+    borderColor: colors.blue3,
+  },
+  btnLabel: {
+    textAlign: 'center',
+    fontSize: 21,
+    fontWeight: '500',
+    color: colors.blue3,
+  },
+})
diff --git a/src/view/shell/web/index.tsx b/src/view/shell/web/index.tsx
index fedc9c3d6..1059aa280 100644
--- a/src/view/shell/web/index.tsx
+++ b/src/view/shell/web/index.tsx
@@ -5,6 +5,7 @@ import {useStores} from '../../../state'
 import {match, MatchResult} from '../../routes'
 import {DesktopLeftColumn} from './left-column'
 import {DesktopRightColumn} from './right-column'
+import {Onboard} from '../../screens/Onboard'
 import {Login} from '../../screens/Login'
 import {ErrorBoundary} from '../../com/util/ErrorBoundary'
 import {usePalette} from '../../lib/hooks/usePalette'
@@ -22,6 +23,15 @@ export const WebShell: React.FC = observer(() => {
       </View>
     )
   }
+  if (store.onboard.isOnboarding) {
+    return (
+      <View style={styles.outerContainer}>
+        <ErrorBoundary>
+          <Onboard />
+        </ErrorBoundary>
+      </View>
+    )
+  }
 
   return (
     <View style={[styles.outerContainer, pal.view]}>
@@ -104,12 +114,6 @@ const styles = StyleSheet.create({
   outerContainer: {
     height: '100%',
   },
-  innerContainer: {
-    marginLeft: 'auto',
-    marginRight: 'auto',
-    width: '600px',
-    height: '100%',
-  },
   visible: {
     display: 'flex',
   },