Implement signin flow

This commit is contained in:
Paul Frazee 2022-09-26 21:03:07 -05:00
parent 2e352f383e
commit 0208302907
19 changed files with 652 additions and 300 deletions

View file

@ -102,24 +102,32 @@ export const s = StyleSheet.create({
p2: {padding: 2},
p5: {padding: 5},
p10: {padding: 10},
p20: {padding: 20},
pr2: {paddingRight: 2},
pr5: {paddingRight: 5},
pr10: {paddingRight: 10},
pr20: {paddingRight: 20},
pl2: {paddingLeft: 2},
pl5: {paddingLeft: 5},
pl10: {paddingLeft: 10},
pl20: {paddingLeft: 20},
pt2: {paddingTop: 2},
pt5: {paddingTop: 5},
pt10: {paddingTop: 10},
pt20: {paddingTop: 20},
pb2: {paddingBottom: 2},
pb5: {paddingBottom: 5},
pb10: {paddingBottom: 10},
pb20: {paddingBottom: 20},
// flex
flexRow: {flexDirection: 'row'},
flexCol: {flexDirection: 'column'},
flex1: {flex: 1},
// position
absolute: {position: 'absolute'},
// dimensions
w100pct: {width: '100%'},
h100pct: {height: '100%'},

View file

@ -3,8 +3,6 @@ import {IconProp} from '@fortawesome/fontawesome-svg-core'
import {Home} from './screens/Home'
import {Search} from './screens/Search'
import {Notifications} from './screens/Notifications'
import {Login} from './screens/Login'
import {Signup} from './screens/Signup'
import {NotFound} from './screens/NotFound'
import {PostThread} from './screens/PostThread'
import {PostLikedBy} from './screens/PostLikedBy'
@ -47,8 +45,6 @@ export const routes: Route[] = [
'retweet',
r('/profile/(?<name>[^/]+)/post/(?<recordKey>[^/]+)/reposted-by'),
],
[Login, ['far', 'user'], r('/login')],
[Signup, ['far', 'user'], r('/signup')],
]
export function match(url: string): MatchResult {

View file

@ -1,27 +1,287 @@
import React from 'react'
import {Text, View} from 'react-native'
import React, {useState} from 'react'
import {
ActivityIndicator,
KeyboardAvoidingView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
useWindowDimensions,
} from 'react-native'
import Svg, {Line} from 'react-native-svg'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {observer} from 'mobx-react-lite'
// import {useStores} from '../../state'
import {s, colors} from '../lib/styles'
import {useStores} from '../../state'
enum ScreenState {
SigninOrCreateAccount,
Signin,
}
const SigninOrCreateAccount = ({
onPressSignin,
}: {
onPressSignin: () => void
}) => {
const winDim = useWindowDimensions()
const halfWidth = winDim.width / 2
return (
<>
<View style={styles.hero}>
<Text style={styles.title}>Bluesky</Text>
<Text style={styles.subtitle}>[ private beta ]</Text>
</View>
<View style={s.flex1}>
<TouchableOpacity style={styles.btn}>
<Text style={styles.btnLabel}>Create a new account</Text>
</TouchableOpacity>
<View style={styles.or}>
<Svg height="1" width={winDim.width} style={styles.orLine}>
<Line
x1="30"
y1="0"
x2={halfWidth - 20}
y2="0"
stroke="white"
strokeWidth="1"
/>
<Line
x1={halfWidth + 20}
y1="0"
x2={winDim.width - 30}
y2="0"
stroke="white"
strokeWidth="1"
/>
</Svg>
<Text style={styles.orLabel}>or</Text>
</View>
<TouchableOpacity style={styles.btn} onPress={onPressSignin}>
<Text style={styles.btnLabel}>Sign in</Text>
</TouchableOpacity>
</View>
</>
)
}
const Signin = ({onPressBack}: {onPressBack: () => void}) => {
const store = useStores()
const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [error, setError] = useState<string>('')
const [username, setUsername] = useState<string>('')
const [password, setPassword] = useState<string>('')
const onPressNext = async () => {
setError('')
setIsProcessing(true)
try {
await store.session.login({
service: 'http://localhost:2583/',
username,
password,
})
} catch (e: any) {
const errMsg = e.toString()
console.log(e)
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:/, ''))
}
} finally {
setIsProcessing(false)
}
}
return (
<KeyboardAvoidingView behavior="padding" style={{flex: 1}}>
<View style={styles.hero}>
<Text style={styles.title}>Bluesky</Text>
<Text style={styles.subtitle}>[ private beta ]</Text>
</View>
<View style={s.flex1}>
<View style={styles.group}>
<View style={styles.groupTitle}>
<Text style={[s.white, s.f18]}>Sign in</Text>
</View>
<View style={styles.groupContent}>
<View style={[s.mb5]}>
<TextInput
style={styles.textInput}
placeholder="Email or username"
autoCapitalize="none"
autoFocus
value={username}
onChangeText={setUsername}
editable={!isProcessing}
/>
</View>
<View style={[s.mb5]}>
<TextInput
style={styles.textInput}
placeholder="Password"
autoCapitalize="none"
secureTextEntry
value={password}
onChangeText={setPassword}
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>
)}
</TouchableOpacity>
</View>
</View>
</KeyboardAvoidingView>
)
}
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={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Sign In</Text>
{/*store.session.uiError && <Text>{store.session.uiError}</Text>}
{!store.session.uiIsProcessing ? (
<>
<Button title="Login" onPress={() => store.session.login()} />
<Button
title="Sign Up"
onPress={() => navigation.navigate('Signup')}
/>
</>
) : (
<ActivityIndicator />
)*/}
<View style={styles.outer}>
{screenState === ScreenState.SigninOrCreateAccount ? (
<SigninOrCreateAccount onPressSignin={onPressSignin} />
) : undefined}
{screenState === ScreenState.Signin ? (
<Signin
onPressBack={() =>
setScreenState(ScreenState.SigninOrCreateAccount)
}
/>
) : undefined}
</View>
)
},
)
const styles = StyleSheet.create({
outer: {
flex: 1,
},
hero: {
flex: 1,
justifyContent: 'center',
},
title: {
textAlign: 'center',
color: colors.white,
fontSize: 68,
fontWeight: 'bold',
},
subtitle: {
textAlign: 'center',
color: colors.white,
fontSize: 18,
},
btn: {
borderWidth: 1,
borderColor: colors.white,
borderRadius: 10,
paddingVertical: 16,
marginBottom: 20,
marginHorizontal: 20,
},
btnLabel: {
textAlign: 'center',
color: colors.white,
fontSize: 18,
fontWeight: 'bold',
},
or: {
marginBottom: 20,
},
orLine: {
position: 'absolute',
top: 10,
},
orLabel: {
textAlign: 'center',
color: colors.white,
fontSize: 16,
},
group: {
borderWidth: 1,
borderColor: colors.white,
borderRadius: 10,
marginBottom: 20,
marginHorizontal: 20,
},
groupTitle: {
paddingVertical: 8,
paddingHorizontal: 12,
borderBottomWidth: 1,
borderBottomColor: colors.blue1,
},
groupContent: {
paddingVertical: 8,
paddingHorizontal: 12,
},
textInput: {
width: '100%',
backgroundColor: colors.white,
paddingHorizontal: 8,
paddingVertical: 8,
borderRadius: 4,
fontSize: 18,
},
error: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 5,
backgroundColor: colors.purple3,
paddingHorizontal: 8,
paddingVertical: 5,
borderRadius: 4,
},
errorIcon: {
borderWidth: 1,
borderColor: colors.white,
color: colors.white,
borderRadius: 30,
width: 16,
height: 16,
alignItems: 'center',
justifyContent: 'center',
marginRight: 5,
},
})

View file

@ -1,30 +0,0 @@
import React from 'react'
import {Text, View} from 'react-native'
import {observer} from 'mobx-react-lite'
// import {useStores} from '../../state'
export const Signup = observer(
(/*{navigation}: RootTabsScreenProps<'Signup'>*/) => {
// const store = useStores()
return (
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Create Account</Text>
{/*store.session.uiError ?? <Text>{store.session.uiError}</Text>}
{!store.session.uiIsProcessing ? (
<>
<Button
title="Create new account"
onPress={() => store.session.login()}
/>
<Button
title="Log in to an existing account"
onPress={() => navigation.navigate('Login')}
/>
</>
) : (
<ActivityIndicator />
)*/}
</View>
)
},
)

View file

@ -1,4 +1,4 @@
import React, {useState, useRef, useEffect} from 'react'
import React, {useState, useEffect} from 'react'
import {observer} from 'mobx-react-lite'
import {
useWindowDimensions,
@ -11,6 +11,8 @@ import {
View,
} 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,
@ -25,12 +27,13 @@ import {useStores} from '../../../state'
import {NavigationModel} from '../../../state/models/navigation'
import {TabsSelectorModel} from '../../../state/models/shell'
import {match, MatchResult} from '../../routes'
import {Login} from '../../screens/Login'
import {Modal} from '../../com/modals/Modal'
import {LocationNavigator} from './location-navigator'
import {createBackMenu, createForwardMenu} from './history-menu'
import {createAccountsMenu} from './accounts-menu'
import {createLocationMenu} from './location-menu'
import {s, colors} from '../../lib/styles'
import {s, colors, gradients} from '../../lib/styles'
import {AVIS} from '../../lib/assets'
const locationIconNeedsNudgeUp = (icon: IconProp) => icon === 'house'
@ -164,6 +167,53 @@ export const MobileShell: React.FC = observer(() => {
opacity: interpolate(swipeGestureInterp.value, [0, 1.0], [0.6, 0.0]),
}))
console.log('authed?', store.session.isAuthed)
if (!store.session.isAuthed) {
return (
<LinearGradient
colors={['#007CFF', '#00BCFF']}
start={{x: 0, y: 0.8}}
end={{x: 1, 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>
</LinearGradient>
)
}
return (
<View style={styles.outerContainer}>
<View style={styles.topBar}>