I, as the user, should be able to login and utilize the application, if I have previously logged in and registered. If not, I should be redirected to a registration form.
Acceptance Criteria
As laid out by MVP:
User Authentication: Staff will log in securely using Firebase Authentication, ensuring a reliable and secure access system.
Users will be asked for their email address, utilizing FireBase Oauth to log into the system.
This firebase UID is captured, sent to the database, and the database checks for the existence of this UID.
If it exists, the user should be routed to the homepage. If not, the user isn't prompted to register, as new users cannot be created client-side for security reasons.
FireBase authentication will need to be completed as well.
Dependecies
All preceding tickets/creation of components hinges on this specific issue.
All backend calls should be finished at this point.
All USER backend calls especially need to be done.
.env file must be fully completed, which requires FireBase project to be created.
Dev Notes
Authentication utilities pulled from previous full-stack projects. Compare and contrast and ensure API calls align with backend.
auth.js
import firebase from 'firebase/app';
import 'firebase/auth';
import { clientCredentials } from './client';
import React, {
createContext, //
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { checkUser } from '../auth';
import { firebase } from '../client';
const AuthContext = createContext();
AuthContext.displayName = 'AuthContext'; // Context object accepts a displayName string property. React DevTools uses this string to determine what to display for the context. https://reactjs.org/docs/context.html#contextdisplayname
// there are 3 states for the user:
// null = application initial state, not yet loaded
// false = user is not logged in, but the app has loaded
// an object/value = user is logged in
useEffect(() => {
firebase.auth().onAuthStateChanged((fbUser) => {
if (fbUser) {
setOAuthUser(fbUser);
checkUser(fbUser.uid).then((gamerInfo) => {
let userObj = {};
if ('null' in gamerInfo) {
userObj = gamerInfo;
} else {
userObj = { fbUser, uid: fbUser.uid, ...gamerInfo };
}
setUser(userObj);
});
} else {
setOAuthUser(false);
setUser(false);
}
}); // creates a single global listener for auth state changed
}, []);
const value = useMemo(
// https://reactjs.org/docs/hooks-reference.html#usememo
() => ({
user,
updateUser,
userLoading: user === null || oAuthUser === null,
// as long as user === null, will be true
// As soon as the user value !== null, value will be false
}),
[user, oAuthUser, updateUser],
);
User Story
I, as the user, should be able to login and utilize the application, if I have previously logged in and registered. If not, I should be redirected to a registration form.
Acceptance Criteria
As laid out by MVP:
Dependecies
Dev Notes
Authentication utilities pulled from previous full-stack projects. Compare and contrast and ensure API calls align with backend.
const checkUser = (uid) => new Promise((resolve, reject) => { fetch(
${clientCredentials.databaseURL}/api/checkuser
, { method: 'POST', body: JSON.stringify({ uid }), headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }) .then((resp) => { if (resp.ok) { resolve(resp.json()); } else { resolve({}); } }) .catch(reject); });const registerUser = (userInfo) => new Promise((resolve, reject) => { fetch(
${clientCredentials.databaseURL}/api/register
, { method: 'POST', body: JSON.stringify(userInfo), headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }) .then((resp) => resolve(resp.json())) .catch(reject); });const signIn = () => { const provider = new firebase.auth.GoogleAuthProvider(); firebase.auth().signInWithPopup(provider); };
const signOut = () => { firebase.auth().signOut(); };
export { signIn, // signOut, checkUser, registerUser, };
// Context API Docs: https://beta.reactjs.org/learn/passing-data-deeply-with-context
import React, { createContext, // useContext, useEffect, useMemo, useState, } from 'react'; import { checkUser } from '../auth'; import { firebase } from '../client';
const AuthContext = createContext();
AuthContext.displayName = 'AuthContext'; // Context object accepts a displayName string property. React DevTools uses this string to determine what to display for the context. https://reactjs.org/docs/context.html#contextdisplayname
const AuthProvider = (props) => { const [user, setUser] = useState(null); const [oAuthUser, setOAuthUser] = useState(null);
// there are 3 states for the user: // null = application initial state, not yet loaded // false = user is not logged in, but the app has loaded // an object/value = user is logged in
const updateUser = useMemo( () => (uid) => checkUser(uid).then((gamerInfo) => { setUser({ fbUser: oAuthUser, ...gamerInfo }); }), [oAuthUser], );
useEffect(() => { firebase.auth().onAuthStateChanged((fbUser) => { if (fbUser) { setOAuthUser(fbUser); checkUser(fbUser.uid).then((gamerInfo) => { let userObj = {}; if ('null' in gamerInfo) { userObj = gamerInfo; } else { userObj = { fbUser, uid: fbUser.uid, ...gamerInfo }; } setUser(userObj); }); } else { setOAuthUser(false); setUser(false); } }); // creates a single global listener for auth state changed }, []);
const value = useMemo( // https://reactjs.org/docs/hooks-reference.html#usememo () => ({ user, updateUser, userLoading: user === null || oAuthUser === null, // as long as user === null, will be true // As soon as the user value !== null, value will be false }), [user, oAuthUser, updateUser], );
return <AuthContext.Provider value={value} {...props} />; }; const AuthConsumer = AuthContext.Consumer;
const useAuth = () => { const context = useContext(AuthContext);
if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider'); } return context; };
export { AuthProvider, useAuth, AuthConsumer };