Skip to content

Commit

Permalink
[nav] Refactor auth to follow design user flows (#21)
Browse files Browse the repository at this point in the history
* Refactor components to be screens

* Working on email verification redirect

* Redirect to onboarding after sign up

* Fix eslint errors

* Run prettier

* Fix navigation and global styles

* Add update profile to setting page

---------

Co-authored-by: Aditya Pawar <[email protected]>
  • Loading branch information
adityapawar1 and adityapawar1 authored Oct 19, 2023
1 parent 0f24043 commit ef3803e
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 275 deletions.
1 change: 1 addition & 0 deletions src/app/auth/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ function StackLayout() {
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="signup" options={{ headerShown: false }} />
<Stack.Screen name="login" options={{ headerShown: false }} />
<Stack.Screen name="onboarding" options={{ headerShown: false }} />
</Stack>
);
Expand Down
62 changes: 62 additions & 0 deletions src/app/auth/login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useState } from 'react';
import { Redirect, Link } from 'expo-router';
import { Alert, View } from 'react-native';
import { Button, Input } from 'react-native-elements';
import { useSession } from '../../utils/AuthContext';
import globalStyles from '../../styles/globalStyles';

function LoginScreen() {
const sessionHandler = useSession();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);

if (sessionHandler.session) {
return <Redirect href="/home" />;
}

const signInWithEmail = async () => {
setLoading(true);
const { error, data } = await sessionHandler.signInWithEmail(
email,
password,
);

if (error) Alert.alert(error.message);
setLoading(false);
};

return (
<View style={globalStyles.auth_container}>
<View style={[globalStyles.verticallySpaced, globalStyles.mt20]}>
<Input
label="Email"
leftIcon={{ type: 'font-awesome', name: 'envelope' }}
onChangeText={text => setEmail(text)}
value={email}
placeholder="[email protected]"
autoCapitalize="none"
/>
</View>
<View style={globalStyles.verticallySpaced}>
<Input
label="Password"
leftIcon={{ type: 'font-awesome', name: 'lock' }}
onChangeText={text => setPassword(text)}
value={password}
secureTextEntry
placeholder="Password"
autoCapitalize="none"
/>
</View>

<Link href="/auth/signup">Don&apos;t have an account? Sign Up</Link>

<View style={[globalStyles.verticallySpaced, globalStyles.mt20]}>
<Button title="Log In" disabled={loading} onPress={signInWithEmail} />
</View>
</View>
);
}

export default LoginScreen;
159 changes: 153 additions & 6 deletions src/app/auth/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,161 @@
import { View } from 'react-native';

import Account from '../../components/Account';
import Login from '../../components/Login';
import { useState, useEffect } from 'react';
import { View, Alert, ScrollView, Platform } from 'react-native';
import { Button, Input } from 'react-native-elements';
import DateTimePicker from '@react-native-community/datetimepicker';
import { Redirect, router } from 'expo-router';
import supabase from '../../utils/supabase';
import UserStringInput from '../../components/UserStringInput';
import { useSession } from '../../utils/AuthContext';
import globalStyles from '../../styles/globalStyles';

function OnboardingScreen() {
const { session } = useSession();
const { session, signOut } = useSession();
const [loading, setLoading] = useState(true);
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [birthday, setBirthday] = useState(new Date());
const [gender, setGender] = useState('');
const [raceEthnicity, setRaceEthnicity] = useState('');
const [showDatePicker, setShowDatePicker] = useState(Platform.OS === 'ios');

const getProfile = async () => {
try {
setLoading(true);
if (!session?.user) throw new Error('No user on the session!');

const { data, error, status } = await supabase
.from('profiles')
.select(`first_name, last_name, birthday, gender, race_ethnicity`)
.eq('user_id', session?.user.id)
.single();

if (error && status !== 406) {
throw error;
}

if (data) {
setFirstName(data.first_name || firstName);
setLastName(data.last_name || lastName);
setBirthday(new Date(data.birthday) || birthday);
setGender(data.gender || gender);
setRaceEthnicity(data.race_ethnicity || raceEthnicity);
}
} catch (error) {
if (error instanceof Error) {
Alert.alert(`Get profile error: ${error.message}`);
}
} finally {
setLoading(false);
}
};

useEffect(() => {
if (session) getProfile();
}, [session]);

const updateProfileAndGoHome = async () => {
try {
setLoading(true);
if (!session?.user) throw new Error('No user on the session!');

// Only update values that are not blank
const updates = {
...(firstName && { first_name: firstName }),
...(lastName && { last_name: lastName }),
...(gender && { gender }),
...(raceEthnicity && { race_ethnicity: raceEthnicity }),
...(birthday && { birthday }),
};

// Check if user exists
const { count } = await supabase
.from('profiles')
.select(`*`, { count: 'exact' })
.eq('user_id', session?.user.id);

if (count && count >= 1) {
// Update user if they exist
const { error } = await supabase
.from('profiles')
.update(updates)
.eq('user_id', session?.user.id)
.select('*');

if (error) throw error;
} else {
// Create user if they don't exist
const { error } = await supabase.from('profiles').insert(updates);

if (error) throw error;
}

Alert.alert('Succesfully updated user!');
router.replace('/home');
} catch (error) {
if (error instanceof Error) {
Alert.alert(error.message);
}
} finally {
setLoading(false);
}
};

if (!session) {
return <Redirect href="/auth/login" />;
}

return (
<View>{session?.user ? <Account key={session.user.id} /> : <Login />}</View>
<ScrollView style={globalStyles.auth_container}>
<View style={[globalStyles.verticallySpaced, globalStyles.mt20]}>
<Input label="Email" value={session?.user?.email} disabled />
</View>
<UserStringInput
label="First Name"
value={firstName}
onChange={setFirstName}
/>
<UserStringInput
label="Last Name"
value={lastName}
onChange={setLastName}
/>
<UserStringInput label="Gender" value={gender} onChange={setGender} />
<UserStringInput
label="Race/Ethnicity"
value={raceEthnicity}
onChange={setRaceEthnicity}
/>

{Platform.OS !== 'ios' && (
<Button
title="Change Birthday"
onPress={() => setShowDatePicker(true)}
/>
)}
{showDatePicker && (
<DateTimePicker
testID="dateTimePicker"
value={birthday}
mode="date"
onChange={date => {
setShowDatePicker(Platform.OS === 'ios');
if (date.nativeEvent.timestamp) {
setBirthday(new Date(date.nativeEvent.timestamp));
}
}}
/>
)}
<View style={[globalStyles.verticallySpaced, globalStyles.mt20]}>
<Button
title={loading ? 'Loading ...' : 'Update profile'}
onPress={updateProfileAndGoHome}
disabled={loading}
/>
</View>
<View style={globalStyles.verticallySpaced}>
<Button title="Skip" onPress={() => router.push('/home')} />
</View>
</ScrollView>
);
}

Expand Down
84 changes: 78 additions & 6 deletions src/app/auth/signup.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,86 @@
import { View } from 'react-native';

import Account from '../../components/Account';
import Login from '../../components/Login';
import React, { useEffect, useState } from 'react';
import { Redirect, Link, router } from 'expo-router';
import { Alert, View, Text } from 'react-native';
import { Button, Input } from 'react-native-elements';
import { useSession } from '../../utils/AuthContext';
import globalStyles from '../../styles/globalStyles';

function SignUpScreen() {
const { session } = useSession();
const { session, signUp, signInWithEmail } = useSession();

const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [signedUp, setSignedUp] = useState(false);

if (session) {
return <Redirect href="/home" />;
}

const signIn = async () => {
setLoading(true);
const { error } = await signInWithEmail(email, password);

if (error) Alert.alert(error.message);
else router.replace('/auth/onboarding');

setLoading(false);
};

const signUpWithEmail = async () => {
setLoading(true);
const { error } = await signUp(email, password);

if (error) Alert.alert(error.message);
else {
Alert.alert(
'Please follow the instructions in your email to verify your account, then login',
);
setSignedUp(true);
}
setLoading(false);
};

return (
<View>{session?.user ? <Account key={session.user.id} /> : <Login />}</View>
<View style={globalStyles.auth_container}>
<View style={[globalStyles.verticallySpaced, globalStyles.mt20]}>
<Input
label="Email"
leftIcon={{ type: 'font-awesome', name: 'envelope' }}
onChangeText={text => setEmail(text)}
value={email}
placeholder="[email protected]"
autoCapitalize="none"
/>
</View>
<View style={globalStyles.verticallySpaced}>
<Input
label="Password"
leftIcon={{ type: 'font-awesome', name: 'lock' }}
onChangeText={text => setPassword(text)}
value={password}
secureTextEntry
placeholder="Password"
autoCapitalize="none"
/>
</View>
{signedUp ? (
<View style={[globalStyles.verticallySpaced, globalStyles.mt20]}>
<Button title="Log In" disabled={loading} onPress={signIn} />
</View>
) : (
<>
<Link href="/auth/login">Already have an account? Log In</Link>
<View style={[globalStyles.verticallySpaced, globalStyles.mt20]}>
<Button
title="Sign Up"
disabled={loading}
onPress={signUpWithEmail}
/>
</View>
</>
)}
</View>
);
}

Expand Down
6 changes: 5 additions & 1 deletion src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Redirect } from 'expo-router';
import { useSession } from '../utils/AuthContext';

function StartPage() {
return <Redirect href="/auth" />;
const { session } = useSession();

if (!session) return <Redirect href="/auth/login" />;
return <Redirect href="/home" />;
}

export default StartPage;
16 changes: 14 additions & 2 deletions src/app/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import { Text } from 'react-native';
import { Redirect, router } from 'expo-router';
import { Text, View } from 'react-native';
import { Button } from 'react-native-elements';
import { SafeAreaView } from 'react-native-safe-area-context';

import { useSession } from '../utils/AuthContext';
import globalStyles from '../styles/globalStyles';

function SettingsScreen() {
const { session, signOut } = useSession();

if (!session) return <Redirect href="/auth/login" />;
return (
<SafeAreaView style={globalStyles.container}>
<Text style={globalStyles.h1}>Settings</Text>
<View>
<Button
title="Update Profile"
onPress={() => router.push('/auth/onboarding')}
/>
<Button title="Sign Out" onPress={signOut} />
</View>
</SafeAreaView>
);
}
Expand Down
Loading

0 comments on commit ef3803e

Please sign in to comment.