[Auth rework] Add a header to login flow (#9368)
This commit is contained in:
@@ -43,10 +43,10 @@ export function SwitchAccountDialog({
|
|||||||
return (
|
return (
|
||||||
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
|
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
|
||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
<Dialog.ScrollableInner label={_(msg`Switch Account`)}>
|
<Dialog.ScrollableInner label={_(msg`Switch account`)}>
|
||||||
<View style={[a.gap_lg]}>
|
<View style={[a.gap_lg]}>
|
||||||
<Text style={[a.text_2xl, a.font_semi_bold]}>
|
<Text style={[a.text_2xl, a.font_semi_bold]}>
|
||||||
<Trans>Switch Account</Trans>
|
<Trans>Switch account</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<AccountList
|
<AccountList
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import {useCallback, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -12,6 +12,7 @@ import {AccountList} from '#/components/AccountList'
|
|||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import * as TextField from '#/components/forms/TextField'
|
import * as TextField from '#/components/forms/TextField'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
|
import {IS_WEB} from '#/env'
|
||||||
import {FormContainer} from './FormContainer'
|
import {FormContainer} from './FormContainer'
|
||||||
|
|
||||||
export const ChooseAccountForm = ({
|
export const ChooseAccountForm = ({
|
||||||
@@ -21,14 +22,14 @@ export const ChooseAccountForm = ({
|
|||||||
onSelectAccount: (account?: SessionAccount) => void
|
onSelectAccount: (account?: SessionAccount) => void
|
||||||
onPressBack: () => void
|
onPressBack: () => void
|
||||||
}) => {
|
}) => {
|
||||||
const [pendingDid, setPendingDid] = React.useState<string | null>(null)
|
const [pendingDid, setPendingDid] = useState<string | null>(null)
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {resumeSession} = useSessionApi()
|
const {resumeSession} = useSessionApi()
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
|
|
||||||
const onSelect = React.useCallback(
|
const onSelect = useCallback(
|
||||||
async (account: SessionAccount) => {
|
async (account: SessionAccount) => {
|
||||||
if (pendingDid) {
|
if (pendingDid) {
|
||||||
// The session API isn't resilient to race conditions so let's just ignore this.
|
// The session API isn't resilient to race conditions so let's just ignore this.
|
||||||
@@ -54,7 +55,7 @@ export const ChooseAccountForm = ({
|
|||||||
Toast.show(_(msg`Signed in as @${account.handle}`))
|
Toast.show(_(msg`Signed in as @${account.handle}`))
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.error('choose account: initSession failed', {
|
logger.error('choose account: initSession failed', {
|
||||||
message: e.message,
|
message: e instanceof Error ? e.message : 'Unknown error',
|
||||||
})
|
})
|
||||||
// Move to login form.
|
// Move to login form.
|
||||||
onSelectAccount(account)
|
onSelectAccount(account)
|
||||||
@@ -69,6 +70,7 @@ export const ChooseAccountForm = ({
|
|||||||
onSelectAccount,
|
onSelectAccount,
|
||||||
setShowLoggedOut,
|
setShowLoggedOut,
|
||||||
_,
|
_,
|
||||||
|
ax,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,19 +80,21 @@ export const ChooseAccountForm = ({
|
|||||||
titleText={<Trans>Select account</Trans>}
|
titleText={<Trans>Select account</Trans>}
|
||||||
style={web([a.py_2xl])}>
|
style={web([a.py_2xl])}>
|
||||||
<View>
|
<View>
|
||||||
|
{IS_WEB && (
|
||||||
<TextField.LabelText>
|
<TextField.LabelText>
|
||||||
<Trans>Sign in as...</Trans>
|
<Trans>Sign in as...</Trans>
|
||||||
</TextField.LabelText>
|
</TextField.LabelText>
|
||||||
|
)}
|
||||||
<AccountList
|
<AccountList
|
||||||
onSelectAccount={onSelect}
|
onSelectAccount={onSelect}
|
||||||
onSelectOther={() => onSelectAccount()}
|
onSelectOther={() => onSelectAccount()}
|
||||||
pendingDid={pendingDid}
|
pendingDid={pendingDid}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
{IS_WEB && (
|
||||||
<View style={[a.flex_row]}>
|
<View style={[a.flex_row]}>
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Back`)}
|
label={_(msg`Back`)}
|
||||||
variant="solid"
|
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="large"
|
size="large"
|
||||||
onPress={onPressBack}>
|
onPress={onPressBack}>
|
||||||
@@ -98,6 +102,7 @@ export const ChooseAccountForm = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<View style={[a.flex_1]} />
|
<View style={[a.flex_1]} />
|
||||||
</View>
|
</View>
|
||||||
|
)}
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
import React, {useState} from 'react'
|
import React, {useState} from 'react'
|
||||||
import {ActivityIndicator, Keyboard, View} from 'react-native'
|
import {Keyboard, View} from 'react-native'
|
||||||
import {type ComAtprotoServerDescribeServer} from '@atproto/api'
|
import {type ComAtprotoServerDescribeServer} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import * as EmailValidator from 'email-validator'
|
import * as EmailValidator from 'email-validator'
|
||||||
|
|
||||||
import {isNetworkError} from '#/lib/strings/errors'
|
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Agent} from '#/state/session/agent'
|
import {Agent} from '#/state/session/agent'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {FormError} from '#/components/forms/FormError'
|
import {FormError} from '#/components/forms/FormError'
|
||||||
import {HostingProvider} from '#/components/forms/HostingProvider'
|
import {HostingProvider} from '#/components/forms/HostingProvider'
|
||||||
import * as TextField from '#/components/forms/TextField'
|
import * as TextField from '#/components/forms/TextField'
|
||||||
import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At'
|
import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import {IS_WEB} from '#/env'
|
||||||
import {FormContainer} from './FormContainer'
|
import {FormContainer} from './FormContainer'
|
||||||
|
|
||||||
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
||||||
@@ -118,10 +119,11 @@ export const ForgotPasswordForm = ({
|
|||||||
|
|
||||||
<FormError error={error} />
|
<FormError error={error} />
|
||||||
|
|
||||||
<View style={[a.flex_row, a.align_center, a.pt_md]}>
|
<View style={[web([a.flex_row, a.align_center]), a.pt_md]}>
|
||||||
|
{IS_WEB && (
|
||||||
|
<>
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Back`)}
|
label={_(msg`Back`)}
|
||||||
variant="solid"
|
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="large"
|
size="large"
|
||||||
onPress={onPressBack}>
|
onPress={onPressBack}>
|
||||||
@@ -130,31 +132,37 @@ export const ForgotPasswordForm = ({
|
|||||||
</ButtonText>
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<View style={a.flex_1} />
|
<View style={a.flex_1} />
|
||||||
{!serviceDescription || isProcessing ? (
|
</>
|
||||||
<ActivityIndicator />
|
)}
|
||||||
|
{!serviceDescription ? (
|
||||||
|
<Button
|
||||||
|
label={_(msg`Connecting to service...`)}
|
||||||
|
size="large"
|
||||||
|
color="secondary"
|
||||||
|
disabled>
|
||||||
|
<ButtonIcon icon={Loader} />
|
||||||
|
<ButtonText>Connecting...</ButtonText>
|
||||||
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Next`)}
|
label={_(msg`Next`)}
|
||||||
variant="solid"
|
accessibilityHint={_(msg`Navigates to the next screen`)}
|
||||||
color={'primary'}
|
color="primary"
|
||||||
size="large"
|
size="large"
|
||||||
onPress={onPressNext}>
|
onPress={onPressNext}
|
||||||
|
disabled={isProcessing}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Next</Trans>
|
<Trans>Next</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
|
{isProcessing && <ButtonIcon icon={Loader} />}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!serviceDescription || isProcessing ? (
|
|
||||||
<Text style={[t.atoms.text_contrast_high, a.pl_md]}>
|
|
||||||
<Trans>Processing...</Trans>
|
|
||||||
</Text>
|
|
||||||
) : undefined}
|
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
t.atoms.border_contrast_medium,
|
t.atoms.border_contrast_medium,
|
||||||
a.border_t,
|
a.border_t,
|
||||||
a.pt_2xl,
|
a.pt_xl,
|
||||||
a.mt_md,
|
a.mt_md,
|
||||||
a.flex_row,
|
a.flex_row,
|
||||||
a.justify_center,
|
a.justify_center,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
||||||
import type React from 'react'
|
import type React from 'react'
|
||||||
|
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useBreakpoints, useGutters} from '#/alf'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
export function FormContainer({
|
export function FormContainer({
|
||||||
@@ -16,15 +16,14 @@ export function FormContainer({
|
|||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
}) {
|
}) {
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const t = useTheme()
|
const gutter = useGutters([0, 'wide'])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
testID={testID}
|
testID={testID}
|
||||||
style={[a.gap_md, a.flex_1, !gtMobile && [a.px_lg, a.py_md], style]}>
|
style={[a.gap_md, a.flex_1, !gtMobile && gutter, style]}>
|
||||||
{titleText && !gtMobile && (
|
{titleText && !gtMobile && (
|
||||||
<Text style={[a.text_xl, a.font_semi_bold, t.atoms.text_contrast_high]}>
|
<Text style={[a.text_3xl, a.font_bold]}>{titleText}</Text>
|
||||||
{titleText}
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import React, {useRef, useState} from 'react'
|
import {useCallback, useRef, useState} from 'react'
|
||||||
import {
|
import {Keyboard, type TextInput, View} from 'react-native'
|
||||||
ActivityIndicator,
|
|
||||||
Keyboard,
|
|
||||||
LayoutAnimation,
|
|
||||||
type TextInput,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import {
|
import {
|
||||||
ComAtprotoServerCreateSession,
|
ComAtprotoServerCreateSession,
|
||||||
type ComAtprotoServerDescribeServer,
|
type ComAtprotoServerDescribeServer,
|
||||||
@@ -14,14 +8,13 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
||||||
import {isNetworkError} from '#/lib/strings/errors'
|
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
|
||||||
import {createFullHandle} from '#/lib/strings/handles'
|
import {createFullHandle} from '#/lib/strings/handles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
|
import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
|
||||||
import {useSessionApi} from '#/state/session'
|
import {useSessionApi} from '#/state/session'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
import {atoms as a, ios, useTheme} from '#/alf'
|
import {atoms as a, ios, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {FormError} from '#/components/forms/FormError'
|
import {FormError} from '#/components/forms/FormError'
|
||||||
import {HostingProvider} from '#/components/forms/HostingProvider'
|
import {HostingProvider} from '#/components/forms/HostingProvider'
|
||||||
@@ -31,7 +24,7 @@ import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock'
|
|||||||
import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket'
|
import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {IS_IOS} from '#/env'
|
import {IS_IOS, IS_WEB} from '#/env'
|
||||||
import {FormContainer} from './FormContainer'
|
import {FormContainer} from './FormContainer'
|
||||||
|
|
||||||
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
|
||||||
@@ -62,9 +55,11 @@ export const LoginForm = ({
|
|||||||
onAttemptFailed: () => void
|
onAttemptFailed: () => void
|
||||||
}) => {
|
}) => {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
const [isProcessing, setIsProcessing] = useState(false)
|
||||||
const [isAuthFactorTokenNeeded, setIsAuthFactorTokenNeeded] =
|
const [errorField, setErrorField] = useState<
|
||||||
useState<boolean>(false)
|
'none' | 'identifier' | 'password' | '2fa'
|
||||||
|
>('none')
|
||||||
|
const [isAuthFactorTokenNeeded, setIsAuthFactorTokenNeeded] = useState(false)
|
||||||
const identifierValueRef = useRef<string>(initialHandle || '')
|
const identifierValueRef = useRef<string>(initialHandle || '')
|
||||||
const passwordValueRef = useRef<string>('')
|
const passwordValueRef = useRef<string>('')
|
||||||
const [authFactorToken, setAuthFactorToken] = useState('')
|
const [authFactorToken, setAuthFactorToken] = useState('')
|
||||||
@@ -77,26 +72,28 @@ export const LoginForm = ({
|
|||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack()
|
const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack()
|
||||||
|
|
||||||
const onPressSelectService = React.useCallback(() => {
|
const onPressSelectService = useCallback(() => {
|
||||||
Keyboard.dismiss()
|
Keyboard.dismiss()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const onPressNext = async () => {
|
const onPressNext = async () => {
|
||||||
if (isProcessing) return
|
if (isProcessing) return
|
||||||
Keyboard.dismiss()
|
Keyboard.dismiss()
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
|
||||||
setError('')
|
setError('')
|
||||||
|
setErrorField('none')
|
||||||
|
|
||||||
const identifier = identifierValueRef.current.toLowerCase().trim()
|
const identifier = identifierValueRef.current.toLowerCase().trim()
|
||||||
const password = passwordValueRef.current
|
const password = passwordValueRef.current
|
||||||
|
|
||||||
if (!identifier) {
|
if (!identifier) {
|
||||||
setError(_(msg`Please enter your username`))
|
setError(_(msg`Please enter your username`))
|
||||||
|
setErrorField('identifier')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!password) {
|
if (!password) {
|
||||||
setError(_(msg`Please enter your password`))
|
setError(_(msg`Please enter your password`))
|
||||||
|
setErrorField('password')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +138,6 @@ export const LoginForm = ({
|
|||||||
requestNotificationsPermission('Login')
|
requestNotificationsPermission('Login')
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
const errMsg = e.toString()
|
const errMsg = e.toString()
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
if (
|
if (
|
||||||
e instanceof ComAtprotoServerCreateSession.AuthFactorTokenRequiredError
|
e instanceof ComAtprotoServerCreateSession.AuthFactorTokenRequiredError
|
||||||
@@ -154,6 +150,7 @@ export const LoginForm = ({
|
|||||||
error: errMsg,
|
error: errMsg,
|
||||||
})
|
})
|
||||||
setError(_(msg`Invalid 2FA confirmation code.`))
|
setError(_(msg`Invalid 2FA confirmation code.`))
|
||||||
|
setErrorField('2fa')
|
||||||
} else if (
|
} else if (
|
||||||
errMsg.includes('Authentication Required') ||
|
errMsg.includes('Authentication Required') ||
|
||||||
errMsg.includes('Invalid identifier or password')
|
errMsg.includes('Invalid identifier or password')
|
||||||
@@ -178,7 +175,7 @@ export const LoginForm = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormContainer testID="loginForm" titleText={<Trans>Sign in</Trans>}>
|
<FormContainer testID="loginForm" titleText={<Trans>Log in</Trans>}>
|
||||||
<View>
|
<View>
|
||||||
<TextField.LabelText>
|
<TextField.LabelText>
|
||||||
<Trans>Hosting provider</Trans>
|
<Trans>Hosting provider</Trans>
|
||||||
@@ -194,7 +191,7 @@ export const LoginForm = ({
|
|||||||
<Trans>Account</Trans>
|
<Trans>Account</Trans>
|
||||||
</TextField.LabelText>
|
</TextField.LabelText>
|
||||||
<View style={[a.gap_sm]}>
|
<View style={[a.gap_sm]}>
|
||||||
<TextField.Root>
|
<TextField.Root isInvalid={errorField === 'identifier'}>
|
||||||
<TextField.Icon icon={At} />
|
<TextField.Icon icon={At} />
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
testID="loginUsernameInput"
|
testID="loginUsernameInput"
|
||||||
@@ -209,6 +206,7 @@ export const LoginForm = ({
|
|||||||
defaultValue={initialHandle || ''}
|
defaultValue={initialHandle || ''}
|
||||||
onChangeText={v => {
|
onChangeText={v => {
|
||||||
identifierValueRef.current = v
|
identifierValueRef.current = v
|
||||||
|
if (errorField) setErrorField('none')
|
||||||
}}
|
}}
|
||||||
onSubmitEditing={() => {
|
onSubmitEditing={() => {
|
||||||
passwordRef.current?.focus()
|
passwordRef.current?.focus()
|
||||||
@@ -221,7 +219,7 @@ export const LoginForm = ({
|
|||||||
/>
|
/>
|
||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
|
|
||||||
<TextField.Root>
|
<TextField.Root isInvalid={errorField === 'password'}>
|
||||||
<TextField.Icon icon={Lock} />
|
<TextField.Icon icon={Lock} />
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
testID="loginPasswordInput"
|
testID="loginPasswordInput"
|
||||||
@@ -236,6 +234,7 @@ export const LoginForm = ({
|
|||||||
clearButtonMode="while-editing"
|
clearButtonMode="while-editing"
|
||||||
onChangeText={v => {
|
onChangeText={v => {
|
||||||
passwordValueRef.current = v
|
passwordValueRef.current = v
|
||||||
|
if (errorField) setErrorField('none')
|
||||||
}}
|
}}
|
||||||
onSubmitEditing={onPressNext}
|
onSubmitEditing={onPressNext}
|
||||||
blurOnSubmit={false} // HACK: https://github.com/facebook/react-native/issues/21911#issuecomment-558343069 Keyboard blur behavior is now handled in onSubmitEditing
|
blurOnSubmit={false} // HACK: https://github.com/facebook/react-native/issues/21911#issuecomment-558343069 Keyboard blur behavior is now handled in onSubmitEditing
|
||||||
@@ -277,7 +276,7 @@ export const LoginForm = ({
|
|||||||
<TextField.LabelText>
|
<TextField.LabelText>
|
||||||
<Trans>2FA Confirmation</Trans>
|
<Trans>2FA Confirmation</Trans>
|
||||||
</TextField.LabelText>
|
</TextField.LabelText>
|
||||||
<TextField.Root>
|
<TextField.Root isInvalid={errorField === '2fa'}>
|
||||||
<TextField.Icon icon={Ticket} />
|
<TextField.Icon icon={Ticket} />
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
testID="loginAuthFactorTokenInput"
|
testID="loginAuthFactorTokenInput"
|
||||||
@@ -288,8 +287,11 @@ export const LoginForm = ({
|
|||||||
autoComplete="one-time-code"
|
autoComplete="one-time-code"
|
||||||
returnKeyType="done"
|
returnKeyType="done"
|
||||||
blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field
|
blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field
|
||||||
onChangeText={setAuthFactorToken}
|
|
||||||
value={authFactorToken} // controlled input due to uncontrolled input not receiving pasted values properly
|
value={authFactorToken} // controlled input due to uncontrolled input not receiving pasted values properly
|
||||||
|
onChangeText={text => {
|
||||||
|
setAuthFactorToken(text)
|
||||||
|
if (errorField) setErrorField('none')
|
||||||
|
}}
|
||||||
onSubmitEditing={onPressNext}
|
onSubmitEditing={onPressNext}
|
||||||
editable={!isProcessing}
|
editable={!isProcessing}
|
||||||
accessibilityHint={_(
|
accessibilityHint={_(
|
||||||
@@ -308,10 +310,10 @@ export const LoginForm = ({
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<FormError error={error} />
|
<FormError error={error} />
|
||||||
<View style={[a.flex_row, a.align_center, a.pt_md]}>
|
<View style={[a.pt_md, web([a.justify_between, a.flex_row])]}>
|
||||||
|
{IS_WEB && (
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Back`)}
|
label={_(msg`Back`)}
|
||||||
variant="solid"
|
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="large"
|
size="large"
|
||||||
onPress={onPressBack}>
|
onPress={onPressBack}>
|
||||||
@@ -319,14 +321,13 @@ export const LoginForm = ({
|
|||||||
<Trans>Back</Trans>
|
<Trans>Back</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<View style={a.flex_1} />
|
)}
|
||||||
{!serviceDescription && error ? (
|
{!serviceDescription && error ? (
|
||||||
<Button
|
<Button
|
||||||
testID="loginRetryButton"
|
testID="loginRetryButton"
|
||||||
label={_(msg`Retry`)}
|
label={_(msg`Retry`)}
|
||||||
accessibilityHint={_(msg`Retries signing in`)}
|
accessibilityHint={_(msg`Retries signing in`)}
|
||||||
variant="solid"
|
color="primary_subtle"
|
||||||
color="secondary"
|
|
||||||
size="large"
|
size="large"
|
||||||
onPress={onPressRetryConnect}>
|
onPress={onPressRetryConnect}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
@@ -334,23 +335,24 @@ export const LoginForm = ({
|
|||||||
</ButtonText>
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
) : !serviceDescription ? (
|
) : !serviceDescription ? (
|
||||||
<>
|
<Button
|
||||||
<ActivityIndicator />
|
label={_(msg`Connecting to service...`)}
|
||||||
<Text style={[t.atoms.text_contrast_high, a.pl_md]}>
|
size="large"
|
||||||
<Trans>Connecting...</Trans>
|
color="secondary"
|
||||||
</Text>
|
disabled>
|
||||||
</>
|
<ButtonIcon icon={Loader} />
|
||||||
|
<ButtonText>Connecting...</ButtonText>
|
||||||
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
testID="loginNextButton"
|
testID="loginNextButton"
|
||||||
label={_(msg`Next`)}
|
label={_(msg`Log in`)}
|
||||||
accessibilityHint={_(msg`Navigates to the next screen`)}
|
accessibilityHint={_(msg`Navigates to the next screen`)}
|
||||||
variant="solid"
|
|
||||||
color="primary"
|
color="primary"
|
||||||
size="large"
|
size="large"
|
||||||
onPress={onPressNext}>
|
onPress={onPressNext}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Next</Trans>
|
<Trans>Log in</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
{isProcessing && <ButtonIcon icon={Loader} />}
|
{isProcessing && <ButtonIcon icon={Loader} />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {View} from 'react-native'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {atoms as a, useBreakpoints} from '#/alf'
|
import {atoms as a, useBreakpoints, web} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {FormContainer} from './FormContainer'
|
import {FormContainer} from './FormContainer'
|
||||||
@@ -19,18 +19,17 @@ export const PasswordUpdatedForm = ({
|
|||||||
<FormContainer
|
<FormContainer
|
||||||
testID="passwordUpdatedForm"
|
testID="passwordUpdatedForm"
|
||||||
style={[a.gap_2xl, !gtMobile && a.mt_5xl]}>
|
style={[a.gap_2xl, !gtMobile && a.mt_5xl]}>
|
||||||
<Text style={[a.text_3xl, a.font_semi_bold, a.text_center]}>
|
<Text style={[a.text_3xl, a.font_bold, a.text_center]}>
|
||||||
<Trans>Password updated!</Trans>
|
<Trans>Password updated!</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[a.text_center, a.mx_auto, {maxWidth: '80%'}]}>
|
<Text style={[a.text_center, a.mx_auto, {maxWidth: '80%'}]}>
|
||||||
<Trans>You can now sign in with your new password.</Trans>
|
<Trans>You can now sign in with your new password.</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<View style={[a.flex_row, a.justify_center]}>
|
<View style={web([a.flex_row, a.justify_center])}>
|
||||||
<Button
|
<Button
|
||||||
onPress={onPressNext}
|
onPress={onPressNext}
|
||||||
label={_(msg`Close alert`)}
|
label={_(msg`Close alert`)}
|
||||||
accessibilityHint={_(msg`Closes password update alert`)}
|
accessibilityHint={_(msg`Closes password update alert`)}
|
||||||
variant="solid"
|
|
||||||
color="primary"
|
color="primary"
|
||||||
size="large">
|
size="large">
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {useState} from 'react'
|
import {useState} from 'react'
|
||||||
import {ActivityIndicator, View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
@@ -7,14 +7,16 @@ import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
|||||||
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Agent} from '#/state/session/agent'
|
import {Agent} from '#/state/session/agent'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, web} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {FormError} from '#/components/forms/FormError'
|
import {FormError} from '#/components/forms/FormError'
|
||||||
import * as TextField from '#/components/forms/TextField'
|
import * as TextField from '#/components/forms/TextField'
|
||||||
import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock'
|
import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock'
|
||||||
import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket'
|
import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
|
import {IS_WEB} from '#/env'
|
||||||
import {FormContainer} from './FormContainer'
|
import {FormContainer} from './FormContainer'
|
||||||
|
|
||||||
export const SetNewPasswordForm = ({
|
export const SetNewPasswordForm = ({
|
||||||
@@ -31,7 +33,6 @@ export const SetNewPasswordForm = ({
|
|||||||
onPasswordSet: () => void
|
onPasswordSet: () => void
|
||||||
}) => {
|
}) => {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
|
|
||||||
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
||||||
@@ -163,7 +164,9 @@ export const SetNewPasswordForm = ({
|
|||||||
|
|
||||||
<FormError error={error} />
|
<FormError error={error} />
|
||||||
|
|
||||||
<View style={[a.flex_row, a.align_center, a.pt_lg]}>
|
<View style={[web([a.flex_row, a.align_center]), a.pt_lg]}>
|
||||||
|
{IS_WEB && (
|
||||||
|
<>
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Back`)}
|
label={_(msg`Back`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
@@ -175,25 +178,20 @@ export const SetNewPasswordForm = ({
|
|||||||
</ButtonText>
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<View style={a.flex_1} />
|
<View style={a.flex_1} />
|
||||||
{isProcessing ? (
|
</>
|
||||||
<ActivityIndicator />
|
)}
|
||||||
) : (
|
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Next`)}
|
label={_(msg`Next`)}
|
||||||
variant="solid"
|
|
||||||
color="primary"
|
color="primary"
|
||||||
size="large"
|
size="large"
|
||||||
onPress={onPressNext}>
|
onPress={onPressNext}
|
||||||
|
disabled={isProcessing}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Next</Trans>
|
<Trans>Next</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
|
{isProcessing && <ButtonIcon icon={Loader} />}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{isProcessing ? (
|
|
||||||
<Text style={[t.atoms.text_contrast_high, a.pl_md]}>
|
|
||||||
<Trans>Updating...</Trans>
|
|
||||||
</Text>
|
|
||||||
) : undefined}
|
|
||||||
</View>
|
</View>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import {useContext} from 'react'
|
||||||
|
import {type GestureResponderEvent, View} from 'react-native'
|
||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {HITSLOP_30} from '#/lib/constants'
|
||||||
|
import {Logomark} from '#/view/icons/Logomark'
|
||||||
|
import {atoms as a, platform, useGutters, useTheme} from '#/alf'
|
||||||
|
import {Button, ButtonIcon, type ButtonProps} from '#/components/Button'
|
||||||
|
import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow'
|
||||||
|
import {
|
||||||
|
BUTTON_VISUAL_ALIGNMENT_OFFSET,
|
||||||
|
Header,
|
||||||
|
HEADER_SLOT_SIZE,
|
||||||
|
} from '#/components/Layout'
|
||||||
|
import {IS_WEB} from '#/env'
|
||||||
|
import {AuthLayoutNavigationContext} from '../context'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a simplified version of `Layout.Header` for the auth screens.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const Slot = Header.Slot
|
||||||
|
|
||||||
|
export function Outer({children}: {children: React.ReactNode}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const gutters = useGutters([0, 'wide'])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
|
gutters,
|
||||||
|
platform({
|
||||||
|
native: [a.pb_xs, {minHeight: 48}],
|
||||||
|
web: [a.py_xs, {minHeight: 52}],
|
||||||
|
}),
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Content({children}: {children?: React.ReactNode}) {
|
||||||
|
return (
|
||||||
|
<View style={[a.flex_1, a.justify_center, {minHeight: HEADER_SLOT_SIZE}]}>
|
||||||
|
{IS_WEB ? children : <Logo />}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Logo() {
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
|
return <Logomark fill={t.palette.primary_500} style={[a.mx_auto]} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const navigation = useContext(AuthLayoutNavigationContext)
|
||||||
|
|
||||||
|
const onPressBack = (evt: GestureResponderEvent) => {
|
||||||
|
onPress?.(evt)
|
||||||
|
if (evt.defaultPrevented) return
|
||||||
|
navigation?.goBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Slot>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Go back`)}
|
||||||
|
onPress={onPressBack}
|
||||||
|
size="small"
|
||||||
|
variant="ghost"
|
||||||
|
color="secondary"
|
||||||
|
shape="round"
|
||||||
|
hitSlop={HITSLOP_30}
|
||||||
|
style={[
|
||||||
|
{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET},
|
||||||
|
a.bg_transparent,
|
||||||
|
style,
|
||||||
|
]}
|
||||||
|
{...props}>
|
||||||
|
<ButtonIcon icon={ArrowLeft} size="lg" />
|
||||||
|
</Button>
|
||||||
|
</Slot>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import {type ButtonProps} from '#/components/Button'
|
||||||
|
import {Header} from '#/components/Layout'
|
||||||
|
|
||||||
|
// TEMP: Web needs a much more comprehensive layout change to support the new design
|
||||||
|
// so to do this incrementally, we'll do native only and migrate to web later
|
||||||
|
|
||||||
|
export const Slot = Header.Slot
|
||||||
|
|
||||||
|
export function Outer({}: {children: React.ReactNode}) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Content({}: {children?: React.ReactNode}) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Logo() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BackButton({}: Partial<ButtonProps>) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import {createContext} from 'react'
|
||||||
|
|
||||||
|
export const AuthLayoutNavigationContext = createContext<{
|
||||||
|
goBack: () => void
|
||||||
|
} | null>(null)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * as Header from './Header'
|
||||||
@@ -18,6 +18,8 @@ import {atoms as a, native} from '#/alf'
|
|||||||
import {ScreenTransition} from '#/components/ScreenTransition'
|
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {ChooseAccountForm} from './ChooseAccountForm'
|
import {ChooseAccountForm} from './ChooseAccountForm'
|
||||||
|
import * as AuthLayout from './components/AuthLayout'
|
||||||
|
import {AuthLayoutNavigationContext} from './components/AuthLayout/context'
|
||||||
|
|
||||||
enum Forms {
|
enum Forms {
|
||||||
Login,
|
Login,
|
||||||
@@ -131,11 +133,14 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||||||
let content = null
|
let content = null
|
||||||
let title = ''
|
let title = ''
|
||||||
let description = ''
|
let description = ''
|
||||||
|
let goBack = null
|
||||||
|
|
||||||
switch (currentForm) {
|
switch (currentForm) {
|
||||||
case Forms.Login:
|
case Forms.Login:
|
||||||
title = _(msg`Sign in`)
|
title = _(msg`Sign in`)
|
||||||
description = _(msg`Enter your username and password`)
|
description = _(msg`Enter your username and password`)
|
||||||
|
goBack = () =>
|
||||||
|
accounts.length ? gotoForm(Forms.ChooseAccount) : handlePressBack()
|
||||||
content = (
|
content = (
|
||||||
<LoginForm
|
<LoginForm
|
||||||
error={error}
|
error={error}
|
||||||
@@ -146,9 +151,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||||||
onAttemptFailed={onAttemptFailed}
|
onAttemptFailed={onAttemptFailed}
|
||||||
onAttemptSuccess={onAttemptSuccess}
|
onAttemptSuccess={onAttemptSuccess}
|
||||||
setServiceUrl={setServiceUrl}
|
setServiceUrl={setServiceUrl}
|
||||||
onPressBack={() =>
|
onPressBack={goBack}
|
||||||
accounts.length ? gotoForm(Forms.ChooseAccount) : handlePressBack()
|
|
||||||
}
|
|
||||||
onPressForgotPassword={onPressForgotPassword}
|
onPressForgotPassword={onPressForgotPassword}
|
||||||
onPressRetryConnect={refetchService}
|
onPressRetryConnect={refetchService}
|
||||||
/>
|
/>
|
||||||
@@ -157,16 +160,18 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||||||
case Forms.ChooseAccount:
|
case Forms.ChooseAccount:
|
||||||
title = _(msg`Sign in`)
|
title = _(msg`Sign in`)
|
||||||
description = _(msg`Select from an existing account`)
|
description = _(msg`Select from an existing account`)
|
||||||
|
goBack = handlePressBack
|
||||||
content = (
|
content = (
|
||||||
<ChooseAccountForm
|
<ChooseAccountForm
|
||||||
onSelectAccount={onSelectAccount}
|
onSelectAccount={onSelectAccount}
|
||||||
onPressBack={handlePressBack}
|
onPressBack={goBack}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case Forms.ForgotPassword:
|
case Forms.ForgotPassword:
|
||||||
title = _(msg`Forgot Password`)
|
title = _(msg`Forgot Password`)
|
||||||
description = _(msg`Let's get your password reset!`)
|
description = _(msg`Let's get your password reset!`)
|
||||||
|
goBack = () => gotoForm(Forms.Login)
|
||||||
content = (
|
content = (
|
||||||
<ForgotPasswordForm
|
<ForgotPasswordForm
|
||||||
error={error}
|
error={error}
|
||||||
@@ -174,7 +179,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||||||
serviceDescription={serviceDescription}
|
serviceDescription={serviceDescription}
|
||||||
setError={setError}
|
setError={setError}
|
||||||
setServiceUrl={setServiceUrl}
|
setServiceUrl={setServiceUrl}
|
||||||
onPressBack={() => gotoForm(Forms.Login)}
|
onPressBack={goBack}
|
||||||
onEmailSent={() => gotoForm(Forms.SetNewPassword)}
|
onEmailSent={() => gotoForm(Forms.SetNewPassword)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -182,12 +187,13 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||||||
case Forms.SetNewPassword:
|
case Forms.SetNewPassword:
|
||||||
title = _(msg`Forgot Password`)
|
title = _(msg`Forgot Password`)
|
||||||
description = _(msg`Let's get your password reset!`)
|
description = _(msg`Let's get your password reset!`)
|
||||||
|
goBack = () => gotoForm(Forms.ForgotPassword)
|
||||||
content = (
|
content = (
|
||||||
<SetNewPasswordForm
|
<SetNewPasswordForm
|
||||||
error={error}
|
error={error}
|
||||||
serviceUrl={serviceUrl}
|
serviceUrl={serviceUrl}
|
||||||
setError={setError}
|
setError={setError}
|
||||||
onPressBack={() => gotoForm(Forms.ForgotPassword)}
|
onPressBack={goBack}
|
||||||
onPasswordSet={() => gotoForm(Forms.PasswordUpdated)}
|
onPasswordSet={() => gotoForm(Forms.PasswordUpdated)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -201,9 +207,20 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const navigation = goBack ? {goBack} : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<AuthLayoutNavigationContext value={navigation}>
|
||||||
<Animated.View style={a.flex_1} entering={native(FadeIn.duration(90))}>
|
<Animated.View style={a.flex_1} entering={native(FadeIn.duration(90))}>
|
||||||
<KeyboardAvoidingView testID="signIn" behavior="padding" style={a.flex_1}>
|
<KeyboardAvoidingView
|
||||||
|
testID="signIn"
|
||||||
|
behavior="padding"
|
||||||
|
style={a.flex_1}>
|
||||||
|
<AuthLayout.Header.Outer>
|
||||||
|
<AuthLayout.Header.BackButton />
|
||||||
|
<AuthLayout.Header.Content />
|
||||||
|
<AuthLayout.Header.Slot />
|
||||||
|
</AuthLayout.Header.Outer>
|
||||||
<LoggedOutLayout
|
<LoggedOutLayout
|
||||||
leadin=""
|
leadin=""
|
||||||
title={title}
|
title={title}
|
||||||
@@ -219,5 +236,6 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||||||
</LoggedOutLayout>
|
</LoggedOutLayout>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
</AuthLayoutNavigationContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import React from 'react'
|
import {useCallback, useEffect, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {PressableScale} from '#/lib/custom-animations/PressableScale'
|
import {PressableScale} from '#/lib/custom-animations/PressableScale'
|
||||||
|
import {STALE} from '#/state/queries'
|
||||||
|
import {profilesQueryKey} from '#/state/queries/profile'
|
||||||
|
import {useAgent, useSession} from '#/state/session'
|
||||||
import {
|
import {
|
||||||
useLoggedOutView,
|
useLoggedOutView,
|
||||||
useLoggedOutViewControls,
|
useLoggedOutViewControls,
|
||||||
@@ -35,7 +39,7 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
|
|||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {requestedAccountSwitchTo} = useLoggedOutView()
|
const {requestedAccountSwitchTo} = useLoggedOutView()
|
||||||
const [screenState, setScreenState] = React.useState<ScreenState>(() => {
|
const [screenState, setScreenState] = useState<ScreenState>(() => {
|
||||||
if (requestedAccountSwitchTo === 'new') {
|
if (requestedAccountSwitchTo === 'new') {
|
||||||
return ScreenState.S_CreateAccount
|
return ScreenState.S_CreateAccount
|
||||||
} else if (requestedAccountSwitchTo === 'starterpack') {
|
} else if (requestedAccountSwitchTo === 'starterpack') {
|
||||||
@@ -48,11 +52,26 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
|
|||||||
})
|
})
|
||||||
const {clearRequestedAccount} = useLoggedOutViewControls()
|
const {clearRequestedAccount} = useLoggedOutViewControls()
|
||||||
|
|
||||||
React.useEffect(() => {
|
const queryClient = useQueryClient()
|
||||||
|
const {accounts} = useSession()
|
||||||
|
const agent = useAgent()
|
||||||
|
useEffect(() => {
|
||||||
|
const actors = accounts.map(acc => acc.did)
|
||||||
|
void queryClient.prefetchQuery({
|
||||||
|
queryKey: profilesQueryKey(actors),
|
||||||
|
staleTime: STALE.MINUTES.FIVE,
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await agent.getProfiles({actors})
|
||||||
|
return res.data
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [accounts, agent, queryClient])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
setMinimalShellMode(true)
|
setMinimalShellMode(true)
|
||||||
}, [setMinimalShellMode])
|
}, [setMinimalShellMode])
|
||||||
|
|
||||||
const onPressDismiss = React.useCallback(() => {
|
const onPressDismiss = useCallback(() => {
|
||||||
if (onDismiss) {
|
if (onDismiss) {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ export const LoggedOutLayout = ({
|
|||||||
contentContainerStyle={[
|
contentContainerStyle={[
|
||||||
{paddingBottom: isKeyboardVisible ? 300 : 0},
|
{paddingBottom: isKeyboardVisible ? 300 : 0},
|
||||||
]}>
|
]}>
|
||||||
<View style={a.pt_md}>{children}</View>
|
<View style={a.pt_lg}>{children}</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return <View style={a.pt_md}>{children}</View>
|
return <View style={a.pt_lg}>{children}</View>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user