add header to login flow
This commit is contained in:
@@ -43,10 +43,10 @@ export function SwitchAccountDialog({
|
||||
return (
|
||||
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
|
||||
<Dialog.Handle />
|
||||
<Dialog.ScrollableInner label={_(msg`Switch Account`)}>
|
||||
<Dialog.ScrollableInner label={_(msg`Switch account`)}>
|
||||
<View style={[a.gap_lg]}>
|
||||
<Text style={[a.text_2xl, a.font_semi_bold]}>
|
||||
<Trans>Switch Account</Trans>
|
||||
<Trans>Switch account</Trans>
|
||||
</Text>
|
||||
|
||||
<AccountList
|
||||
|
||||
@@ -5,6 +5,7 @@ import {useLingui} from '@lingui/react'
|
||||
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {type SessionAccount, useSession, useSessionApi} from '#/state/session'
|
||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
@@ -77,26 +78,30 @@ export const ChooseAccountForm = ({
|
||||
titleText={<Trans>Select account</Trans>}
|
||||
style={web([a.py_2xl])}>
|
||||
<View>
|
||||
<TextField.LabelText>
|
||||
<Trans>Sign in as...</Trans>
|
||||
</TextField.LabelText>
|
||||
{isWeb && (
|
||||
<TextField.LabelText>
|
||||
<Trans>Sign in as...</Trans>
|
||||
</TextField.LabelText>
|
||||
)}
|
||||
<AccountList
|
||||
onSelectAccount={onSelect}
|
||||
onSelectOther={() => onSelectAccount()}
|
||||
pendingDid={pendingDid}
|
||||
/>
|
||||
</View>
|
||||
<View style={[a.flex_row]}>
|
||||
<Button
|
||||
label={_(msg`Back`)}
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onPress={onPressBack}>
|
||||
<ButtonText>{_(msg`Back`)}</ButtonText>
|
||||
</Button>
|
||||
<View style={[a.flex_1]} />
|
||||
</View>
|
||||
{isWeb && (
|
||||
<View style={[a.flex_row]}>
|
||||
<Button
|
||||
label={_(msg`Back`)}
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onPress={onPressBack}>
|
||||
<ButtonText>{_(msg`Back`)}</ButtonText>
|
||||
</Button>
|
||||
<View style={[a.flex_1]} />
|
||||
</View>
|
||||
)}
|
||||
</FormContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import React, {useRef, useState} from 'react'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Keyboard,
|
||||
LayoutAnimation,
|
||||
type TextInput,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {Keyboard, type TextInput, View} from 'react-native'
|
||||
import {
|
||||
ComAtprotoServerCreateSession,
|
||||
type ComAtprotoServerDescribeServer,
|
||||
@@ -19,10 +13,11 @@ import {cleanError} from '#/lib/strings/errors'
|
||||
import {createFullHandle} from '#/lib/strings/handles'
|
||||
import {logger} from '#/logger'
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
|
||||
import {useSessionApi} from '#/state/session'
|
||||
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 {FormError} from '#/components/forms/FormError'
|
||||
import {HostingProvider} from '#/components/forms/HostingProvider'
|
||||
@@ -84,7 +79,6 @@ export const LoginForm = ({
|
||||
const onPressNext = async () => {
|
||||
if (isProcessing) return
|
||||
Keyboard.dismiss()
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setError('')
|
||||
|
||||
const identifier = identifierValueRef.current.toLowerCase().trim()
|
||||
@@ -141,7 +135,6 @@ export const LoginForm = ({
|
||||
requestNotificationsPermission('Login')
|
||||
} catch (e: any) {
|
||||
const errMsg = e.toString()
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setIsProcessing(false)
|
||||
if (
|
||||
e instanceof ComAtprotoServerCreateSession.AuthFactorTokenRequiredError
|
||||
@@ -178,7 +171,7 @@ export const LoginForm = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<FormContainer testID="loginForm" titleText={<Trans>Sign in</Trans>}>
|
||||
<FormContainer testID="loginForm" titleText={<Trans>Log in</Trans>}>
|
||||
<View>
|
||||
<TextField.LabelText>
|
||||
<Trans>Hosting provider</Trans>
|
||||
@@ -308,25 +301,26 @@ export const LoginForm = ({
|
||||
</View>
|
||||
)}
|
||||
<FormError error={error} />
|
||||
<View style={[a.flex_row, a.align_center, a.pt_md]}>
|
||||
<Button
|
||||
label={_(msg`Back`)}
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onPress={onPressBack}>
|
||||
<ButtonText>
|
||||
<Trans>Back</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
<View style={a.flex_1} />
|
||||
<View style={[a.pt_md, web([a.justify_between, a.flex_row])]}>
|
||||
{isWeb && (
|
||||
<>
|
||||
<Button
|
||||
label={_(msg`Back`)}
|
||||
color="secondary"
|
||||
size="large"
|
||||
onPress={onPressBack}>
|
||||
<ButtonText>
|
||||
<Trans>Back</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{!serviceDescription && error ? (
|
||||
<Button
|
||||
testID="loginRetryButton"
|
||||
label={_(msg`Retry`)}
|
||||
accessibilityHint={_(msg`Retries signing in`)}
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
color="primary_subtle"
|
||||
size="large"
|
||||
onPress={onPressRetryConnect}>
|
||||
<ButtonText>
|
||||
@@ -334,23 +328,24 @@ export const LoginForm = ({
|
||||
</ButtonText>
|
||||
</Button>
|
||||
) : !serviceDescription ? (
|
||||
<>
|
||||
<ActivityIndicator />
|
||||
<Text style={[t.atoms.text_contrast_high, a.pl_md]}>
|
||||
<Trans>Connecting...</Trans>
|
||||
</Text>
|
||||
</>
|
||||
<Button
|
||||
label={_(msg`Connecting to service...`)}
|
||||
size="large"
|
||||
color="secondary"
|
||||
disabled>
|
||||
<ButtonIcon icon={Loader} />
|
||||
<ButtonText>Connecting...</ButtonText>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
testID="loginNextButton"
|
||||
label={_(msg`Next`)}
|
||||
label={_(msg`Log in`)}
|
||||
accessibilityHint={_(msg`Navigates to the next screen`)}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="large"
|
||||
onPress={onPressNext}>
|
||||
<ButtonText>
|
||||
<Trans>Next</Trans>
|
||||
<Trans>Log in</Trans>
|
||||
</ButtonText>
|
||||
{isProcessing && <ButtonIcon icon={Loader} />}
|
||||
</Button>
|
||||
|
||||
@@ -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 {isWeb} from '#/platform/detection'
|
||||
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 {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}]}>
|
||||
{isWeb ? 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="square"
|
||||
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'
|
||||
+41
-23
@@ -18,6 +18,8 @@ import {SetNewPasswordForm} from '#/screens/Login/SetNewPasswordForm'
|
||||
import {atoms as a, native} from '#/alf'
|
||||
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||
import {ChooseAccountForm} from './ChooseAccountForm'
|
||||
import * as AuthLayout from './components/AuthLayout'
|
||||
import {AuthLayoutNavigationContext} from './components/AuthLayout/context'
|
||||
|
||||
enum Forms {
|
||||
Login,
|
||||
@@ -130,11 +132,14 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
||||
let content = null
|
||||
let title = ''
|
||||
let description = ''
|
||||
let goBack = null
|
||||
|
||||
switch (currentForm) {
|
||||
case Forms.Login:
|
||||
title = _(msg`Sign in`)
|
||||
description = _(msg`Enter your username and password`)
|
||||
goBack = () =>
|
||||
accounts.length ? gotoForm(Forms.ChooseAccount) : handlePressBack()
|
||||
content = (
|
||||
<LoginForm
|
||||
error={error}
|
||||
@@ -145,9 +150,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
||||
onAttemptFailed={onAttemptFailed}
|
||||
onAttemptSuccess={onAttemptSuccess}
|
||||
setServiceUrl={setServiceUrl}
|
||||
onPressBack={() =>
|
||||
accounts.length ? gotoForm(Forms.ChooseAccount) : handlePressBack()
|
||||
}
|
||||
onPressBack={goBack}
|
||||
onPressForgotPassword={onPressForgotPassword}
|
||||
onPressRetryConnect={refetchService}
|
||||
/>
|
||||
@@ -156,16 +159,18 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
||||
case Forms.ChooseAccount:
|
||||
title = _(msg`Sign in`)
|
||||
description = _(msg`Select from an existing account`)
|
||||
goBack = handlePressBack
|
||||
content = (
|
||||
<ChooseAccountForm
|
||||
onSelectAccount={onSelectAccount}
|
||||
onPressBack={handlePressBack}
|
||||
onPressBack={goBack}
|
||||
/>
|
||||
)
|
||||
break
|
||||
case Forms.ForgotPassword:
|
||||
title = _(msg`Forgot Password`)
|
||||
description = _(msg`Let's get your password reset!`)
|
||||
goBack = () => gotoForm(Forms.Login)
|
||||
content = (
|
||||
<ForgotPasswordForm
|
||||
error={error}
|
||||
@@ -173,7 +178,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
||||
serviceDescription={serviceDescription}
|
||||
setError={setError}
|
||||
setServiceUrl={setServiceUrl}
|
||||
onPressBack={() => gotoForm(Forms.Login)}
|
||||
onPressBack={goBack}
|
||||
onEmailSent={() => gotoForm(Forms.SetNewPassword)}
|
||||
/>
|
||||
)
|
||||
@@ -181,12 +186,13 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
||||
case Forms.SetNewPassword:
|
||||
title = _(msg`Forgot Password`)
|
||||
description = _(msg`Let's get your password reset!`)
|
||||
goBack = () => gotoForm(Forms.ForgotPassword)
|
||||
content = (
|
||||
<SetNewPasswordForm
|
||||
error={error}
|
||||
serviceUrl={serviceUrl}
|
||||
setError={setError}
|
||||
onPressBack={() => gotoForm(Forms.ForgotPassword)}
|
||||
onPressBack={goBack}
|
||||
onPasswordSet={() => gotoForm(Forms.PasswordUpdated)}
|
||||
/>
|
||||
)
|
||||
@@ -200,23 +206,35 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
||||
break
|
||||
}
|
||||
|
||||
const navigation = goBack ? {goBack} : null
|
||||
|
||||
return (
|
||||
<Animated.View style={a.flex_1} entering={native(FadeIn.duration(90))}>
|
||||
<KeyboardAvoidingView testID="signIn" behavior="padding" style={a.flex_1}>
|
||||
<LoggedOutLayout
|
||||
leadin=""
|
||||
title={title}
|
||||
description={description}
|
||||
scrollable>
|
||||
<LayoutAnimationConfig skipEntering>
|
||||
<ScreenTransition
|
||||
key={currentForm}
|
||||
direction={screenTransitionDirection}>
|
||||
{content}
|
||||
</ScreenTransition>
|
||||
</LayoutAnimationConfig>
|
||||
</LoggedOutLayout>
|
||||
</KeyboardAvoidingView>
|
||||
</Animated.View>
|
||||
<AuthLayoutNavigationContext value={navigation}>
|
||||
<Animated.View style={a.flex_1} entering={native(FadeIn.duration(90))}>
|
||||
<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
|
||||
leadin=""
|
||||
title={title}
|
||||
description={description}
|
||||
scrollable>
|
||||
<LayoutAnimationConfig skipEntering>
|
||||
<ScreenTransition
|
||||
key={currentForm}
|
||||
direction={screenTransitionDirection}>
|
||||
{content}
|
||||
</ScreenTransition>
|
||||
</LayoutAnimationConfig>
|
||||
</LoggedOutLayout>
|
||||
</KeyboardAvoidingView>
|
||||
</Animated.View>
|
||||
</AuthLayoutNavigationContext>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user