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

Select language at welcome sets app language #107

Open
wants to merge 5 commits 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
29 changes: 14 additions & 15 deletions src/context/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { getAuth } from 'firebase/auth';
import React, { createContext, useEffect, useMemo, useReducer } from 'react';
import React, {
createContext,
useContext,
useEffect,
useMemo,
useReducer,
} from 'react';
import firebaseApp from '../firebase/firebaseApp';
import { getUser } from '../firebase/firestore/user';
import { langToDictMap } from '../translation/languages';
import { checkAndGetLang, dictionaryList } from '../translation/languages';
import { Dictionary, RegularUser } from '../types/types';

export type AuthDispatch = React.Dispatch<AuthContextAction>;
Expand All @@ -13,7 +19,7 @@ export interface AuthState {
isLoading: boolean;
userObject: RegularUser | null;
dispatch: AuthDispatch;
langState: Dictionary | null;
langState: Dictionary;
langUpdate: React.Dispatch<React.SetStateAction<Dictionary>>;
}

Expand Down Expand Up @@ -70,30 +76,24 @@ export const useAuthReducer = () =>
isLoading: true,
userObject: null,
dispatch: () => null,
langState: null,
langState: dictionaryList.EN,
langUpdate: () => null,
},
);

// get translated string function
const I18n = ({ str }) => {
const dict = React.useContext(AuthContext).langState;
export const Translate = (str: string) => {
const dict = useContext(AuthContext).langState;
const translated = dict && dict[str] ? dict[str] : str;
return translated;
};

// wrapper function for I18n
export function GetText(str: string) {
return <I18n str={str} />;
}

export function AuthContextProvider({
children,
}: {
children: React.ReactNode;
}) {
const [authState, dispatch] = useAuthReducer();
const [langState, langUpdate] = React.useState<Dictionary>(); // set this state in the useAuthReducer switch statement --> a dictionary
const [langState, langUpdate] = React.useState<Dictionary>(dictionaryList.EN); // set this state in the useAuthReducer switch statement --> a dictionary

// Subscribe to auth state changes and restore the user if they're already signed in
useEffect(() => {
Expand All @@ -102,8 +102,7 @@ export function AuthContextProvider({
if (user) {
UserObject = await getUser(user.uid);
if (UserObject?.language) {
const val = langToDictMap.get(UserObject?.language);
langUpdate(val);
langUpdate(checkAndGetLang(UserObject.language));
}
}
dispatch({
Expand Down
46 changes: 37 additions & 9 deletions src/firebase/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
signOut,
User,
} from 'firebase/auth';
import { SetStateAction } from 'react';
import { AuthDispatch } from '../context/AuthContext';
import { checkAndGetLang } from '../translation/languages';
import { Dictionary } from '../types/types';
Expand Down Expand Up @@ -90,12 +91,18 @@ export const signUpEmail = async (
password: string;
phoneNumber: string;
userType: string;
language: string;
},
) => {
createUserWithEmailAndPassword(auth, params.email, params.password)
.then(async userCredential => {
const { user } = userCredential;
await checkAndAddUser(user, params.userType, params.phoneNumber);
await checkAndAddUser(
user,
params.userType,
params.phoneNumber,
params.language,
);
console.log('Email sign up successful', user.email);
await activateUser(params.phoneNumber);
const UserObject = await getUser(user.uid);
Expand All @@ -108,13 +115,23 @@ export const signUpEmail = async (

export const signInEmail = async (
dispatch: AuthDispatch,
params: { email: string; password: string },
langUpdate: React.Dispatch<SetStateAction<Dictionary>>,
params: { email: string; password: string; language: string },
) => {
signInWithEmailAndPassword(auth, params.email, params.password)
.then(async userCredential => {
const { user } = userCredential;
console.log('Email sign in successful', user.email);
const UserObject = await getUser(user.uid);
if (UserObject) {
const map: Map<string, string> = new Map([
['language', params.language],
]);
await updateUser(UserObject.id, map, UserObject.access);
UserObject.language = params.language;
langUpdate(checkAndGetLang(params.language));
console.log(UserObject);
}
dispatch({ type: 'SIGN_IN', userObject: UserObject });
})
.catch(error => {
Expand All @@ -124,15 +141,22 @@ export const signInEmail = async (

export const signInPhone = async (
dispatch: AuthDispatch,
params: { verificationId: string; verificationCode: string },
langUpdate: React.Dispatch<SetStateAction<Dictionary>>,
params: {
verificationId: string;
verificationCode: string;
language: string;
},
) => {
try {
const credential = await PhoneAuthProvider.credential(
params.verificationId,
params.verificationCode,
);
const result = await signInWithCredential(auth, credential);
await checkAndAddUser(result.user, 'regularUser', null);
console.log(params.language);
await checkAndAddUser(result.user, 'regularUser', null, params.language);
langUpdate(checkAndGetLang(params.language));
console.log('Phone authentication successful', result.user.phoneNumber);
const UserObject = await getUser(result.user.uid);
dispatch({ type: 'SIGN_IN', userObject: UserObject });
Expand All @@ -157,9 +181,13 @@ export const updateLanguage = async (
langUpdate: React.Dispatch<React.SetStateAction<Dictionary>>,
params: { language: string },
) => {
const user = await getUser(auth.currentUser.uid);
const map: Map<string, string> = new Map([['language', params.language]]);
updateUser(user?.id, map, user?.access);
langUpdate(checkAndGetLang(params.language));
dispatch({ type: 'UPDATE_LANGUAGE', language: params.language });
if (auth.currentUser) {
const user = await getUser(auth.currentUser.uid);
if (user) {
const map: Map<string, string> = new Map([['language', params.language]]);
await updateUser(user.id, map, user.access);
langUpdate(checkAndGetLang(params.language));
dispatch({ type: 'UPDATE_LANGUAGE', language: params.language });
}
}
};
5 changes: 5 additions & 0 deletions src/firebase/firestore/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,14 @@ export const checkAndAddUser = async (
user: UserCredential['user'],
accessLevel: string,
phoneNumber: string | null,
language: string,
) => {
const userObject = await getUser(user.uid);
if (userObject !== null) {
console.log('Got user from users collection');
const map: Map<string, string> = new Map([['language', language]]);
await updateUser(userObject.id, map, userObject.access);
userObject.language = language;
} else {
console.log('Create new user flow');
let assignPhoneNumber = null;
Expand All @@ -124,6 +128,7 @@ export const checkAndAddUser = async (
name: 'test phone',
phoneNumber: assignPhoneNumber,
verified: true,
language,
});
}
};
Expand Down
7 changes: 4 additions & 3 deletions src/navigation/stacks/FeedStackNavigator.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';
import FeedScreen from '../../screens/Feed/FeedScreen';
import { FeedStackParamList } from '../../types/navigation';
import Header from '../../components/Header/Header';
import styles from '../../components/Header/styles';
import { Translate } from '../../context/AuthContext';
import FeedScreen from '../../screens/Feed/FeedScreen';
import { FeedStackParamList } from '../../types/navigation';

const FeedStack = createStackNavigator<FeedStackParamList>();

function FeedHeader() {
return <Header title="Jobs" />;
return <Header title={Translate('Jobs')} />;
}

export default function FeedStackNavigator() {
Expand Down
8 changes: 6 additions & 2 deletions src/screens/Authentication/AdminSignin/AdminSignin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import AuthInput from '../../../components/AuthInput/AuthInput';
import StyledButton from '../../../components/StyledButton/StyledButton';
import { AuthContext } from '../../../context/AuthContext';
import { signInEmail } from '../../../firebase/auth';
import { dictToLang } from '../../../translation/languages';
import { AuthStackScreenProps } from '../../../types/navigation';
import styles from './styles';

Expand All @@ -31,12 +32,15 @@ function AdminSigninScreen({
const [password, setPassword] = useState('');
const [emailError, setEmailError] = useState('');
const [signInError, setSignInError] = useState('');
const { dispatch } = useContext(AuthContext);
const { dispatch, langState, langUpdate } = useContext(AuthContext);
// const [language, setLanguage] = useState('');

const language = dictToLang(langState);

const onSubmit: SubmitHandler<FormValues> = async data => {
try {
emailSchema.parse(email);
await signInEmail(dispatch, { email, password });
await signInEmail(dispatch, langUpdate, { email, password, language });
} catch (e) {
if (e instanceof z.ZodError) {
setEmailError('Oops! Invalid email. Try again.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import AuthInput from '../../../components/AuthInput/AuthInput';
import StyledButton from '../../../components/StyledButton/StyledButton';
import { AuthContext } from '../../../context/AuthContext';
import { signUpEmail } from '../../../firebase/auth';
import { dictToLang } from '../../../translation/languages';
import { AuthStackScreenProps } from '../../../types/navigation';
import styles from './styles';

Expand All @@ -33,15 +34,22 @@ function EmailPasswordRegisterScreen({
const [passwordError, setPasswordError] = useState('');
const [confirmPass, setConfirmPass] = useState('');
const [confirmError, setConfirmError] = useState('');
const { dispatch } = useContext(AuthContext);
const { dispatch, langState } = useContext(AuthContext);
const language = dictToLang(langState);

const onSubmit: SubmitHandler<FormValues> = async data => {
if (confirmPass !== password) {
setConfirmError('Oops! These passwords do not match. Please try again.');
}
try {
emailSchema.parse(email);
await signUpEmail(dispatch, { email, password, phoneNumber, userType });
await signUpEmail(dispatch, {
email,
password,
phoneNumber,
userType,
language,
});
} catch (e) {
if (e instanceof z.ZodError) {
setEmailError('Oops! Invalid email. Try again.');
Expand Down
10 changes: 8 additions & 2 deletions src/screens/Authentication/VerificationCode/VerificationCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import StyledButton from '../../../components/StyledButton/StyledButton';
import { AuthContext } from '../../../context/AuthContext';
import { signInPhone, signUpPhoneAdmin } from '../../../firebase/auth';
import { getAccess } from '../../../firebase/firestore/access';
import { dictToLang } from '../../../translation/languages';
import { AuthStackScreenProps } from '../../../types/navigation';
import styles from './styles';

Expand All @@ -26,7 +27,8 @@ function VerificationScreen({
const { ...methods } = useForm<FormValues>();
const [verificationCode, setVerificationCode] = useState('');
const { verificationId, phoneNumber, userType } = route.params;
const { dispatch } = useContext(AuthContext);
const { dispatch, langState, langUpdate } = useContext(AuthContext);
const language = dictToLang(langState);

const onSubmit: SubmitHandler<FormValues> = async () => {
try {
Expand All @@ -38,7 +40,11 @@ function VerificationScreen({
navigation.navigate('EmployerRegisterScreen', { phoneNumber });
}
if (userType === 'jobSeeker') {
await signInPhone(dispatch, { verificationId, verificationCode });
await signInPhone(dispatch, langUpdate, {
verificationId,
verificationCode,
language,
});
}
} else {
// check type of doc, if employer then nav to error
Expand Down
65 changes: 48 additions & 17 deletions src/screens/Authentication/Welcome/Welcome.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { FirebaseRecaptchaVerifierModal } from 'expo-firebase-recaptcha';
import React, { useRef } from 'react';
import { Image, Text, View } from 'react-native';
import React, { useContext, useRef } from 'react';
import { Image, Modal, Text, TouchableOpacity, View } from 'react-native';
import logo from '../../../assets/cnsc-logo.png';
import StyledButton from '../../../components/StyledButton/StyledButton';
import { AuthContext } from '../../../context/AuthContext';
import { firebaseApp } from '../../../firebase/firebaseApp';
import '../../../translation/languages';
import { checkAndGetLang } from '../../../translation/languages';
import { AuthStackScreenProps } from '../../../types/navigation';
import styles from './styles';

function WelcomeScreen({ navigation }: AuthStackScreenProps<'WelcomeScreen'>) {
const recaptchaVerifier = useRef(null);

const [langModalVisibile, setLangModalVisible] = React.useState(true);
const { langUpdate } = useContext(AuthContext);

return (
<View style={styles.container}>
<View style={styles.logoContainer}>
Expand All @@ -23,26 +26,54 @@ function WelcomeScreen({ navigation }: AuthStackScreenProps<'WelcomeScreen'>) {
</Text>
</View>
<View style={styles.buttonContainer}>
<StyledButton
text="SIGN UP"
<TouchableOpacity
style={styles.welcomeButtons}
onPress={() => navigation.navigate('UserTypeScreen')}
buttonStyle={{}}
textStyle={{}}
/>

<Text style={styles.orText}> OR </Text>

<StyledButton
text="SIGN IN"
>
<Text style={styles.welcomeButtonText}>SIGN UP</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.welcomeButtons, { backgroundColor: '#fff' }]}
onPress={() => navigation.navigate('SigninScreen')}
buttonStyle={{ backgroundColor: '#FFFFFF', borderColor: '#CC433C' }}
textStyle={{ color: '#CC433C' }}
/>
>
<Text style={[styles.welcomeButtonText, { color: '#D82D1F' }]}>
LOG IN
</Text>
</TouchableOpacity>
</View>
<FirebaseRecaptchaVerifierModal
ref={recaptchaVerifier}
firebaseConfig={firebaseApp.options}
/>
<Modal visible={langModalVisibile} transparent>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<View style={styles.modalHeader}>
<Text style={styles.modalChooseLang}>Select Language</Text>
</View>
<View style={styles.modalButtonsContainer}>
<TouchableOpacity
style={styles.modalButtons}
onPress={() => {
setLangModalVisible(false);
langUpdate(checkAndGetLang('english'));
}}
>
<Text style={styles.modalButtonsText}>English</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.modalButtons}
onPress={() => {
setLangModalVisible(false);
langUpdate(checkAndGetLang('chinese'));
}}
>
<Text style={styles.modalButtonsText}>中文</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</View>
);
}
Expand Down
Loading