forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useState} from 'react'
2import {View} from 'react-native'
3import {msg, Trans} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
5
6import {cleanError, isNetworkError} from '#/lib/strings/errors'
7import {checkAndFormatResetCode} from '#/lib/strings/password'
8import {logger} from '#/logger'
9import {Agent} from '#/state/session/agent'
10import {atoms as a, web} from '#/alf'
11import {Button, ButtonIcon, ButtonText} from '#/components/Button'
12import {FormError} from '#/components/forms/FormError'
13import * as TextField from '#/components/forms/TextField'
14import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock'
15import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket'
16import {Loader} from '#/components/Loader'
17import {Text} from '#/components/Typography'
18import {useAnalytics} from '#/analytics'
19import {IS_WEB} from '#/env'
20import {FormContainer} from './FormContainer'
21
22export const SetNewPasswordForm = ({
23 error,
24 serviceUrl,
25 setError,
26 onPressBack,
27 onPasswordSet,
28}: {
29 error: string
30 serviceUrl: string
31 setError: (v: string) => void
32 onPressBack: () => void
33 onPasswordSet: () => void
34}) => {
35 const {_} = useLingui()
36 const ax = useAnalytics()
37
38 const [isProcessing, setIsProcessing] = useState<boolean>(false)
39 const [resetCode, setResetCode] = useState<string>('')
40 const [password, setPassword] = useState<string>('')
41
42 const onPressNext = async () => {
43 // Check that the code is correct. We do this again just incase the user enters the code after their pw and we
44 // don't get to call onBlur first
45 const formattedCode = checkAndFormatResetCode(resetCode)
46
47 if (!formattedCode) {
48 setError(
49 _(
50 msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`,
51 ),
52 )
53 ax.metric('signin:passwordResetFailure', {})
54 return
55 }
56
57 // TODO Better password strength check
58 if (!password) {
59 setError(_(msg`Please enter a password.`))
60 return
61 }
62
63 setError('')
64 setIsProcessing(true)
65
66 try {
67 const agent = new Agent(null, {service: serviceUrl})
68 await agent.com.atproto.server.resetPassword({
69 token: formattedCode,
70 password,
71 })
72 onPasswordSet()
73 ax.metric('signin:passwordResetSuccess', {})
74 } catch (e: any) {
75 const errMsg = e.toString()
76 logger.warn('Failed to set new password', {error: e})
77 ax.metric('signin:passwordResetFailure', {})
78 setIsProcessing(false)
79 if (isNetworkError(e)) {
80 setError(
81 _(
82 msg`Unable to contact your service. Please check your Internet connection.`,
83 ),
84 )
85 } else {
86 setError(cleanError(errMsg))
87 }
88 }
89 }
90
91 const onBlur = () => {
92 const formattedCode = checkAndFormatResetCode(resetCode)
93 if (!formattedCode) {
94 setError(
95 _(
96 msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`,
97 ),
98 )
99 return
100 }
101 setResetCode(formattedCode)
102 }
103
104 return (
105 <FormContainer
106 testID="setNewPasswordForm"
107 titleText={<Trans>Set new password</Trans>}>
108 <Text style={[a.leading_snug, a.mb_sm]}>
109 <Trans>
110 You will receive an email with a "reset code." Enter that code here,
111 then enter your new password.
112 </Trans>
113 </Text>
114
115 <View>
116 <TextField.LabelText>
117 <Trans>Reset code</Trans>
118 </TextField.LabelText>
119 <TextField.Root>
120 <TextField.Icon icon={Ticket} />
121 <TextField.Input
122 testID="resetCodeInput"
123 label={_(msg`Looks like XXXXX-XXXXX`)}
124 autoCapitalize="none"
125 autoFocus={true}
126 autoCorrect={false}
127 autoComplete="off"
128 value={resetCode}
129 onChangeText={setResetCode}
130 onFocus={() => setError('')}
131 onBlur={onBlur}
132 editable={!isProcessing}
133 accessibilityHint={_(
134 msg`Input code sent to your email for password reset`,
135 )}
136 />
137 </TextField.Root>
138 </View>
139
140 <View>
141 <TextField.LabelText>
142 <Trans>New password</Trans>
143 </TextField.LabelText>
144 <TextField.Root>
145 <TextField.Icon icon={Lock} />
146 <TextField.Input
147 testID="newPasswordInput"
148 label={_(msg`Enter a password`)}
149 autoCapitalize="none"
150 autoCorrect={false}
151 returnKeyType="done"
152 secureTextEntry={true}
153 autoComplete="new-password"
154 passwordRules="minlength: 8;"
155 clearButtonMode="while-editing"
156 value={password}
157 onChangeText={setPassword}
158 onSubmitEditing={onPressNext}
159 editable={!isProcessing}
160 accessibilityHint={_(msg`Input new password`)}
161 />
162 </TextField.Root>
163 </View>
164
165 <FormError error={error} />
166
167 <View style={[web([a.flex_row, a.align_center]), a.pt_lg]}>
168 {IS_WEB && (
169 <>
170 <Button
171 label={_(msg`Back`)}
172 variant="solid"
173 color="secondary"
174 size="large"
175 onPress={onPressBack}>
176 <ButtonText>
177 <Trans>Back</Trans>
178 </ButtonText>
179 </Button>
180 <View style={a.flex_1} />
181 </>
182 )}
183
184 <Button
185 label={_(msg`Next`)}
186 color="primary"
187 size="large"
188 onPress={onPressNext}
189 disabled={isProcessing}>
190 <ButtonText>
191 <Trans>Next</Trans>
192 </ButtonText>
193 {isProcessing && <ButtonIcon icon={Loader} />}
194 </Button>
195 </View>
196 </FormContainer>
197 )
198}