about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-08-30 17:55:01 -0700
committerGitHub <noreply@github.com>2023-08-30 17:55:01 -0700
commit04992f14f15bb7227dd1812d3fdb3cda912c2bba (patch)
tree5f7ea9d298ac959aaff9df7b3d604f1bfdea1c66 /src
parenta498acab6e9c4722b8abc509529e712755c2b01c (diff)
downloadvoidsky-04992f14f15bb7227dd1812d3fdb3cda912c2bba.tar.zst
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
Diffstat (limited to 'src')
-rw-r--r--src/state/models/ui/create-account.ts9
-rw-r--r--src/view/com/auth/LoggedOut.tsx39
-rw-r--r--src/view/com/auth/create/CreateAccount.tsx106
-rw-r--r--src/view/com/auth/create/Policies.tsx1
-rw-r--r--src/view/com/auth/login/Login.tsx102
-rw-r--r--src/view/com/util/layouts/LoggedOutLayout.tsx102
6 files changed, 239 insertions, 120 deletions
diff --git a/src/state/models/ui/create-account.ts b/src/state/models/ui/create-account.ts
index d9d4f51b9..c5d9f6d9b 100644
--- a/src/state/models/ui/create-account.ts
+++ b/src/state/models/ui/create-account.ts
@@ -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 =
diff --git a/src/view/com/auth/LoggedOut.tsx b/src/view/com/auth/LoggedOut.tsx
index e35706466..6d3b87dd3 100644
--- a/src/view/com/auth/LoggedOut.tsx
+++ b/src/view/com/auth/LoggedOut.tsx
@@ -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>
   )
 })
diff --git a/src/view/com/auth/create/CreateAccount.tsx b/src/view/com/auth/create/CreateAccount.tsx
index d6cb1a0a7..8cf1cfaf5 100644
--- a/src/view/com/auth/create/CreateAccount.tsx
+++ b/src/view/com/auth/create/CreateAccount.tsx
@@ -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-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]}>
-                  Retry
+                <Text type="xl" style={pal.link}>
+                  Back
                 </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>
+              <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]}>
+                    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>
+      </LoggedOutLayout>
     )
   },
 )
diff --git a/src/view/com/auth/create/Policies.tsx b/src/view/com/auth/create/Policies.tsx
index a3943d8cc..8eb669bcf 100644
--- a/src/view/com/auth/create/Policies.tsx
+++ b/src/view/com/auth/create/Policies.tsx
@@ -93,6 +93,7 @@ function validWebLink(url?: string): string | undefined {
 
 const styles = StyleSheet.create({
   policies: {
+    flexDirection: 'row',
     gap: 8,
   },
   errorIcon: {
diff --git a/src/view/com/auth/login/Login.tsx b/src/view/com/auth/login/Login.tsx
index c277bfb9e..fac923393 100644
--- a/src/view/com/auth/login/Login.tsx
+++ b/src/view/com/auth/login/Login.tsx
@@ -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 (
diff --git a/src/view/com/util/layouts/LoggedOutLayout.tsx b/src/view/com/util/layouts/LoggedOutLayout.tsx
new file mode 100644
index 000000000..daa33cece
--- /dev/null
+++ b/src/view/com/util/layouts/LoggedOutLayout.tsx
@@ -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,
+  },
+})