Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

126 changes: 126 additions & 0 deletions packages/client/src/components/auth/Auth.component.tsx
Original file line number Diff line number Diff line change
@@ -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<AuthComponentProps> = ({ handleAuthenticated }) => {
const [activeTab, setActiveTab] = useState<'login' | 'signup' | 'reset'>('login');
const [organization, setOrganization] = useState<Organization | null>(null);
const [organizationList, setOrganizationList] = useState<Organization[]>([]);

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<string>) => {
const selectedOrganization = organizationList.find((org) => org.name === event.target.value);
setOrganization(selectedOrganization || null);
};

return (
<Box sx={{ width: '100%', alignItems: 'center', justifyContent: 'center', display: 'flex' }}>
<Stack sx={{ justifyContent: 'center', maxWidth: 350 }}>
<Box sx={{ borderBottom: 1 }}>
<Tabs value={activeTab} onChange={handleTabChange} aria-label="login signup tabs" variant="fullWidth">
<Tab label="Login" value="login" />
<Tab label="Signup" value="signup" />
</Tabs>
</Box>
<Typography variant="h5">Organization</Typography>
<FormControl fullWidth>
<Select
value={organization ? organization.name : ''}
onChange={handleOrganizationSelect}
>
{organizationList.map((organization, index) => (
<MenuItem key={index} value={organization.name}>
{organization.name}
</MenuItem>
))}
</Select>
</FormControl>
{organization && (
<FirebaseLoginWrapper setToken={handleAuthenticated} organization={organization} activeTab={activeTab} />
)}
{activeTab !== 'reset' && (
<Button
onClick={(event) => handleTabChange(event, 'reset')}
variant="text"
sx={{ color: 'blue', textTransform: 'none' }}
>
Reset Password
</Button>
)}
</Stack>
</Box>
);
};

interface FirebaseLoginWrapperProps {
setToken: (token: string) => void;
organization: Organization;
activeTab: 'login' | 'signup' | 'reset';
}

const FirebaseLoginWrapper: React.FC<FirebaseLoginWrapperProps> = ({ 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 (
<Box>
{activeTab === 'login' && <LoginComponent onLoginSuccess={setToken} auth={auth} />}
{activeTab === 'signup' && <SignUpComponent auth={auth} />}
{activeTab === 'reset' && <ResetPasswordComponent auth={auth} />}
<Box id="firebaseui-auth-container" style={{ display: activeTab === 'reset' ? 'none' : 'block' }} />
</Box>
);
};
22 changes: 7 additions & 15 deletions packages/client/src/components/auth/Login.component.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
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;
auth: firebaseauth.Auth;
}

// Login Page Component
const LoginComponent: FC<LoginComponentProps> = ({ auth, onLoginSuccess }) => {
export const LoginComponent: FC<LoginComponentProps> = ({ auth, onLoginSuccess }) => {
const [email, setEmail] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [openDialog, setOpenDialog] = useState(false);
Expand All @@ -33,17 +33,12 @@ const LoginComponent: FC<LoginComponentProps> = ({ auth, onLoginSuccess }) => {
};

return (
<Box
<Stack
component="form"
onSubmit={handleLogin}
sx={{
'& .MuiTextField-root': { m: 1, width: '30ch' },
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
spacing={1}
>
<Typography variant="h5" sx={{ mt: -1, mb: -2 }}>
<Typography variant="h5">
Enter Username
</Typography>
<TextField
Expand All @@ -55,7 +50,7 @@ const LoginComponent: FC<LoginComponentProps> = ({ auth, onLoginSuccess }) => {
placeholder="Email"
required
/>
<Typography variant="h5" sx={{ mt: -1, mb: -2 }}>
<Typography variant="h5">
Enter Password
</Typography>
<TextField
Expand All @@ -70,7 +65,6 @@ const LoginComponent: FC<LoginComponentProps> = ({ auth, onLoginSuccess }) => {
<Button
type="submit"
variant="contained"
sx={{ mt: 1, mb: -5, width: '30ch', display: 'flex', justifyContent: 'center' }}
>
Login
</Button>
Expand All @@ -81,8 +75,6 @@ const LoginComponent: FC<LoginComponentProps> = ({ auth, onLoginSuccess }) => {
<Button onClick={handleClose}>Close</Button>
</DialogActions>
</Dialog>
</Box>
</Stack>
);
};

export default LoginComponent;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface ResetPasswordComponentProps {
}

// Reset-Password Page Component
const ResetPasswordComponent: FC<ResetPasswordComponentProps> = ({ auth }) => {
export const ResetPasswordComponent: FC<ResetPasswordComponentProps> = ({ auth }) => {
const [email, setEmail] = useState<string>('');
const [openDialog, setOpenDialog] = useState(false);
const [dialogMessage, setDialogMessage] = useState('');
Expand Down Expand Up @@ -71,5 +71,3 @@ const ResetPasswordComponent: FC<ResetPasswordComponentProps> = ({ auth }) => {
</Box>
);
};

export default ResetPasswordComponent;
24 changes: 8 additions & 16 deletions packages/client/src/components/auth/Signup.component.tsx
Original file line number Diff line number Diff line change
@@ -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<SignUpComponentProps> = ({ auth }) => {
export const SignUpComponent: FC<SignUpComponentProps> = ({ auth }) => {
const [email, setEmail] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [confirmPassword, setConfirmPassword] = useState<string>('');
Expand Down Expand Up @@ -40,17 +40,12 @@ const SignUpComponent: FC<SignUpComponentProps> = ({ auth }) => {
};

return (
<Box
<Stack
component="form"
onSubmit={handleSignUp}
sx={{
'& .MuiTextField-root': { m: 1, width: '30ch' },
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
spacing={1}
>
<Typography variant="h5" sx={{ mt: -1, mb: -2 }}>
<Typography variant="h5">
Enter Username
</Typography>
<TextField
Expand All @@ -62,7 +57,7 @@ const SignUpComponent: FC<SignUpComponentProps> = ({ auth }) => {
placeholder="Email"
required
/>
<Typography variant="h5" sx={{ mt: -1, mb: -2 }}>
<Typography variant="h5">
Enter Password
</Typography>
<TextField
Expand All @@ -74,7 +69,7 @@ const SignUpComponent: FC<SignUpComponentProps> = ({ auth }) => {
placeholder="Password"
required
/>
<Typography variant="h5" sx={{ mt: -1, mb: -2 }}>
<Typography variant="h5">
Re-enter Password
</Typography>
<TextField
Expand All @@ -89,7 +84,6 @@ const SignUpComponent: FC<SignUpComponentProps> = ({ auth }) => {
<Button
type="submit"
variant="contained"
sx={{ mt: 1, mb: -5, width: '30ch', display: 'flex', justifyContent: 'center' }}
>
Sign Up
</Button>
Expand All @@ -100,8 +94,6 @@ const SignUpComponent: FC<SignUpComponentProps> = ({ auth }) => {
<Button onClick={handleClose}>Close</Button>
</DialogActions>
</Dialog>
</Box>
</Stack>
);
};

export default SignUpComponent;
Loading