Skip to content

Commit

Permalink
Fix dark screen transition between screen (#2339)
Browse files Browse the repository at this point in the history
* Make the wallet icon dark mode friendly

* Fix dark mode on onboarding screen

* Wrap with suspense

* Refactor email handling

* Refactor OnboardingRecommendedUsers styling

* fix conflict

* wrap suspense
  • Loading branch information
jakzaizzat authored Mar 7, 2024
1 parent e966c94 commit fce77fa
Show file tree
Hide file tree
Showing 5 changed files with 486 additions and 410 deletions.
300 changes: 173 additions & 127 deletions apps/mobile/src/screens/Login/OnboardingEmailScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react';
import { Suspense, useCallback, useMemo, useState } from 'react';
import { KeyboardAvoidingView, Platform, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { graphql, useLazyLoadQuery } from 'react-relay';
import useDebounce from 'shared/hooks/useDebounce';

import { BackButton } from '~/components/BackButton';
import { OnboardingProgressBar } from '~/components/Onboarding/OnboardingProgressBar';
Expand All @@ -23,29 +24,19 @@ import { magic } from '../../magic';

const FALLBACK_ERROR_MESSAGE = `Something unexpected went wrong while logging in. We've been notified and are looking into it`;

export function OnboardingEmailScreen() {
function InnerOnboardingEmailScreen() {
const [email, setEmail] = useState('');
const query = useLazyLoadQuery<OnboardingEmailScreenQuery>(
graphql`
query OnboardingEmailScreenQuery($emailAddress: Email!) {
isEmailAddressAvailable(emailAddress: $emailAddress)
}
`,
{
emailAddress: email,
}
);
const [isLoggingIn, setIsLoggingIn] = useState(false);

const navigation = useNavigation<LoginStackNavigatorProp>();
const route = useRoute<RouteProp<LoginStackNavigatorParamList, 'OnboardingEmail'>>();

const authMethod = route.params.authMethod;
const authMechanism = route.params.authMechanism;

const { top, bottom } = useSafeAreaInsets();
const { bottom } = useSafeAreaInsets();

const [error, setError] = useState('');
const [isLoggingIn, setIsLoggingIn] = useState(false);

const [login] = useLogin();
const reportError = useReportError();
Expand All @@ -56,15 +47,11 @@ export function OnboardingEmailScreen() {
setEmail(text);
}, []);

const isInvalidEmail = useMemo(() => {
return !EMAIL_FORMAT.test(email);
}, [email]);

const handleContinue = useCallback(async () => {
const handleContinueEmailFlow = useCallback(async () => {
let hasNavigatedForward = false;

setError('');
setIsLoggingIn(true);
setError('');

function handleLoginError({
message,
Expand All @@ -88,41 +75,9 @@ export function OnboardingEmailScreen() {
}
}

if (isInvalidEmail) {
setError("That doesn't look like a valid email address. Please double-check and try again");
setIsLoggingIn(false);
return;
}

// If it's a wallet auth mechanism, we need to check if the email is available
if (authMethod === 'Wallet' && authMechanism?.authMechanismType === 'eoa') {
try {
const isEmailAddressAvailable = query.isEmailAddressAvailable;

if (!isEmailAddressAvailable) {
setError('This email address is already in use');
setIsLoggingIn(false);
return;
}
} catch (error) {
handleLoginError({ message: FALLBACK_ERROR_MESSAGE, underlyingError: error as Error });
return;
}
}

try {
hasNavigatedForward = true;

if (authMethod === 'Wallet' && authMechanism?.authMechanismType === 'eoa') {
// Redirect to the next screen with the wallet auth mechanism
navigation.navigate('OnboardingUsername', {
authMechanism,
authMethod: 'Wallet',
email,
});
return;
}

const token = await magic.auth.loginWithMagicLink({ email, showUI: false });

if (!token) {
Expand Down Expand Up @@ -153,93 +108,184 @@ export function OnboardingEmailScreen() {
} finally {
setIsLoggingIn(false);
}
}, [
authMechanism,
authMethod,
email,
isInvalidEmail,
login,
navigation,
query.isEmailAddressAvailable,
reportError,
track,
]);
}, [email, login, navigation, reportError, track]);

const handleContinueWalletFlow = useCallback(() => {
if (authMechanism?.authMechanismType === 'eoa') {
navigation.navigate('OnboardingUsername', {
authMechanism,
authMethod: 'Wallet',
email,
});
}
}, [authMechanism, email, navigation]);

const handleContinue = useCallback(() => {
if (authMethod === 'Email') {
handleContinueEmailFlow();
} else {
handleContinueWalletFlow();
}
}, [authMethod, handleContinueEmailFlow, handleContinueWalletFlow]);

const handleBack = useCallback(() => {
navigation.goBack();
}, [navigation]);

return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ paddingTop: top }}
className="flex flex-1 flex-col bg-white dark:bg-black-900"
>
<View className="flex flex-col flex-grow space-y-8 px-4">
<View>
<View className="relative flex-row items-center justify-between pb-4">
<BackButton onPress={handleBack} />

<View
className="absolute w-full flex flex-row justify-center items-center"
pointerEvents="none"
>
<Typography className="text-sm" font={{ family: 'ABCDiatype', weight: 'Bold' }}>
Add your email
</Typography>
</View>

<View />
<View className="flex flex-col flex-grow space-y-8 px-4">
<View>
<View className="relative flex-row items-center justify-between pb-4">
<BackButton onPress={handleBack} />

<View
className="absolute w-full flex flex-row justify-center items-center"
pointerEvents="none"
>
<Typography className="text-sm" font={{ family: 'ABCDiatype', weight: 'Bold' }}>
Add your email
</Typography>
</View>

<OnboardingProgressBar from={0} to={20} />
<View />
</View>
<View
className="flex-1 justify-center items-center space-y-12 px-8"
style={{
marginBottom: bottom,
}}
>
<OnboardingTextInput
autoFocus
placeholder="your@email.com"
keyboardType="email-address"
autoComplete="email"
value={email}
onChange={(e) => handleEmailChange(e.nativeEvent.text)}
/>
<View className="space-y-4 w-full">
<Button
eventElementId="Submit Email Button"
eventName="Sign In Attempt"
eventContext={contexts.Onboarding}
className={clsx(
'w-full',
email.length > 0 && 'opacity-100',
email.length === 0 && 'opacity-0'
)}
loading={isLoggingIn}
onPress={handleContinue}
variant={isInvalidEmail ? 'disabled' : 'primary'}
text="Next"
/>

<Typography
className={clsx(
'text-sm text-red',
email.length > 0 && 'opacity-100',
email.length === 0 && 'opacity-0',
error && 'text-red',
!error && 'text-shadow'
)}
font={{ family: 'ABCDiatype', weight: 'Regular' }}
>
{error ? error : 'You will receive an email with a link to verify your account'}
</Typography>
</View>
<View />
<OnboardingProgressBar from={0} to={20} />
</View>
<View
className="flex-1 justify-center items-center space-y-12 px-8"
style={{
marginBottom: bottom,
}}
>
<OnboardingTextInput
autoFocus
placeholder="your@email.com"
keyboardType="email-address"
autoComplete="email"
value={email}
onChange={(e) => handleEmailChange(e.nativeEvent.text)}
/>
<View className="space-y-4 w-full">
<Suspense
fallback={
<Button
eventElementId="Submit Email Button"
eventName="Sign In Attempt"
eventContext={contexts.Onboarding}
className="w-full"
text="Next"
/>
}
>
<SubmitEmailButton
authMethod={authMethod}
email={email}
onSubmit={handleContinue}
onErrorMessage={setError}
isLoggingIn={isLoggingIn}
/>
</Suspense>

<Typography
className={clsx(
'text-sm text-red',
email.length > 0 && 'opacity-100',
email.length === 0 && 'opacity-0',
error && 'text-red',
!error && 'text-shadow'
)}
font={{ family: 'ABCDiatype', weight: 'Regular' }}
>
{error ? error : 'You will receive an email with a link to verify your account'}
</Typography>
</View>
<View />
</View>
</View>
);
}

type SubmitEmailButtonProps = {
authMethod: 'Email' | 'Wallet';
email: string;
onSubmit: () => void;
onErrorMessage: (message: string) => void;
isLoggingIn: boolean;
};

function SubmitEmailButton({
authMethod,
email,
onErrorMessage,
onSubmit,
isLoggingIn,
}: SubmitEmailButtonProps) {
const debouncedEmail = useDebounce(email, 500);

const query = useLazyLoadQuery<OnboardingEmailScreenQuery>(
graphql`
query OnboardingEmailScreenQuery($emailAddress: Email!) {
isEmailAddressAvailable(emailAddress: $emailAddress)
}
`,
{
emailAddress: debouncedEmail,
}
);

const isInvalidEmail = useMemo(() => {
return !EMAIL_FORMAT.test(email);
}, [email]);

const handleSubmit = useCallback(() => {
if (isInvalidEmail) {
onErrorMessage(
"That doesn't look like a valid email address. Please double-check and try again"
);
return;
}

const isEmailAddressAvailable = query.isEmailAddressAvailable ?? false;

if (!isEmailAddressAvailable && authMethod === 'Wallet') {
onErrorMessage('This email address is already in use');
return;
}

onSubmit();
}, [authMethod, isInvalidEmail, onErrorMessage, onSubmit, query.isEmailAddressAvailable]);

return (
<Button
eventElementId="Submit Email Button"
eventName="Sign In Attempt"
eventContext={contexts.Onboarding}
className={clsx(
'w-full',
email.length > 0 && 'opacity-100',
email.length === 0 && 'opacity-0'
)}
loading={isLoggingIn}
onPress={handleSubmit}
variant={isInvalidEmail ? 'disabled' : 'primary'}
text="Next"
/>
);
}

export function OnboardingEmailScreen() {
const { top } = useSafeAreaInsets();

return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ paddingTop: top }}
className="flex flex-1 flex-col bg-white dark:bg-black-900"
>
<Suspense fallback={null}>
<InnerOnboardingEmailScreen />
</Suspense>
</KeyboardAvoidingView>
);
}
Loading

0 comments on commit fce77fa

Please sign in to comment.