Skip to content

Commit

Permalink
[auth] Implement email account creation (#10)
Browse files Browse the repository at this point in the history
* Add components from tutorials

* Add profile columns to update page

* Update supabase data working

* Only update non-blank entries

* Run eslint fix

* Fix eslint errors

* Fix tsc errors

* Fix trivial review changes

* Add components from tutorials

* Add profile columns to update page

* Update supabase data working

* Only update non-blank entries

* Run eslint fix

* Fix eslint errors

* Fix tsc errors

* Fix trivial review changes

* Downgrade ts to be compatible with deps

* Add components from tutorials

* Add profile columns to update page

* Update supabase data working

* Only update non-blank entries

* Run eslint fix

* Fix eslint errors

* Fix trivial review changes

* Add components from tutorials

* Add profile columns to update page

* Update supabase data working

* Only update non-blank entries

* Run eslint fix

* Fix eslint errors

* Fixes after merge :(

* Fix eslint errors

* Fix routing for sign up/in

* Downgrade ts

* Fix andriod date taking up the screen

* Fix build

* Downgrade ts

* Fix android date issue

* Fix package-lock.jsonO

---------

Co-authored-by: Aditya Pawar <[email protected]>
  • Loading branch information
adityapawar1 and adityapawar1 authored Oct 7, 2023
1 parent d0a7f60 commit e30c936
Show file tree
Hide file tree
Showing 10 changed files with 974 additions and 631 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
EXPO_PUBLIC_SUPABASE_URL =
EXPO_PUBLIC_SUPABASE_ANON_KEY =
1,221 changes: 612 additions & 609 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
},
"dependencies": {
"@expo/vector-icons": "^13.0.0",
"@react-native-async-storage/async-storage": "1.18.2",
"@react-native-community/datetimepicker": "7.2.0",
"@react-navigation/bottom-tabs": "^6.5.9",
"@react-navigation/material-bottom-tabs": "^6.2.17",
"@react-navigation/native": "^6.1.8",
"@react-navigation/native-stack": "^6.9.14",
"@react-navigation/stack": "^6.3.18",
"@supabase/supabase-js": "^2.36.0",
"axios": "^1.5.0",
"deprecated-react-native-prop-types": "^4.2.1",
"dom-parser": "^0.1.6",
Expand All @@ -36,9 +39,12 @@
"react-native-root-siblings": "^4.1.1",
"react-native-root-toast": "^3.5.1",
"react-native-ionicons": "^4.6.5",
"react-native": "0.72.5",
"react-native-elements": "^3.4.3",
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0",
"react-native-vector-icons": "^10.0.0"
"react-native-vector-icons": "^10.0.0",
"react-native-url-polyfill": "^2.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
Expand All @@ -59,7 +65,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"husky": "^8.0.3",
"prettier": "^2.8.8",
"typescript": "^4.9.5"
"typescript": "^4.2.0"
},
"private": true
}
4 changes: 2 additions & 2 deletions src/app/auth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import globalStyles from '../../../globalStyles';
function LoginScreen() {
return (
<SafeAreaView style={globalStyles.container}>
<Text style={globalStyles.h1}>Login</Text>
<Button title="Login" onPress={() => router.push('/home')} />
<Text style={globalStyles.h1}>Auth</Text>
<Button title="Login" onPress={() => router.push('/auth/onboarding')} />
<Button title="Sign Up" onPress={() => router.push('/auth/signup')} />
</SafeAreaView>
);
Expand Down
35 changes: 25 additions & 10 deletions src/app/auth/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import { Link } from 'expo-router';
import { Button, Text } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import globalStyles from '../../../globalStyles';
import { useState, useEffect } from 'react';
import { View } from 'react-native';
import { Session } from '@supabase/supabase-js';
import supabase from '../../utils/supabase';
import Login from '../../components/Login';
import Account from '../../components/Account';

function OnboardingScreen() {
const [session, setSession] = useState<Session | null>(null);

useEffect(() => {
supabase.auth.getSession().then(({ data: { session: newSession } }) => {
setSession(newSession);
});

supabase.auth.onAuthStateChange((_event, newSession) => {
setSession(newSession);
});
}, []);

return (
<SafeAreaView style={globalStyles.container}>
<Text style={globalStyles.h1}>Onboarding</Text>
<Link href="/home" asChild>
<Button title="Update Profile" />
</Link>
</SafeAreaView>
<View>
{session && session.user ? (
<Account key={session.user.id} session={session} />
) : (
<Login />
)}
</View>
);
}

Expand Down
33 changes: 25 additions & 8 deletions src/app/auth/signup.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import { router } from 'expo-router';
import { Button, Text } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import globalStyles from '../../../globalStyles';
import { Session } from '@supabase/supabase-js';
import { useEffect, useState } from 'react';
import { View } from 'react-native';
import Account from '../../components/Account';
import Login from '../../components/Login';
import supabase from '../../utils/supabase';

function SignUpScreen() {
const [session, setSession] = useState<Session | null>(null);

useEffect(() => {
supabase.auth.getSession().then(({ data: { session: newSession } }) => {
setSession(newSession);
});

supabase.auth.onAuthStateChange((_event, newSession) => {
setSession(newSession);
});
}, []);

return (
<SafeAreaView style={globalStyles.container}>
<Text style={globalStyles.h1}>Sign Up</Text>
<Button title="Sign Up" onPress={() => router.push('/auth/onboarding')} />
</SafeAreaView>
<View>
{session && session.user ? (
<Account key={session.user.id} session={session} />
) : (
<Login />
)}
</View>
);
}

Expand Down
167 changes: 167 additions & 0 deletions src/components/Account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { useState, useEffect } from 'react';
import { StyleSheet, View, Alert, ScrollView, Platform } from 'react-native';
import { Button, Input } from 'react-native-elements';
import { Session } from '@supabase/supabase-js';
import DateTimePicker from '@react-native-community/datetimepicker';
import supabase from '../utils/supabase';
import UserStringInput from './UserStringInput';

const styles = StyleSheet.create({
container: {
marginTop: 40,
padding: 12,
},
verticallySpaced: {
paddingTop: 4,
paddingBottom: 4,
alignSelf: 'stretch',
},
mt20: {
marginTop: 20,
},
});

export default function Account({ session }: { session: Session }) {
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 updateProfile = 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!');
} catch (error) {
if (error instanceof Error) {
Alert.alert(error.message);
}
} finally {
setLoading(false);
}
};

return (
<ScrollView style={styles.container}>
<View style={[styles.verticallySpaced, styles.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={[styles.verticallySpaced, styles.mt20]}>
<Button
title={loading ? 'Loading ...' : 'Update'}
onPress={updateProfile}
disabled={loading}
/>
</View>
<View style={styles.verticallySpaced}>
<Button title="Sign Out" onPress={() => supabase.auth.signOut()} />
</View>
</ScrollView>
);
}
80 changes: 80 additions & 0 deletions src/components/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { useState } from 'react';
import { Alert, StyleSheet, View } from 'react-native';
import { Button, Input } from 'react-native-elements';
import supabase from '../utils/supabase';

const styles = StyleSheet.create({
container: {
marginTop: 40,
padding: 12,
},
verticallySpaced: {
paddingTop: 4,
paddingBottom: 4,
alignSelf: 'stretch',
},
mt20: {
marginTop: 20,
},
});

export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);

const signInWithEmail = async () => {
setLoading(true);
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});

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

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

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

return (
<View style={styles.container}>
<View style={[styles.verticallySpaced, styles.mt20]}>
<Input
label="Email"
leftIcon={{ type: 'font-awesome', name: 'envelope' }}
onChangeText={text => setEmail(text)}
value={email}
placeholder="[email protected]"
autoCapitalize="none"
/>
</View>
<View style={styles.verticallySpaced}>
<Input
label="Password"
leftIcon={{ type: 'font-awesome', name: 'lock' }}
onChangeText={text => setPassword(text)}
value={password}
secureTextEntry
placeholder="Password"
autoCapitalize="none"
/>
</View>
<View style={[styles.verticallySpaced, styles.mt20]}>
<Button title="Sign in" disabled={loading} onPress={signInWithEmail} />
</View>
<View style={styles.verticallySpaced}>
<Button title="Sign up" disabled={loading} onPress={signUpWithEmail} />
</View>
</View>
);
}
Loading

0 comments on commit e30c936

Please sign in to comment.