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