import React, {useState, useRef} from 'react' import { ActivityIndicator, Keyboard, TextInput, TouchableOpacity, View, } from 'react-native' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {ComAtprotoServerDescribeServer} from '@atproto/api' import {useAnalytics} from 'lib/analytics/analytics' import {Text} from '../../util/text/Text' import {s} from 'lib/styles' import {createFullHandle} from 'lib/strings/handles' import {toNiceDomain} from 'lib/strings/url-helpers' import {isNetworkError} from 'lib/strings/errors' import {usePalette} from 'lib/hooks/usePalette' import {useTheme} from 'lib/ThemeContext' import {useSessionApi} from '#/state/session' import {cleanError} from 'lib/strings/errors' import {logger} from '#/logger' import {Trans, msg} from '@lingui/macro' import {styles} from './styles' import {useLingui} from '@lingui/react' import {useModalControls} from '#/state/modals' type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema export const LoginForm = ({ error, serviceUrl, serviceDescription, initialHandle, setError, setServiceUrl, onPressRetryConnect, onPressBack, onPressForgotPassword, }: { error: string serviceUrl: string serviceDescription: ServiceDescription | undefined initialHandle: string setError: (v: string) => void setServiceUrl: (v: string) => void onPressRetryConnect: () => void onPressBack: () => void onPressForgotPassword: () => void }) => { const {track} = useAnalytics() const pal = usePalette('default') const theme = useTheme() const [isProcessing, setIsProcessing] = useState(false) const [identifier, setIdentifier] = useState(initialHandle) const [password, setPassword] = useState('') const passwordInputRef = useRef(null) const {_} = useLingui() const {openModal} = useModalControls() const {login} = useSessionApi() const onPressSelectService = () => { openModal({ name: 'server-input', initialService: serviceUrl, onSelect: setServiceUrl, }) Keyboard.dismiss() track('Signin:PressedSelectService') } const onPressNext = async () => { Keyboard.dismiss() setError('') setIsProcessing(true) try { // try to guess the handle if the user just gave their own username let fullIdent = identifier if ( !identifier.includes('@') && // not an email !identifier.includes('.') && // not a domain serviceDescription && serviceDescription.availableUserDomains.length > 0 ) { let matched = false for (const domain of serviceDescription.availableUserDomains) { if (fullIdent.endsWith(domain)) { matched = true } } if (!matched) { fullIdent = createFullHandle( identifier, serviceDescription.availableUserDomains[0], ) } } // TODO remove double login await login({ service: serviceUrl, identifier: fullIdent, password, }) } catch (e: any) { const errMsg = e.toString() logger.warn('Failed to login', {error: e}) setIsProcessing(false) if (errMsg.includes('Authentication Required')) { setError(_(msg`Invalid username or password`)) } else if (isNetworkError(e)) { setError( _( msg`Unable to contact your service. Please check your Internet connection.`, ), ) } else { setError(cleanError(errMsg)) } } } const isReady = !!serviceDescription && !!identifier && !!password return ( Sign into {toNiceDomain(serviceUrl)} Account { passwordInputRef.current?.focus() }} blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field keyboardAppearance={theme.colorScheme} value={identifier} onChangeText={str => setIdentifier((str || '').toLowerCase().trim()) } editable={!isProcessing} accessibilityLabel={_(msg`Username or email address`)} accessibilityHint="Input the username or email address you used at signup" /> Forgot {error ? ( {error} ) : undefined} Back {!serviceDescription && error ? ( Retry ) : !serviceDescription ? ( <> Connecting... ) : isProcessing ? ( ) : isReady ? ( Next ) : undefined} ) }