Bluesky app fork with some witchin' additions 💫

convert base login component and ChooseAccountForm

+363 -330
+6
src/alf/atoms.ts
··· 154 154 align_end: { 155 155 alignItems: 'flex-end', 156 156 }, 157 + align_baseline: { 158 + alignItems: 'baseline', 159 + }, 160 + align_stretch: { 161 + alignItems: 'stretch', 162 + }, 157 163 self_auto: { 158 164 alignSelf: 'auto', 159 165 },
+183
src/screens/Login/ChooseAccountForm.tsx
··· 1 + import React from 'react' 2 + import {ScrollView, TouchableOpacity, View} from 'react-native' 3 + import {Trans, msg} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + import flattenReactChildren from 'react-keyed-flatten-children' 6 + 7 + import {useAnalytics} from 'lib/analytics/analytics' 8 + import {UserAvatar} from '../../view/com/util/UserAvatar' 9 + import {colors} from 'lib/styles' 10 + import {styles} from '../../view/com/auth/login/styles' 11 + import {useSession, useSessionApi, SessionAccount} from '#/state/session' 12 + import {useProfileQuery} from '#/state/queries/profile' 13 + import {useLoggedOutViewControls} from '#/state/shell/logged-out' 14 + import * as Toast from '#/view/com/util/Toast' 15 + import {Button} from '#/components/Button' 16 + import {atoms as a, useTheme} from '#/alf' 17 + import {Text} from '#/components/Typography' 18 + import {ChevronRight_Stroke2_Corner0_Rounded as Chevron} from '#/components/icons/Chevron' 19 + import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 20 + 21 + function Group({children}: {children: React.ReactNode}) { 22 + const t = useTheme() 23 + return ( 24 + <View 25 + style={[ 26 + a.rounded_md, 27 + a.overflow_hidden, 28 + a.border, 29 + t.atoms.border_contrast_low, 30 + ]}> 31 + {flattenReactChildren(children).map((child, i) => { 32 + return React.isValidElement(child) ? ( 33 + <React.Fragment key={i}> 34 + {i > 0 ? ( 35 + <View style={[a.border_b, t.atoms.border_contrast_low]} /> 36 + ) : null} 37 + {React.cloneElement(child, { 38 + // @ts-ignore 39 + style: { 40 + borderRadius: 0, 41 + borderWidth: 0, 42 + }, 43 + })} 44 + </React.Fragment> 45 + ) : null 46 + })} 47 + </View> 48 + ) 49 + } 50 + 51 + function AccountItem({ 52 + account, 53 + onSelect, 54 + isCurrentAccount, 55 + }: { 56 + account: SessionAccount 57 + onSelect: (account: SessionAccount) => void 58 + isCurrentAccount: boolean 59 + }) { 60 + const t = useTheme() 61 + const {_} = useLingui() 62 + const {data: profile} = useProfileQuery({did: account.did}) 63 + 64 + const onPress = React.useCallback(() => { 65 + onSelect(account) 66 + }, [account, onSelect]) 67 + 68 + return ( 69 + <TouchableOpacity 70 + testID={`chooseAccountBtn-${account.handle}`} 71 + key={account.did} 72 + style={[a.flex_1]} 73 + onPress={onPress} 74 + accessibilityRole="button" 75 + accessibilityLabel={_(msg`Sign in as ${account.handle}`)} 76 + accessibilityHint={_(msg`Double tap to sign in`)}> 77 + <View style={[a.flex_1, a.flex_row, a.align_center, {height: 48}]}> 78 + <View style={a.p_md}> 79 + <UserAvatar avatar={profile?.avatar} size={24} /> 80 + </View> 81 + <Text style={[a.align_baseline, a.flex_1, a.flex_row, a.py_sm]}> 82 + <Text style={[a.font_bold]}> 83 + {profile?.displayName || account.handle}{' '} 84 + </Text> 85 + <Text style={[t.atoms.text_contrast_medium]}>{account.handle}</Text> 86 + </Text> 87 + {isCurrentAccount ? ( 88 + <Check size="sm" style={[{color: colors.green3}, a.mr_md]} /> 89 + ) : ( 90 + <Chevron size="sm" style={[t.atoms.text, a.mr_md]} /> 91 + )} 92 + </View> 93 + </TouchableOpacity> 94 + ) 95 + } 96 + export const ChooseAccountForm = ({ 97 + onSelectAccount, 98 + onPressBack, 99 + }: { 100 + onSelectAccount: (account?: SessionAccount) => void 101 + onPressBack: () => void 102 + }) => { 103 + const {track, screen} = useAnalytics() 104 + const {_} = useLingui() 105 + const t = useTheme() 106 + const {accounts, currentAccount} = useSession() 107 + const {initSession} = useSessionApi() 108 + const {setShowLoggedOut} = useLoggedOutViewControls() 109 + 110 + React.useEffect(() => { 111 + screen('Choose Account') 112 + }, [screen]) 113 + 114 + const onSelect = React.useCallback( 115 + async (account: SessionAccount) => { 116 + if (account.accessJwt) { 117 + if (account.did === currentAccount?.did) { 118 + setShowLoggedOut(false) 119 + Toast.show(_(msg`Already signed in as @${account.handle}`)) 120 + } else { 121 + await initSession(account) 122 + track('Sign In', {resumedSession: true}) 123 + setTimeout(() => { 124 + Toast.show(_(msg`Signed in as @${account.handle}`)) 125 + }, 100) 126 + } 127 + } else { 128 + onSelectAccount(account) 129 + } 130 + }, 131 + [currentAccount, track, initSession, onSelectAccount, setShowLoggedOut, _], 132 + ) 133 + 134 + return ( 135 + <ScrollView testID="chooseAccountForm" style={styles.maxHeight}> 136 + <Text style={[a.mt_md, a.mb_lg, a.font_bold]}> 137 + <Trans>Sign in as...</Trans> 138 + </Text> 139 + <Group> 140 + {accounts.map(account => ( 141 + <AccountItem 142 + key={account.did} 143 + account={account} 144 + onSelect={onSelect} 145 + isCurrentAccount={account.did === currentAccount?.did} 146 + /> 147 + ))} 148 + <TouchableOpacity 149 + testID="chooseNewAccountBtn" 150 + style={[a.flex_1]} 151 + onPress={() => onSelectAccount(undefined)} 152 + accessibilityRole="button" 153 + accessibilityLabel={_(msg`Login to account that is not listed`)} 154 + accessibilityHint=""> 155 + <View style={[a.flex_row, a.flex_row, a.align_center, {height: 48}]}> 156 + <Text 157 + style={[ 158 + a.align_baseline, 159 + a.flex_1, 160 + a.flex_row, 161 + a.py_sm, 162 + {paddingLeft: 48}, 163 + ]}> 164 + <Trans>Other account</Trans> 165 + </Text> 166 + <Chevron size="sm" style={[t.atoms.text, a.mr_md]} /> 167 + </View> 168 + </TouchableOpacity> 169 + </Group> 170 + <View style={[a.flex_row, a.mt_lg]}> 171 + <Button 172 + label={_(msg`Back`)} 173 + variant="solid" 174 + color="secondary" 175 + size="small" 176 + onPress={onPressBack}> 177 + <Trans>Back</Trans> 178 + </Button> 179 + <View style={[a.flex_1]} /> 180 + </View> 181 + </ScrollView> 182 + ) 183 + }
+166
src/screens/Login/index.tsx
··· 1 + import React from 'react' 2 + import {useAnalytics} from '#/lib/analytics/analytics' 3 + import {useLingui} from '@lingui/react' 4 + import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' 5 + import {SessionAccount, useSession} from '#/state/session' 6 + import {DEFAULT_SERVICE} from '#/lib/constants' 7 + import {useLoggedOutView} from '#/state/shell/logged-out' 8 + import {useServiceQuery} from '#/state/queries/service' 9 + import {msg} from '@lingui/macro' 10 + import {logger} from '#/logger' 11 + import {atoms as a} from '#/alf' 12 + import {KeyboardAvoidingView} from 'react-native' 13 + import {ChooseAccountForm} from './ChooseAccountForm' 14 + import {ForgotPasswordForm} from '#/view/com/auth/login/ForgotPasswordForm' 15 + import {SetNewPasswordForm} from '#/view/com/auth/login/SetNewPasswordForm' 16 + import {PasswordUpdatedForm} from '#/view/com/auth/login/PasswordUpdatedForm' 17 + import {LoginForm} from '#/view/com/auth/login/LoginForm' 18 + 19 + enum Forms { 20 + Login, 21 + ChooseAccount, 22 + ForgotPassword, 23 + SetNewPassword, 24 + PasswordUpdated, 25 + } 26 + 27 + export const Login = ({onPressBack}: {onPressBack: () => void}) => { 28 + const {_} = useLingui() 29 + 30 + const {accounts} = useSession() 31 + const {track} = useAnalytics() 32 + const {requestedAccountSwitchTo} = useLoggedOutView() 33 + const requestedAccount = accounts.find( 34 + acc => acc.did === requestedAccountSwitchTo, 35 + ) 36 + 37 + const [error, setError] = React.useState<string>('') 38 + const [serviceUrl, setServiceUrl] = React.useState<string>( 39 + requestedAccount?.service || DEFAULT_SERVICE, 40 + ) 41 + const [initialHandle, setInitialHandle] = React.useState<string>( 42 + requestedAccount?.handle || '', 43 + ) 44 + const [currentForm, setCurrentForm] = React.useState<Forms>( 45 + requestedAccount 46 + ? Forms.Login 47 + : accounts.length 48 + ? Forms.ChooseAccount 49 + : Forms.Login, 50 + ) 51 + 52 + const { 53 + data: serviceDescription, 54 + error: serviceError, 55 + refetch: refetchService, 56 + } = useServiceQuery(serviceUrl) 57 + 58 + const onSelectAccount = (account?: SessionAccount) => { 59 + if (account?.service) { 60 + setServiceUrl(account.service) 61 + } 62 + setInitialHandle(account?.handle || '') 63 + setCurrentForm(Forms.Login) 64 + } 65 + 66 + const gotoForm = (form: Forms) => () => { 67 + setError('') 68 + setCurrentForm(form) 69 + } 70 + 71 + React.useEffect(() => { 72 + if (serviceError) { 73 + setError( 74 + _( 75 + msg`Unable to contact your service. Please check your Internet connection.`, 76 + ), 77 + ) 78 + logger.warn(`Failed to fetch service description for ${serviceUrl}`, { 79 + error: String(serviceError), 80 + }) 81 + } else { 82 + setError('') 83 + } 84 + }, [serviceError, serviceUrl, _]) 85 + 86 + const onPressRetryConnect = () => refetchService() 87 + const onPressForgotPassword = () => { 88 + track('Signin:PressedForgotPassword') 89 + setCurrentForm(Forms.ForgotPassword) 90 + } 91 + 92 + let content = null 93 + let title = '' 94 + let description = '' 95 + 96 + switch (currentForm) { 97 + case Forms.Login: 98 + title = _(msg`Sign in`) 99 + description = _(msg`Enter your username and password`) 100 + content = ( 101 + <LoginForm 102 + error={error} 103 + serviceUrl={serviceUrl} 104 + serviceDescription={serviceDescription} 105 + initialHandle={initialHandle} 106 + setError={setError} 107 + setServiceUrl={setServiceUrl} 108 + onPressBack={onPressBack} 109 + onPressForgotPassword={onPressForgotPassword} 110 + onPressRetryConnect={onPressRetryConnect} 111 + /> 112 + ) 113 + break 114 + case Forms.ChooseAccount: 115 + title = _(msg`Sign in`) 116 + description = _(msg`Select from an existing account`) 117 + content = ( 118 + <ChooseAccountForm 119 + onSelectAccount={onSelectAccount} 120 + onPressBack={onPressBack} 121 + /> 122 + ) 123 + break 124 + case Forms.ForgotPassword: 125 + title = _(msg`Forgot Password`) 126 + description = _(msg`Let's get your password reset!`) 127 + content = ( 128 + <ForgotPasswordForm 129 + error={error} 130 + serviceUrl={serviceUrl} 131 + serviceDescription={serviceDescription} 132 + setError={setError} 133 + setServiceUrl={setServiceUrl} 134 + onPressBack={gotoForm(Forms.Login)} 135 + onEmailSent={gotoForm(Forms.SetNewPassword)} 136 + /> 137 + ) 138 + break 139 + case Forms.SetNewPassword: 140 + title = _(msg`Forgot Password`) 141 + description = _(msg`Let's get your password reset!`) 142 + content = ( 143 + <SetNewPasswordForm 144 + error={error} 145 + serviceUrl={serviceUrl} 146 + setError={setError} 147 + onPressBack={gotoForm(Forms.ForgotPassword)} 148 + onPasswordSet={gotoForm(Forms.PasswordUpdated)} 149 + /> 150 + ) 151 + break 152 + case Forms.PasswordUpdated: 153 + title = _(msg`Password updated`) 154 + description = _(msg`You can now sign in with your new password.`) 155 + content = <PasswordUpdatedForm onPressNext={gotoForm(Forms.Login)} /> 156 + break 157 + } 158 + 159 + return ( 160 + <KeyboardAvoidingView testID="signIn" behavior="padding" style={a.flex_1}> 161 + <LoggedOutLayout leadin="" title={title} description={description}> 162 + {content} 163 + </LoggedOutLayout> 164 + </KeyboardAvoidingView> 165 + ) 166 + }
+8 -8
src/view/com/auth/LoggedOut.tsx
··· 5 5 import {Trans, msg} from '@lingui/macro' 6 6 import {useNavigation} from '@react-navigation/native' 7 7 8 - import {isIOS, isNative} from 'platform/detection' 9 - import {Login} from 'view/com/auth/login/Login' 10 - import {CreateAccount} from 'view/com/auth/create/CreateAccount' 11 - import {ErrorBoundary} from 'view/com/util/ErrorBoundary' 12 - import {s} from 'lib/styles' 13 - import {usePalette} from 'lib/hooks/usePalette' 14 - import {useAnalytics} from 'lib/analytics/analytics' 8 + import {isIOS, isNative} from '#/platform/detection' 9 + import {Login} from '#/screens/Login' 10 + import {CreateAccount} from '#/view/com/auth/create/CreateAccount' 11 + import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 12 + import {s} from '#/lib/styles' 13 + import {usePalette} from '#/lib/hooks/usePalette' 14 + import {useAnalytics} from '#/lib/analytics/analytics' 15 15 import {SplashScreen} from './SplashScreen' 16 16 import {useSetMinimalShellMode} from '#/state/shell/minimal-mode' 17 - import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 17 + import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 18 18 import { 19 19 useLoggedOutView, 20 20 useLoggedOutViewControls,
-158
src/view/com/auth/login/ChooseAccountForm.tsx
··· 1 - import React from 'react' 2 - import {ScrollView, TouchableOpacity, View} from 'react-native' 3 - import { 4 - FontAwesomeIcon, 5 - FontAwesomeIconStyle, 6 - } from '@fortawesome/react-native-fontawesome' 7 - import {useAnalytics} from 'lib/analytics/analytics' 8 - import {Text} from '../../util/text/Text' 9 - import {UserAvatar} from '../../util/UserAvatar' 10 - import {s, colors} from 'lib/styles' 11 - import {usePalette} from 'lib/hooks/usePalette' 12 - import {Trans, msg} from '@lingui/macro' 13 - import {useLingui} from '@lingui/react' 14 - import {styles} from './styles' 15 - import {useSession, useSessionApi, SessionAccount} from '#/state/session' 16 - import {useProfileQuery} from '#/state/queries/profile' 17 - import {useLoggedOutViewControls} from '#/state/shell/logged-out' 18 - import * as Toast from '#/view/com/util/Toast' 19 - 20 - function AccountItem({ 21 - account, 22 - onSelect, 23 - isCurrentAccount, 24 - }: { 25 - account: SessionAccount 26 - onSelect: (account: SessionAccount) => void 27 - isCurrentAccount: boolean 28 - }) { 29 - const pal = usePalette('default') 30 - const {_} = useLingui() 31 - const {data: profile} = useProfileQuery({did: account.did}) 32 - 33 - const onPress = React.useCallback(() => { 34 - onSelect(account) 35 - }, [account, onSelect]) 36 - 37 - return ( 38 - <TouchableOpacity 39 - testID={`chooseAccountBtn-${account.handle}`} 40 - key={account.did} 41 - style={[pal.view, pal.border, styles.account]} 42 - onPress={onPress} 43 - accessibilityRole="button" 44 - accessibilityLabel={_(msg`Sign in as ${account.handle}`)} 45 - accessibilityHint={_(msg`Double tap to sign in`)}> 46 - <View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> 47 - <View style={s.p10}> 48 - <UserAvatar avatar={profile?.avatar} size={30} /> 49 - </View> 50 - <Text style={styles.accountText}> 51 - <Text type="lg-bold" style={pal.text}> 52 - {profile?.displayName || account.handle}{' '} 53 - </Text> 54 - <Text type="lg" style={[pal.textLight]}> 55 - {account.handle} 56 - </Text> 57 - </Text> 58 - {isCurrentAccount ? ( 59 - <FontAwesomeIcon 60 - icon="check" 61 - size={16} 62 - style={[{color: colors.green3} as FontAwesomeIconStyle, s.mr10]} 63 - /> 64 - ) : ( 65 - <FontAwesomeIcon 66 - icon="angle-right" 67 - size={16} 68 - style={[pal.text, s.mr10]} 69 - /> 70 - )} 71 - </View> 72 - </TouchableOpacity> 73 - ) 74 - } 75 - export const ChooseAccountForm = ({ 76 - onSelectAccount, 77 - onPressBack, 78 - }: { 79 - onSelectAccount: (account?: SessionAccount) => void 80 - onPressBack: () => void 81 - }) => { 82 - const {track, screen} = useAnalytics() 83 - const pal = usePalette('default') 84 - const {_} = useLingui() 85 - const {accounts, currentAccount} = useSession() 86 - const {initSession} = useSessionApi() 87 - const {setShowLoggedOut} = useLoggedOutViewControls() 88 - 89 - React.useEffect(() => { 90 - screen('Choose Account') 91 - }, [screen]) 92 - 93 - const onSelect = React.useCallback( 94 - async (account: SessionAccount) => { 95 - if (account.accessJwt) { 96 - if (account.did === currentAccount?.did) { 97 - setShowLoggedOut(false) 98 - Toast.show(_(msg`Already signed in as @${account.handle}`)) 99 - } else { 100 - await initSession(account) 101 - track('Sign In', {resumedSession: true}) 102 - setTimeout(() => { 103 - Toast.show(_(msg`Signed in as @${account.handle}`)) 104 - }, 100) 105 - } 106 - } else { 107 - onSelectAccount(account) 108 - } 109 - }, 110 - [currentAccount, track, initSession, onSelectAccount, setShowLoggedOut, _], 111 - ) 112 - 113 - return ( 114 - <ScrollView testID="chooseAccountForm" style={styles.maxHeight}> 115 - <Text 116 - type="2xl-medium" 117 - style={[pal.text, styles.groupLabel, s.mt5, s.mb10]}> 118 - <Trans>Sign in as...</Trans> 119 - </Text> 120 - {accounts.map(account => ( 121 - <AccountItem 122 - key={account.did} 123 - account={account} 124 - onSelect={onSelect} 125 - isCurrentAccount={account.did === currentAccount?.did} 126 - /> 127 - ))} 128 - <TouchableOpacity 129 - testID="chooseNewAccountBtn" 130 - style={[pal.view, pal.border, styles.account, styles.accountLast]} 131 - onPress={() => onSelectAccount(undefined)} 132 - accessibilityRole="button" 133 - accessibilityLabel={_(msg`Login to account that is not listed`)} 134 - accessibilityHint=""> 135 - <View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> 136 - <Text style={[styles.accountText, styles.accountTextOther]}> 137 - <Text type="lg" style={pal.text}> 138 - <Trans>Other account</Trans> 139 - </Text> 140 - </Text> 141 - <FontAwesomeIcon 142 - icon="angle-right" 143 - size={16} 144 - style={[pal.text, s.mr10]} 145 - /> 146 - </View> 147 - </TouchableOpacity> 148 - <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}> 149 - <TouchableOpacity onPress={onPressBack} accessibilityRole="button"> 150 - <Text type="xl" style={[pal.link, s.pl5]}> 151 - <Trans>Back</Trans> 152 - </Text> 153 - </TouchableOpacity> 154 - <View style={s.flex1} /> 155 - </View> 156 - </ScrollView> 157 - ) 158 - }
-164
src/view/com/auth/login/Login.tsx
··· 1 - import React, {useState, useEffect} from 'react' 2 - import {KeyboardAvoidingView} from 'react-native' 3 - import {useAnalytics} from 'lib/analytics/analytics' 4 - import {LoggedOutLayout} from 'view/com/util/layouts/LoggedOutLayout' 5 - import {DEFAULT_SERVICE} from '#/lib/constants' 6 - import {usePalette} from 'lib/hooks/usePalette' 7 - import {logger} from '#/logger' 8 - import {ChooseAccountForm} from './ChooseAccountForm' 9 - import {LoginForm} from './LoginForm' 10 - import {ForgotPasswordForm} from './ForgotPasswordForm' 11 - import {SetNewPasswordForm} from './SetNewPasswordForm' 12 - import {PasswordUpdatedForm} from './PasswordUpdatedForm' 13 - import {useLingui} from '@lingui/react' 14 - import {msg} from '@lingui/macro' 15 - import {useSession, SessionAccount} from '#/state/session' 16 - import {useServiceQuery} from '#/state/queries/service' 17 - import {useLoggedOutView} from '#/state/shell/logged-out' 18 - 19 - enum Forms { 20 - Login, 21 - ChooseAccount, 22 - ForgotPassword, 23 - SetNewPassword, 24 - PasswordUpdated, 25 - } 26 - 27 - export const Login = ({onPressBack}: {onPressBack: () => void}) => { 28 - const {_} = useLingui() 29 - const pal = usePalette('default') 30 - 31 - const {accounts} = useSession() 32 - const {track} = useAnalytics() 33 - const {requestedAccountSwitchTo} = useLoggedOutView() 34 - const requestedAccount = accounts.find( 35 - a => a.did === requestedAccountSwitchTo, 36 - ) 37 - 38 - const [error, setError] = useState<string>('') 39 - const [serviceUrl, setServiceUrl] = useState<string>( 40 - requestedAccount?.service || DEFAULT_SERVICE, 41 - ) 42 - const [initialHandle, setInitialHandle] = useState<string>( 43 - requestedAccount?.handle || '', 44 - ) 45 - const [currentForm, setCurrentForm] = useState<Forms>( 46 - requestedAccount 47 - ? Forms.Login 48 - : accounts.length 49 - ? Forms.ChooseAccount 50 - : Forms.Login, 51 - ) 52 - 53 - const { 54 - data: serviceDescription, 55 - error: serviceError, 56 - refetch: refetchService, 57 - } = useServiceQuery(serviceUrl) 58 - 59 - const onSelectAccount = (account?: SessionAccount) => { 60 - if (account?.service) { 61 - setServiceUrl(account.service) 62 - } 63 - setInitialHandle(account?.handle || '') 64 - setCurrentForm(Forms.Login) 65 - } 66 - 67 - const gotoForm = (form: Forms) => () => { 68 - setError('') 69 - setCurrentForm(form) 70 - } 71 - 72 - useEffect(() => { 73 - if (serviceError) { 74 - setError( 75 - _( 76 - msg`Unable to contact your service. Please check your Internet connection.`, 77 - ), 78 - ) 79 - logger.warn(`Failed to fetch service description for ${serviceUrl}`, { 80 - error: String(serviceError), 81 - }) 82 - } else { 83 - setError('') 84 - } 85 - }, [serviceError, serviceUrl, _]) 86 - 87 - const onPressRetryConnect = () => refetchService() 88 - const onPressForgotPassword = () => { 89 - track('Signin:PressedForgotPassword') 90 - setCurrentForm(Forms.ForgotPassword) 91 - } 92 - 93 - return ( 94 - <KeyboardAvoidingView testID="signIn" behavior="padding" style={pal.view}> 95 - {currentForm === Forms.Login ? ( 96 - <LoggedOutLayout 97 - leadin="" 98 - title={_(msg`Sign in`)} 99 - description={_(msg`Enter your username and password`)}> 100 - <LoginForm 101 - error={error} 102 - serviceUrl={serviceUrl} 103 - serviceDescription={serviceDescription} 104 - initialHandle={initialHandle} 105 - setError={setError} 106 - setServiceUrl={setServiceUrl} 107 - onPressBack={onPressBack} 108 - onPressForgotPassword={onPressForgotPassword} 109 - onPressRetryConnect={onPressRetryConnect} 110 - /> 111 - </LoggedOutLayout> 112 - ) : undefined} 113 - {currentForm === Forms.ChooseAccount ? ( 114 - <LoggedOutLayout 115 - leadin="" 116 - title={_(msg`Sign in as...`)} 117 - description={_(msg`Select from an existing account`)}> 118 - <ChooseAccountForm 119 - onSelectAccount={onSelectAccount} 120 - onPressBack={onPressBack} 121 - /> 122 - </LoggedOutLayout> 123 - ) : undefined} 124 - {currentForm === Forms.ForgotPassword ? ( 125 - <LoggedOutLayout 126 - leadin="" 127 - title={_(msg`Forgot Password`)} 128 - description={_(msg`Let's get your password reset!`)}> 129 - <ForgotPasswordForm 130 - error={error} 131 - serviceUrl={serviceUrl} 132 - serviceDescription={serviceDescription} 133 - setError={setError} 134 - setServiceUrl={setServiceUrl} 135 - onPressBack={gotoForm(Forms.Login)} 136 - onEmailSent={gotoForm(Forms.SetNewPassword)} 137 - /> 138 - </LoggedOutLayout> 139 - ) : undefined} 140 - {currentForm === Forms.SetNewPassword ? ( 141 - <LoggedOutLayout 142 - leadin="" 143 - title={_(msg`Forgot Password`)} 144 - description={_(msg`Let's get your password reset!`)}> 145 - <SetNewPasswordForm 146 - error={error} 147 - serviceUrl={serviceUrl} 148 - setError={setError} 149 - onPressBack={gotoForm(Forms.ForgotPassword)} 150 - onPasswordSet={gotoForm(Forms.PasswordUpdated)} 151 - /> 152 - </LoggedOutLayout> 153 - ) : undefined} 154 - {currentForm === Forms.PasswordUpdated ? ( 155 - <LoggedOutLayout 156 - leadin="" 157 - title={_(msg`Password updated`)} 158 - description={_(msg`You can now sign in with your new password.`)}> 159 - <PasswordUpdatedForm onPressNext={gotoForm(Forms.Login)} /> 160 - </LoggedOutLayout> 161 - ) : undefined} 162 - </KeyboardAvoidingView> 163 - ) 164 - }