Skip to content

feat(Onboarding) UI update and basic text input verification #24

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
81 changes: 39 additions & 42 deletions packages/app/Root.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import React, { useEffect } from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
Root.js
import React, { useState, useEffect } from 'react';
import { View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import AsyncStorage from '@react-native-async-storage/async-storage';

import { UserProvider, useUserContext } from "@app/utils/UserContext";
import { getUserInfo } from "@app/utils/datalayer";
import * as SecureStore from "@app/utils/SecureStore";
import { ENDPOINT } from "./utils/constants";
import { UserProvider, useUserContext } from '@app/utils/UserContext';
import { getUserInfo } from '@app/utils/datalayer';

import LandingStack from "@app/screens/landing/LandingStack";
import FeedStack from "@app/screens/feed/FeedStack";
import GroupStack from "@app/screens/group/GroupStack";
import ExploreStack from "@app/screens/explore/ExploreStack";
import axios from "axios";
import LandingStack from '@app/screens/landing/LandingStack';
import FeedStack from '@app/screens/feed/FeedStack';
import GroupStack from '@app/screens/group/GroupStack';
import ExploreStack from '@app/screens/explore/ExploreStack';
import OnboardingStack from '@app/screens/onboarding/OnboardingStack';

const LINKING_CONFIG = {
prefixes: ["icebreak://"],
prefixes: ['icebreak://'],
};

const Tab = createBottomTabNavigator();
Expand All @@ -33,56 +34,52 @@ function TabNavigation() {

function App() {
const { user, setUser } = useUserContext();
const [isFirstTime, setIsFirstTime] = useState(null);

const currentSession = async () => {
// automatically log in depending on which token is set in expo secure store

// log in with local auth
const localAuthToken = await SecureStore.getValueFor("local_auth_token");
if (localAuthToken) {
const payload = await getUserInfo(localAuthToken);
const token = await AsyncStorage.getItem('token');
const isFirstTime = await AsyncStorage.getItem('isFirstTime');
Copy link
Member

@nappalion nappalion Aug 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should isFirstTime be stored in AsyncStorage? I'm guessing it should be stored on a database somewhere so that the user only needs to do onboarding once. With AsyncStorage, if the user were to log on in another device, they would see the onboarding screen again.
You can remove the AsyncStorage code for now. Luke will add a isNewUser boolean property to google and local login routes.

setIsFirstTime(isFirstTime === null ? true : JSON.parse(isFirstTime));
if (token) {
const payload = await getUserInfo();
setUser({
...user,
isLoggedIn: true,
data: payload,
});
return;
}

// log in with google auth
const googleAuthToken = await SecureStore.getValueFor("google_auth_token");
if (googleAuthToken) {
const body = {
token: googleAuthToken,
};
const { data } = await axios.post(`${ENDPOINT}/auth/google`, body);
if (data?.success) {
setUser({
...user,
isLoggedIn: true,
data: data.payload,
});
}
}
};

useEffect(() => {
currentSession();
}, []);

// Check if isFirstTime is not null
if (isFirstTime === null) {
return <View />;
}

return (
<Stack.Navigator
initialRouteName="Landing"
screenOptions={{ headerShown: false }}>
{user?.isLoggedIn ? (
<Stack.Screen name="Tab" component={TabNavigation} />
) : (
initialRouteName="Landing"
screenOptions={{ headerShown: false }}
>
{!user?.isLoggedIn ? (
<Stack.Screen name="Landing" component={LandingStack} />
) : (
<>
{/* This always sends the user to the onboarding screen upon logging in for testing purposes */}
<Stack.Screen name="Onboarding" component={OnboardingStack} />
<Stack.Screen name="TabNavigation" component={TabNavigation} />
</>
)}
</Stack.Navigator>


);
}


function Root() {
return (
<UserProvider>
Expand Down
197 changes: 197 additions & 0 deletions packages/app/screens/onboarding/OnboardingStack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import React, { useState, useRef } from 'react';
import { View, Text, TouchableOpacity, TextInput, StyleSheet, ScrollView, Dimensions, Animated } from 'react-native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useUserContext } from '@app/utils/UserContext';

const Stack = createNativeStackNavigator();

function OnboardingScreen({ navigation }) {
const [school, setSchool] = useState('');
const [birthday, setBirthday] = useState('');
const [interests, setInterests] = useState('');
const [error1, setError1 ] = useState(false);
const [error2, setError2 ] = useState(false);
const [error3, setError3 ] = useState(false);
const [error4, setError4 ] = useState(false);

const { onboardingCompleted } = useUserContext();

const handleSubmit = async () => {
await AsyncStorage.setItem('isFirstTime', JSON.stringify(false));
onboardingCompleted();

if (!school.trim())
{
setError1(true);
}
if (!birthday.trim())
{
setError2(true);
}
if (!interests.trim())
{
setError3(true);
}
if (!school.trim() || !birthday.trim() || !interests.trim())
{
setError4(true);
}
else
{
navigation.navigate('TabNavigation');
}
};

const handleSkip = async () => {
navigation.navigate('TabNavigation');
};

return (
<View>
<ScrollView
pagingEnabled={true}
horizontal={true}
showsHorizontalScrollIndicator={false}>
<View style={styles.slide}>
<Text>Where do you go to school?</Text>
<TextInput
style={styles.input}
placeholder= "Your School"
onChangeText={setSchool}
value={school}
/>
{error2 ? (
<Text style={styles.error}>*Your School is Required</Text>
) : null
}
</View>
Comment on lines +56 to +68
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more optional, but I've seen other websites recommend a list of schools as you type.


<View style={styles.slide}>
<Text>When's your birthday?</Text>
<TextInput
style={styles.input}
placeholder= "MM/DD/YYYY"
onChangeText={setBirthday}
value={birthday}
/>
{error2 ? (
<Text style={styles.error}>*Your Birthday is Required</Text>
) : null
}
</View>
Comment on lines +70 to +82
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be input validation checking if the correct values are typed in for Birthday (maybe School too?). A date picker would also be a good choice for the Birthday.


<View style={styles.slide}>
<Text>List some of your interest:</Text>
<TextInput
style={styles.input}
placeholder= "Your Interests"
onChangeText={setInterests}
value={interests}
/>
{error3 ? (
<Text style={styles.error}>*Your Interests are Required</Text>
) : null
}
<TouchableOpacity style={styles.button} onPress={handleSubmit}>
<Text style={styles.buttonText}>Submit</Text>
</TouchableOpacity>
{error4 ? (
<Text style={styles.error}>*Missing Required Responses</Text>
) : null
}
</View>
Comment on lines +84 to +103
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the interests, the Twitter reference you posted in the RFC looks good. You can try implementing selecting interests like that with mock data.

</ScrollView>
<View style={styles.indicatorContainer}>
<TouchableOpacity style={styles.skipBtn} onPress={handleSkip}>
<Text>SKIP</Text>
</TouchableOpacity>
<View style={styles.indicator}></View>
<View style={styles.indicator}></View>
<View style={styles.indicator}></View>
Comment on lines +109 to +111
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The page indicators on the bottom should show you what page you're on (maybe with a change of color or size).

<TouchableOpacity style={styles.nextBtn}>
<Text>NEXT</Text>
</TouchableOpacity>
Comment on lines +112 to +114
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next button doesn't work yet.

</View>
</View>
);
}

const {width, height} = Dimensions.get('window');

function OnboardingStack() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="onboardingscreeen" component={OnboardingScreen} />
</Stack.Navigator>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
error: {
color: 'red',
},
slide: {
width: width,
height: height,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 24,
marginBottom: 20,
fontWeight: 'bold',
},
input: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 4,
width: 270,
height: 45,
margin: 12,
padding: 10,
},
button: {
backgroundColor: '#007AFF',
padding: 10,
borderRadius: 4,
},
buttonText: {
color: '#fff',
textAlign: 'center',
fontWeight: 'bold',
},
skipText: {
textAlign: 'center',
marginTop: 10,
textDecorationLine: 'underline',
},
indicatorContainer: {
position: 'absolute',
width: width,
bottom: 0,
height: 60,
flexDirection: 'row',
justifyContent: 'center',
},
indicator: {
width: 10,
height: 10,
backgroundColor: 'gray',
borderRadius: 5,
marginHorizontal: 10,
},
skipBtn: {
marginRight: 65,
},
nextBtn: {
marginLeft: 65,
},
});

export default OnboardingStack;
3 changes: 3 additions & 0 deletions packages/app/utils/UserContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export function UserProvider({ children }) {
return {
user,
setUser,
onboardingCompleted: () => {
setUser((prevUser) => ({ ...prevUser, isLoggedIn: true }));
},
};
}, [user]);

Expand Down
2 changes: 1 addition & 1 deletion packages/app/utils/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
const URL = "https://e924-24-205-170-250.ngrok-free.app";
const URL = "https://edbe-2603-8000-4507-f23e-4988-bbd6-d792-84bd.ngrok.io";

export const ENDPOINT = `${URL}/api`;
Loading