Improvements to UI in web logged-out views (#1341)

* Add LoggedOutLayout for desktop/tablet web

* Avoid screen flash in the transition to onboarding

* Fix comment
zio/stable
Paul Frazee 2023-08-30 17:55:01 -07:00 committed by GitHub
parent a498acab6e
commit 04992f14f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 239 additions and 120 deletions

View File

@ -109,13 +109,8 @@ export class CreateAccountModel {
this.setError('')
this.setIsProcessing(true)
// open the onboarding screens after the session is created
const sessionReadySub = this.rootStore.onSessionReady(() => {
sessionReadySub.remove()
this.rootStore.onboarding.start()
})
try {
this.rootStore.onboarding.start() // start now to avoid flashing the wrong view
await this.rootStore.session.createAccount({
service: this.serviceUrl,
email: this.email,
@ -125,7 +120,7 @@ export class CreateAccountModel {
})
track('Create Account')
} catch (e: any) {
sessionReadySub.remove()
this.rootStore.onboarding.skip() // undo starting the onboard
let errMsg = e.toString()
if (e instanceof ComAtprotoServerCreateAccount.InvalidInviteCodeError) {
errMsg =

View File

@ -9,7 +9,6 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {useAnalytics} from 'lib/analytics/analytics'
import {SplashScreen} from './SplashScreen'
import {CenteredView} from '../util/Views'
enum ScreenState {
S_LoginOrCreateAccount,
@ -43,25 +42,23 @@ export const LoggedOut = observer(() => {
}
return (
<CenteredView style={[s.hContentRegion, pal.view]}>
<SafeAreaView testID="noSessionView" style={s.hContentRegion}>
<ErrorBoundary>
{screenState === ScreenState.S_Login ? (
<Login
onPressBack={() =>
setScreenState(ScreenState.S_LoginOrCreateAccount)
}
/>
) : undefined}
{screenState === ScreenState.S_CreateAccount ? (
<CreateAccount
onPressBack={() =>
setScreenState(ScreenState.S_LoginOrCreateAccount)
}
/>
) : undefined}
</ErrorBoundary>
</SafeAreaView>
</CenteredView>
<SafeAreaView testID="noSessionView" style={[s.hContentRegion, pal.view]}>
<ErrorBoundary>
{screenState === ScreenState.S_Login ? (
<Login
onPressBack={() =>
setScreenState(ScreenState.S_LoginOrCreateAccount)
}
/>
) : undefined}
{screenState === ScreenState.S_CreateAccount ? (
<CreateAccount
onPressBack={() =>
setScreenState(ScreenState.S_LoginOrCreateAccount)
}
/>
) : undefined}
</ErrorBoundary>
</SafeAreaView>
)
})

View File

@ -10,6 +10,7 @@ import {
import {observer} from 'mobx-react-lite'
import {useAnalytics} from 'lib/analytics/analytics'
import {Text} from '../../util/text/Text'
import {LoggedOutLayout} from 'view/com/util/layouts/LoggedOutLayout'
import {s} from 'lib/styles'
import {useStores} from 'state/index'
import {CreateAccountModel} from 'state/models/ui/create-account'
@ -65,60 +66,65 @@ export const CreateAccount = observer(
}, [model, track])
return (
<ScrollView testID="createAccount" style={pal.view}>
<KeyboardAvoidingView behavior="padding">
<View style={styles.stepContainer}>
{model.step === 1 && <Step1 model={model} />}
{model.step === 2 && <Step2 model={model} />}
{model.step === 3 && <Step3 model={model} />}
</View>
<View style={[s.flexRow, s.pl20, s.pr20]}>
<TouchableOpacity
onPress={onPressBackInner}
testID="backBtn"
accessibilityRole="button">
<Text type="xl" style={pal.link}>
Back
</Text>
</TouchableOpacity>
<View style={s.flex1} />
{model.canNext ? (
<LoggedOutLayout
leadin={`Step ${model.step}`}
title="Create Account"
description="We're so excited to have you join us!">
<ScrollView testID="createAccount" style={pal.view}>
<KeyboardAvoidingView behavior="padding">
<View style={styles.stepContainer}>
{model.step === 1 && <Step1 model={model} />}
{model.step === 2 && <Step2 model={model} />}
{model.step === 3 && <Step3 model={model} />}
</View>
<View style={[s.flexRow, s.pl20, s.pr20]}>
<TouchableOpacity
testID="nextBtn"
onPress={onPressNext}
onPress={onPressBackInner}
testID="backBtn"
accessibilityRole="button">
{model.isProcessing ? (
<ActivityIndicator />
) : (
<Text type="xl" style={pal.link}>
Back
</Text>
</TouchableOpacity>
<View style={s.flex1} />
{model.canNext ? (
<TouchableOpacity
testID="nextBtn"
onPress={onPressNext}
accessibilityRole="button">
{model.isProcessing ? (
<ActivityIndicator />
) : (
<Text type="xl-bold" style={[pal.link, s.pr5]}>
Next
</Text>
)}
</TouchableOpacity>
) : model.didServiceDescriptionFetchFail ? (
<TouchableOpacity
testID="retryConnectBtn"
onPress={onPressRetryConnect}
accessibilityRole="button"
accessibilityLabel="Retry"
accessibilityHint="Retries account creation"
accessibilityLiveRegion="polite">
<Text type="xl-bold" style={[pal.link, s.pr5]}>
Next
Retry
</Text>
)}
</TouchableOpacity>
) : model.didServiceDescriptionFetchFail ? (
<TouchableOpacity
testID="retryConnectBtn"
onPress={onPressRetryConnect}
accessibilityRole="button"
accessibilityLabel="Retry"
accessibilityHint="Retries account creation"
accessibilityLiveRegion="polite">
<Text type="xl-bold" style={[pal.link, s.pr5]}>
Retry
</Text>
</TouchableOpacity>
) : model.isFetchingServiceDescription ? (
<>
<ActivityIndicator color="#fff" />
<Text type="xl" style={[pal.text, s.pr5]}>
Connecting...
</Text>
</>
) : undefined}
</View>
<View style={s.footerSpacer} />
</KeyboardAvoidingView>
</ScrollView>
</TouchableOpacity>
) : model.isFetchingServiceDescription ? (
<>
<ActivityIndicator color="#fff" />
<Text type="xl" style={[pal.text, s.pr5]}>
Connecting...
</Text>
</>
) : undefined}
</View>
<View style={s.footerSpacer} />
</KeyboardAvoidingView>
</ScrollView>
</LoggedOutLayout>
)
},
)

View File

@ -93,6 +93,7 @@ function validWebLink(url?: string): string | undefined {
const styles = StyleSheet.create({
policies: {
flexDirection: 'row',
gap: 8,
},
errorIcon: {

View File

@ -17,6 +17,7 @@ import {BskyAgent} from '@atproto/api'
import {useAnalytics} from 'lib/analytics/analytics'
import {Text} from '../../util/text/Text'
import {UserAvatar} from '../../util/UserAvatar'
import {LoggedOutLayout} from 'view/com/util/layouts/LoggedOutLayout'
import {s, colors} from 'lib/styles'
import {createFullHandle} from 'lib/strings/handles'
import {toNiceDomain} from 'lib/strings/url-helpers'
@ -99,52 +100,69 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
}
return (
<KeyboardAvoidingView
testID="signIn"
behavior="padding"
style={[pal.view, s.pt10]}>
<KeyboardAvoidingView testID="signIn" behavior="padding" style={pal.view}>
{currentForm === Forms.Login ? (
<LoginForm
store={store}
error={error}
serviceUrl={serviceUrl}
serviceDescription={serviceDescription}
initialHandle={initialHandle}
setError={setError}
setServiceUrl={setServiceUrl}
onPressBack={onPressBack}
onPressForgotPassword={onPressForgotPassword}
onPressRetryConnect={onPressRetryConnect}
/>
<LoggedOutLayout
leadin=""
title="Sign in"
description="Enter your username and password">
<LoginForm
store={store}
error={error}
serviceUrl={serviceUrl}
serviceDescription={serviceDescription}
initialHandle={initialHandle}
setError={setError}
setServiceUrl={setServiceUrl}
onPressBack={onPressBack}
onPressForgotPassword={onPressForgotPassword}
onPressRetryConnect={onPressRetryConnect}
/>
</LoggedOutLayout>
) : undefined}
{currentForm === Forms.ChooseAccount ? (
<ChooseAccountForm
store={store}
onSelectAccount={onSelectAccount}
onPressBack={onPressBack}
/>
<LoggedOutLayout
leadin=""
title="Sign in as..."
description="Select from an existing account">
<ChooseAccountForm
store={store}
onSelectAccount={onSelectAccount}
onPressBack={onPressBack}
/>
</LoggedOutLayout>
) : undefined}
{currentForm === Forms.ForgotPassword ? (
<ForgotPasswordForm
store={store}
error={error}
serviceUrl={serviceUrl}
serviceDescription={serviceDescription}
setError={setError}
setServiceUrl={setServiceUrl}
onPressBack={gotoForm(Forms.Login)}
onEmailSent={gotoForm(Forms.SetNewPassword)}
/>
<LoggedOutLayout
leadin=""
title="Forgot Password"
description="Let's get your password reset!">
<ForgotPasswordForm
store={store}
error={error}
serviceUrl={serviceUrl}
serviceDescription={serviceDescription}
setError={setError}
setServiceUrl={setServiceUrl}
onPressBack={gotoForm(Forms.Login)}
onEmailSent={gotoForm(Forms.SetNewPassword)}
/>
</LoggedOutLayout>
) : undefined}
{currentForm === Forms.SetNewPassword ? (
<SetNewPasswordForm
store={store}
error={error}
serviceUrl={serviceUrl}
setError={setError}
onPressBack={gotoForm(Forms.ForgotPassword)}
onPasswordSet={gotoForm(Forms.PasswordUpdated)}
/>
<LoggedOutLayout
leadin=""
title="Forgot Password"
description="Let's get your password reset!">
<SetNewPasswordForm
store={store}
error={error}
serviceUrl={serviceUrl}
setError={setError}
onPressBack={gotoForm(Forms.ForgotPassword)}
onPasswordSet={gotoForm(Forms.PasswordUpdated)}
/>
</LoggedOutLayout>
) : undefined}
{currentForm === Forms.PasswordUpdated ? (
<PasswordUpdatedForm onPressNext={gotoForm(Forms.Login)} />
@ -834,9 +852,9 @@ const SetNewPasswordForm = ({
const PasswordUpdatedForm = ({onPressNext}: {onPressNext: () => void}) => {
const {screen} = useAnalytics()
// useEffect(() => {
screen('Signin:PasswordUpdatedForm')
// }, [screen])
useEffect(() => {
screen('Signin:PasswordUpdatedForm')
}, [screen])
const pal = usePalette('default')
return (

View File

@ -0,0 +1,102 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {Text} from '../text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
export const LoggedOutLayout = ({
leadin,
title,
description,
children,
}: React.PropsWithChildren<{
leadin: string
title: string
description: string
}>) => {
const {isMobile, isTabletOrMobile} = useWebMediaQueries()
const pal = usePalette('default')
const sideBg = useColorSchemeStyle(pal.viewLight, pal.view)
const contentBg = useColorSchemeStyle(pal.view, {
backgroundColor: pal.colors.background,
borderColor: pal.colors.border,
borderLeftWidth: 1,
})
if (isMobile) {
return <View style={{paddingTop: 10}}>{children}</View>
}
return (
<View style={styles.container}>
<View style={[styles.side, sideBg]}>
<Text
style={[
pal.textLight,
styles.leadinText,
isTabletOrMobile && styles.leadinTextSmall,
]}>
{leadin}
</Text>
<Text
style={[
pal.link,
styles.titleText,
isTabletOrMobile && styles.titleTextSmall,
]}>
{title}
</Text>
<Text type="2xl-medium" style={[pal.textLight, styles.descriptionText]}>
{description}
</Text>
</View>
<View style={[styles.content, contentBg]}>
<View style={styles.contentWrapper}>{children}</View>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
height: '100vh',
},
side: {
flex: 1,
paddingHorizontal: 40,
paddingBottom: 80,
justifyContent: 'center',
},
content: {
flex: 2,
paddingHorizontal: 40,
justifyContent: 'center',
},
leadinText: {
fontSize: 36,
fontWeight: '800',
textAlign: 'right',
},
leadinTextSmall: {
fontSize: 24,
},
titleText: {
fontSize: 58,
fontWeight: '800',
textAlign: 'right',
},
titleTextSmall: {
fontSize: 36,
},
descriptionText: {
maxWidth: 400,
marginTop: 10,
marginLeft: 'auto',
textAlign: 'right',
},
contentWrapper: {
maxWidth: 600,
},
})