Changeset View
Changeset View
Standalone View
Standalone View
native/account/registration/password-selection.react.js
// @flow | // @flow | ||||
import invariant from 'invariant'; | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { View, Text, Platform } from 'react-native'; | import { View, Text, Platform } from 'react-native'; | ||||
import sleep from 'lib/utils/sleep.js'; | import sleep from 'lib/utils/sleep.js'; | ||||
import RegistrationButtonContainer from './registration-button-container.react.js'; | import RegistrationButtonContainer from './registration-button-container.react.js'; | ||||
import RegistrationButton from './registration-button.react.js'; | import RegistrationButton from './registration-button.react.js'; | ||||
import RegistrationContainer from './registration-container.react.js'; | import RegistrationContainer from './registration-container.react.js'; | ||||
import RegistrationContentContainer from './registration-content-container.react.js'; | import RegistrationContentContainer from './registration-content-container.react.js'; | ||||
import { RegistrationContext } from './registration-context.js'; | |||||
import type { RegistrationNavigationProp } from './registration-navigator.react.js'; | import type { RegistrationNavigationProp } from './registration-navigator.react.js'; | ||||
import RegistrationTextInput from './registration-text-input.react.js'; | import RegistrationTextInput from './registration-text-input.react.js'; | ||||
import type { CoolOrNerdMode } from './registration-types.js'; | import type { CoolOrNerdMode } from './registration-types.js'; | ||||
import { | import { | ||||
type NavigationRoute, | type NavigationRoute, | ||||
AvatarSelectionRouteName, | AvatarSelectionRouteName, | ||||
} from '../../navigation/route-names.js'; | } from '../../navigation/route-names.js'; | ||||
import { useStyles } from '../../themes/colors.js'; | import { useStyles } from '../../themes/colors.js'; | ||||
Show All 9 Lines | |||||
type PasswordError = 'passwords_dont_match' | 'empty_password'; | type PasswordError = 'passwords_dont_match' | 'empty_password'; | ||||
type Props = { | type Props = { | ||||
+navigation: RegistrationNavigationProp<'PasswordSelection'>, | +navigation: RegistrationNavigationProp<'PasswordSelection'>, | ||||
+route: NavigationRoute<'PasswordSelection'>, | +route: NavigationRoute<'PasswordSelection'>, | ||||
}; | }; | ||||
function PasswordSelection(props: Props): React.Node { | function PasswordSelection(props: Props): React.Node { | ||||
const [password, setPassword] = React.useState(''); | const registrationContext = React.useContext(RegistrationContext); | ||||
const [confirmPassword, setConfirmPassword] = React.useState(''); | invariant(registrationContext, 'registrationContext should be set'); | ||||
const { cachedSelections, setCachedSelections } = registrationContext; | |||||
const [password, setPassword] = React.useState( | |||||
cachedSelections.password ?? '', | |||||
); | |||||
const [confirmPassword, setConfirmPassword] = React.useState( | |||||
cachedSelections.password ?? '', | |||||
); | |||||
const passwordsMatch = password === confirmPassword; | const passwordsMatch = password === confirmPassword; | ||||
const passwordIsEmpty = password === ''; | const passwordIsEmpty = password === ''; | ||||
const [passwordError, setPasswordError] = React.useState<?PasswordError>(); | const [passwordError, setPasswordError] = React.useState<?PasswordError>(); | ||||
const potentiallyClearErrors = React.useCallback(() => { | const potentiallyClearErrors = React.useCallback(() => { | ||||
if (!passwordsMatch || passwordIsEmpty) { | if (!passwordsMatch || passwordIsEmpty) { | ||||
return false; | return false; | ||||
} | } | ||||
Show All 24 Lines | const newUserSelections = { | ||||
coolOrNerdMode, | coolOrNerdMode, | ||||
keyserverUsername, | keyserverUsername, | ||||
accountSelection: { | accountSelection: { | ||||
accountType: 'username', | accountType: 'username', | ||||
username, | username, | ||||
password, | password, | ||||
}, | }, | ||||
}; | }; | ||||
setCachedSelections(oldUserSelections => ({ | |||||
...oldUserSelections, | |||||
password, | |||||
})); | |||||
navigate<'AvatarSelection'>({ | navigate<'AvatarSelection'>({ | ||||
name: AvatarSelectionRouteName, | name: AvatarSelectionRouteName, | ||||
params: { userSelections: newUserSelections }, | params: { userSelections: newUserSelections }, | ||||
}); | }); | ||||
}, [checkPasswordValidity, userSelections, password, navigate]); | }, [ | ||||
checkPasswordValidity, | |||||
userSelections, | |||||
password, | |||||
setCachedSelections, | |||||
navigate, | |||||
]); | |||||
const styles = useStyles(unboundStyles); | const styles = useStyles(unboundStyles); | ||||
let errorText; | let errorText; | ||||
if (passwordError === 'passwords_dont_match') { | if (passwordError === 'passwords_dont_match') { | ||||
errorText = ( | errorText = ( | ||||
<Text style={styles.errorText}>Passwords don’t match</Text> | <Text style={styles.errorText}>Passwords don’t match</Text> | ||||
); | ); | ||||
} else if (passwordError === 'empty_password') { | } else if (passwordError === 'empty_password') { | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | (input: string) => { | ||||
// password autofill | // password autofill | ||||
setConfirmPassword(input); | setConfirmPassword(input); | ||||
passwordInputRef.current?.blur(); | passwordInputRef.current?.blur(); | ||||
} | } | ||||
}, | }, | ||||
[passwordLength, confirmPasswordEmpty], | [passwordLength, confirmPasswordEmpty], | ||||
); | ); | ||||
const shouldAutoFocus = React.useRef(!cachedSelections.password); | |||||
/* eslint-disable react-hooks/rules-of-hooks */ | /* eslint-disable react-hooks/rules-of-hooks */ | ||||
if (Platform.OS === 'android') { | if (Platform.OS === 'android') { | ||||
// It's okay to call this hook conditionally because | // It's okay to call this hook conditionally because | ||||
// the condition is guaranteed to never change | // the condition is guaranteed to never change | ||||
React.useEffect(() => { | React.useEffect(() => { | ||||
(async () => { | (async () => { | ||||
await sleep(250); | await sleep(250); | ||||
if (shouldAutoFocus.current) { | |||||
passwordInputRef.current?.focus(); | passwordInputRef.current?.focus(); | ||||
} | |||||
})(); | })(); | ||||
}, []); | }, []); | ||||
} | } | ||||
/* eslint-enable react-hooks/rules-of-hooks */ | /* eslint-enable react-hooks/rules-of-hooks */ | ||||
const autoFocus = Platform.OS !== 'android' && shouldAutoFocus.current; | |||||
return ( | return ( | ||||
<RegistrationContainer> | <RegistrationContainer> | ||||
<RegistrationContentContainer> | <RegistrationContentContainer> | ||||
<Text style={styles.header}>Pick a password</Text> | <Text style={styles.header}>Pick a password</Text> | ||||
<RegistrationTextInput | <RegistrationTextInput | ||||
value={password} | value={password} | ||||
onChangeText={onChangePasswordInput} | onChangeText={onChangePasswordInput} | ||||
placeholder="Password" | placeholder="Password" | ||||
autoFocus={Platform.select({ | autoFocus={autoFocus} | ||||
android: false, | |||||
default: true, | |||||
})} | |||||
secureTextEntry={true} | secureTextEntry={true} | ||||
textContentType="newPassword" | textContentType="newPassword" | ||||
autoComplete="password-new" | autoComplete="password-new" | ||||
returnKeyType="next" | returnKeyType="next" | ||||
onSubmitEditing={focusConfirmPasswordInput} | onSubmitEditing={focusConfirmPasswordInput} | ||||
onKeyPress={onPasswordKeyPress} | onKeyPress={onPasswordKeyPress} | ||||
onBlur={potentiallyClearErrors} | onBlur={potentiallyClearErrors} | ||||
ref={passwordInputRef} | ref={passwordInputRef} | ||||
▲ Show 20 Lines • Show All 48 Lines • Show Last 20 Lines |