about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/util/Picker.tsx155
-rw-r--r--src/view/index.ts12
-rw-r--r--src/view/lib/styles.ts1
-rw-r--r--src/view/screens/Login.tsx492
-rw-r--r--src/view/shell/mobile/index.tsx37
5 files changed, 543 insertions, 154 deletions
diff --git a/src/view/com/util/Picker.tsx b/src/view/com/util/Picker.tsx
new file mode 100644
index 000000000..a02daef15
--- /dev/null
+++ b/src/view/com/util/Picker.tsx
@@ -0,0 +1,155 @@
+import React, {useRef} from 'react'
+import {
+  StyleProp,
+  StyleSheet,
+  Text,
+  TextStyle,
+  TouchableOpacity,
+  TouchableWithoutFeedback,
+  View,
+  ViewStyle,
+} from 'react-native'
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
+import RootSiblings from 'react-native-root-siblings'
+import {colors} from '../../lib/styles'
+
+interface PickerItem {
+  value: string
+  label: string
+}
+
+interface PickerOpts {
+  style?: StyleProp<ViewStyle>
+  labelStyle?: StyleProp<TextStyle>
+  iconStyle?: FontAwesomeIconStyle
+  items: PickerItem[]
+  value: string
+  onChange: (value: string) => void
+  enabled?: boolean
+}
+
+const MENU_WIDTH = 200
+
+export function Picker({
+  style,
+  labelStyle,
+  iconStyle,
+  items,
+  value,
+  onChange,
+  enabled,
+}: PickerOpts) {
+  const ref = useRef<View>(null)
+  const valueLabel = items.find(item => item.value === value)?.label || value
+  const onPress = () => {
+    if (!enabled) {
+      return
+    }
+    ref.current?.measure(
+      (
+        _x: number,
+        _y: number,
+        width: number,
+        height: number,
+        pageX: number,
+        pageY: number,
+      ) => {
+        createDropdownMenu(pageX, pageY + height, MENU_WIDTH, items, onChange)
+      },
+    )
+  }
+  return (
+    <TouchableWithoutFeedback onPress={onPress}>
+      <View style={[styles.outer, style]} ref={ref}>
+        <View style={styles.label}>
+          <Text style={labelStyle}>{valueLabel}</Text>
+        </View>
+        <FontAwesomeIcon icon="angle-down" style={[styles.icon, iconStyle]} />
+      </View>
+    </TouchableWithoutFeedback>
+  )
+}
+
+function createDropdownMenu(
+  x: number,
+  y: number,
+  width: number,
+  items: PickerItem[],
+  onChange: (value: string) => void,
+): RootSiblings {
+  const onPressItem = (index: number) => {
+    sibling.destroy()
+    onChange(items[index].value)
+  }
+  const onOuterPress = () => sibling.destroy()
+  const sibling = new RootSiblings(
+    (
+      <>
+        <TouchableWithoutFeedback onPress={onOuterPress}>
+          <View style={styles.bg} />
+        </TouchableWithoutFeedback>
+        <View style={[styles.menu, {left: x, top: y, width}]}>
+          {items.map((item, index) => (
+            <TouchableOpacity
+              key={index}
+              style={[styles.menuItem, index !== 0 && styles.menuItemBorder]}
+              onPress={() => onPressItem(index)}>
+              <Text style={styles.menuItemLabel}>{item.label}</Text>
+            </TouchableOpacity>
+          ))}
+        </View>
+      </>
+    ),
+  )
+  return sibling
+}
+
+const styles = StyleSheet.create({
+  outer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+  },
+  label: {
+    marginRight: 5,
+  },
+  icon: {},
+  bg: {
+    position: 'absolute',
+    top: 0,
+    right: 0,
+    bottom: 0,
+    left: 0,
+    backgroundColor: '#000',
+    opacity: 0.1,
+  },
+  menu: {
+    position: 'absolute',
+    backgroundColor: '#fff',
+    borderRadius: 14,
+    opacity: 1,
+    paddingVertical: 6,
+  },
+  menuItem: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingVertical: 6,
+    paddingLeft: 15,
+    paddingRight: 30,
+  },
+  menuItemBorder: {
+    borderTopWidth: 1,
+    borderTopColor: colors.gray2,
+    marginTop: 4,
+    paddingTop: 12,
+  },
+  menuItemIcon: {
+    marginLeft: 6,
+    marginRight: 8,
+  },
+  menuItemLabel: {
+    fontSize: 15,
+  },
+})
diff --git a/src/view/index.ts b/src/view/index.ts
index fb26844c7..c7577436f 100644
--- a/src/view/index.ts
+++ b/src/view/index.ts
@@ -1,5 +1,6 @@
 import {library} from '@fortawesome/fontawesome-svg-core'
 
+import {faAngleDown} from '@fortawesome/free-solid-svg-icons/faAngleDown'
 import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft'
 import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight'
 import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft'
@@ -7,6 +8,7 @@ import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons'
 import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket'
 import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare'
 import {faArrowsRotate} from '@fortawesome/free-solid-svg-icons/faArrowsRotate'
+import {faAt} from '@fortawesome/free-solid-svg-icons/faAt'
 import {faBars} from '@fortawesome/free-solid-svg-icons/faBars'
 import {faBell} from '@fortawesome/free-solid-svg-icons/faBell'
 import {faBell as farBell} from '@fortawesome/free-regular-svg-icons/faBell'
@@ -16,12 +18,15 @@ import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck'
 import {faClone} from '@fortawesome/free-regular-svg-icons/faClone'
 import {faComment} from '@fortawesome/free-regular-svg-icons/faComment'
 import {faEllipsis} from '@fortawesome/free-solid-svg-icons/faEllipsis'
+import {faEnvelope} from '@fortawesome/free-solid-svg-icons/faEnvelope'
 import {faExclamation} from '@fortawesome/free-solid-svg-icons/faExclamation'
 import {faGear} from '@fortawesome/free-solid-svg-icons/faGear'
+import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe'
 import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart'
 import {faHeart as fasHeart} from '@fortawesome/free-solid-svg-icons/faHeart'
 import {faHouse} from '@fortawesome/free-solid-svg-icons/faHouse'
 import {faLink} from '@fortawesome/free-solid-svg-icons/faLink'
+import {faLock} from '@fortawesome/free-solid-svg-icons/faLock'
 import {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass'
 import {faMessage} from '@fortawesome/free-regular-svg-icons/faMessage'
 import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib'
@@ -32,10 +37,12 @@ import {faShield} from '@fortawesome/free-solid-svg-icons/faShield'
 import {faRetweet} from '@fortawesome/free-solid-svg-icons/faRetweet'
 import {faUser} from '@fortawesome/free-regular-svg-icons/faUser'
 import {faUsers} from '@fortawesome/free-solid-svg-icons/faUsers'
+import {faTicket} from '@fortawesome/free-solid-svg-icons/faTicket'
 import {faX} from '@fortawesome/free-solid-svg-icons/faX'
 
 export function setup() {
   library.add(
+    faAngleDown,
     faAngleLeft,
     faAngleRight,
     faArrowLeft,
@@ -43,6 +50,7 @@ export function setup() {
     faArrowUpFromBracket,
     faArrowUpRightFromSquare,
     faArrowsRotate,
+    faAt,
     faBars,
     faBell,
     farBell,
@@ -52,12 +60,15 @@ export function setup() {
     faClone,
     faComment,
     faEllipsis,
+    faEnvelope,
     faExclamation,
     faGear,
+    faGlobe,
     faHeart,
     fasHeart,
     faHouse,
     faLink,
+    faLock,
     faMagnifyingGlass,
     faMessage,
     faPenNib,
@@ -68,6 +79,7 @@ export function setup() {
     faShield,
     faUser,
     faUsers,
+    faTicket,
     faX,
   )
 }
diff --git a/src/view/lib/styles.ts b/src/view/lib/styles.ts
index 9cc776020..e3f3edb69 100644
--- a/src/view/lib/styles.ts
+++ b/src/view/lib/styles.ts
@@ -11,6 +11,7 @@ export const colors = {
   gray4: '#968d8d',
   gray5: '#645454',
 
+  blue0: '#bfe1ff',
   blue1: '#8bc7fd',
   blue2: '#52acfe',
   blue3: '#0085ff',
diff --git a/src/view/screens/Login.tsx b/src/view/screens/Login.tsx
index 73a49fb87..91df7015d 100644
--- a/src/view/screens/Login.tsx
+++ b/src/view/screens/Login.tsx
@@ -1,4 +1,4 @@
-import React, {useState} from 'react'
+import React, {useState, useEffect} from 'react'
 import {
   ActivityIndicator,
   KeyboardAvoidingView,
@@ -11,85 +11,69 @@ import {
 } from 'react-native'
 import Svg, {Circle, Line, Text as SvgText} from 'react-native-svg'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import * as EmailValidator from 'email-validator'
 import {observer} from 'mobx-react-lite'
+import {Picker} from '../com/util/Picker'
 import {s, colors} from '../lib/styles'
 import {useStores} from '../../state'
+import {ServiceDescription} from '../../state/models/session'
 
 enum ScreenState {
   SigninOrCreateAccount,
   Signin,
+  CreateAccount,
+}
+
+const Logo = () => {
+  return (
+    <View style={styles.logo}>
+      <Svg width="100" height="100">
+        <Circle
+          cx="50"
+          cy="50"
+          r="46"
+          fill="none"
+          stroke="white"
+          strokeWidth={2}
+        />
+        <Line stroke="white" strokeWidth={1} x1="30" x2="30" y1="0" y2="100" />
+        <Line stroke="white" strokeWidth={1} x1="74" x2="74" y1="0" y2="100" />
+        <Line stroke="white" strokeWidth={1} x1="0" x2="100" y1="22" y2="22" />
+        <Line stroke="white" strokeWidth={1} x1="0" x2="100" y1="74" y2="74" />
+        <SvgText
+          fill="none"
+          stroke="white"
+          strokeWidth={2}
+          fontSize="60"
+          fontWeight="bold"
+          x="52"
+          y="70"
+          textAnchor="middle">
+          B
+        </SvgText>
+      </Svg>
+    </View>
+  )
 }
 
 const SigninOrCreateAccount = ({
   onPressSignin,
+  onPressCreateAccount,
 }: {
   onPressSignin: () => void
+  onPressCreateAccount: () => void
 }) => {
   const winDim = useWindowDimensions()
   const halfWidth = winDim.width / 2
   return (
     <>
       <View style={styles.hero}>
-        <View style={styles.logo}>
-          <Svg width="100" height="100">
-            <Circle
-              cx="50"
-              cy="50"
-              r="46"
-              fill="none"
-              stroke="white"
-              strokeWidth={2}
-            />
-            <Line
-              stroke="white"
-              strokeWidth={1}
-              x1="30"
-              x2="30"
-              y1="0"
-              y2="100"
-            />
-            <Line
-              stroke="white"
-              strokeWidth={1}
-              x1="74"
-              x2="74"
-              y1="0"
-              y2="100"
-            />
-            <Line
-              stroke="white"
-              strokeWidth={1}
-              x1="0"
-              x2="100"
-              y1="22"
-              y2="22"
-            />
-            <Line
-              stroke="white"
-              strokeWidth={1}
-              x1="0"
-              x2="100"
-              y1="74"
-              y2="74"
-            />
-            <SvgText
-              fill="none"
-              stroke="white"
-              strokeWidth={2}
-              fontSize="60"
-              fontWeight="bold"
-              x="52"
-              y="70"
-              textAnchor="middle">
-              B
-            </SvgText>
-          </Svg>
-        </View>
+        <Logo />
         <Text style={styles.title}>Bluesky</Text>
         <Text style={styles.subtitle}>[ private beta ]</Text>
       </View>
       <View style={s.flex1}>
-        <TouchableOpacity style={styles.btn}>
+        <TouchableOpacity style={styles.btn} onPress={onPressCreateAccount}>
           <Text style={styles.btnLabel}>Create a new account</Text>
         </TouchableOpacity>
         <View style={styles.or}>
@@ -155,31 +139,221 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
 
   return (
     <KeyboardAvoidingView behavior="padding" style={{flex: 1}}>
-      <View style={styles.smallHero}>
-        <Text style={styles.title}>Bluesky</Text>
-        <Text style={styles.subtitle}>[ private beta ]</Text>
+      <View style={styles.logoHero}>
+        <Logo />
       </View>
-      <View style={s.flex1}>
-        <View style={styles.group}>
-          <View style={styles.groupTitle}>
-            <Text style={[s.white, s.f18]}>Sign in</Text>
+      <View style={styles.group}>
+        <View style={styles.groupTitle}>
+          <Text style={[s.white, s.f18, s.bold]}>Sign in</Text>
+        </View>
+        {error ? (
+          <View style={styles.error}>
+            <View style={styles.errorIcon}>
+              <FontAwesomeIcon icon="exclamation" style={s.white} size={10} />
+            </View>
+            <View style={s.flex1}>
+              <Text style={[s.white, s.bold]}>{error}</Text>
+            </View>
+          </View>
+        ) : undefined}
+        <View style={styles.groupContent}>
+          <FontAwesomeIcon icon="envelope" style={styles.groupContentIcon} />
+          <TextInput
+            style={styles.textInput}
+            placeholder="Email or username"
+            placeholderTextColor={colors.blue0}
+            autoCapitalize="none"
+            autoFocus
+            value={username}
+            onChangeText={setUsername}
+            editable={!isProcessing}
+          />
+        </View>
+        <View style={styles.groupContent}>
+          <FontAwesomeIcon icon="lock" style={styles.groupContentIcon} />
+          <TextInput
+            style={styles.textInput}
+            placeholder="Password"
+            placeholderTextColor={colors.blue0}
+            autoCapitalize="none"
+            secureTextEntry
+            value={password}
+            onChangeText={setPassword}
+            editable={!isProcessing}
+          />
+        </View>
+      </View>
+      <View style={[s.flexRow, s.pl20, s.pr20]}>
+        <TouchableOpacity onPress={onPressBack}>
+          <Text style={[s.white, s.f18, s.pl5]}>Back</Text>
+        </TouchableOpacity>
+        <View style={s.flex1} />
+        <TouchableOpacity onPress={onPressNext}>
+          {isProcessing ? (
+            <ActivityIndicator color="#fff" />
+          ) : (
+            <Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text>
+          )}
+        </TouchableOpacity>
+      </View>
+    </KeyboardAvoidingView>
+  )
+}
+
+const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
+  const store = useStores()
+  const [isProcessing, setIsProcessing] = useState<boolean>(false)
+  const [error, setError] = useState<string>('')
+  const [serviceDescription, setServiceDescription] = useState<
+    ServiceDescription | undefined
+  >(undefined)
+  const [userDomain, setUserDomain] = useState<string>('')
+  const [inviteCode, setInviteCode] = useState<string>('')
+  const [email, setEmail] = useState<string>('')
+  const [password, setPassword] = useState<string>('')
+  const [username, setUsername] = useState<string>('')
+
+  useEffect(() => {
+    if (serviceDescription || error) {
+      return
+    }
+    store.session.describeService('http://localhost:2583/').then(
+      desc => {
+        setServiceDescription(desc)
+        setUserDomain(desc.availableUserDomains[0])
+      },
+      err => {
+        console.error(err)
+        setError(
+          'Unable to contact your service. Please check your Internet connection.',
+        )
+      },
+    )
+  }, [])
+
+  const onPressNext = async () => {
+    if (!email) {
+      return setError('Please enter your email.')
+    }
+    if (!EmailValidator.validate(email)) {
+      return setError('Your email appears to be invalid.')
+    }
+    if (!password) {
+      return setError('Please choose your password.')
+    }
+    if (!username) {
+      return setError('Please choose your username.')
+    }
+    setError('')
+    setIsProcessing(true)
+    try {
+      await store.session.createAccount({
+        service: 'http://localhost:2583/',
+        email,
+        username: `${username}.${userDomain}`,
+        password,
+        inviteCode,
+      })
+    } catch (e: any) {
+      const errMsg = e.toString()
+      console.log(e)
+      setIsProcessing(false)
+      // if (errMsg.includes('Authentication Required')) {
+      //   setError('Invalid username or password')
+      // } else if (errMsg.includes('Network request failed')) {
+      //   setError(
+      //     'Unable to contact your service. Please check your Internet connection.',
+      //   )
+      // } else {
+      setError(errMsg.replace(/^Error:/, ''))
+      // }
+    }
+  }
+
+  const InitialLoadView = () => (
+    <>
+      {error ? (
+        <>
+          <View style={[styles.error, styles.errorFloating]}>
+            <View style={styles.errorIcon}>
+              <FontAwesomeIcon icon="exclamation" style={s.white} size={10} />
+            </View>
+            <View style={s.flex1}>
+              <Text style={[s.white, s.bold]}>{error}</Text>
+            </View>
           </View>
-          <View style={styles.groupContent}>
-            <View style={[s.mb5]}>
+          <View style={[s.flexRow, s.pl20, s.pr20]}>
+            <TouchableOpacity onPress={onPressBack}>
+              <Text style={[s.white, s.f18, s.pl5]}>Back</Text>
+            </TouchableOpacity>
+          </View>
+        </>
+      ) : (
+        <ActivityIndicator color="#fff" />
+      )}
+    </>
+  )
+
+  return (
+    <KeyboardAvoidingView behavior="padding" style={{flex: 1}}>
+      <View style={styles.logoHero}>
+        <Logo />
+      </View>
+      {serviceDescription ? (
+        <>
+          {error ? (
+            <View style={[styles.error, styles.errorFloating]}>
+              <View style={styles.errorIcon}>
+                <FontAwesomeIcon icon="exclamation" style={s.white} size={10} />
+              </View>
+              <View style={s.flex1}>
+                <Text style={[s.white, s.bold]}>{error}</Text>
+              </View>
+            </View>
+          ) : undefined}
+          <View style={styles.group}>
+            <View style={styles.groupTitle}>
+              <Text style={[s.white, s.f18, s.bold]}>Create a new account</Text>
+            </View>
+            {serviceDescription?.inviteCodeRequired ? (
+              <View style={styles.groupContent}>
+                <FontAwesomeIcon
+                  icon="ticket"
+                  style={styles.groupContentIcon}
+                />
+                <TextInput
+                  style={[styles.textInput]}
+                  placeholder="Invite code"
+                  placeholderTextColor={colors.blue0}
+                  autoCapitalize="none"
+                  autoFocus
+                  value={inviteCode}
+                  onChangeText={setInviteCode}
+                  editable={!isProcessing}
+                />
+              </View>
+            ) : undefined}
+            <View style={styles.groupContent}>
+              <FontAwesomeIcon
+                icon="envelope"
+                style={styles.groupContentIcon}
+              />
               <TextInput
-                style={styles.textInput}
-                placeholder="Email or username"
+                style={[styles.textInput]}
+                placeholder="Email address"
+                placeholderTextColor={colors.blue0}
                 autoCapitalize="none"
-                autoFocus
-                value={username}
-                onChangeText={setUsername}
+                value={email}
+                onChangeText={setEmail}
                 editable={!isProcessing}
               />
             </View>
-            <View style={[s.mb5]}>
+            <View style={styles.groupContent}>
+              <FontAwesomeIcon icon="lock" style={styles.groupContentIcon} />
               <TextInput
-                style={styles.textInput}
-                placeholder="Password"
+                style={[styles.textInput]}
+                placeholder="Choose your password"
+                placeholderTextColor={colors.blue0}
                 autoCapitalize="none"
                 secureTextEntry
                 value={password}
@@ -187,54 +361,94 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
                 editable={!isProcessing}
               />
             </View>
-            {error ? (
-              <View style={styles.error}>
-                <View style={styles.errorIcon}>
-                  <FontAwesomeIcon
-                    icon="exclamation"
-                    style={s.white}
-                    size={10}
-                  />
-                </View>
-                <View style={s.flex1}>
-                  <Text style={[s.white, s.bold]}>{error}</Text>
-                </View>
-              </View>
-            ) : undefined}
           </View>
-        </View>
-        <View style={[s.flexRow, s.pl20, s.pr20]}>
-          <TouchableOpacity onPress={onPressBack}>
-            <Text style={[s.white, s.f18, s.bold, s.pl5]}>Back</Text>
-          </TouchableOpacity>
-          <View style={s.flex1} />
-          <TouchableOpacity onPress={onPressNext}>
-            {isProcessing ? (
-              <ActivityIndicator color="#fff" />
-            ) : (
-              <Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text>
+          <View style={styles.group}>
+            <View style={styles.groupTitle}>
+              <Text style={[s.white, s.f18, s.bold]}>Choose your username</Text>
+            </View>
+            <View style={styles.groupContent}>
+              <FontAwesomeIcon icon="at" style={styles.groupContentIcon} />
+              <TextInput
+                style={[styles.textInput]}
+                placeholder="eg alice"
+                placeholderTextColor={colors.blue0}
+                autoCapitalize="none"
+                value={username}
+                onChangeText={v => setUsername(cleanUsername(v))}
+                editable={!isProcessing}
+              />
+            </View>
+            {serviceDescription.availableUserDomains.length > 1 && (
+              <View style={styles.groupContent}>
+                <FontAwesomeIcon icon="globe" style={styles.groupContentIcon} />
+                <Picker
+                  style={styles.picker}
+                  labelStyle={styles.pickerLabel}
+                  iconStyle={styles.pickerIcon}
+                  value={userDomain}
+                  items={serviceDescription.availableUserDomains.map(d => ({
+                    label: `.${d}`,
+                    value: d,
+                  }))}
+                  onChange={itemValue => setUserDomain(itemValue)}
+                  enabled={!isProcessing}
+                />
+              </View>
             )}
-          </TouchableOpacity>
-        </View>
-      </View>
+            <View style={styles.groupContent}>
+              <Text style={[s.white, s.p10]}>
+                Your full username will be{' '}
+                <Text style={s.bold}>
+                  @{username}.{userDomain}
+                </Text>
+              </Text>
+            </View>
+          </View>
+          <View style={[s.flexRow, s.pl20, s.pr20]}>
+            <TouchableOpacity onPress={onPressBack}>
+              <Text style={[s.white, s.f18, s.pl5]}>Back</Text>
+            </TouchableOpacity>
+            <View style={s.flex1} />
+            <TouchableOpacity onPress={onPressNext}>
+              {isProcessing ? (
+                <ActivityIndicator color="#fff" />
+              ) : (
+                <Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text>
+              )}
+            </TouchableOpacity>
+          </View>
+        </>
+      ) : (
+        <InitialLoadView />
+      )}
     </KeyboardAvoidingView>
   )
 }
 
+function cleanUsername(v: string): string {
+  v = v.trim()
+  if (v.length > 63) {
+    v = v.slice(0, 63)
+  }
+  return v.toLowerCase().replace(/[^a-z0-9-]/g, '')
+}
+
 export const Login = observer(
   (/*{navigation}: RootTabsScreenProps<'Login'>*/) => {
     // const store = useStores()
     const [screenState, setScreenState] = useState<ScreenState>(
       ScreenState.SigninOrCreateAccount,
     )
-    const onPressSignin = () => {
-      setScreenState(ScreenState.Signin)
-    }
 
     return (
       <View style={styles.outer}>
         {screenState === ScreenState.SigninOrCreateAccount ? (
-          <SigninOrCreateAccount onPressSignin={onPressSignin} />
+          <SigninOrCreateAccount
+            onPressSignin={() => setScreenState(ScreenState.Signin)}
+            onPressCreateAccount={() =>
+              setScreenState(ScreenState.CreateAccount)
+            }
+          />
         ) : undefined}
         {screenState === ScreenState.Signin ? (
           <Signin
@@ -243,6 +457,13 @@ export const Login = observer(
             }
           />
         ) : undefined}
+        {screenState === ScreenState.CreateAccount ? (
+          <CreateAccount
+            onPressBack={() =>
+              setScreenState(ScreenState.SigninOrCreateAccount)
+            }
+          />
+        ) : undefined}
       </View>
     )
   },
@@ -256,9 +477,9 @@ const styles = StyleSheet.create({
     flex: 2,
     justifyContent: 'center',
   },
-  smallHero: {
-    flex: 1,
-    justifyContent: 'center',
+  logoHero: {
+    paddingTop: 30,
+    paddingBottom: 40,
   },
   logo: {
     flexDirection: 'row',
@@ -282,6 +503,7 @@ const styles = StyleSheet.create({
     paddingVertical: 16,
     marginBottom: 20,
     marginHorizontal: 20,
+    backgroundColor: colors.blue3,
   },
   btnLabel: {
     textAlign: 'center',
@@ -307,33 +529,65 @@ const styles = StyleSheet.create({
     borderRadius: 10,
     marginBottom: 20,
     marginHorizontal: 20,
+    backgroundColor: colors.blue3,
   },
   groupTitle: {
     paddingVertical: 8,
     paddingHorizontal: 12,
-    borderBottomWidth: 1,
-    borderBottomColor: colors.blue1,
   },
   groupContent: {
-    paddingVertical: 8,
-    paddingHorizontal: 12,
+    borderTopWidth: 1,
+    borderTopColor: colors.blue1,
+    flexDirection: 'row',
+    alignItems: 'center',
+  },
+  groupContentIcon: {
+    color: 'white',
+    marginLeft: 10,
   },
   textInput: {
+    flex: 1,
     width: '100%',
-    backgroundColor: colors.white,
-    paddingHorizontal: 8,
-    paddingVertical: 8,
-    borderRadius: 4,
+    backgroundColor: colors.blue3,
+    color: colors.white,
+    paddingVertical: 10,
+    paddingHorizontal: 12,
+    fontSize: 18,
+    borderRadius: 10,
+  },
+  picker: {
+    flex: 1,
+    width: '100%',
+    backgroundColor: colors.blue3,
+    color: colors.white,
+    paddingVertical: 10,
+    paddingHorizontal: 12,
+    fontSize: 18,
+    borderRadius: 10,
+  },
+  pickerLabel: {
+    color: colors.white,
     fontSize: 18,
   },
+  pickerIcon: {
+    color: colors.white,
+  },
   error: {
+    borderTopWidth: 1,
+    borderTopColor: colors.blue1,
     flexDirection: 'row',
     alignItems: 'center',
     marginTop: 5,
-    backgroundColor: colors.purple3,
+    backgroundColor: colors.blue2,
     paddingHorizontal: 8,
     paddingVertical: 5,
-    borderRadius: 4,
+  },
+  errorFloating: {
+    borderWidth: 1,
+    borderColor: colors.blue1,
+    marginBottom: 20,
+    marginHorizontal: 20,
+    borderRadius: 8,
   },
   errorIcon: {
     borderWidth: 1,
diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx
index 2e0e1b36b..d289b126d 100644
--- a/src/view/shell/mobile/index.tsx
+++ b/src/view/shell/mobile/index.tsx
@@ -12,7 +12,6 @@ import {
 } from 'react-native'
 import {ScreenContainer, Screen} from 'react-native-screens'
 import LinearGradient from 'react-native-linear-gradient'
-// import Svg, {Polygon} from 'react-native-svg'
 import {GestureDetector, Gesture} from 'react-native-gesture-handler'
 import Animated, {
   useSharedValue,
@@ -33,7 +32,7 @@ import {LocationNavigator} from './location-navigator'
 import {createBackMenu, createForwardMenu} from './history-menu'
 import {createAccountsMenu} from './accounts-menu'
 import {createLocationMenu} from './location-menu'
-import {s, colors, gradients} from '../../lib/styles'
+import {s, colors} from '../../lib/styles'
 import {AVIS} from '../../lib/assets'
 
 const locationIconNeedsNudgeUp = (icon: IconProp) => icon === 'house'
@@ -174,40 +173,8 @@ export const MobileShell: React.FC = observer(() => {
       <LinearGradient
         colors={['#007CFF', '#00BCFF']}
         start={{x: 0, y: 0.8}}
-        end={{x: 1, y: 1}}
+        end={{x: 0, y: 1}}
         style={styles.outerContainer}>
-        {
-          undefined /* TODO want this? <Svg height={winDim.height} width={winDim.width} style={s.absolute}>
-          <Polygon
-            points={`
-            ${winDim.width},0
-            ${winDim.width - 250},0
-            0,${winDim.height - 140}
-            0,${winDim.height}
-            ${winDim.width},${winDim.height}`}
-            fill="#fff"
-            fillOpacity="0.04"
-          />
-          <Polygon
-            points={`
-            ${winDim.width},0
-            ${winDim.width - 100},0
-            0,${winDim.height - 60}
-            0,${winDim.height}
-            ${winDim.width},${winDim.height}`}
-            fill="#fff"
-            fillOpacity="0.04"
-          />
-          <Polygon
-            points={`
-            ${winDim.width},100
-            0,${winDim.height}
-            ${winDim.width},${winDim.height}`}
-            fill="#fff"
-            fillOpacity="0.04"
-          />
-    </Svg>*/
-        }
         <SafeAreaView style={styles.innerContainer}>
           <Login />
         </SafeAreaView>