From a54e775c806d6a9299928755e42706897e86500b Mon Sep 17 00:00:00 2001 From: MaolinWei Date: Thu, 7 Mar 2024 13:05:00 -0500 Subject: [PATCH 01/11] feat: Login/Signup UI --- packages/client/src/context/Auth.context.tsx | 77 ++++++++++++++++--- .../context/UIComponents/Login.component.tsx | 39 ++++++++++ .../UIComponents/ResetPassword.component.tsx | 45 +++++++++++ .../context/UIComponents/Signup.component.tsx | 40 ++++++++++ .../context/UIComponents/styles.component.tsx | 59 ++++++++++++++ 5 files changed, 249 insertions(+), 11 deletions(-) create mode 100644 packages/client/src/context/UIComponents/Login.component.tsx create mode 100644 packages/client/src/context/UIComponents/ResetPassword.component.tsx create mode 100644 packages/client/src/context/UIComponents/Signup.component.tsx create mode 100644 packages/client/src/context/UIComponents/styles.component.tsx diff --git a/packages/client/src/context/Auth.context.tsx b/packages/client/src/context/Auth.context.tsx index 1c0a4ebf..5e09ef9e 100644 --- a/packages/client/src/context/Auth.context.tsx +++ b/packages/client/src/context/Auth.context.tsx @@ -6,6 +6,13 @@ import * as firebaseauth from '@firebase/auth'; import { Organization } from '../graphql/graphql'; import { useGetOrganizationsQuery } from '../graphql/organization/organization'; +import styles from './UIComponents/styles.component'; +import LoginComponent from './UIComponents/Login.component'; +import SignUpComponent from './UIComponents/Signup.component'; +import ResetPasswordComponent from './UIComponents/ResetPassword.component'; +import { BrowserRouter as Router, Route, Link, Routes, useLocation } from 'react-router-dom'; +import { Select, MenuItem } from '@mui/material'; + const firebaseConfig = { apiKey: import.meta.env.VITE_AUTH_API_KEY, authDomain: import.meta.env.VITE_AUTH_DOMAIN @@ -52,12 +59,15 @@ export const AuthProvider: FC = ({ children }) => { const [authenticated, setAuthenticated] = useState(true); const [decodedToken, setDecodedToken] = useState(null); const [organization, setOrganization] = useState(null); + const [organizationList, setOrganizationList] = useState(null); const getOrganizationResult = useGetOrganizationsQuery(); useEffect(() => { // TODO: Handle multi-organization login if (getOrganizationResult.data && getOrganizationResult.data.getOrganizations.length > 0) { + setOrganizationList(getOrganizationResult.data.getOrganizations); + console.log('Here' + getOrganizationResult); setOrganization(getOrganizationResult.data.getOrganizations[0]); } }, [getOrganizationResult.data]); @@ -106,28 +116,59 @@ export const AuthProvider: FC = ({ children }) => { handleUnauthenticated(); }; + // Handle switch tab + const [activeTab, setActiveTab] = useState('login'); + const handleTabClick = (tab:string) => { + setActiveTab(tab); + }; + + const location = useLocation(); + const isResetPassword = location.pathname === "/reset-password"; // determine if it is reset password page + return ( - - {!authenticated && organization && ( - - )} - {authenticated && children} - +
+ {!isResetPassword && + (
+
handleTabClick('login')}>Login
+
handleTabClick('signup')}>Signup
+
)} + + {!isResetPassword && (
+ + +
)} + + {!authenticated && organization && ( + + )} + {authenticated && children} + + +
); }; interface FirebaseLoginWrapperProps { setToken: (token: string) => void; organization: Organization; + activeTab : string; + isResetPassword: boolean; } -const FirebaseLoginWrapper: FC = ({ setToken, organization }) => { +const FirebaseLoginWrapper: FC = ({ setToken, organization, activeTab, isResetPassword }) => { 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) => { @@ -142,11 +183,25 @@ const FirebaseLoginWrapper: FC = ({ setToken, organiz return true; } }, - signInOptions: [firebaseauth.GoogleAuthProvider.PROVIDER_ID, firebaseauth.EmailAuthProvider.PROVIDER_ID] + signInOptions: [firebaseauth.GoogleAuthProvider.PROVIDER_ID] }); }, []); - - return
; + + // return
; + return ( + <> +
+ + : } /> + } /> + + {!isResetPassword && Reset Password} +
+
+
+ + ); }; + export const useAuth = () => useContext(AuthContext); diff --git a/packages/client/src/context/UIComponents/Login.component.tsx b/packages/client/src/context/UIComponents/Login.component.tsx new file mode 100644 index 00000000..d28901ab --- /dev/null +++ b/packages/client/src/context/UIComponents/Login.component.tsx @@ -0,0 +1,39 @@ +import { useState, FC } from 'react'; +import * as firebaseauth from '@firebase/auth'; +import styles from './styles.component'; + +interface LoginComponentProps { + onLoginSuccess: (token: string) => void; + auth: firebaseauth.Auth; +} + +// Login Page Component +const LoginComponent: FC = ({ auth, onLoginSuccess }) => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + // Handle Login + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + try { + const userCredential = await firebaseauth.signInWithEmailAndPassword(auth, email, password); + const token = await userCredential.user.getIdToken(); + onLoginSuccess(token); + } catch (error) { + alert((error as Error).message); + setPassword(''); + } + }; + + return ( +
+ + setEmail(e.target.value)} placeholder="Email" /> + + setPassword(e.target.value)} placeholder="Password" /> + +
+ ); +}; + +export default LoginComponent; diff --git a/packages/client/src/context/UIComponents/ResetPassword.component.tsx b/packages/client/src/context/UIComponents/ResetPassword.component.tsx new file mode 100644 index 00000000..4cb8a0d5 --- /dev/null +++ b/packages/client/src/context/UIComponents/ResetPassword.component.tsx @@ -0,0 +1,45 @@ +import { useState, FC } from 'react'; +import * as firebaseauth from '@firebase/auth'; +import styles from './styles.component'; +import { useNavigate } from 'react-router-dom'; + +interface ResetPasswordComponentProps { + auth: firebaseauth.Auth; + } + +// Reset-Password Page Component +const ResetPasswordComponent: FC = ({ auth }) => { +const [email, setEmail] = useState(''); +const navigate = useNavigate(); + +// Handle Reset-Password +const handleResetPassword = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await firebaseauth.sendPasswordResetEmail(auth, email); + alert('An email for reset password has been sent to your email. Please check and follow the instructions.') + navigate('/'); + } catch (error) { + alert((error as Error).message); +} +}; + +return ( +
+
+ + setEmail(e.target.value)} + placeholder="Email" + required + /> + +
+
+ ); +}; + +export default ResetPasswordComponent; \ No newline at end of file diff --git a/packages/client/src/context/UIComponents/Signup.component.tsx b/packages/client/src/context/UIComponents/Signup.component.tsx new file mode 100644 index 00000000..48bc85c3 --- /dev/null +++ b/packages/client/src/context/UIComponents/Signup.component.tsx @@ -0,0 +1,40 @@ +import { useState, FC } from 'react'; +import * as firebaseauth from '@firebase/auth'; +import styles from './styles.component'; +import { useNavigate } from 'react-router-dom'; + +interface SignUpComponentProps { + auth: firebaseauth.Auth; +} + +// SignUp Page Component +const SignUpComponent: FC = ({ auth }) => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const navigate = useNavigate(); + + // Handle Sign Up + const handleSignUp = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await firebaseauth.createUserWithEmailAndPassword(auth, email, password); + alert('Sign Up Successfully'); + setPassword(''); + navigate('/'); + } catch (error) { + alert((error as Error).message); + } + }; + + return ( +
+ + setEmail(e.target.value)} placeholder="Email" /> + + setPassword(e.target.value)} placeholder="Password" /> + +
+ ); +}; + +export default SignUpComponent; diff --git a/packages/client/src/context/UIComponents/styles.component.tsx b/packages/client/src/context/UIComponents/styles.component.tsx new file mode 100644 index 00000000..2ec17d2f --- /dev/null +++ b/packages/client/src/context/UIComponents/styles.component.tsx @@ -0,0 +1,59 @@ +// Styles for signup/login/resetpassword page +const styles = { + container: { + fontFamily: 'Arial, sans-serif', + width: '400px', + margin: '0 auto', + border: '1px solid #ccc', + borderRadius: '8px', + padding: '20px', + }, + tabContainer: { + display: 'flex', + justifyContent: 'center', + marginBottom: '20px', + }, + tab: { + flex: 1, + textAlign: 'center' as 'center', + listStyle: 'none', + padding: '10px', + cursor: 'pointer', + borderBottom: '3px solid transparent', + }, + activeTab: { + borderBottom: '3px solid #6200EE', + }, + form: { + display: 'flex', + flexDirection: 'column' as 'column', + gap: '10px', + }, + inputContainer: { + marginBottom: '15px', + }, + label: { + display: 'block', + textAlign: 'left' as 'left', + }, + input: { + width: '100%', + padding: '10px', + border: '1px solid #ddd', + borderRadius: '4px', + marginBottom: '10px', + }, + button: { + padding: '10px', + background: '#6200EE', + color: 'white', + border: 'none', + borderRadius: '4px', + cursor: 'pointer', + }, + error: { + color: 'red', + }, + }; + + export default styles; \ No newline at end of file From a6a5c5cbde88c48f66756d9e58bd3f5e1268dfd3 Mon Sep 17 00:00:00 2001 From: MaolinWei Date: Thu, 7 Mar 2024 13:39:52 -0500 Subject: [PATCH 02/11] feat: Login/Signup UI --- packages/client/src/context/Auth.context.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/context/Auth.context.tsx b/packages/client/src/context/Auth.context.tsx index 5e09ef9e..c154ed10 100644 --- a/packages/client/src/context/Auth.context.tsx +++ b/packages/client/src/context/Auth.context.tsx @@ -10,7 +10,7 @@ import styles from './UIComponents/styles.component'; import LoginComponent from './UIComponents/Login.component'; import SignUpComponent from './UIComponents/Signup.component'; import ResetPasswordComponent from './UIComponents/ResetPassword.component'; -import { BrowserRouter as Router, Route, Link, Routes, useLocation } from 'react-router-dom'; +import { Route, Link, Routes, useLocation } from 'react-router-dom'; import { Select, MenuItem } from '@mui/material'; const firebaseConfig = { From 779cf45d414be50200209b0d06737ec3becf4461 Mon Sep 17 00:00:00 2001 From: MaolinWei Date: Fri, 15 Mar 2024 22:24:19 -0400 Subject: [PATCH 03/11] feat: Login/Signup UI --- .../src/components/auth/Login.component.tsx | 63 ++++++++++ .../auth/ResetPassword.component.tsx | 55 +++++++++ .../src/components/auth/Signup.component.tsx | 59 +++++++++ packages/client/src/context/Auth.context.tsx | 114 ++++++++++-------- .../context/UIComponents/Login.component.tsx | 39 ------ .../UIComponents/ResetPassword.component.tsx | 45 ------- .../context/UIComponents/Signup.component.tsx | 40 ------ .../context/UIComponents/styles.component.tsx | 59 --------- 8 files changed, 240 insertions(+), 234 deletions(-) create mode 100644 packages/client/src/components/auth/Login.component.tsx create mode 100644 packages/client/src/components/auth/ResetPassword.component.tsx create mode 100644 packages/client/src/components/auth/Signup.component.tsx delete mode 100644 packages/client/src/context/UIComponents/Login.component.tsx delete mode 100644 packages/client/src/context/UIComponents/ResetPassword.component.tsx delete mode 100644 packages/client/src/context/UIComponents/Signup.component.tsx delete mode 100644 packages/client/src/context/UIComponents/styles.component.tsx diff --git a/packages/client/src/components/auth/Login.component.tsx b/packages/client/src/components/auth/Login.component.tsx new file mode 100644 index 00000000..4273e7ac --- /dev/null +++ b/packages/client/src/components/auth/Login.component.tsx @@ -0,0 +1,63 @@ +import { useState, FC } from 'react'; +import * as firebaseauth from '@firebase/auth'; +import { TextField, Button, Box, Typography } from '@mui/material'; + + +interface LoginComponentProps { + onLoginSuccess: (token: string) => void; + auth: firebaseauth.Auth; +} + +// Login Page Component +const LoginComponent: FC = ({ auth, onLoginSuccess }) => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + // Handle Login + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + try { + const userCredential = await firebaseauth.signInWithEmailAndPassword(auth, email, password); + const token = await userCredential.user.getIdToken(); + onLoginSuccess(token); + } catch (error) { + alert((error as Error).message); + setPassword(''); + } + }; + + return ( + + + Enter Username + + setEmail(e.target.value)} + placeholder="Email" + required + /> + + Enter Password + + setPassword(e.target.value)} + placeholder="Password" + required + /> + + + ); + +}; + +export default LoginComponent; diff --git a/packages/client/src/components/auth/ResetPassword.component.tsx b/packages/client/src/components/auth/ResetPassword.component.tsx new file mode 100644 index 00000000..22630a95 --- /dev/null +++ b/packages/client/src/components/auth/ResetPassword.component.tsx @@ -0,0 +1,55 @@ +import { useState, FC } from 'react'; +import * as firebaseauth from '@firebase/auth'; +import { TextField, Button, Box, Typography } from '@mui/material'; + +interface ResetPasswordComponentProps { + auth: firebaseauth.Auth; + } + +// Reset-Password Page Component +const ResetPasswordComponent: FC = ({ auth }) => { + const [email, setEmail] = useState(''); + + // Handle Reset-Password + const handleResetPassword = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await firebaseauth.sendPasswordResetEmail(auth, email); + alert('An email for reset password has been sent to your email. Please check and follow the instructions.') + } catch (error) { + alert((error as Error).message); + } + }; + + return ( + + + Enter Your Email + + setEmail(e.target.value)} + placeholder="Email" + required + sx={{ mb: 2 }} + /> + + + ); +}; + +export default ResetPasswordComponent; \ No newline at end of file diff --git a/packages/client/src/components/auth/Signup.component.tsx b/packages/client/src/components/auth/Signup.component.tsx new file mode 100644 index 00000000..282d72ac --- /dev/null +++ b/packages/client/src/components/auth/Signup.component.tsx @@ -0,0 +1,59 @@ +import { useState, FC } from 'react'; +import * as firebaseauth from '@firebase/auth'; +import { TextField, Button, Box, Typography } from '@mui/material'; + +interface SignUpComponentProps { + auth: firebaseauth.Auth; +} + +// SignUp Page Component +const SignUpComponent: FC = ({ auth }) => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + // Handle Sign Up + const handleSignUp = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await firebaseauth.createUserWithEmailAndPassword(auth, email, password); + alert('Sign Up Successfully'); + setPassword(''); + } catch (error) { + alert((error as Error).message); + } + }; + + return ( + + + Enter Username + + setEmail(e.target.value)} + placeholder="Email" + required + /> + + Enter Password + + setPassword(e.target.value)} + placeholder="Password" + required + /> + + + ); + +}; + +export default SignUpComponent; diff --git a/packages/client/src/context/Auth.context.tsx b/packages/client/src/context/Auth.context.tsx index c154ed10..6f4ff093 100644 --- a/packages/client/src/context/Auth.context.tsx +++ b/packages/client/src/context/Auth.context.tsx @@ -5,13 +5,11 @@ import * as firebase from '@firebase/app'; import * as firebaseauth from '@firebase/auth'; import { Organization } from '../graphql/graphql'; import { useGetOrganizationsQuery } from '../graphql/organization/organization'; - -import styles from './UIComponents/styles.component'; -import LoginComponent from './UIComponents/Login.component'; -import SignUpComponent from './UIComponents/Signup.component'; -import ResetPasswordComponent from './UIComponents/ResetPassword.component'; -import { Route, Link, Routes, useLocation } from 'react-router-dom'; -import { Select, MenuItem } from '@mui/material'; +import LoginComponent from '../components/auth/Login.component'; +import SignUpComponent from '../components/auth/Signup.component'; +import ResetPasswordComponent from '../components/auth/ResetPassword.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, @@ -59,7 +57,8 @@ export const AuthProvider: FC = ({ children }) => { const [authenticated, setAuthenticated] = useState(true); const [decodedToken, setDecodedToken] = useState(null); const [organization, setOrganization] = useState(null); - const [organizationList, setOrganizationList] = useState(null); + const [organizationList, setOrganizationList] = useState([]); + const [activeTab, setActiveTab] = useState<'login' | 'signup' | 'reset'>('login'); const getOrganizationResult = useGetOrganizationsQuery(); @@ -67,7 +66,6 @@ export const AuthProvider: FC = ({ children }) => { // TODO: Handle multi-organization login if (getOrganizationResult.data && getOrganizationResult.data.getOrganizations.length > 0) { setOrganizationList(getOrganizationResult.data.getOrganizations); - console.log('Here' + getOrganizationResult); setOrganization(getOrganizationResult.data.getOrganizations[0]); } }, [getOrganizationResult.data]); @@ -117,53 +115,72 @@ export const AuthProvider: FC = ({ children }) => { }; // Handle switch tab - const [activeTab, setActiveTab] = useState('login'); - const handleTabClick = (tab:string) => { + const handleTabChange = (_event: React.SyntheticEvent, tab: 'login' | 'signup' | 'reset') => { setActiveTab(tab); - }; + }; - const location = useLocation(); - const isResetPassword = location.pathname === "/reset-password"; // determine if it is reset password page + // Handle organization select + const handleOrganizationSelect = (event: SelectChangeEvent) => { + const selectedOrganization = organizationList.find(org => org.name === event.target.value); + setOrganization(selectedOrganization || null); + }; return ( -
- {!isResetPassword && - (
-
handleTabClick('login')}>Login
-
handleTabClick('signup')}>Signup
-
)} - - {!isResetPassword && (
- - -
)} + + + + + + + + + + + {activeTab !== 'reset' && ( + + + + Organization + + + + + + + )} {!authenticated && organization && ( - + )} {authenticated && children} -
+ {activeTab !== 'reset' && ( + + + + )} + + ); }; interface FirebaseLoginWrapperProps { setToken: (token: string) => void; organization: Organization; - activeTab : string; - isResetPassword: boolean; + activeTab: 'login' | 'signup' | 'reset'; } -const FirebaseLoginWrapper: FC = ({ setToken, organization, activeTab, isResetPassword }) => { +const FirebaseLoginWrapper: FC = ({ setToken, organization, activeTab }) => { firebase.initializeApp(firebaseConfig); // Handle multi-tenant login @@ -187,21 +204,16 @@ const FirebaseLoginWrapper: FC = ({ setToken, organiz }); }, []); - // return
; return ( - <> -
- - : } /> - } /> - - {!isResetPassword && Reset Password} -
-
-
- + + {activeTab === 'login' && } + {activeTab === 'signup' && } + + {activeTab === 'reset' && } + + + ); }; - export const useAuth = () => useContext(AuthContext); diff --git a/packages/client/src/context/UIComponents/Login.component.tsx b/packages/client/src/context/UIComponents/Login.component.tsx deleted file mode 100644 index d28901ab..00000000 --- a/packages/client/src/context/UIComponents/Login.component.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useState, FC } from 'react'; -import * as firebaseauth from '@firebase/auth'; -import styles from './styles.component'; - -interface LoginComponentProps { - onLoginSuccess: (token: string) => void; - auth: firebaseauth.Auth; -} - -// Login Page Component -const LoginComponent: FC = ({ auth, onLoginSuccess }) => { - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - - // Handle Login - const handleLogin = async (e: React.FormEvent) => { - e.preventDefault(); - try { - const userCredential = await firebaseauth.signInWithEmailAndPassword(auth, email, password); - const token = await userCredential.user.getIdToken(); - onLoginSuccess(token); - } catch (error) { - alert((error as Error).message); - setPassword(''); - } - }; - - return ( -
- - setEmail(e.target.value)} placeholder="Email" /> - - setPassword(e.target.value)} placeholder="Password" /> - -
- ); -}; - -export default LoginComponent; diff --git a/packages/client/src/context/UIComponents/ResetPassword.component.tsx b/packages/client/src/context/UIComponents/ResetPassword.component.tsx deleted file mode 100644 index 4cb8a0d5..00000000 --- a/packages/client/src/context/UIComponents/ResetPassword.component.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useState, FC } from 'react'; -import * as firebaseauth from '@firebase/auth'; -import styles from './styles.component'; -import { useNavigate } from 'react-router-dom'; - -interface ResetPasswordComponentProps { - auth: firebaseauth.Auth; - } - -// Reset-Password Page Component -const ResetPasswordComponent: FC = ({ auth }) => { -const [email, setEmail] = useState(''); -const navigate = useNavigate(); - -// Handle Reset-Password -const handleResetPassword = async (e: React.FormEvent) => { - e.preventDefault(); - try { - await firebaseauth.sendPasswordResetEmail(auth, email); - alert('An email for reset password has been sent to your email. Please check and follow the instructions.') - navigate('/'); - } catch (error) { - alert((error as Error).message); -} -}; - -return ( -
-
- - setEmail(e.target.value)} - placeholder="Email" - required - /> - -
-
- ); -}; - -export default ResetPasswordComponent; \ No newline at end of file diff --git a/packages/client/src/context/UIComponents/Signup.component.tsx b/packages/client/src/context/UIComponents/Signup.component.tsx deleted file mode 100644 index 48bc85c3..00000000 --- a/packages/client/src/context/UIComponents/Signup.component.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useState, FC } from 'react'; -import * as firebaseauth from '@firebase/auth'; -import styles from './styles.component'; -import { useNavigate } from 'react-router-dom'; - -interface SignUpComponentProps { - auth: firebaseauth.Auth; -} - -// SignUp Page Component -const SignUpComponent: FC = ({ auth }) => { - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const navigate = useNavigate(); - - // Handle Sign Up - const handleSignUp = async (e: React.FormEvent) => { - e.preventDefault(); - try { - await firebaseauth.createUserWithEmailAndPassword(auth, email, password); - alert('Sign Up Successfully'); - setPassword(''); - navigate('/'); - } catch (error) { - alert((error as Error).message); - } - }; - - return ( -
- - setEmail(e.target.value)} placeholder="Email" /> - - setPassword(e.target.value)} placeholder="Password" /> - -
- ); -}; - -export default SignUpComponent; diff --git a/packages/client/src/context/UIComponents/styles.component.tsx b/packages/client/src/context/UIComponents/styles.component.tsx deleted file mode 100644 index 2ec17d2f..00000000 --- a/packages/client/src/context/UIComponents/styles.component.tsx +++ /dev/null @@ -1,59 +0,0 @@ -// Styles for signup/login/resetpassword page -const styles = { - container: { - fontFamily: 'Arial, sans-serif', - width: '400px', - margin: '0 auto', - border: '1px solid #ccc', - borderRadius: '8px', - padding: '20px', - }, - tabContainer: { - display: 'flex', - justifyContent: 'center', - marginBottom: '20px', - }, - tab: { - flex: 1, - textAlign: 'center' as 'center', - listStyle: 'none', - padding: '10px', - cursor: 'pointer', - borderBottom: '3px solid transparent', - }, - activeTab: { - borderBottom: '3px solid #6200EE', - }, - form: { - display: 'flex', - flexDirection: 'column' as 'column', - gap: '10px', - }, - inputContainer: { - marginBottom: '15px', - }, - label: { - display: 'block', - textAlign: 'left' as 'left', - }, - input: { - width: '100%', - padding: '10px', - border: '1px solid #ddd', - borderRadius: '4px', - marginBottom: '10px', - }, - button: { - padding: '10px', - background: '#6200EE', - color: 'white', - border: 'none', - borderRadius: '4px', - cursor: 'pointer', - }, - error: { - color: 'red', - }, - }; - - export default styles; \ No newline at end of file From c58065fa549505e2596e6f74939c71d4477e3481 Mon Sep 17 00:00:00 2001 From: MaolinWei Date: Fri, 15 Mar 2024 22:53:10 -0400 Subject: [PATCH 04/11] Use MUI dialog and Add "Re-enter Password" field for Signup --- .../src/components/auth/Login.component.tsx | 20 +++++++-- .../auth/ResetPassword.component.tsx | 22 ++++++++-- .../src/components/auth/Signup.component.tsx | 43 +++++++++++++++++-- 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/packages/client/src/components/auth/Login.component.tsx b/packages/client/src/components/auth/Login.component.tsx index 4273e7ac..3dd21c4c 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 } from '@mui/material'; +import { TextField, Button, Box, Typography, Dialog, DialogTitle, DialogActions } from '@mui/material'; interface LoginComponentProps { @@ -12,7 +12,9 @@ interface LoginComponentProps { const LoginComponent: FC = ({ auth, onLoginSuccess }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - + const [openDialog, setOpenDialog] = useState(false); + const [dialogMessage, setDialogMessage] = useState(''); + // Handle Login const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); @@ -21,11 +23,16 @@ const LoginComponent: FC = ({ auth, onLoginSuccess }) => { const token = await userCredential.user.getIdToken(); onLoginSuccess(token); } catch (error) { - alert((error as Error).message); + setDialogMessage((error as Error).message); + setOpenDialog(true); setPassword(''); } }; + const handleClose = () => { + setOpenDialog(false); + }; + return ( @@ -55,6 +62,13 @@ const LoginComponent: FC = ({ auth, onLoginSuccess }) => { + + + {dialogMessage} + + + + ); diff --git a/packages/client/src/components/auth/ResetPassword.component.tsx b/packages/client/src/components/auth/ResetPassword.component.tsx index 22630a95..44e33691 100644 --- a/packages/client/src/components/auth/ResetPassword.component.tsx +++ b/packages/client/src/components/auth/ResetPassword.component.tsx @@ -1,6 +1,6 @@ import { useState, FC } from 'react'; import * as firebaseauth from '@firebase/auth'; -import { TextField, Button, Box, Typography } from '@mui/material'; +import { TextField, Button, Box, Typography, Dialog, DialogTitle, DialogActions } from '@mui/material'; interface ResetPasswordComponentProps { auth: firebaseauth.Auth; @@ -9,17 +9,25 @@ interface ResetPasswordComponentProps { // Reset-Password Page Component const ResetPasswordComponent: FC = ({ auth }) => { const [email, setEmail] = useState(''); + const [openDialog, setOpenDialog] = useState(false); + const [dialogMessage, setDialogMessage] = useState(''); // Handle Reset-Password const handleResetPassword = async (e: React.FormEvent) => { e.preventDefault(); try { await firebaseauth.sendPasswordResetEmail(auth, email); - alert('An email for reset password has been sent to your email. Please check and follow the instructions.') + setDialogMessage('An email for reset password has been sent to your email. Please check and follow the instructions.'); + setOpenDialog(true); } catch (error) { - alert((error as Error).message); + setDialogMessage((error as Error).message); + setOpenDialog(true); } }; + + const handleClose = () => { + setOpenDialog(false); + }; return ( = ({ auth }) => { - + + {dialogMessage} + + + + +
); }; diff --git a/packages/client/src/components/auth/Signup.component.tsx b/packages/client/src/components/auth/Signup.component.tsx index 282d72ac..3e7e1d51 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 } from '@mui/material'; +import { TextField, Button, Box, Typography, Dialog, DialogTitle, DialogActions } from '@mui/material'; interface SignUpComponentProps { auth: firebaseauth.Auth; @@ -10,18 +10,35 @@ interface SignUpComponentProps { const SignUpComponent: FC = ({ auth }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [openDialog, setOpenDialog] = useState(false); + const [dialogMessage, setDialogMessage] = useState(''); + // Handle Sign Up const handleSignUp = async (e: React.FormEvent) => { e.preventDefault(); + // Check if passwords match + if (password !== confirmPassword) { + setDialogMessage("Passwords do not match"); + setOpenDialog(true); + return; + } try { await firebaseauth.createUserWithEmailAndPassword(auth, email, password); - alert('Sign Up Successfully'); + setDialogMessage('Sign Up Successfully'); + setOpenDialog(true); setPassword(''); + setConfirmPassword(''); } catch (error) { - alert((error as Error).message); + setDialogMessage((error as Error).message); + setOpenDialog(true); } }; + const handleClose = () => { + setOpenDialog(false); + }; + return ( @@ -48,9 +65,29 @@ const SignUpComponent: FC = ({ auth }) => { placeholder="Password" required /> + + Re-enter Password + + setConfirmPassword(e.target.value)} + placeholder="Re-enter Password" + required + /> + + + {dialogMessage} + + + + + ); From f311e5efbc6c700d51db1be49d3e02d282480b3c Mon Sep 17 00:00:00 2001 From: MaolinWei Date: Sat, 16 Mar 2024 23:35:29 -0400 Subject: [PATCH 05/11] Add a navigation sidebar --- .../auth/NavigationSideBar.component.tsx | 66 +++++++++++++++++++ packages/client/src/context/Auth.context.tsx | 4 +- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/components/auth/NavigationSideBar.component.tsx diff --git a/packages/client/src/components/auth/NavigationSideBar.component.tsx b/packages/client/src/components/auth/NavigationSideBar.component.tsx new file mode 100644 index 00000000..930ffd63 --- /dev/null +++ b/packages/client/src/components/auth/NavigationSideBar.component.tsx @@ -0,0 +1,66 @@ +import { Drawer, List, ListItem, ListItemText, Typography, Box, Collapse } from '@mui/material'; +import { useState } from 'react'; +import ExpandLess from '@mui/icons-material/ExpandLess'; +import ExpandMore from '@mui/icons-material/ExpandMore'; + +const drawerWidth = 270; + +const NavigationSidebar = () => { + const [open, setOpen] = useState(true); + + const handleClick = () => { + setOpen(!open); + }; + + return ( + + + + ASL-LEX SignLab + + + + + + {open ? : } + + + + + + + + + + + ); +}; + +export default NavigationSidebar; diff --git a/packages/client/src/context/Auth.context.tsx b/packages/client/src/context/Auth.context.tsx index 6f4ff093..7e946810 100644 --- a/packages/client/src/context/Auth.context.tsx +++ b/packages/client/src/context/Auth.context.tsx @@ -8,6 +8,7 @@ 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'; @@ -125,8 +126,9 @@ export const AuthProvider: FC = ({ children }) => { setOrganization(selectedOrganization || null); }; - return ( + return ( + From b3ee9e93e400b1d32f8c1f0b7bb47f21c85b4198 Mon Sep 17 00:00:00 2001 From: MaolinWei Date: Mon, 18 Mar 2024 15:10:52 -0400 Subject: [PATCH 06/11] Adjust the formatting of code --- .../src/components/auth/Login.component.tsx | 25 ++-- .../auth/NavigationSideBar.component.tsx | 12 +- .../auth/ResetPassword.component.tsx | 108 +++++++++--------- .../src/components/auth/Signup.component.tsx | 23 +++- packages/client/src/context/Auth.context.tsx | 99 +++++++++------- 5 files changed, 156 insertions(+), 111 deletions(-) diff --git a/packages/client/src/components/auth/Login.component.tsx b/packages/client/src/components/auth/Login.component.tsx index 3dd21c4c..6947596a 100644 --- a/packages/client/src/components/auth/Login.component.tsx +++ b/packages/client/src/components/auth/Login.component.tsx @@ -2,7 +2,6 @@ import { useState, FC } from 'react'; import * as firebaseauth from '@firebase/auth'; import { TextField, Button, Box, Typography, Dialog, DialogTitle, DialogActions } from '@mui/material'; - interface LoginComponentProps { onLoginSuccess: (token: string) => void; auth: firebaseauth.Auth; @@ -32,9 +31,18 @@ const LoginComponent: FC = ({ auth, onLoginSuccess }) => { const handleClose = () => { setOpenDialog(false); }; - + return ( - + Enter Username @@ -47,7 +55,7 @@ const LoginComponent: FC = ({ auth, onLoginSuccess }) => { placeholder="Email" required /> - + Enter Password = ({ auth, onLoginSuccess }) => { placeholder="Password" required /> - - + {dialogMessage} @@ -71,7 +83,6 @@ const LoginComponent: FC = ({ auth, onLoginSuccess }) => { ); - }; export default LoginComponent; diff --git a/packages/client/src/components/auth/NavigationSideBar.component.tsx b/packages/client/src/components/auth/NavigationSideBar.component.tsx index 930ffd63..7d260fc2 100644 --- a/packages/client/src/components/auth/NavigationSideBar.component.tsx +++ b/packages/client/src/components/auth/NavigationSideBar.component.tsx @@ -20,8 +20,8 @@ const NavigationSidebar = () => { '& .MuiDrawer-paper': { width: drawerWidth, boxSizing: 'border-box', - backgroundColor: '#283593', - }, + backgroundColor: '#283593' + } }} variant="permanent" anchor="left" @@ -31,7 +31,7 @@ const NavigationSidebar = () => { display: 'flex', alignItems: 'center', justifyContent: 'center', - padding: '20px', + padding: '20px' }} > @@ -40,11 +40,11 @@ const NavigationSidebar = () => { diff --git a/packages/client/src/components/auth/ResetPassword.component.tsx b/packages/client/src/components/auth/ResetPassword.component.tsx index 44e33691..9f681155 100644 --- a/packages/client/src/components/auth/ResetPassword.component.tsx +++ b/packages/client/src/components/auth/ResetPassword.component.tsx @@ -3,67 +3,73 @@ import * as firebaseauth from '@firebase/auth'; import { TextField, Button, Box, Typography, Dialog, DialogTitle, DialogActions } from '@mui/material'; interface ResetPasswordComponentProps { - auth: firebaseauth.Auth; - } + auth: firebaseauth.Auth; +} // Reset-Password Page Component const ResetPasswordComponent: FC = ({ auth }) => { - const [email, setEmail] = useState(''); - const [openDialog, setOpenDialog] = useState(false); - const [dialogMessage, setDialogMessage] = useState(''); + const [email, setEmail] = useState(''); + const [openDialog, setOpenDialog] = useState(false); + const [dialogMessage, setDialogMessage] = useState(''); - // Handle Reset-Password - const handleResetPassword = async (e: React.FormEvent) => { - e.preventDefault(); - try { - await firebaseauth.sendPasswordResetEmail(auth, email); - setDialogMessage('An email for reset password has been sent to your email. Please check and follow the instructions.'); - setOpenDialog(true); - } catch (error) { - setDialogMessage((error as Error).message); - setOpenDialog(true); - } - }; - - const handleClose = () => { - setOpenDialog(false); - }; + // Handle Reset-Password + const handleResetPassword = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await firebaseauth.sendPasswordResetEmail(auth, email); + setDialogMessage( + 'An email for reset password has been sent to your email. Please check and follow the instructions.' + ); + setOpenDialog(true); + } catch (error) { + setDialogMessage((error as Error).message); + setOpenDialog(true); + } + }; - return ( - - - Enter Your Email - - setEmail(e.target.value)} - placeholder="Email" - required - sx={{ mb: 2 }} - /> - - + const handleClose = () => { + setOpenDialog(false); + }; + + return ( + + + Enter Your Email + + setEmail(e.target.value)} + placeholder="Email" + required + sx={{ mb: 2 }} + /> + + {dialogMessage} - + ); }; -export default ResetPasswordComponent; \ No newline at end of file +export default ResetPasswordComponent; diff --git a/packages/client/src/components/auth/Signup.component.tsx b/packages/client/src/components/auth/Signup.component.tsx index 3e7e1d51..ad959961 100644 --- a/packages/client/src/components/auth/Signup.component.tsx +++ b/packages/client/src/components/auth/Signup.component.tsx @@ -19,7 +19,7 @@ const SignUpComponent: FC = ({ auth }) => { e.preventDefault(); // Check if passwords match if (password !== confirmPassword) { - setDialogMessage("Passwords do not match"); + setDialogMessage('Passwords do not match'); setOpenDialog(true); return; } @@ -38,9 +38,18 @@ const SignUpComponent: FC = ({ auth }) => { const handleClose = () => { setOpenDialog(false); }; - + return ( - + Enter Username @@ -77,7 +86,11 @@ const SignUpComponent: FC = ({ auth }) => { placeholder="Re-enter Password" required /> - @@ -87,10 +100,8 @@ 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 7e946810..cf3f2e81 100644 --- a/packages/client/src/context/Auth.context.tsx +++ b/packages/client/src/context/Auth.context.tsx @@ -118,59 +118,76 @@ export const AuthProvider: FC = ({ children }) => { // 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); + const selectedOrganization = organizationList.find((org) => org.name === event.target.value); setOrganization(selectedOrganization || null); }; - return ( - - + return ( + + - - - - - - + + + + + + {activeTab !== 'reset' && ( - - - - Organization - - - - + + + Organization + + + + - )} - - {!authenticated && organization && ( - - )} - {authenticated && children} - + + {!authenticated && organization && ( + + )} + {authenticated && children} + - {activeTab !== 'reset' && ( - - - - )} + {activeTab !== 'reset' && ( + + + + )} ); @@ -205,7 +222,7 @@ const FirebaseLoginWrapper: FC = ({ setToken, organiz signInOptions: [firebaseauth.GoogleAuthProvider.PROVIDER_ID] }); }, []); - + return ( {activeTab === 'login' && } From 0e1917513d03481d849a1b294d76d65d4905cfbe Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Fri, 22 Mar 2024 11:07:15 -0400 Subject: [PATCH 07/11] chore: MUI Cleanup (#68) * Some cleaning up of the MUI usage --- package-lock.json | 2 +- .../src/components/auth/Auth.component.tsx | 126 +++++++++++++++ .../src/components/auth/Login.component.tsx | 22 +-- .../auth/ResetPassword.component.tsx | 4 +- .../src/components/auth/Signup.component.tsx | 24 +-- packages/client/src/context/Auth.context.tsx | 149 +----------------- 6 files changed, 149 insertions(+), 178 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..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' && } - - - + ): } + ); }; From 85bbe0ade6b44d20905430b71567d31863e2d90f Mon Sep 17 00:00:00 2001 From: MaolinWei Date: Fri, 22 Mar 2024 19:38:01 -0400 Subject: [PATCH 08/11] feat: Login/Signup UI Page --- .../client/public/locales/en/translation.json | 30 ++++ .../client/public/locales/es/translation.json | 30 ++++ .../src/components/auth/Auth.component.tsx | 126 +++++++++++++++ .../src/components/auth/Login.component.tsx | 68 ++++---- .../auth/NavigationSideBar.component.tsx | 66 -------- .../auth/ResetPassword.component.tsx | 63 ++++---- .../src/components/auth/Signup.component.tsx | 81 +++++----- packages/client/src/context/Auth.context.tsx | 153 +----------------- 8 files changed, 300 insertions(+), 317 deletions(-) create mode 100644 packages/client/src/components/auth/Auth.component.tsx delete mode 100644 packages/client/src/components/auth/NavigationSideBar.component.tsx diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json index 404144b9..542822a3 100644 --- a/packages/client/public/locales/en/translation.json +++ b/packages/client/public/locales/en/translation.json @@ -127,5 +127,35 @@ "tagView": { "originalEntry": "Original Entry" } + }, + "Auth": { + "errorUnexpected": "Unknown error. Please contact the administrator", + "email": "Email", + "password": "Password", + "enterUsername": "Enter Username", + "enterPassword": "Enter Password", + "dialogClose": "Close", + "submit": "Submit", + "organization": "Organization", + "login": { + "wrongPassword": "Wrong Password", + "userNotFound": "Username does not exist. Please sign up first", + "login": "Login" + }, + "signup": { + "emailAlreadyInUse": "This email has already used. Please login directly", + "weakPassword": "The password is too weak. Please use a stronger password", + "invalidEmail": "The email address is invalid. Please re-enter", + "passwordNotMatch": "The passwords entered twice do not match", + "success": "Sign Up Successfully. Please login", + "reEnterPassword": "Re-enter Password", + "signup": "Sign Up" + }, + "resetPassword": { + "userNotFound": "No user found with this email address", + "invalidEmail": "The email address is invalid. Please re-enter", + "confirmDialog": "An email for reset password has been sent to your email. Please check and follow the instructions", + "resetPassword": "Reset Password" + } } } diff --git a/packages/client/public/locales/es/translation.json b/packages/client/public/locales/es/translation.json index fcd5d96e..797434ac 100644 --- a/packages/client/public/locales/es/translation.json +++ b/packages/client/public/locales/es/translation.json @@ -29,5 +29,35 @@ "languageSelector": { "selectLanguage": "Seleccione el idioma" } + }, + "Auth": { + "errorUnexpected": "Error desconocido. Por favor, contacta con el administrador", + "email": "Correo electrónico", + "password": "Contraseña", + "enterUsername": "Ingresa tu nombre de usuario", + "enterPassword": "Ingresa tu contraseña", + "dialogClose": "Cerrar", + "submit": "Enviar", + "organization": "Organización", + "login": { + "wrongPassword": "Contraseña incorrecta", + "userNotFound": "El usuario no existe. Por favor, regístrate primero", + "login": "Iniciar sesión" + }, + "signup": { + "emailAlreadyInUse": "Este correo electrónico ya está en uso. Por favor, inicia sesión directamente", + "weakPassword": "La contraseña es demasiado débil. Utilice una contraseña más segura", + "invalidEmail": "La dirección de correo electrónico es inválida. Por favor, ingrésala nuevamente", + "passwordNotMatch": "Las contraseñas ingresadas no coinciden", + "success": "Registro exitoso. Por favor, inicia sesión", + "reEnterPassword": "Reingresa la contraseña", + "signup": "Registrarse" + }, + "resetPassword": { + "userNotFound": "No se encontró ningún usuario con esta dirección de correo electrónico", + "invalidEmail": "La dirección de correo electrónico es inválida. Por favor, ingrésala nuevamente", + "confirmDialog": "Se ha enviado un correo electrónico para restablecer tu contraseña. Por favor, revisa tu correo y sigue las instrucciones", + "resetPassword": "Restablecer la contraseña" + } } } 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..4ae204d6 --- /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'; +import { useTranslation } from 'react-i18next'; +import { LanguageSelector } from '../LanguageSelector'; + +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 { t } = useTranslation(); + + 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 ( + + + + + + + + + + {t('Auth.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..8634403b 100644 --- a/packages/client/src/components/auth/Login.component.tsx +++ b/packages/client/src/components/auth/Login.component.tsx @@ -1,6 +1,8 @@ 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'; +import { useTranslation } from 'react-i18next'; +import { FirebaseError } from '@firebase/util'; interface LoginComponentProps { onLoginSuccess: (token: string) => void; @@ -8,11 +10,12 @@ 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); const [dialogMessage, setDialogMessage] = useState(''); + const { t } = useTranslation(); // Handle Login const handleLogin = async (e: React.FormEvent) => { @@ -21,8 +24,24 @@ const LoginComponent: FC = ({ auth, onLoginSuccess }) => { const userCredential = await firebaseauth.signInWithEmailAndPassword(auth, email, password); const token = await userCredential.user.getIdToken(); onLoginSuccess(token); - } catch (error) { - setDialogMessage((error as Error).message); + } catch (error: unknown) { + let errorMessage = ''; + if (error instanceof FirebaseError) { + switch (error.code) { + case 'auth/wrong-password': + errorMessage = t('Auth.login.wrongPassword'); + break; + case 'auth/user-not-found': + errorMessage = t('Auth.login.userNotFound'); + break; + default: + errorMessage = t('Auth.errorUnexpected'); + break; + } + } else { + errorMessage = t('Auth.errorUnexpected'); + } + setDialogMessage(errorMessage); setOpenDialog(true); setPassword(''); } @@ -33,56 +52,37 @@ const LoginComponent: FC = ({ auth, onLoginSuccess }) => { }; return ( - - - Enter Username - + + {t('Auth.enterUsername')} setEmail(e.target.value)} - placeholder="Email" + placeholder={t('Auth.enterUsername')} required /> - - Enter Password - + {t('Auth.enterPassword')} setPassword(e.target.value)} - placeholder="Password" + placeholder={t('Auth.enterPassword')} required /> - {dialogMessage} - + - + ); }; - -export default LoginComponent; diff --git a/packages/client/src/components/auth/NavigationSideBar.component.tsx b/packages/client/src/components/auth/NavigationSideBar.component.tsx deleted file mode 100644 index 7d260fc2..00000000 --- a/packages/client/src/components/auth/NavigationSideBar.component.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Drawer, List, ListItem, ListItemText, Typography, Box, Collapse } from '@mui/material'; -import { useState } from 'react'; -import ExpandLess from '@mui/icons-material/ExpandLess'; -import ExpandMore from '@mui/icons-material/ExpandMore'; - -const drawerWidth = 270; - -const NavigationSidebar = () => { - const [open, setOpen] = useState(true); - - const handleClick = () => { - setOpen(!open); - }; - - return ( - - - - ASL-LEX SignLab - - - - - - {open ? : } - - - - - - - - - - - ); -}; - -export default NavigationSidebar; diff --git a/packages/client/src/components/auth/ResetPassword.component.tsx b/packages/client/src/components/auth/ResetPassword.component.tsx index 9f681155..a0bb13d6 100644 --- a/packages/client/src/components/auth/ResetPassword.component.tsx +++ b/packages/client/src/components/auth/ResetPassword.component.tsx @@ -1,28 +1,45 @@ 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'; +import { useTranslation } from 'react-i18next'; +import { FirebaseError } from '@firebase/util'; interface ResetPasswordComponentProps { auth: firebaseauth.Auth; } // 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(''); + const { t } = useTranslation(); // Handle Reset-Password const handleResetPassword = async (e: React.FormEvent) => { e.preventDefault(); try { await firebaseauth.sendPasswordResetEmail(auth, email); - setDialogMessage( - 'An email for reset password has been sent to your email. Please check and follow the instructions.' - ); + setDialogMessage(t('Auth.resetPassword.confirmDialog')); setOpenDialog(true); } catch (error) { - setDialogMessage((error as Error).message); + let errorMessage = ''; + if (error instanceof FirebaseError) { + switch (error.code) { + case 'auth/user-not-found': + errorMessage = t('Auth.resetPassword.userNotFound'); + break; + case 'auth/invalid-email': + errorMessage = t('Auth.resetPassword.invalidEmail'); + break; + default: + errorMessage = t('Auth.errorUnexpected'); + break; + } + } else { + errorMessage = t('Auth.errorUnexpected'); + } + setDialogMessage(errorMessage); setOpenDialog(true); } }; @@ -32,44 +49,26 @@ const ResetPasswordComponent: FC = ({ auth }) => { }; return ( - - - Enter Your Email - + + {t('Auth.enterUsername')} setEmail(e.target.value)} - placeholder="Email" + placeholder={t('Auth.enterUsername')} required - sx={{ mb: 2 }} /> - {dialogMessage} - + - + ); }; - -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..ec9b6063 100644 --- a/packages/client/src/components/auth/Signup.component.tsx +++ b/packages/client/src/components/auth/Signup.component.tsx @@ -1,36 +1,58 @@ 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'; +import { useTranslation } from 'react-i18next'; +import { FirebaseError } from '@firebase/util'; 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(''); const [openDialog, setOpenDialog] = useState(false); const [dialogMessage, setDialogMessage] = useState(''); + const { t } = useTranslation(); // Handle Sign Up const handleSignUp = async (e: React.FormEvent) => { e.preventDefault(); // Check if passwords match if (password !== confirmPassword) { - setDialogMessage('Passwords do not match'); + setDialogMessage(t('Auth.signup.passwordNotMatch')); setOpenDialog(true); return; } try { await firebaseauth.createUserWithEmailAndPassword(auth, email, password); - setDialogMessage('Sign Up Successfully'); + setDialogMessage(t('Auth.signup.success')); setOpenDialog(true); setPassword(''); setConfirmPassword(''); } catch (error) { - setDialogMessage((error as Error).message); + let errorMessage = ''; + if (error instanceof FirebaseError) { + switch (error.code) { + case 'auth/email-already-in-use': + errorMessage = t('Auth.signup.emailAlreadyInUse'); + break; + case 'auth/weak-password': + errorMessage = t('Auth.signup.weakPassword'); + break; + case 'auth/invalid-email': + errorMessage = t('Auth.signup.invalidEmail'); + break; + default: + errorMessage = t('Auth.errorUnexpected'); + break; + } + } else { + errorMessage = t('Auth.errorUnexpected'); + } + setDialogMessage(errorMessage); setOpenDialog(true); } }; @@ -40,68 +62,47 @@ const SignUpComponent: FC = ({ auth }) => { }; return ( - - - Enter Username - + + {t('Auth.enterUsername')} setEmail(e.target.value)} - placeholder="Email" + placeholder={t('Auth.enterUsername')} required /> - - Enter Password - + {t('Auth.enterPassword')} setPassword(e.target.value)} - placeholder="Password" + placeholder={t('Auth.enterPassword')} required /> - - Re-enter Password - + {t('Auth.signup.reEnterPassword')} setConfirmPassword(e.target.value)} - placeholder="Re-enter Password" + placeholder={t('Auth.signup.reEnterPassword')} required /> - {dialogMessage} - + - + ); }; - -export default SignUpComponent; diff --git a/packages/client/src/context/Auth.context.tsx b/packages/client/src/context/Auth.context.tsx index cf3f2e81..c8910831 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 && organization && ( - - )} - {authenticated && 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' && } - - - + <> + {authenticated ? ( + {children} + ) : ( + + )} + ); }; From 4a374cd759c55f55645224b8c765a4c8100aad6a Mon Sep 17 00:00:00 2001 From: MaolinWei Date: Fri, 22 Mar 2024 19:51:25 -0400 Subject: [PATCH 09/11] feat: Login/Signup UI Page --- packages/client/src/context/Auth.context.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/client/src/context/Auth.context.tsx b/packages/client/src/context/Auth.context.tsx index 087c239f..c8910831 100644 --- a/packages/client/src/context/Auth.context.tsx +++ b/packages/client/src/context/Auth.context.tsx @@ -1,7 +1,6 @@ import { createContext, FC, useContext, useEffect, useState, ReactNode } from 'react'; import jwt_decode from 'jwt-decode'; import { AuthComponent } from '../components/auth/Auth.component'; -import { AuthComponent } from '../components/auth/Auth.component'; export const AUTH_TOKEN_STR = 'token'; From bc1d24b845deddc94080b250253a53e7f935b62a Mon Sep 17 00:00:00 2001 From: MaolinWei Date: Fri, 22 Mar 2024 19:55:27 -0400 Subject: [PATCH 10/11] feat: Login/Signup UI Page --- packages/client/src/components/auth/Login.component.tsx | 2 -- packages/client/src/components/auth/Signup.component.tsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/packages/client/src/components/auth/Login.component.tsx b/packages/client/src/components/auth/Login.component.tsx index 99aeecd2..8634403b 100644 --- a/packages/client/src/components/auth/Login.component.tsx +++ b/packages/client/src/components/auth/Login.component.tsx @@ -10,7 +10,6 @@ interface LoginComponentProps { } // Login Page Component -export const LoginComponent: FC = ({ auth, onLoginSuccess }) => { export const LoginComponent: FC = ({ auth, onLoginSuccess }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); @@ -85,6 +84,5 @@ 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 5d2c9679..ec9b6063 100644 --- a/packages/client/src/components/auth/Signup.component.tsx +++ b/packages/client/src/components/auth/Signup.component.tsx @@ -9,7 +9,6 @@ interface SignUpComponentProps { } // SignUp Page Component -export const SignUpComponent: FC = ({ auth }) => { export const SignUpComponent: FC = ({ auth }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); @@ -105,6 +104,5 @@ export const SignUpComponent: FC = ({ auth }) => { - ); }; From 75f9e3612a59d0eb5280b7e9e17fadee072b1616 Mon Sep 17 00:00:00 2001 From: MaolinWei Date: Fri, 22 Mar 2024 20:00:53 -0400 Subject: [PATCH 11/11] feat: Login/Signup UI Page --- .../client/public/locales/en/translation.json | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json index 542822a3..4ef9ec6a 100644 --- a/packages/client/public/locales/en/translation.json +++ b/packages/client/public/locales/en/translation.json @@ -72,9 +72,11 @@ }, "datasetControl": { "deleteEntry": "Delete Entry", - "deleteDescription": "Are you sure you want to delete this project? Doing so will delete all associated tags", + "deleteEntries": "Delete Entries", + "deleteDescription": "Are you sure you want to delete this entry? Doing so will delete all cooresponding tags", "addDataset": " Add New Dataset", - "uploadEntries": " Upload Entries" + "uploadEntries": " Upload Entries", + "deleteMultipleEntries": "Are you sure you want to delete selected entries? All cooresponding tags will be deleted" }, "projectAccess": { "datasetName": "Dataset Name", @@ -104,7 +106,8 @@ "completed": "All steps completed - your new study is created", "startOver": "Start Over", "formTitle": "Study Information", - "tagsDescription": "Number of times each entry needs to be tagged (default 1)" + "tagsDescription": "Number of times each entry needs to be tagged (default 1)", + "noDatasets": "No datasets available, create a dataset and grant the project access under Datasets > Project Access" }, "studyControl": { "deleteStudy": "Delete Study", @@ -128,6 +131,24 @@ "originalEntry": "Original Entry" } }, + "errors": { + "datasetCreate": "Failed to create dataset, please try again or report this issue", + "uploadSessionCreate": "Could not start the upload process, please try again", + "csvUpload": "Could not upload the CSV, please try again", + "entryUpload": "Could not upload entry, please try again", + "entryUploadComplete": "Could not complete the upload process, please try again", + "entryQuery": "Failed to get entries for the dataset", + "studyExists": "Could not check if a study with that name exists", + "datasetsForProject": "Failed to get possible datasets", + "tagComplete": "Failed to submit tag, please try again", + "datasetPermissionUpdate": "Could not give project permission", + "projectCreate": "Could not create the project, please try again", + "projectDelete": "An issue took place deleting the project", + "projectAdminUpdate": "Could not update the user's permssions", + "studyDelete": "Failed to delete the target study", + "studyCreate": "Failed to create the new study", + "tagsQuery": "Failed to get tags" + }, "Auth": { "errorUnexpected": "Unknown error. Please contact the administrator", "email": "Email",