Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[auth] Implement email account creation #10

Merged
merged 42 commits into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
4dfaba6
Add components from tutorials
adityapawar1 Sep 27, 2023
b970ec4
Add profile columns to update page
adityapawar1 Sep 29, 2023
77319ab
Update supabase data working
adityapawar1 Sep 29, 2023
53bd2c3
Only update non-blank entries
adityapawar1 Sep 29, 2023
2f29992
Run eslint fix
adityapawar1 Sep 29, 2023
3114e12
Fix eslint errors
adityapawar1 Sep 29, 2023
fd6cfff
Fix tsc errors
adityapawar1 Sep 29, 2023
83bbbbd
Fix trivial review changes
adityapawar1 Oct 3, 2023
536a2d9
Add components from tutorials
adityapawar1 Sep 27, 2023
74b77d4
Add profile columns to update page
adityapawar1 Sep 29, 2023
57aa90c
Update supabase data working
adityapawar1 Sep 29, 2023
6cbb24f
Only update non-blank entries
adityapawar1 Sep 29, 2023
3a9a2f8
Run eslint fix
adityapawar1 Sep 29, 2023
5b48100
Fix eslint errors
adityapawar1 Sep 29, 2023
ab2a06e
Fix tsc errors
adityapawar1 Sep 29, 2023
ea56070
Fix trivial review changes
adityapawar1 Oct 3, 2023
0e4eed8
Merge
adityapawar1 Oct 3, 2023
142e05f
Downgrade ts to be compatible with deps
adityapawar1 Oct 3, 2023
1e2f1d3
Add components from tutorials
adityapawar1 Sep 27, 2023
d87c56a
Add profile columns to update page
adityapawar1 Sep 29, 2023
b0464bc
Update supabase data working
adityapawar1 Sep 29, 2023
6fb0e10
Only update non-blank entries
adityapawar1 Sep 29, 2023
8f3ba0a
Run eslint fix
adityapawar1 Sep 29, 2023
8688585
Fix eslint errors
adityapawar1 Sep 29, 2023
a485a9d
Fix trivial review changes
adityapawar1 Oct 3, 2023
4b6d64a
Add components from tutorials
adityapawar1 Sep 27, 2023
81f8271
Add profile columns to update page
adityapawar1 Sep 29, 2023
0824996
Update supabase data working
adityapawar1 Sep 29, 2023
28c00d1
Only update non-blank entries
adityapawar1 Sep 29, 2023
2350fad
Run eslint fix
adityapawar1 Sep 29, 2023
db1c8b4
Fix eslint errors
adityapawar1 Sep 29, 2023
5989a25
Fix merge conflicts
adityapawar1 Oct 4, 2023
6bcfdb3
Fixes after merge :(
adityapawar1 Oct 4, 2023
5eb03ed
Fix eslint errors
adityapawar1 Oct 4, 2023
460091f
Fix routing for sign up/in
adityapawar1 Oct 4, 2023
4a3d744
Downgrade ts
adityapawar1 Oct 4, 2023
7f9f305
Fix andriod date taking up the screen
adityapawar1 Oct 4, 2023
e2d3cbe
Fix build
adityapawar1 Oct 4, 2023
d9cf4b5
Downgrade ts
adityapawar1 Oct 4, 2023
422c7fa
Fix android date issue
adityapawar1 Oct 7, 2023
16fbd23
Merge branch 'main' into adi/email-auth
adityapawar1 Oct 7, 2023
45e4a25
Fix package-lock.jsonO
adityapawar1 Oct 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]);
adityapawar1 marked this conversation as resolved.
Show resolved Hide resolved

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