mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at remove-hackfix 358 lines 12 kB view raw
1import React, {useRef, useState} from 'react' 2import { 3 ActivityIndicator, 4 Keyboard, 5 LayoutAnimation, 6 type TextInput, 7 View, 8} from 'react-native' 9import { 10 ComAtprotoServerCreateSession, 11 type ComAtprotoServerDescribeServer, 12} from '@atproto/api' 13import {msg, Trans} from '@lingui/macro' 14import {useLingui} from '@lingui/react' 15 16import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' 17import {isNetworkError} from '#/lib/strings/errors' 18import {cleanError} from '#/lib/strings/errors' 19import {createFullHandle} from '#/lib/strings/handles' 20import {logger} from '#/logger' 21import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' 22import {useSessionApi} from '#/state/session' 23import {useLoggedOutViewControls} from '#/state/shell/logged-out' 24import {atoms as a, useTheme} from '#/alf' 25import {Button, ButtonIcon, ButtonText} from '#/components/Button' 26import {FormError} from '#/components/forms/FormError' 27import {HostingProvider} from '#/components/forms/HostingProvider' 28import * as TextField from '#/components/forms/TextField' 29import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At' 30import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock' 31import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket' 32import {Loader} from '#/components/Loader' 33import {Text} from '#/components/Typography' 34import {FormContainer} from './FormContainer' 35 36type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema 37 38export const LoginForm = ({ 39 error, 40 serviceUrl, 41 serviceDescription, 42 initialHandle, 43 setError, 44 setServiceUrl, 45 onPressRetryConnect, 46 onPressBack, 47 onPressForgotPassword, 48 onAttemptSuccess, 49 onAttemptFailed, 50}: { 51 error: string 52 serviceUrl: string 53 serviceDescription: ServiceDescription | undefined 54 initialHandle: string 55 setError: (v: string) => void 56 setServiceUrl: (v: string) => void 57 onPressRetryConnect: () => void 58 onPressBack: () => void 59 onPressForgotPassword: () => void 60 onAttemptSuccess: () => void 61 onAttemptFailed: () => void 62}) => { 63 const t = useTheme() 64 const [isProcessing, setIsProcessing] = useState<boolean>(false) 65 const [isAuthFactorTokenNeeded, setIsAuthFactorTokenNeeded] = 66 useState<boolean>(false) 67 const [isAuthFactorTokenValueEmpty, setIsAuthFactorTokenValueEmpty] = 68 useState<boolean>(true) 69 const identifierValueRef = useRef<string>(initialHandle || '') 70 const passwordValueRef = useRef<string>('') 71 const authFactorTokenValueRef = useRef<string>('') 72 const passwordRef = useRef<TextInput>(null) 73 const {_} = useLingui() 74 const {login} = useSessionApi() 75 const requestNotificationsPermission = useRequestNotificationsPermission() 76 const {setShowLoggedOut} = useLoggedOutViewControls() 77 const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack() 78 79 const onPressSelectService = React.useCallback(() => { 80 Keyboard.dismiss() 81 }, []) 82 83 const onPressNext = async () => { 84 if (isProcessing) return 85 Keyboard.dismiss() 86 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) 87 setError('') 88 89 const identifier = identifierValueRef.current.toLowerCase().trim() 90 const password = passwordValueRef.current 91 const authFactorToken = authFactorTokenValueRef.current 92 93 if (!identifier) { 94 setError(_(msg`Please enter your username`)) 95 return 96 } 97 98 if (!password) { 99 setError(_(msg`Please enter your password`)) 100 return 101 } 102 103 setIsProcessing(true) 104 105 try { 106 // try to guess the handle if the user just gave their own username 107 let fullIdent = identifier 108 if ( 109 !identifier.includes('@') && // not an email 110 !identifier.includes('.') && // not a domain 111 serviceDescription && 112 serviceDescription.availableUserDomains.length > 0 113 ) { 114 let matched = false 115 for (const domain of serviceDescription.availableUserDomains) { 116 if (fullIdent.endsWith(domain)) { 117 matched = true 118 } 119 } 120 if (!matched) { 121 fullIdent = createFullHandle( 122 identifier, 123 serviceDescription.availableUserDomains[0], 124 ) 125 } 126 } 127 128 // TODO remove double login 129 await login( 130 { 131 service: serviceUrl, 132 identifier: fullIdent, 133 password, 134 authFactorToken: authFactorToken.trim(), 135 }, 136 'LoginForm', 137 ) 138 onAttemptSuccess() 139 setShowLoggedOut(false) 140 setHasCheckedForStarterPack(true) 141 requestNotificationsPermission('Login') 142 } catch (e: any) { 143 const errMsg = e.toString() 144 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) 145 setIsProcessing(false) 146 if ( 147 e instanceof ComAtprotoServerCreateSession.AuthFactorTokenRequiredError 148 ) { 149 setIsAuthFactorTokenNeeded(true) 150 } else { 151 onAttemptFailed() 152 if (errMsg.includes('Token is invalid')) { 153 logger.debug('Failed to login due to invalid 2fa token', { 154 error: errMsg, 155 }) 156 setError(_(msg`Invalid 2FA confirmation code.`)) 157 } else if ( 158 errMsg.includes('Authentication Required') || 159 errMsg.includes('Invalid identifier or password') 160 ) { 161 logger.debug('Failed to login due to invalid credentials', { 162 error: errMsg, 163 }) 164 setError(_(msg`Incorrect username or password`)) 165 } else if (isNetworkError(e)) { 166 logger.warn('Failed to login due to network error', {error: errMsg}) 167 setError( 168 _( 169 msg`Unable to contact your service. Please check your Internet connection.`, 170 ), 171 ) 172 } else { 173 logger.warn('Failed to login', {error: errMsg}) 174 setError(cleanError(errMsg)) 175 } 176 } 177 } 178 } 179 180 return ( 181 <FormContainer testID="loginForm" titleText={<Trans>Sign in</Trans>}> 182 <View> 183 <TextField.LabelText> 184 <Trans>Hosting provider</Trans> 185 </TextField.LabelText> 186 <HostingProvider 187 serviceUrl={serviceUrl} 188 onSelectServiceUrl={setServiceUrl} 189 onOpenDialog={onPressSelectService} 190 /> 191 </View> 192 <View> 193 <TextField.LabelText> 194 <Trans>Account</Trans> 195 </TextField.LabelText> 196 <View style={[a.gap_sm]}> 197 <TextField.Root> 198 <TextField.Icon icon={At} /> 199 <TextField.Input 200 testID="loginUsernameInput" 201 label={_(msg`Username or email address`)} 202 autoCapitalize="none" 203 autoFocus 204 autoCorrect={false} 205 autoComplete="username" 206 returnKeyType="next" 207 textContentType="username" 208 defaultValue={initialHandle || ''} 209 onChangeText={v => { 210 identifierValueRef.current = v 211 }} 212 onSubmitEditing={() => { 213 passwordRef.current?.focus() 214 }} 215 blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field 216 editable={!isProcessing} 217 accessibilityHint={_( 218 msg`Enter the username or email address you used when you created your account`, 219 )} 220 /> 221 </TextField.Root> 222 223 <TextField.Root> 224 <TextField.Icon icon={Lock} /> 225 <TextField.Input 226 testID="loginPasswordInput" 227 inputRef={passwordRef} 228 label={_(msg`Password`)} 229 autoCapitalize="none" 230 autoCorrect={false} 231 autoComplete="password" 232 returnKeyType="done" 233 enablesReturnKeyAutomatically={true} 234 secureTextEntry={true} 235 textContentType="password" 236 clearButtonMode="while-editing" 237 onChangeText={v => { 238 passwordValueRef.current = v 239 }} 240 onSubmitEditing={onPressNext} 241 blurOnSubmit={false} // HACK: https://github.com/facebook/react-native/issues/21911#issuecomment-558343069 Keyboard blur behavior is now handled in onSubmitEditing 242 editable={!isProcessing} 243 accessibilityHint={_(msg`Enter your password`)} 244 /> 245 <Button 246 testID="forgotPasswordButton" 247 onPress={onPressForgotPassword} 248 label={_(msg`Forgot password?`)} 249 accessibilityHint={_(msg`Opens password reset form`)} 250 variant="solid" 251 color="secondary" 252 style={[ 253 a.rounded_sm, 254 // t.atoms.bg_contrast_100, 255 {marginLeft: 'auto', left: 6, padding: 6}, 256 a.z_10, 257 ]}> 258 <ButtonText> 259 <Trans>Forgot?</Trans> 260 </ButtonText> 261 </Button> 262 </TextField.Root> 263 </View> 264 </View> 265 {isAuthFactorTokenNeeded && ( 266 <View> 267 <TextField.LabelText> 268 <Trans>2FA Confirmation</Trans> 269 </TextField.LabelText> 270 <TextField.Root> 271 <TextField.Icon icon={Ticket} /> 272 <TextField.Input 273 testID="loginAuthFactorTokenInput" 274 label={_(msg`Confirmation code`)} 275 autoCapitalize="none" 276 autoFocus 277 autoCorrect={false} 278 autoComplete="one-time-code" 279 returnKeyType="done" 280 textContentType="username" 281 blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field 282 onChangeText={v => { 283 setIsAuthFactorTokenValueEmpty(v === '') 284 authFactorTokenValueRef.current = v 285 }} 286 onSubmitEditing={onPressNext} 287 editable={!isProcessing} 288 accessibilityHint={_( 289 msg`Input the code which has been emailed to you`, 290 )} 291 style={[ 292 { 293 textTransform: isAuthFactorTokenValueEmpty 294 ? 'none' 295 : 'uppercase', 296 }, 297 ]} 298 /> 299 </TextField.Root> 300 <Text style={[a.text_sm, t.atoms.text_contrast_medium, a.mt_sm]}> 301 <Trans> 302 Check your email for a sign in code and enter it here. 303 </Trans> 304 </Text> 305 </View> 306 )} 307 <FormError error={error} /> 308 <View style={[a.flex_row, a.align_center, a.pt_md]}> 309 <Button 310 label={_(msg`Back`)} 311 variant="solid" 312 color="secondary" 313 size="large" 314 onPress={onPressBack}> 315 <ButtonText> 316 <Trans>Back</Trans> 317 </ButtonText> 318 </Button> 319 <View style={a.flex_1} /> 320 {!serviceDescription && error ? ( 321 <Button 322 testID="loginRetryButton" 323 label={_(msg`Retry`)} 324 accessibilityHint={_(msg`Retries signing in`)} 325 variant="solid" 326 color="secondary" 327 size="large" 328 onPress={onPressRetryConnect}> 329 <ButtonText> 330 <Trans>Retry</Trans> 331 </ButtonText> 332 </Button> 333 ) : !serviceDescription ? ( 334 <> 335 <ActivityIndicator /> 336 <Text style={[t.atoms.text_contrast_high, a.pl_md]}> 337 <Trans>Connecting...</Trans> 338 </Text> 339 </> 340 ) : ( 341 <Button 342 testID="loginNextButton" 343 label={_(msg`Next`)} 344 accessibilityHint={_(msg`Navigates to the next screen`)} 345 variant="solid" 346 color="primary" 347 size="large" 348 onPress={onPressNext}> 349 <ButtonText> 350 <Trans>Next</Trans> 351 </ButtonText> 352 {isProcessing && <ButtonIcon icon={Loader} />} 353 </Button> 354 )} 355 </View> 356 </FormContainer> 357 ) 358}