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..d2dec8e7 --- /dev/null +++ b/packages/client/src/components/auth/Auth.component.tsx @@ -0,0 +1,126 @@ +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'; +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 ( + + + + + + + + + Organization + + + + {organization && ( + + )} + {activeTab !== 'reset' && ( + + )} + + + ); +}; + +interface FirebaseLoginWrapperProps { + setToken: (token: string) => void; + organization: Organization; + activeTab: 'login' | 'signup' | 'reset'; +} + +const FirebaseLoginWrapper: React.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..34eb337c 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; @@ -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); @@ -33,17 +33,12 @@ const LoginComponent: FC = ({ auth, onLoginSuccess }) => { }; return ( - - + Enter Username = ({ auth, onLoginSuccess }) => { placeholder="Email" required /> - + Enter Password = ({ auth, onLoginSuccess }) => { @@ -81,8 +75,6 @@ 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..17557a51 100644 --- a/packages/client/src/components/auth/Signup.component.tsx +++ b/packages/client/src/components/auth/Signup.component.tsx @@ -1,13 +1,13 @@ 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; } // SignUp Page Component -const SignUpComponent: FC = ({ auth }) => { +export const SignUpComponent: FC = ({ auth }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); @@ -40,17 +40,12 @@ const SignUpComponent: FC = ({ auth }) => { }; return ( - - + Enter Username = ({ auth }) => { placeholder="Email" required /> - + Enter Password = ({ auth }) => { placeholder="Password" required /> - + Re-enter Password = ({ auth }) => { @@ -100,8 +94,6 @@ 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' && } - - - + ): } + ); };