* add button to skip sending reset code * add validation to reset code * comments * update test id * consistency sneak in - everything capitalized * add change password button to settings * create a modal for password change * change password modal * remove unused styles * more improvements * improve layout * change done button color * add already have a code to modal * remove unused prop * icons, auto add dash * cleanup * better appearance on android * Remove log * Improve error messages and add specificity to function names --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com>
224 lines
7.1 KiB
TypeScript
224 lines
7.1 KiB
TypeScript
import React, {useState, useEffect} from 'react'
|
|
import {
|
|
ActivityIndicator,
|
|
TextInput,
|
|
TouchableOpacity,
|
|
View,
|
|
} from 'react-native'
|
|
import {
|
|
FontAwesomeIcon,
|
|
FontAwesomeIconStyle,
|
|
} from '@fortawesome/react-native-fontawesome'
|
|
import {ComAtprotoServerDescribeServer} from '@atproto/api'
|
|
import * as EmailValidator from 'email-validator'
|
|
import {BskyAgent} from '@atproto/api'
|
|
import {useAnalytics} from 'lib/analytics/analytics'
|
|
import {Text} from '../../util/text/Text'
|
|
import {s} from 'lib/styles'
|
|
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 {cleanError} from 'lib/strings/errors'
|
|
import {logger} from '#/logger'
|
|
import {Trans, msg} from '@lingui/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
import {styles} from './styles'
|
|
import {useModalControls} from '#/state/modals'
|
|
|
|
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
|
|
|
export const ForgotPasswordForm = ({
|
|
error,
|
|
serviceUrl,
|
|
serviceDescription,
|
|
setError,
|
|
setServiceUrl,
|
|
onPressBack,
|
|
onEmailSent,
|
|
}: {
|
|
error: string
|
|
serviceUrl: string
|
|
serviceDescription: ServiceDescription | undefined
|
|
setError: (v: string) => void
|
|
setServiceUrl: (v: string) => void
|
|
onPressBack: () => void
|
|
onEmailSent: () => void
|
|
}) => {
|
|
const pal = usePalette('default')
|
|
const theme = useTheme()
|
|
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
|
const [email, setEmail] = useState<string>('')
|
|
const {screen} = useAnalytics()
|
|
const {_} = useLingui()
|
|
const {openModal} = useModalControls()
|
|
|
|
useEffect(() => {
|
|
screen('Signin:ForgotPassword')
|
|
}, [screen])
|
|
|
|
const onPressSelectService = () => {
|
|
openModal({
|
|
name: 'server-input',
|
|
initialService: serviceUrl,
|
|
onSelect: setServiceUrl,
|
|
})
|
|
}
|
|
|
|
const onPressNext = async () => {
|
|
if (!EmailValidator.validate(email)) {
|
|
return setError(_(msg`Your email appears to be invalid.`))
|
|
}
|
|
|
|
setError('')
|
|
setIsProcessing(true)
|
|
|
|
try {
|
|
const agent = new BskyAgent({service: serviceUrl})
|
|
await agent.com.atproto.server.requestPasswordReset({email})
|
|
onEmailSent()
|
|
} catch (e: any) {
|
|
const errMsg = e.toString()
|
|
logger.warn('Failed to request password reset', {error: e})
|
|
setIsProcessing(false)
|
|
if (isNetworkError(e)) {
|
|
setError(
|
|
_(
|
|
msg`Unable to contact your service. Please check your Internet connection.`,
|
|
),
|
|
)
|
|
} else {
|
|
setError(cleanError(errMsg))
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<View>
|
|
<Text type="title-lg" style={[pal.text, styles.screenTitle]}>
|
|
<Trans>Reset password</Trans>
|
|
</Text>
|
|
<Text type="md" style={[pal.text, styles.instructions]}>
|
|
<Trans>
|
|
Enter the email you used to create your account. We'll send you a
|
|
"reset code" so you can set a new password.
|
|
</Trans>
|
|
</Text>
|
|
<View
|
|
testID="forgotPasswordView"
|
|
style={[pal.borderDark, pal.view, styles.group]}>
|
|
<TouchableOpacity
|
|
testID="forgotPasswordSelectServiceButton"
|
|
style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}
|
|
onPress={onPressSelectService}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={_(msg`Hosting provider`)}
|
|
accessibilityHint={_(
|
|
msg`Sets hosting provider for password reset`,
|
|
)}>
|
|
<FontAwesomeIcon
|
|
icon="globe"
|
|
style={[pal.textLight, styles.groupContentIcon]}
|
|
/>
|
|
<Text style={[pal.text, styles.textInput]} numberOfLines={1}>
|
|
{toNiceDomain(serviceUrl)}
|
|
</Text>
|
|
<View style={[pal.btn, styles.textBtnFakeInnerBtn]}>
|
|
<FontAwesomeIcon
|
|
icon="pen"
|
|
size={12}
|
|
style={pal.text as FontAwesomeIconStyle}
|
|
/>
|
|
</View>
|
|
</TouchableOpacity>
|
|
<View style={[pal.borderDark, styles.groupContent]}>
|
|
<FontAwesomeIcon
|
|
icon="envelope"
|
|
style={[pal.textLight, styles.groupContentIcon]}
|
|
/>
|
|
<TextInput
|
|
testID="forgotPasswordEmail"
|
|
style={[pal.text, styles.textInput]}
|
|
placeholder={_(msg`Email address`)}
|
|
placeholderTextColor={pal.colors.textLight}
|
|
autoCapitalize="none"
|
|
autoFocus
|
|
autoCorrect={false}
|
|
keyboardAppearance={theme.colorScheme}
|
|
value={email}
|
|
onChangeText={setEmail}
|
|
editable={!isProcessing}
|
|
accessibilityLabel={_(msg`Email`)}
|
|
accessibilityHint={_(msg`Sets email for password reset`)}
|
|
/>
|
|
</View>
|
|
</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 style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}>
|
|
<TouchableOpacity onPress={onPressBack} accessibilityRole="button">
|
|
<Text type="xl" style={[pal.link, s.pl5]}>
|
|
<Trans>Back</Trans>
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<View style={s.flex1} />
|
|
{!serviceDescription || isProcessing ? (
|
|
<ActivityIndicator />
|
|
) : !email ? (
|
|
<Text type="xl-bold" style={[pal.link, s.pr5, styles.dimmed]}>
|
|
<Trans>Next</Trans>
|
|
</Text>
|
|
) : (
|
|
<TouchableOpacity
|
|
testID="newPasswordButton"
|
|
onPress={onPressNext}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={_(msg`Go to next`)}
|
|
accessibilityHint={_(msg`Navigates to the next screen`)}>
|
|
<Text type="xl-bold" style={[pal.link, s.pr5]}>
|
|
<Trans>Next</Trans>
|
|
</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
{!serviceDescription || isProcessing ? (
|
|
<Text type="xl" style={[pal.textLight, s.pl10]}>
|
|
<Trans>Processing...</Trans>
|
|
</Text>
|
|
) : undefined}
|
|
</View>
|
|
<View
|
|
style={[
|
|
s.flexRow,
|
|
s.alignCenter,
|
|
s.mt20,
|
|
s.mb20,
|
|
pal.border,
|
|
s.borderBottom1,
|
|
{alignSelf: 'center', width: '90%'},
|
|
]}
|
|
/>
|
|
<View style={[s.flexRow, s.justifyCenter]}>
|
|
<TouchableOpacity
|
|
testID="skipSendEmailButton"
|
|
onPress={onEmailSent}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={_(msg`Go to next`)}
|
|
accessibilityHint={_(msg`Navigates to the next screen`)}>
|
|
<Text type="xl" style={[pal.link, s.pr5]}>
|
|
<Trans>Already have a code?</Trans>
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
</>
|
|
)
|
|
}
|