Implement signin flow
This commit is contained in:
parent
2e352f383e
commit
0208302907
19 changed files with 652 additions and 300 deletions
|
@ -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%'},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
},
|
||||
)
|
|
@ -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}>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue