From 08b9a2150fc416b713989e31413d4c4b57ba047b Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 18 Mar 2024 16:05:49 -0400 Subject: [PATCH 1/5] Extract authentication view to its own component --- package-lock.json | 2 +- .../src/components/auth/Auth.component.tsx | 151 ++++++++++++++++++ .../src/components/auth/Login.component.tsx | 4 +- .../auth/ResetPassword.component.tsx | 4 +- .../src/components/auth/Signup.component.tsx | 4 +- packages/client/src/context/Auth.context.tsx | 149 +---------------- 6 files changed, 161 insertions(+), 153 deletions(-) create mode 100644 packages/client/src/components/auth/Auth.component.tsx diff --git a/package-lock.json b/package-lock.json index d2c6affb..314439c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46260,7 +46260,7 @@ "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", - "firebase-admin": "*", + "firebase-admin": "^12.0.0", "graphql-request": "^6.1.0", "graphql-type-json": "^0.3.2", "jest": "28.1.3", diff --git a/packages/client/src/components/auth/Auth.component.tsx b/packages/client/src/components/auth/Auth.component.tsx new file mode 100644 index 00000000..78fc7e24 --- /dev/null +++ b/packages/client/src/components/auth/Auth.component.tsx @@ -0,0 +1,151 @@ +import { Box, Tabs, Tab, Select, MenuItem, FormControl, Button, Typography, Container } from '@mui/material'; +import { useState, useEffect } from 'react'; +import { Organization } from '../../graphql/graphql'; +import { SelectChangeEvent } from '@mui/material'; +import * as firebaseui from 'firebaseui'; +import * as firebase from '@firebase/app'; +import * as firebaseauth from '@firebase/auth'; +import { LoginComponent } from './Login.component' +import { SignUpComponent } from './Signup.component'; +import { ResetPasswordComponent } from './ResetPassword.component'; +import { useGetOrganizationsQuery } from '../../graphql/organization/organization'; + +const firebaseConfig = { + apiKey: import.meta.env.VITE_AUTH_API_KEY, + authDomain: import.meta.env.VITE_AUTH_DOMAIN +}; + +export interface AuthComponentProps { + handleAuthenticated: (token: string) => void; +} + +export const AuthComponent: React.FC = ({ handleAuthenticated }) => { + const [activeTab, setActiveTab] = useState<'login' | 'signup' | 'reset'>('login'); + const [organization, setOrganization] = useState(null); + const [organizationList, setOrganizationList] = useState([]); + + const getOrganizationResult = useGetOrganizationsQuery(); + + useEffect(() => { + // TODO: Handle multi-organization login + if (getOrganizationResult.data && getOrganizationResult.data.getOrganizations.length > 0) { + setOrganizationList(getOrganizationResult.data.getOrganizations); + setOrganization(getOrganizationResult.data.getOrganizations[0]); + } + }, [getOrganizationResult.data]); + + + // Handle switch tab + const handleTabChange = (_event: React.SyntheticEvent, tab: 'login' | 'signup' | 'reset') => { + setActiveTab(tab); + }; + + // Handle organization select + const handleOrganizationSelect = (event: SelectChangeEvent) => { + const selectedOrganization = organizationList.find((org) => org.name === event.target.value); + setOrganization(selectedOrganization || null); + }; + + return ( + + + + + + + + + + + {activeTab !== 'reset' && ( + + + Organization + + + + + + )} + {organization && ( + + )} + {activeTab !== 'reset' && ( + + + + )} + + + ); +}; + +interface FirebaseLoginWrapperProps { + setToken: (token: string) => void; + organization: Organization; + activeTab: 'login' | 'signup' | 'reset'; +} + +const FirebaseLoginWrapper: FC = ({ setToken, organization, activeTab }) => { + firebase.initializeApp(firebaseConfig); + + // Handle multi-tenant login + const auth = firebaseauth.getAuth(); + auth.tenantId = organization.tenantID; + const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(auth); + + const signInSuccess = async (authResult: any) => { + setToken(await authResult.user.getIdToken()); + }; + + useEffect(() => { + ui.start('#firebaseui-auth-container', { + callbacks: { + signInSuccessWithAuthResult: (authResult, _redirectUrl) => { + signInSuccess(authResult); + return true; + } + }, + signInOptions: [firebaseauth.GoogleAuthProvider.PROVIDER_ID] + }); + }, []); + + return ( + + {activeTab === 'login' && } + {activeTab === 'signup' && } + + {activeTab === 'reset' && } + + + + ); +}; diff --git a/packages/client/src/components/auth/Login.component.tsx b/packages/client/src/components/auth/Login.component.tsx index 6947596a..f3b88a6d 100644 --- a/packages/client/src/components/auth/Login.component.tsx +++ b/packages/client/src/components/auth/Login.component.tsx @@ -8,7 +8,7 @@ interface LoginComponentProps { } // Login Page Component -const LoginComponent: FC = ({ auth, onLoginSuccess }) => { +export const LoginComponent: FC = ({ auth, onLoginSuccess }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [openDialog, setOpenDialog] = useState(false); @@ -84,5 +84,3 @@ const LoginComponent: FC = ({ auth, onLoginSuccess }) => { ); }; - -export default LoginComponent; diff --git a/packages/client/src/components/auth/ResetPassword.component.tsx b/packages/client/src/components/auth/ResetPassword.component.tsx index 9f681155..df6f0d9e 100644 --- a/packages/client/src/components/auth/ResetPassword.component.tsx +++ b/packages/client/src/components/auth/ResetPassword.component.tsx @@ -7,7 +7,7 @@ interface ResetPasswordComponentProps { } // Reset-Password Page Component -const ResetPasswordComponent: FC = ({ auth }) => { +export const ResetPasswordComponent: FC = ({ auth }) => { const [email, setEmail] = useState(''); const [openDialog, setOpenDialog] = useState(false); const [dialogMessage, setDialogMessage] = useState(''); @@ -71,5 +71,3 @@ const ResetPasswordComponent: FC = ({ auth }) => { ); }; - -export default ResetPasswordComponent; diff --git a/packages/client/src/components/auth/Signup.component.tsx b/packages/client/src/components/auth/Signup.component.tsx index ad959961..6a625697 100644 --- a/packages/client/src/components/auth/Signup.component.tsx +++ b/packages/client/src/components/auth/Signup.component.tsx @@ -7,7 +7,7 @@ interface SignUpComponentProps { } // SignUp Page Component -const SignUpComponent: FC = ({ auth }) => { +export const SignUpComponent: FC = ({ auth }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); @@ -103,5 +103,3 @@ const SignUpComponent: FC = ({ auth }) => { ); }; - -export default SignUpComponent; diff --git a/packages/client/src/context/Auth.context.tsx b/packages/client/src/context/Auth.context.tsx index cf3f2e81..96a74666 100644 --- a/packages/client/src/context/Auth.context.tsx +++ b/packages/client/src/context/Auth.context.tsx @@ -1,21 +1,6 @@ import { createContext, FC, useContext, useEffect, useState, ReactNode } from 'react'; import jwt_decode from 'jwt-decode'; -import * as firebaseui from 'firebaseui'; -import * as firebase from '@firebase/app'; -import * as firebaseauth from '@firebase/auth'; -import { Organization } from '../graphql/graphql'; -import { useGetOrganizationsQuery } from '../graphql/organization/organization'; -import LoginComponent from '../components/auth/Login.component'; -import SignUpComponent from '../components/auth/Signup.component'; -import ResetPasswordComponent from '../components/auth/ResetPassword.component'; -import NavigationSidebar from '../components/auth/NavigationSideBar.component'; -import { Box, Tabs, Tab, Select, MenuItem, FormControl, Button, Typography, Container } from '@mui/material'; -import { SelectChangeEvent } from '@mui/material/Select'; - -const firebaseConfig = { - apiKey: import.meta.env.VITE_AUTH_API_KEY, - authDomain: import.meta.env.VITE_AUTH_DOMAIN -}; +import { AuthComponent } from '../components/auth/Auth.component'; export const AUTH_TOKEN_STR = 'token'; @@ -57,19 +42,6 @@ export const AuthProvider: FC = ({ children }) => { const [token, setToken] = useState(localStorage.getItem(AUTH_TOKEN_STR)); const [authenticated, setAuthenticated] = useState(true); const [decodedToken, setDecodedToken] = useState(null); - const [organization, setOrganization] = useState(null); - const [organizationList, setOrganizationList] = useState([]); - const [activeTab, setActiveTab] = useState<'login' | 'signup' | 'reset'>('login'); - - const getOrganizationResult = useGetOrganizationsQuery(); - - useEffect(() => { - // TODO: Handle multi-organization login - if (getOrganizationResult.data && getOrganizationResult.data.getOrganizations.length > 0) { - setOrganizationList(getOrganizationResult.data.getOrganizations); - setOrganization(getOrganizationResult.data.getOrganizations[0]); - } - }, [getOrganizationResult.data]); const handleUnauthenticated = () => { // Clear the token and authenticated state @@ -115,123 +87,14 @@ export const AuthProvider: FC = ({ children }) => { handleUnauthenticated(); }; - // Handle switch tab - const handleTabChange = (_event: React.SyntheticEvent, tab: 'login' | 'signup' | 'reset') => { - setActiveTab(tab); - }; - - // Handle organization select - const handleOrganizationSelect = (event: SelectChangeEvent) => { - const selectedOrganization = organizationList.find((org) => org.name === event.target.value); - setOrganization(selectedOrganization || null); - }; - return ( - - - - - - - - - - - - {activeTab !== 'reset' && ( - - - Organization - - - - - - )} + <> + {authenticated ? ( - {!authenticated && organization && ( - - )} - {authenticated && children} + {children} - - {activeTab !== 'reset' && ( - - - - )} - - - ); -}; - -interface FirebaseLoginWrapperProps { - setToken: (token: string) => void; - organization: Organization; - activeTab: 'login' | 'signup' | 'reset'; -} - -const FirebaseLoginWrapper: FC = ({ setToken, organization, activeTab }) => { - firebase.initializeApp(firebaseConfig); - - // Handle multi-tenant login - const auth = firebaseauth.getAuth(); - auth.tenantId = organization.tenantID; - const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(auth); - - const signInSuccess = async (authResult: any) => { - setToken(await authResult.user.getIdToken()); - }; - - useEffect(() => { - ui.start('#firebaseui-auth-container', { - callbacks: { - signInSuccessWithAuthResult: (authResult, _redirectUrl) => { - signInSuccess(authResult); - return true; - } - }, - signInOptions: [firebaseauth.GoogleAuthProvider.PROVIDER_ID] - }); - }, []); - - return ( - - {activeTab === 'login' && } - {activeTab === 'signup' && } - - {activeTab === 'reset' && } - - - + ): } + ); }; From 91b8bc3685337fc4cc0b8e1e8ed166947f7ae957 Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 18 Mar 2024 16:19:39 -0400 Subject: [PATCH 2/5] Change out container with stack --- .../src/components/auth/Auth.component.tsx | 103 +++++++++--------- 1 file changed, 49 insertions(+), 54 deletions(-) diff --git a/packages/client/src/components/auth/Auth.component.tsx b/packages/client/src/components/auth/Auth.component.tsx index 78fc7e24..3f3f34cf 100644 --- a/packages/client/src/components/auth/Auth.component.tsx +++ b/packages/client/src/components/auth/Auth.component.tsx @@ -1,4 +1,4 @@ -import { Box, Tabs, Tab, Select, MenuItem, FormControl, Button, Typography, Container } from '@mui/material'; +import { Box, Tabs, Tab, Select, MenuItem, FormControl, Button, Typography, Stack } from '@mui/material'; import { useState, useEffect } from 'react'; import { Organization } from '../../graphql/graphql'; import { SelectChangeEvent } from '@mui/material'; @@ -47,64 +47,59 @@ export const AuthComponent: React.FC = ({ handleAuthenticate }; return ( - - + + + + + + + + + + {activeTab !== 'reset' && ( - - - - - + + Organization + + + - {activeTab !== 'reset' && ( - - - Organization - - - - - + )} + {organization && ( + )} - {organization && ( - - )} - {activeTab !== 'reset' && ( - - - + {activeTab !== 'reset' && ( + + + )} - - + ); }; From 0941e80601807d205060d18eaea14aa5670d108b Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 18 Mar 2024 16:33:12 -0400 Subject: [PATCH 3/5] Remove duplicate size information; --- .../src/components/auth/Auth.component.tsx | 77 +++++++------------ .../src/components/auth/Login.component.tsx | 15 ++-- .../src/components/auth/Signup.component.tsx | 19 ++--- 3 files changed, 40 insertions(+), 71 deletions(-) diff --git a/packages/client/src/components/auth/Auth.component.tsx b/packages/client/src/components/auth/Auth.component.tsx index 3f3f34cf..e13d899f 100644 --- a/packages/client/src/components/auth/Auth.component.tsx +++ b/packages/client/src/components/auth/Auth.component.tsx @@ -47,58 +47,39 @@ export const AuthComponent: React.FC = ({ handleAuthenticate }; return ( - - - - - - - - + + + + + + - {activeTab !== 'reset' && ( - - - Organization - - - - - - )} + Organization + + + {organization && ( )} {activeTab !== 'reset' && ( - - - - )} + + )} ); }; @@ -109,7 +90,7 @@ interface FirebaseLoginWrapperProps { activeTab: 'login' | 'signup' | 'reset'; } -const FirebaseLoginWrapper: FC = ({ setToken, organization, activeTab }) => { +const FirebaseLoginWrapper: React.FC = ({ setToken, organization, activeTab }) => { firebase.initializeApp(firebaseConfig); // Handle multi-tenant login diff --git a/packages/client/src/components/auth/Login.component.tsx b/packages/client/src/components/auth/Login.component.tsx index f3b88a6d..16cf066c 100644 --- a/packages/client/src/components/auth/Login.component.tsx +++ b/packages/client/src/components/auth/Login.component.tsx @@ -1,6 +1,6 @@ import { useState, FC } from 'react'; import * as firebaseauth from '@firebase/auth'; -import { TextField, Button, Box, Typography, Dialog, DialogTitle, DialogActions } from '@mui/material'; +import { TextField, Button, Box, Typography, Dialog, DialogTitle, DialogActions, Stack } from '@mui/material'; interface LoginComponentProps { onLoginSuccess: (token: string) => void; @@ -33,17 +33,13 @@ export const LoginComponent: FC = ({ auth, onLoginSuccess } }; return ( - - + Enter Username = ({ auth, onLoginSuccess } placeholder="Email" required /> - + Enter Password = ({ auth, onLoginSuccess } @@ -81,6 +76,6 @@ export const LoginComponent: FC = ({ auth, onLoginSuccess } - + ); }; diff --git a/packages/client/src/components/auth/Signup.component.tsx b/packages/client/src/components/auth/Signup.component.tsx index 6a625697..667df631 100644 --- a/packages/client/src/components/auth/Signup.component.tsx +++ b/packages/client/src/components/auth/Signup.component.tsx @@ -1,6 +1,6 @@ import { useState, FC } from 'react'; import * as firebaseauth from '@firebase/auth'; -import { TextField, Button, Box, Typography, Dialog, DialogTitle, DialogActions } from '@mui/material'; +import { TextField, Button, Typography, Dialog, DialogTitle, DialogActions, Stack } from '@mui/material'; interface SignUpComponentProps { auth: firebaseauth.Auth; @@ -40,17 +40,11 @@ export const SignUpComponent: FC = ({ auth }) => { }; return ( - - + Enter Username = ({ auth }) => { placeholder="Email" required /> - + Enter Password = ({ auth }) => { placeholder="Password" required /> - + Re-enter Password = ({ auth }) => { @@ -100,6 +93,6 @@ export const SignUpComponent: FC = ({ auth }) => { - + ); }; From bd34e4eb35adb99fad1c89db0b0dd627e70c4eb6 Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 18 Mar 2024 16:41:25 -0400 Subject: [PATCH 4/5] Add spacing to stack --- packages/client/src/components/auth/Login.component.tsx | 3 +-- packages/client/src/components/auth/Signup.component.tsx | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/auth/Login.component.tsx b/packages/client/src/components/auth/Login.component.tsx index 16cf066c..34eb337c 100644 --- a/packages/client/src/components/auth/Login.component.tsx +++ b/packages/client/src/components/auth/Login.component.tsx @@ -36,8 +36,7 @@ export const LoginComponent: FC = ({ auth, onLoginSuccess } Enter Username diff --git a/packages/client/src/components/auth/Signup.component.tsx b/packages/client/src/components/auth/Signup.component.tsx index 667df631..17557a51 100644 --- a/packages/client/src/components/auth/Signup.component.tsx +++ b/packages/client/src/components/auth/Signup.component.tsx @@ -43,6 +43,7 @@ export const SignUpComponent: FC = ({ auth }) => { Enter Username From dfc183620183c84cdc614184fe3d1bff4431f729 Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 18 Mar 2024 16:41:47 -0400 Subject: [PATCH 5/5] Have auth box align in the center --- .../src/components/auth/Auth.component.tsx | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/packages/client/src/components/auth/Auth.component.tsx b/packages/client/src/components/auth/Auth.component.tsx index e13d899f..d2dec8e7 100644 --- a/packages/client/src/components/auth/Auth.component.tsx +++ b/packages/client/src/components/auth/Auth.component.tsx @@ -47,40 +47,41 @@ export const AuthComponent: React.FC = ({ handleAuthenticate }; return ( - - - - - - - - Organization - - - - {organization && ( - + + + + + + + + + Organization + + + + {organization && ( + + )} + {activeTab !== 'reset' && ( + )} - {activeTab !== 'reset' && ( - - )} - + + ); }; @@ -118,9 +119,7 @@ const FirebaseLoginWrapper: React.FC = ({ setToken, o {activeTab === 'login' && } {activeTab === 'signup' && } - - {activeTab === 'reset' && } - + {activeTab === 'reset' && } );