From 881788cf312951af894d4e2c075003c013795229 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sat, 3 Jun 2023 12:53:17 +0530 Subject: [PATCH 01/29] Create login and register page for user portal --- public/locales/en.json | 46 +++++ public/locales/fr.json | 33 +++ public/locales/hi.json | 33 +++ public/locales/sp.json | 33 +++ public/locales/zh.json | 33 +++ src/App.tsx | 9 + .../UserPortal/Login/Login.module.css | 20 ++ src/components/UserPortal/Login/Login.tsx | 134 +++++++++++++ .../UserPortal/Register/Register.module.css | 11 + .../UserPortal/Register/Register.tsx | 189 ++++++++++++++++++ .../Organizations/Organizations.module.css | 0 .../Organizations/Organizations.tsx | 5 + .../UserLoginPage/UserLoginPage.module.css | 48 +++++ .../UserLoginPage/UserLoginPage.tsx | 68 +++++++ 14 files changed, 662 insertions(+) create mode 100644 src/components/UserPortal/Login/Login.module.css create mode 100644 src/components/UserPortal/Login/Login.tsx create mode 100644 src/components/UserPortal/Register/Register.module.css create mode 100644 src/components/UserPortal/Register/Register.tsx create mode 100644 src/screens/UserPortal/Organizations/Organizations.module.css create mode 100644 src/screens/UserPortal/Organizations/Organizations.tsx create mode 100644 src/screens/UserPortal/UserLoginPage/UserLoginPage.module.css create mode 100644 src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx diff --git a/public/locales/en.json b/public/locales/en.json index c17f8a7adc..e16ab1474d 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -446,5 +446,51 @@ "adminForEvents": "Admin for events", "addedAsAdmin": "User is added as admin.", "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too." + }, + "userNavbar": { + "talawa_portal": "Talawa", + "home": "Home", + "people": "People", + "events": "Events", + "chat": "Chat", + "donate": "Donate", + "myTasks": "My Tasks", + "settings": "Settings", + "language": "Language", + "logout": "Logout", + "close": "Close" + }, + "userLogin": { + "login": "Login", + "forgotPassword": "Forgot Password?", + "loginIntoYourAccount": "Login into your account", + "emailAddress": "Email Address", + "enterEmail": "Enter your email address", + "password": "Password", + "enterPassword": "Enter your password", + "register": "Register", + "invalidDetailsMessage": "Please enter a valid email and password.", + "notAuthorised": "Sorry! you are not Authorised!", + "invalidCredentials": "Entered credentials are incorrect. Please enter valid credentials.", + "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too." + }, + "userRegister": { + "register": "Register", + "firstName": "First Name", + "enterFirstName": "Enter your first name", + "lastName": "Last Name", + "enterLastName": "Enter your last name", + "emailAddress": "Email Address", + "enterEmail": "Enter your email address", + "password": "Password", + "enterPassword": "Enter your password", + "confirmPassword": "Confirm Password", + "enterConfirmPassword": "Enter your password to confirm", + "alreadyhaveAnAccount": "Already have an account?", + "login": "Login", + "afterRegister": "Successfully registered. Please wait for admin to approve your request.", + "passwordNotMatch": "Confirm password and password doesn't match.", + "invalidDetailsMessage": "Please enter valid details.", + "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too." } } diff --git a/public/locales/fr.json b/public/locales/fr.json index cae6384bd0..82e6379dc4 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -440,5 +440,38 @@ "adminForEvents": "Administrateur pour les événements", "addedAsAdmin": "L'utilisateur est ajouté en tant qu'administrateur.", "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau." + }, + "userLogin": { + "login": "Connexion", + "forgotPassword":"Mot de passe oublié?", + "loginIntoYourAccount": "Connectez-vous à votre compte", + "emailAddress": "Email Address", + "enterEmail": "Entrez votre adresse email", + "password": "Mot de passe", + "enterPassword": "Tapez votre mot de passe", + "register": "Enregistrer", + "invalidDetailsMessage": "Veuillez saisir un e-mail et un mot de passe valides.", + "notAuthorised": "Désolé! vous n'êtes pas autorisé !", + "invalidCredentials": "Les informations d'identification saisies sont incorrectes. Veuillez entrer des informations d'identification valides.", + "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau." + }, + "userRegister": { + "register": "Enregistrer", + "firstName": "Prénom", + "enterFirstName": "Entrez votre prénom", + "lastName": "Nom de famille", + "enterLastName": "Entrez votre nom de famille", + "emailAddress": "Email Address", + "enterEmail": "Entrez votre adresse email", + "password": "Mot de passe", + "enterPassword": "Tapez votre mot de passe", + "confirmPassword": "Confirmez le mot de passe", + "enterConfirmPassword": "Entrez votre mot de passe pour confirmer", + "alreadyhaveAnAccount": "Vous avez déjà un compte?", + "login": "Connexion", + "afterRegister": "Enregistré avec succès. Veuillez attendre que l'administrateur approuve votre demande.", + "passwordNotMatch": "Confirmez le mot de passe et le mot de passe ne correspond pas.", + "invalidDetailsMessage": "Veuillez entrer des détails valides.", + "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau." } } diff --git a/public/locales/hi.json b/public/locales/hi.json index b9c5f8c974..1300b3b40f 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -440,5 +440,38 @@ "adminForEvents": "घटनाओं के लिए व्यवस्थापक", "addedAsAdmin": "उपयोगकर्ता को व्यवस्थापक के रूप में जोड़ा गया है।", "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रहा है? अपनी नेटवर्क कनेक्टिविटी भी जांचें।" + }, + "userLogin": { + "login": "लॉगिन", + "forgotPassword":"पासवर्ड भूल गए ?", + "loginIntoYourAccount": "अपने खाते में प्रवेश करें", + "emailAddress": "ईमेल एड्रेस", + "enterEmail": "अपना ईमेल पता दर्ज करें", + "password": "पासवर्ड", + "enterPassword": "अपना पासवर्ड डालें", + "register": "रजिस्टर करें", + "invalidDetailsMessage": "कृपया एक वैध ईमेल और पासवर्ड दर्ज करें।", + "notAuthorised": "क्षमा मांगना! आप अधिकृत नहीं हैं!", + "invalidCredentials": "दर्ज क्रेडेंशियल्स गलत हैं। कृपया मान्य क्रेडेंशियल दर्ज करें।", + "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रहा है? अपनी नेटवर्क कनेक्टिविटी भी जांचें।" + }, + "userRegister": { + "register": "रजिस्टर करें", + "firstName": "पहला नाम", + "enterFirstName": "अपना पहला नाम दर्ज करें", + "lastName": "अंतिम नाम", + "enterLastName": "अपना अंतिम नाम दर्ज करें", + "emailAddress": "ईमेल एड्रेस", + "enterEmail": "अपना ईमेल पता दर्ज करें", + "password": "पासवर्ड", + "enterPassword": "अपना पासवर्ड डालें", + "confirmPassword": "पासवर्ड की पुष्टि कीजिये", + "enterConfirmPassword": "पुष्टि करने के लिए अपना पासवर्ड दर्ज करें", + "alreadyhaveAnAccount": "क्या आपके पास पहले से एक खाता मौजूद है?", + "login": "लॉगिन", + "afterRegister": "पंजीकरण सफलतापूर्वक हो गया है। कृपया आपके अनुरोध को स्वीकार करने के लिए व्यवस्थापक की प्रतीक्षा करें।", + "passwordNotMatch": "पासवर्ड की पुष्टि करें और पासवर्ड मेल नहीं खाता।", + "invalidDetailsMessage": "कृपया मान्य विवरण दर्ज करें।", + "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रहा है? अपनी नेटवर्क कनेक्टिविटी भी जांचें।" } } diff --git a/public/locales/sp.json b/public/locales/sp.json index 4ab34e96e6..b99faa641b 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -442,5 +442,38 @@ "adminForEvents": "Administrador de eventos", "addedAsAdmin": "El usuario se agrega como administrador.", "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red." + }, + "userLogin": { + "login": "Acceso", + "forgotPassword":"Has olvidado tu contraseña ?", + "loginIntoYourAccount": "Inicie sesión en su cuenta", + "emailAddress": "correo electrónico", + "enterEmail": "Ingrese su dirección de correo electrónico", + "password": "Contraseña", + "enterPassword": "Ingresa tu contraseña", + "register": "Registro", + "invalidDetailsMessage": "Por favor, introduzca un correo electrónico y una contraseña válidos.", + "notAuthorised": "¡Lo siento! usted no está autorizado!", + "invalidCredentials": "Las credenciales ingresadas son incorrectas. Ingrese credenciales válidas.", + "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red." + }, + "userRegister": { + "register": "Registro", + "firstName": "Nombre de pila", + "enterFirstName": "Ponga su primer nombre", + "lastName": "Apellido", + "enterLastName": "Ingresa tu apellido", + "emailAddress": "correo electrónico", + "enterEmail": "Ingrese su dirección de correo electrónico", + "password": "Contraseña", + "enterPassword": "Ingresa tu contraseña", + "confirmPassword": "confirmar Contraseña", + "enterConfirmPassword": "Ingrese su contraseña para confirmar", + "alreadyhaveAnAccount": "¿Ya tienes una cuenta?", + "login": "Acceso", + "afterRegister": "Registrado exitosamente. Espere a que el administrador apruebe su solicitud.", + "passwordNotMatch": "Confirmar contraseña y la contraseña no coincide.", + "invalidDetailsMessage": "Ingrese detalles válidos.", + "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red." } } diff --git a/public/locales/zh.json b/public/locales/zh.json index efa9744fa8..8dece292ff 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -442,5 +442,38 @@ "adminForEvents": "事件管理员", "addedAsAdmin": "用戶被添加為管理員。", "talawaApiUnavailable": "服務不可用。它在運行嗎?還要檢查您的網絡連接。" + }, + "userLogin": { + "login": "登錄", + "forgotPassword":"忘記密碼 ?", + "loginIntoYourAccount": "登錄您的賬戶", + "emailAddress": "電子郵件地址", + "enterEmail": "輸入你的電子郵箱地址", + "password": "密碼", + "enterPassword": "輸入您的密碼", + "register": "登記", + "invalidDetailsMessage": "請輸入有效的電子郵件和密碼", + "notAuthorised": "對不起! 你沒有被授權!", + "invalidCredentials": "輸入的憑據不正確。 請輸入有效憑據。", + "talawaApiUnavailable": "服務不可用。它在運行嗎?還要檢查您的網絡連接。" + }, + "userRegister": { + "register": "登記", + "firstName": "名", + "enterFirstName": "輸入您的名字", + "lastName": "姓", + "enterLastName": "輸入您的姓氏", + "emailAddress": "電子郵件地址", + "enterEmail": "輸入你的電子郵箱地址", + "password": "密碼", + "enterPassword": "輸入您的密碼", + "confirmPassword": "確認密碼", + "enterConfirmPassword": "輸入您的密碼以確認", + "alreadyhaveAnAccount": "已有帳戶?", + "login": "登錄", + "afterRegister": "註冊成功。 請等待管理員批准您的請求。", + "passwordNotMatch": "確認密碼和密碼不匹配。", + "invalidDetailsMessage": "請輸入有效的詳細信息。", + "talawaApiUnavailable": "服務不可用。它在運行嗎?還要檢查您的網絡連接。" } } diff --git a/src/App.tsx b/src/App.tsx index 21583fc2d6..f3653646fd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -22,6 +22,10 @@ import Requests from 'screens/Requests/Requests'; import BlockUser from 'screens/BlockUser/BlockUser'; import MemberDetail from 'screens/MemberDetail/MemberDetail'; +// User Portal Components +import UserLoginPage from 'screens/UserPortal/UserLoginPage/UserLoginPage'; +import Organizations from 'screens/UserPortal/Organizations/Organizations'; + function App(): JSX.Element { /*const { updatePluginLinks, updateInstalled } = bindActionCreators( actionCreators, @@ -98,6 +102,11 @@ function App(): JSX.Element { {extraRoutes} + + {/* User Portal Routes */} + + + diff --git a/src/components/UserPortal/Login/Login.module.css b/src/components/UserPortal/Login/Login.module.css new file mode 100644 index 0000000000..468498db09 --- /dev/null +++ b/src/components/UserPortal/Login/Login.module.css @@ -0,0 +1,20 @@ +.forgotPasswordContainer { + display: flex; + justify-content: flex-end; + flex-direction: row; + margin: 5px 0px; +} + +.forgotPasswordText { + color: black; + font-size: 12px; + margin: 2px 0px; +} + +.borderNone { + border: none; +} + +.colorWhite { + color: white; +} diff --git a/src/components/UserPortal/Login/Login.tsx b/src/components/UserPortal/Login/Login.tsx new file mode 100644 index 0000000000..795d86160b --- /dev/null +++ b/src/components/UserPortal/Login/Login.tsx @@ -0,0 +1,134 @@ +import React, { ChangeEvent, SetStateAction } from 'react'; +import { Button, Form, InputGroup } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import EmailOutlinedIcon from '@mui/icons-material/EmailOutlined'; +import { LockOutlined } from '@mui/icons-material'; +import { Link, useHistory } from 'react-router-dom'; +import { useMutation } from '@apollo/client'; +import { toast } from 'react-toastify'; + +import { LOGIN_MUTATION } from 'GraphQl/Mutations/mutations'; +import styles from './Login.module.css'; +import { errorHandler } from 'utils/errorHandler'; + +interface LoginProps { + setCurrentMode: React.Dispatch>; +} + +export default function Login(props: LoginProps) { + const { t } = useTranslation('translation', { keyPrefix: 'userLogin' }); + + const { setCurrentMode } = props; + + const handleModeChangeToRegister = () => { + setCurrentMode('register'); + }; + + const [loginMutation] = useMutation(LOGIN_MUTATION); + + const [loginVariables, setLoginVariables] = React.useState({ + email: '', + password: '', + }); + + const history = useHistory(); + + const handleLogin = async () => { + if (!(loginVariables.email && loginVariables.password)) { + toast.error(t('invalidDetailsMessage')); + } else { + try { + const { data } = await loginMutation({ + variables: { + email: loginVariables.email, + password: loginVariables.password, + }, + }); + + if (data) { + if (data.login.user.adminApproved) { + localStorage.setItem('userToken', data.login.accessToken); + localStorage.setItem('userId', data.login.user._id); + + navigator.clipboard.writeText(''); + history.replace('/user/organizations'); + } else { + toast.warn(t('notAuthorised')); + } + } else { + toast.warn(t('invalidCredentials')); + } + } catch (error: any) { + errorHandler(t, error); + } + } + }; + + const handleEmailChange = (e: ChangeEvent) => { + const email = e.target.value; + + setLoginVariables({ + email, + password: loginVariables.password, + }); + }; + + const handlePasswordChange = (e: ChangeEvent) => { + const password = e.target.value; + + setLoginVariables({ + email: loginVariables.email, + password, + }); + }; + + return ( + <> +

{t('login')}

+
{t('loginIntoYourAccount')}
+ +
+
{t('emailAddress')}
+ + + + + + +
{t('password')}
+ + + + + + +
+ +
+ + {t('forgotPassword')} + +
+ + +
+ + + ); +} diff --git a/src/components/UserPortal/Register/Register.module.css b/src/components/UserPortal/Register/Register.module.css new file mode 100644 index 0000000000..aea9d4666b --- /dev/null +++ b/src/components/UserPortal/Register/Register.module.css @@ -0,0 +1,11 @@ +.loginText { + cursor: pointer; +} + +.borderNone { + border: none; +} + +.colorWhite { + color: white; +} diff --git a/src/components/UserPortal/Register/Register.tsx b/src/components/UserPortal/Register/Register.tsx new file mode 100644 index 0000000000..7f4252fee1 --- /dev/null +++ b/src/components/UserPortal/Register/Register.tsx @@ -0,0 +1,189 @@ +import React, { ChangeEvent, SetStateAction } from 'react'; +import { Button, Form, InputGroup } from 'react-bootstrap'; +import EmailOutlinedIcon from '@mui/icons-material/EmailOutlined'; +import BadgeOutlinedIcon from '@mui/icons-material/BadgeOutlined'; +import { LockOutlined } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; +import { SIGNUP_MUTATION } from 'GraphQl/Mutations/mutations'; + +import styles from './Register.module.css'; +import { useMutation } from '@apollo/client'; +import { toast } from 'react-toastify'; +import { errorHandler } from 'utils/errorHandler'; + +interface RegisterProps { + setCurrentMode: React.Dispatch>; +} + +export default function Register(props: RegisterProps) { + const { setCurrentMode } = props; + + const { t } = useTranslation('translation', { keyPrefix: 'userRegister' }); + + const handleModeChangeToLogin = () => { + setCurrentMode('login'); + }; + + const [registerMutation] = useMutation(SIGNUP_MUTATION); + + const [registerVariables, setRegisterVariables] = React.useState({ + firstName: '', + lastName: '', + email: '', + password: '', + confirmPassword: '', + }); + + const handleRegister = async () => { + if ( + !( + registerVariables.email && + registerVariables.password && + registerVariables.firstName && + registerVariables.lastName + ) + ) { + toast.error(t('invalidDetailsMessage')); + } else if ( + registerVariables.password !== registerVariables.confirmPassword + ) { + toast.error(t('passwordNotMatch')); + } else { + try { + const { data } = await registerMutation({ + variables: { + firstName: registerVariables.firstName, + lastName: registerVariables.lastName, + email: registerVariables.email, + password: registerVariables.password, + }, + }); + + if (data) { + toast.success(t('afterRegister')); + + setRegisterVariables({ + firstName: '', + lastName: '', + email: '', + password: '', + confirmPassword: '', + }); + } + } catch (error: any) { + errorHandler(t, error); + } + } + }; + + const handleFirstName = (e: ChangeEvent) => { + const firstName = e.target.value; + + setRegisterVariables({ ...registerVariables, firstName }); + }; + + const handleLastName = (e: ChangeEvent) => { + const lastName = e.target.value; + + setRegisterVariables({ ...registerVariables, lastName }); + }; + + const handleEmailChange = (e: ChangeEvent) => { + const email = e.target.value; + + setRegisterVariables({ ...registerVariables, email }); + }; + + const handlePasswordChange = (e: ChangeEvent) => { + const password = e.target.value; + + setRegisterVariables({ ...registerVariables, password }); + }; + + const handleConfirmPasswordChange = (e: ChangeEvent) => { + const confirmPassword = e.target.value; + + setRegisterVariables({ ...registerVariables, confirmPassword }); + }; + + return ( + <> +

{t('register')}

+
+
{t('firstName')}
+ + + + + + +
{t('lastName')}
+ + + + + + +
{t('emailAddress')}
+ + + + + + +
{t('password')}
+ + + + + + +
{t('confirmPassword')}
+ + + + + + +
+ + +
+ {t('alreadyhaveAnAccount')}{' '} + + {t('login')} + +
+ + ); +} diff --git a/src/screens/UserPortal/Organizations/Organizations.module.css b/src/screens/UserPortal/Organizations/Organizations.module.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/screens/UserPortal/Organizations/Organizations.tsx b/src/screens/UserPortal/Organizations/Organizations.tsx new file mode 100644 index 0000000000..c67f9bb28f --- /dev/null +++ b/src/screens/UserPortal/Organizations/Organizations.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default function Organizations() { + return
Organizations
; +} diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.module.css b/src/screens/UserPortal/UserLoginPage/UserLoginPage.module.css new file mode 100644 index 0000000000..fcb12ce679 --- /dev/null +++ b/src/screens/UserPortal/UserLoginPage/UserLoginPage.module.css @@ -0,0 +1,48 @@ +body::before { + content: none !important; +} + +.leftPane { + align-items: center; + width: 60%; + min-width: 300px; + display: flex; + flex-direction: column; + justify-content: center; +} + +.palisadoesImage { + width: 100%; + height: auto; + max-width: 700px; +} + +.talawaImage { + width: 40%; + height: auto; + margin-left: 50%; + transform: translateX(-50%); +} + +.mainContainer { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 10px; + min-height: 100vh; +} + +.contentContainer { + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: center; + padding: 20px 50px; + background-color: #f5f5f5; +} + +@media only screen and (max-width: 800px) { + .leftPane { + width: 100%; + } +} diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx b/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx new file mode 100644 index 0000000000..f038baf8da --- /dev/null +++ b/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { Dropdown, DropdownButton } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import cookies from 'js-cookie'; +import { languages } from 'utils/languages'; +import i18next from 'i18next'; + +import styles from './UserLoginPage.module.css'; +import PalisadoesImage from 'assets/images/palisadoes_logo.png'; +import TalawaImage from 'assets/talawa-logo-200x200.png'; +import Login from 'components/UserPortal/Login/Login'; +import Register from 'components/UserPortal/Register/Register'; + +export default function UserLoginPage() { + const { t } = useTranslation('translation', { keyPrefix: 'loginPage' }); + + const currentLanguageCode = cookies.get('i18next') || 'en'; + + const currentLanguage = languages.find( + (language) => language.code === currentLanguageCode + )?.name; + + const [currentMode, setCurrentMode] = React.useState('login'); + + const loginRegisterProps = { + setCurrentMode: setCurrentMode, + }; + + return ( +
+
+ Palisadoes Branding +
+

{t('fromPalisadoes')}

+
+
+
+ + {languages.map((language, index: number) => ( + i18next.changeLanguage(language.code)} + disabled={currentLanguageCode === language.code} + data-testid={`changeLanguageBtn${index}`} + > + + {language.name} + + ))} + + Talawa Branding + {currentMode === 'login' ? ( + + ) : ( + + )} +
+
+ ); +} From 5becb8857c2e823d0383b825d9c2d35c99e63b93 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sat, 3 Jun 2023 12:55:50 +0530 Subject: [PATCH 02/29] Lint public locales --- public/locales/fr.json | 2 +- public/locales/hi.json | 2 +- public/locales/sp.json | 2 +- public/locales/zh.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/locales/fr.json b/public/locales/fr.json index 82e6379dc4..24be08622c 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -443,7 +443,7 @@ }, "userLogin": { "login": "Connexion", - "forgotPassword":"Mot de passe oublié?", + "forgotPassword": "Mot de passe oublié?", "loginIntoYourAccount": "Connectez-vous à votre compte", "emailAddress": "Email Address", "enterEmail": "Entrez votre adresse email", diff --git a/public/locales/hi.json b/public/locales/hi.json index 1300b3b40f..2818136790 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -443,7 +443,7 @@ }, "userLogin": { "login": "लॉगिन", - "forgotPassword":"पासवर्ड भूल गए ?", + "forgotPassword": "पासवर्ड भूल गए ?", "loginIntoYourAccount": "अपने खाते में प्रवेश करें", "emailAddress": "ईमेल एड्रेस", "enterEmail": "अपना ईमेल पता दर्ज करें", diff --git a/public/locales/sp.json b/public/locales/sp.json index b99faa641b..99a52d923f 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -445,7 +445,7 @@ }, "userLogin": { "login": "Acceso", - "forgotPassword":"Has olvidado tu contraseña ?", + "forgotPassword": "Has olvidado tu contraseña ?", "loginIntoYourAccount": "Inicie sesión en su cuenta", "emailAddress": "correo electrónico", "enterEmail": "Ingrese su dirección de correo electrónico", diff --git a/public/locales/zh.json b/public/locales/zh.json index 8dece292ff..0fdc1c9f63 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -445,7 +445,7 @@ }, "userLogin": { "login": "登錄", - "forgotPassword":"忘記密碼 ?", + "forgotPassword": "忘記密碼 ?", "loginIntoYourAccount": "登錄您的賬戶", "emailAddress": "電子郵件地址", "enterEmail": "輸入你的電子郵箱地址", From f923cef411390331d87b5fb285bce8153e6728e0 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 11 Jun 2023 10:46:23 +0530 Subject: [PATCH 03/29] Create tests for login page components --- .../UserPortal/Login/Login.test.tsx | 295 ++++++++++++++++++ src/components/UserPortal/Login/Login.tsx | 30 +- .../UserPortal/Register/Register.test.tsx | 267 ++++++++++++++++ .../UserPortal/Register/Register.tsx | 46 ++- .../Organizations/Organizations.test.tsx | 38 +++ .../UserLoginPage/UserLoginPage.test.tsx | 161 ++++++++++ .../UserLoginPage/UserLoginPage.tsx | 19 +- 7 files changed, 823 insertions(+), 33 deletions(-) create mode 100644 src/components/UserPortal/Login/Login.test.tsx create mode 100644 src/components/UserPortal/Register/Register.test.tsx create mode 100644 src/screens/UserPortal/Organizations/Organizations.test.tsx create mode 100644 src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx diff --git a/src/components/UserPortal/Login/Login.test.tsx b/src/components/UserPortal/Login/Login.test.tsx new file mode 100644 index 0000000000..f491ca1b41 --- /dev/null +++ b/src/components/UserPortal/Login/Login.test.tsx @@ -0,0 +1,295 @@ +import React, { SetStateAction } from 'react'; +import { act, render, screen } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import userEvent from '@testing-library/user-event'; +import { I18nextProvider } from 'react-i18next'; + +import { LOGIN_MUTATION } from 'GraphQl/Mutations/mutations'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import Login from './Login'; +import { toast } from 'react-toastify'; + +const MOCKS = [ + { + request: { + query: LOGIN_MUTATION, + variables: { + email: 'johndoe@gmail.com', + password: 'johndoe', + }, + }, + result: { + data: { + login: { + user: { + _id: '1', + userType: 'ADMIN', + adminApproved: true, + }, + accessToken: 'accessToken', + refreshToken: 'refreshToken', + }, + }, + }, + }, + { + request: { + query: LOGIN_MUTATION, + variables: { + email: 'johndoe@gmail.com', + password: 'jdoe', + }, + }, + result: { + data: { + login: { + user: { + _id: '1', + userType: 'ADMIN', + adminApproved: false, + }, + accessToken: 'accessToken', + refreshToken: 'refreshToken', + }, + }, + }, + }, + { + request: { + query: LOGIN_MUTATION, + variables: { + email: 'invalid@gmail.com', + password: 'anything', + }, + }, + result: {}, + }, +]; + +const link = new StaticMockLink(MOCKS, true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + +const setCurrentMode: React.Dispatch> = jest.fn(); + +const props = { + setCurrentMode, +}; + +describe('Testing Login Component [User Portal]', () => { + test('Component should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + }); + + test('Expect the mode to be changed to Register', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('setRegisterBtn')); + + expect(setCurrentMode).toBeCalledWith('register'); + }); + + test('toast.error is triggered if the email input is empty.', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('loginBtn')); + + expect(toast.error).toBeCalledWith( + 'Please enter a valid email and password.' + ); + }); + + test('toast.error is triggered if the password input is empty.', async () => { + const formData = { + email: 'johndoe@gmail.com', + password: 'joe', + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type( + screen.getByPlaceholderText(/Enter your email address/i), + formData.email + ); + userEvent.click(screen.getByTestId('loginBtn')); + + expect(toast.error).toBeCalledWith( + 'Please enter a valid email and password.' + ); + }); + + test('Incorrect password is entered.', async () => { + const formData = { + email: 'invalid@gmail.com', + password: 'anything', + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type( + screen.getByPlaceholderText(/Enter your email address/i), + formData.email + ); + + userEvent.type( + screen.getByPlaceholderText(/Enter your password/i), + formData.password + ); + + userEvent.click(screen.getByTestId('loginBtn')); + + expect(toast.error).toBeCalled(); + + await wait(); + }); + + test('Login details are entered correctly.', async () => { + const formData = { + email: 'johndoe@gmail.com', + password: 'johndoe', + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type( + screen.getByPlaceholderText(/Enter your email address/i), + formData.email + ); + + userEvent.type( + screen.getByPlaceholderText(/Enter your password/i), + formData.password + ); + + userEvent.click(screen.getByTestId('loginBtn')); + + await wait(); + }); + + test('Current user has not been approved by admin.', async () => { + const formData = { + email: 'johndoe@gmail.com', + password: 'jdoe', + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type( + screen.getByPlaceholderText(/Enter your email address/i), + formData.email + ); + + userEvent.type( + screen.getByPlaceholderText(/Enter your password/i), + formData.password + ); + + userEvent.click(screen.getByTestId('loginBtn')); + + expect(toast.error).toBeCalled(); + + await wait(); + }); +}); diff --git a/src/components/UserPortal/Login/Login.tsx b/src/components/UserPortal/Login/Login.tsx index 358367c03f..412f783729 100644 --- a/src/components/UserPortal/Login/Login.tsx +++ b/src/components/UserPortal/Login/Login.tsx @@ -46,25 +46,24 @@ export default function login(props: InterfaceLoginProps): JSX.Element { }, }); - if (data) { - if (data.login.user.adminApproved) { - localStorage.setItem('userToken', data.login.accessToken); - localStorage.setItem('userId', data.login.user._id); - - navigator.clipboard.writeText(''); - history.replace('/user/organizations'); - } else { - toast.warn(t('notAuthorised')); - } + if (data.login.user.adminApproved) { + localStorage.setItem('userToken', data.login.accessToken); + localStorage.setItem('userId', data.login.user._id); + + navigator.clipboard.writeText(''); + /* istanbul ignore next */ + history.replace('/user/organizations'); } else { - toast.warn(t('invalidCredentials')); + toast.warn(t('notAuthorised')); } } catch (error: any) { + /* istanbul ignore next */ errorHandler(t, error); } } }; + /* istanbul ignore next */ const handleEmailChange = (e: ChangeEvent): void => { const email = e.target.value; @@ -74,6 +73,7 @@ export default function login(props: InterfaceLoginProps): JSX.Element { }); }; + /* istanbul ignore next */ const handlePasswordChange = (e: ChangeEvent): void => { const password = e.target.value; @@ -123,11 +123,15 @@ export default function login(props: InterfaceLoginProps): JSX.Element { -
- diff --git a/src/components/UserPortal/Register/Register.test.tsx b/src/components/UserPortal/Register/Register.test.tsx new file mode 100644 index 0000000000..9af6d58bba --- /dev/null +++ b/src/components/UserPortal/Register/Register.test.tsx @@ -0,0 +1,267 @@ +import React, { SetStateAction } from 'react'; +import { act, render, screen } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import userEvent from '@testing-library/user-event'; +import { I18nextProvider } from 'react-i18next'; + +import { SIGNUP_MUTATION } from 'GraphQl/Mutations/mutations'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import Register from './Register'; +import { toast } from 'react-toastify'; + +const MOCKS = [ + { + request: { + query: SIGNUP_MUTATION, + variables: { + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + password: 'johnDoe', + }, + }, + result: { + data: { + signUp: { + user: { + _id: '1', + }, + accessToken: 'accessToken', + refreshToken: 'refreshToken', + }, + }, + }, + }, +]; + +const formData = { + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + password: 'johnDoe', + confirmPassword: 'johnDoe', +}; + +const link = new StaticMockLink(MOCKS, true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + +const setCurrentMode: React.Dispatch> = jest.fn(); + +const props = { + setCurrentMode, +}; + +describe('Testing Register Component [User Portal]', () => { + test('Component should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + }); + + test('Expect the mode to be changed to Login', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('setLoginBtn')); + + expect(setCurrentMode).toBeCalledWith('login'); + }); + + test('Expect toast.error to be called if email input is empty', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('registerBtn')); + + expect(toast.error).toBeCalledWith('Please enter valid details.'); + }); + + test('Expect toast.error to be called if password input is empty', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type(screen.getByTestId('emailInput'), formData.email); + userEvent.click(screen.getByTestId('registerBtn')); + + expect(toast.error).toBeCalledWith('Please enter valid details.'); + }); + + test('Expect toast.error to be called if first name input is empty', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type(screen.getByTestId('passwordInput'), formData.password); + + userEvent.type(screen.getByTestId('emailInput'), formData.email); + + userEvent.click(screen.getByTestId('registerBtn')); + + expect(toast.error).toBeCalledWith('Please enter valid details.'); + }); + + test('Expect toast.error to be called if last name input is empty', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type(screen.getByTestId('passwordInput'), formData.password); + + userEvent.type(screen.getByTestId('emailInput'), formData.email); + + userEvent.type(screen.getByTestId('firstNameInput'), formData.firstName); + + userEvent.click(screen.getByTestId('registerBtn')); + + expect(toast.error).toBeCalledWith('Please enter valid details.'); + }); + + test("Expect toast.error to be called if confirmPassword doesn't match with password", async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type(screen.getByTestId('passwordInput'), formData.password); + + userEvent.type(screen.getByTestId('emailInput'), formData.email); + + userEvent.type(screen.getByTestId('firstNameInput'), formData.firstName); + + userEvent.type(screen.getByTestId('lastNameInput'), formData.lastName); + + userEvent.click(screen.getByTestId('registerBtn')); + + expect(toast.error).toBeCalledWith( + "Confirm password and password doesn't match." + ); + }); + + test('Expect toast.success to be called if valid credentials are entered.', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type(screen.getByTestId('passwordInput'), formData.password); + + userEvent.type( + screen.getByTestId('confirmPasswordInput'), + formData.confirmPassword + ); + + userEvent.type(screen.getByTestId('emailInput'), formData.email); + + userEvent.type(screen.getByTestId('firstNameInput'), formData.firstName); + + userEvent.type(screen.getByTestId('lastNameInput'), formData.lastName); + + userEvent.click(screen.getByTestId('registerBtn')); + + await wait(); + + expect(toast.success).toBeCalledWith( + 'Successfully registered. Please wait for admin to approve your request.' + ); + }); +}); diff --git a/src/components/UserPortal/Register/Register.tsx b/src/components/UserPortal/Register/Register.tsx index a28c98963d..12863a89bb 100644 --- a/src/components/UserPortal/Register/Register.tsx +++ b/src/components/UserPortal/Register/Register.tsx @@ -51,7 +51,7 @@ export default function register(props: InterfaceRegisterProps): JSX.Element { toast.error(t('passwordNotMatch')); } else { try { - const { data } = await registerMutation({ + await registerMutation({ variables: { firstName: registerVariables.firstName, lastName: registerVariables.lastName, @@ -60,47 +60,52 @@ export default function register(props: InterfaceRegisterProps): JSX.Element { }, }); - if (data) { - toast.success(t('afterRegister')); - - setRegisterVariables({ - firstName: '', - lastName: '', - email: '', - password: '', - confirmPassword: '', - }); - } + toast.success(t('afterRegister')); + + /* istanbul ignore next */ + setRegisterVariables({ + firstName: '', + lastName: '', + email: '', + password: '', + confirmPassword: '', + }); } catch (error: any) { + /* istanbul ignore next */ errorHandler(t, error); } } }; + /* istanbul ignore next */ const handleFirstName = (e: ChangeEvent): void => { const firstName = e.target.value; setRegisterVariables({ ...registerVariables, firstName }); }; + /* istanbul ignore next */ const handleLastName = (e: ChangeEvent): void => { const lastName = e.target.value; setRegisterVariables({ ...registerVariables, lastName }); }; + /* istanbul ignore next */ const handleEmailChange = (e: ChangeEvent): void => { const email = e.target.value; setRegisterVariables({ ...registerVariables, email }); }; + /* istanbul ignore next */ const handlePasswordChange = (e: ChangeEvent): void => { const password = e.target.value; setRegisterVariables({ ...registerVariables, password }); }; + /* istanbul ignore next */ const handleConfirmPasswordChange = ( e: ChangeEvent ): void => { @@ -120,6 +125,7 @@ export default function register(props: InterfaceRegisterProps): JSX.Element { className={styles.borderNone} value={registerVariables.firstName} onChange={handleFirstName} + data-testid="firstNameInput" /> @@ -132,6 +138,7 @@ export default function register(props: InterfaceRegisterProps): JSX.Element { className={styles.borderNone} value={registerVariables.lastName} onChange={handleLastName} + data-testid="lastNameInput" /> @@ -145,6 +152,7 @@ export default function register(props: InterfaceRegisterProps): JSX.Element { className={styles.borderNone} value={registerVariables.email} onChange={handleEmailChange} + data-testid="emailInput" /> @@ -158,6 +166,7 @@ export default function register(props: InterfaceRegisterProps): JSX.Element { className={styles.borderNone} value={registerVariables.password} onChange={handlePasswordChange} + data-testid="passwordInput" /> @@ -171,19 +180,28 @@ export default function register(props: InterfaceRegisterProps): JSX.Element { className={styles.borderNone} value={registerVariables.confirmPassword} onChange={handleConfirmPasswordChange} + data-testid="confirmPasswordInput" /> -
{t('alreadyhaveAnAccount')}{' '} - + {t('login')}
diff --git a/src/screens/UserPortal/Organizations/Organizations.test.tsx b/src/screens/UserPortal/Organizations/Organizations.test.tsx new file mode 100644 index 0000000000..66de09be40 --- /dev/null +++ b/src/screens/UserPortal/Organizations/Organizations.test.tsx @@ -0,0 +1,38 @@ +import { MockedProvider } from '@apollo/react-testing'; +import { act, render } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; + +import Organizations from './Organizations'; + +const link = new StaticMockLink([], true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +describe('Testing Organizations Screen [User Portal]', () => { + test('Screen should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + }); +}); diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx b/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx new file mode 100644 index 0000000000..c52cd949bb --- /dev/null +++ b/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx @@ -0,0 +1,161 @@ +import { MockedProvider } from '@apollo/react-testing'; +import { act, render, screen } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import cookies from 'js-cookie'; +import { StaticMockLink } from 'utils/StaticMockLink'; + +import UserLoginPage from './UserLoginPage'; +import userEvent from '@testing-library/user-event'; + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +const link = new StaticMockLink([], true); + +describe('Testing User Login Page Screen [User Portal]', () => { + afterEach(async () => { + await act(async () => { + await i18nForTest.changeLanguage('en'); + }); + }); + + test('Screen should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + }); + + test('Expect the defualt language to be en', async () => { + cookies.remove('i18next'); + render( + + + + + + + + + + ); + + await wait(); + + expect(screen.getByText(/English/i)).toBeInTheDocument(); + }); + + test('Expect the language to be changed to fr', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByText('English')); + + userEvent.click(screen.getByTestId('changeLanguageBtn1')); + + await wait(); + + expect(cookies.get('i18next')).toBe('fr'); + }); + + test('Expect the language to be changed to hi', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByText('English')); + + userEvent.click(screen.getByTestId('changeLanguageBtn2')); + + await wait(); + + expect(cookies.get('i18next')).toBe('hi'); + }); + + test('Expect the language to be changed to sp', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByText('English')); + + userEvent.click(screen.getByTestId('changeLanguageBtn3')); + + await wait(); + + expect(cookies.get('i18next')).toBe('sp'); + }); + + test('Expect the language to be changed to zh', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByText('English')); + + userEvent.click(screen.getByTestId('changeLanguageBtn4')); + + await wait(); + + expect(cookies.get('i18next')).toBe('zh'); + }); +}); diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx b/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx index 20113e1fcb..2280d4c141 100644 --- a/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx +++ b/src/screens/UserPortal/UserLoginPage/UserLoginPage.tsx @@ -39,7 +39,11 @@ export default function userLoginPage(): JSX.Element {
- + {languages.map((language, index: number) => ( - {currentMode === 'login' ? ( - - ) : ( - - )} + { + /* istanbul ignore next */ + currentMode === 'login' ? ( + + ) : ( + + ) + }
); From e0a73a0b97aab76efb594ed11347fcc6467051c6 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 11 Jun 2023 10:47:49 +0530 Subject: [PATCH 04/29] Add react import in tests --- src/components/UserPortal/Login/Login.test.tsx | 3 ++- src/components/UserPortal/Register/Register.test.tsx | 3 ++- src/screens/UserPortal/Organizations/Organizations.test.tsx | 1 + src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/UserPortal/Login/Login.test.tsx b/src/components/UserPortal/Login/Login.test.tsx index f491ca1b41..2ce9ebe9cc 100644 --- a/src/components/UserPortal/Login/Login.test.tsx +++ b/src/components/UserPortal/Login/Login.test.tsx @@ -1,4 +1,5 @@ -import React, { SetStateAction } from 'react'; +import type { SetStateAction } from 'react'; +import React from 'react'; import { act, render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; diff --git a/src/components/UserPortal/Register/Register.test.tsx b/src/components/UserPortal/Register/Register.test.tsx index 9af6d58bba..cc2b5f8b65 100644 --- a/src/components/UserPortal/Register/Register.test.tsx +++ b/src/components/UserPortal/Register/Register.test.tsx @@ -1,4 +1,5 @@ -import React, { SetStateAction } from 'react'; +import type { SetStateAction } from 'react'; +import React from 'react'; import { act, render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; diff --git a/src/screens/UserPortal/Organizations/Organizations.test.tsx b/src/screens/UserPortal/Organizations/Organizations.test.tsx index 66de09be40..c3cea1c4f6 100644 --- a/src/screens/UserPortal/Organizations/Organizations.test.tsx +++ b/src/screens/UserPortal/Organizations/Organizations.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; import { act, render } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; diff --git a/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx b/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx index c52cd949bb..ff2175ccc1 100644 --- a/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx +++ b/src/screens/UserPortal/UserLoginPage/UserLoginPage.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; import { act, render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; From 8bef0fe4bdfe4b3d8e22580240fb85cd6234c417 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Thu, 22 Jun 2023 14:04:58 +0530 Subject: [PATCH 05/29] Create organizations screen for user portal --- public/locales/en.json | 46 +++-- public/locales/fr.json | 33 ++- public/locales/hi.json | 33 ++- public/locales/sp.json | 33 ++- public/locales/zh.json | 33 ++- src/GraphQl/Queries/Queries.ts | 49 +++++ src/components/UserPortal/Login/Login.tsx | 8 +- .../OrganizationCard.module.css | 16 ++ .../OrganizationCard/OrganizationCard.tsx | 26 +++ .../UserNavbar/UserNavbar.module.css | 17 ++ .../UserPortal/UserNavbar/UserNavbar.tsx | 102 +++++++++ .../UserSidebar/UserSidebar.module.css | 59 ++++++ .../UserPortal/UserSidebar/UserSidebar.tsx | 96 +++++++++ .../Organizations/Organizations.module.css | 40 ++++ .../Organizations/Organizations.tsx | 195 +++++++++++++++++- 15 files changed, 747 insertions(+), 39 deletions(-) create mode 100644 src/components/UserPortal/OrganizationCard/OrganizationCard.module.css create mode 100644 src/components/UserPortal/OrganizationCard/OrganizationCard.tsx create mode 100644 src/components/UserPortal/UserNavbar/UserNavbar.module.css create mode 100644 src/components/UserPortal/UserNavbar/UserNavbar.tsx create mode 100644 src/components/UserPortal/UserSidebar/UserSidebar.module.css create mode 100644 src/components/UserPortal/UserSidebar/UserSidebar.tsx diff --git a/public/locales/en.json b/public/locales/en.json index e16ab1474d..a8a4daa7d6 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -445,20 +445,7 @@ "membershipRequests": "Membership requests", "adminForEvents": "Admin for events", "addedAsAdmin": "User is added as admin.", - "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too." - }, - "userNavbar": { - "talawa_portal": "Talawa", - "home": "Home", - "people": "People", - "events": "Events", - "chat": "Chat", - "donate": "Donate", - "myTasks": "My Tasks", - "settings": "Settings", - "language": "Language", - "logout": "Logout", - "close": "Close" + "talawaApiUnavailable": "Talawa-API service is unavailable. Kindly check your network connection and wait for a while." }, "userLogin": { "login": "Login", @@ -472,7 +459,7 @@ "invalidDetailsMessage": "Please enter a valid email and password.", "notAuthorised": "Sorry! you are not Authorised!", "invalidCredentials": "Entered credentials are incorrect. Please enter valid credentials.", - "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too." + "talawaApiUnavailable": "Talawa-API service is unavailable. Kindly check your network connection and wait for a while." }, "userRegister": { "register": "Register", @@ -489,8 +476,33 @@ "alreadyhaveAnAccount": "Already have an account?", "login": "Login", "afterRegister": "Successfully registered. Please wait for admin to approve your request.", - "passwordNotMatch": "Confirm password and password doesn't match.", + "passwordNotMatch": "Password doesn't match. Confirm Password and try again.", "invalidDetailsMessage": "Please enter valid details.", - "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too." + "talawaApiUnavailable": "Talawa-API service is unavailable. Kindly check your network connection and wait for a while." + }, + "userNavbar": { + "talawa": "Talawa", + "home": "Home", + "people": "People", + "events": "Events", + "chat": "Chat", + "donate": "Donate", + "myTasks": "My Tasks", + "settings": "Settings", + "language": "Language", + "logout": "Logout", + "close": "Close" + }, + "userOrganizations": { + "allOrganizations":"All Organizations", + "joinedOrganizations": "Joined Organizations", + "createdOrganizations": "Created Organizations", + "search": "Search", + "nothingToShow": "Nothing to show here." + }, + "userSidebar": { + "yourOrganizations": "Your Organizations", + "noOrganizations": "You haven't joined any organization yet.", + "viewAll": "View all" } } diff --git a/public/locales/fr.json b/public/locales/fr.json index 24be08622c..5b8c8cadd1 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -439,7 +439,7 @@ "membershipRequests": "Demandes d'adhésion", "adminForEvents": "Administrateur pour les événements", "addedAsAdmin": "L'utilisateur est ajouté en tant qu'administrateur.", - "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau." + "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Veuillez vérifier votre connexion réseau et attendre un moment." }, "userLogin": { "login": "Connexion", @@ -453,7 +453,7 @@ "invalidDetailsMessage": "Veuillez saisir un e-mail et un mot de passe valides.", "notAuthorised": "Désolé! vous n'êtes pas autorisé !", "invalidCredentials": "Les informations d'identification saisies sont incorrectes. Veuillez entrer des informations d'identification valides.", - "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau." + "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Veuillez vérifier votre connexion réseau et attendre un moment." }, "userRegister": { "register": "Enregistrer", @@ -470,8 +470,33 @@ "alreadyhaveAnAccount": "Vous avez déjà un compte?", "login": "Connexion", "afterRegister": "Enregistré avec succès. Veuillez attendre que l'administrateur approuve votre demande.", - "passwordNotMatch": "Confirmez le mot de passe et le mot de passe ne correspond pas.", + "passwordNotMatch": "Le mot de passe ne correspond pas. Confirmez le mot de passe et réessayez.", "invalidDetailsMessage": "Veuillez entrer des détails valides.", - "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau." + "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Veuillez vérifier votre connexion réseau et attendre un moment." + }, + "userNavbar": { + "talawa": "Talawa", + "home": "Maison", + "people": "Personnes", + "events": "Événements", + "chat": "Discuter", + "donate": "Donner", + "myTasks": "Mes tâches", + "settings": "Paramètres", + "language": "Langue", + "logout": "Se déconnecter", + "close": "Fermer" + }, + "userOrganizations": { + "allOrganizations":"Toutes les organisations", + "joinedOrganizations": "Organisations jointes", + "createdOrganizations": "Organisations créées", + "search": "Recherche", + "nothingToShow": "Rien à montrer ici." + }, + "userSidebar": { + "yourOrganizations": "Vos organisations", + "noOrganizations": "Vous n'avez encore rejoint aucune organisation.", + "viewAll": "Voir tout" } } diff --git a/public/locales/hi.json b/public/locales/hi.json index 2818136790..e0f9ba71fd 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -439,7 +439,7 @@ "membershipRequests": "सदस्यता अनुरोध", "adminForEvents": "घटनाओं के लिए व्यवस्थापक", "addedAsAdmin": "उपयोगकर्ता को व्यवस्थापक के रूप में जोड़ा गया है।", - "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रहा है? अपनी नेटवर्क कनेक्टिविटी भी जांचें।" + "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। कृपया अपना नेटवर्क कनेक्शन जांचें और कुछ देर प्रतीक्षा करें।" }, "userLogin": { "login": "लॉगिन", @@ -453,7 +453,7 @@ "invalidDetailsMessage": "कृपया एक वैध ईमेल और पासवर्ड दर्ज करें।", "notAuthorised": "क्षमा मांगना! आप अधिकृत नहीं हैं!", "invalidCredentials": "दर्ज क्रेडेंशियल्स गलत हैं। कृपया मान्य क्रेडेंशियल दर्ज करें।", - "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रहा है? अपनी नेटवर्क कनेक्टिविटी भी जांचें।" + "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। कृपया अपना नेटवर्क कनेक्शन जांचें और कुछ देर प्रतीक्षा करें।" }, "userRegister": { "register": "रजिस्टर करें", @@ -470,8 +470,33 @@ "alreadyhaveAnAccount": "क्या आपके पास पहले से एक खाता मौजूद है?", "login": "लॉगिन", "afterRegister": "पंजीकरण सफलतापूर्वक हो गया है। कृपया आपके अनुरोध को स्वीकार करने के लिए व्यवस्थापक की प्रतीक्षा करें।", - "passwordNotMatch": "पासवर्ड की पुष्टि करें और पासवर्ड मेल नहीं खाता।", + "passwordNotMatch": "पासवर्ड मेल नहीं खाता. पासवर्ड की पुष्टि करें और पुनः प्रयास करें।", "invalidDetailsMessage": "कृपया मान्य विवरण दर्ज करें।", - "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रहा है? अपनी नेटवर्क कनेक्टिविटी भी जांचें।" + "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। कृपया अपना नेटवर्क कनेक्शन जांचें और कुछ देर प्रतीक्षा करें।" + }, + "userNavbar": { + "talawa": "तलावा", + "home": "घर", + "people": "लोग", + "events": "आयोजन", + "chat": "बातचीत", + "donate": "दान देना", + "myTasks": "मेरा काम", + "settings": "समायोजन", + "language": "भाषा", + "logout": "लॉग आउट", + "close": "बंद करना" + }, + "userOrganizations": { + "allOrganizations":"सभी संगठन", + "joinedOrganizations": "संगठन शामिल हुए", + "createdOrganizations": "संगठन बनाये गये", + "search": "खोज", + "nothingToShow": "यहां दिखाने के लिए कुछ भी नहीं है." + }, + "userSidebar": { + "yourOrganizations": "आपके संगठन", + "noOrganizations": "आप अभी तक किसी संगठन में शामिल नहीं हुए हैं.", + "viewAll": "सभी को देखें" } } diff --git a/public/locales/sp.json b/public/locales/sp.json index 99a52d923f..7eb4c2849f 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -441,7 +441,7 @@ "membershipRequests": "Solicitudes de membresía", "adminForEvents": "Administrador de eventos", "addedAsAdmin": "El usuario se agrega como administrador.", - "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red." + "talawaApiUnavailable": "El servicio Talawa-API no está disponible. Compruebe amablemente su conexión de red y espere un momento." }, "userLogin": { "login": "Acceso", @@ -455,7 +455,7 @@ "invalidDetailsMessage": "Por favor, introduzca un correo electrónico y una contraseña válidos.", "notAuthorised": "¡Lo siento! usted no está autorizado!", "invalidCredentials": "Las credenciales ingresadas son incorrectas. Ingrese credenciales válidas.", - "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red." + "talawaApiUnavailable": "El servicio Talawa-API no está disponible. Compruebe amablemente su conexión de red y espere un momento." }, "userRegister": { "register": "Registro", @@ -472,8 +472,33 @@ "alreadyhaveAnAccount": "¿Ya tienes una cuenta?", "login": "Acceso", "afterRegister": "Registrado exitosamente. Espere a que el administrador apruebe su solicitud.", - "passwordNotMatch": "Confirmar contraseña y la contraseña no coincide.", + "passwordNotMatch": "La contraseña no coincide. Confirme la contraseña y vuelva a intentarlo.", "invalidDetailsMessage": "Ingrese detalles válidos.", - "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red." + "talawaApiUnavailable": "El servicio Talawa-API no está disponible. Compruebe amablemente su conexión de red y espere un momento." + }, + "userNavbar": { + "talawa": "Talawa", + "home": "Hogar", + "people": "Gente", + "events": "Eventos", + "chat": "Charlar", + "donate": "Donar", + "myTasks": "Mis tareas", + "settings": "Ajustes", + "language": "Idioma", + "logout": "Cerrar sesión", + "close": "Cerca" + }, + "userOrganizations": { + "allOrganizations":"Todas las organizaciones", + "joinedOrganizations": "Organizaciones unidas", + "createdOrganizations": "Organizaciones creadas", + "search": "Buscar", + "nothingToShow": "Nada que mostrar aquí." + }, + "userSidebar": { + "yourOrganizations": "Tus Organizaciones", + "noOrganizations": "Aún no te has unido a ninguna organización.", + "viewAll": "Ver todo" } } diff --git a/public/locales/zh.json b/public/locales/zh.json index 0fdc1c9f63..b54b58973b 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -441,7 +441,7 @@ "membershipRequests": "会员申请", "adminForEvents": "事件管理员", "addedAsAdmin": "用戶被添加為管理員。", - "talawaApiUnavailable": "服務不可用。它在運行嗎?還要檢查您的網絡連接。" + "talawaApiUnavailable": "Talawa-API 服務不可用。 請檢查您的網絡連接並稍等片刻。" }, "userLogin": { "login": "登錄", @@ -455,7 +455,7 @@ "invalidDetailsMessage": "請輸入有效的電子郵件和密碼", "notAuthorised": "對不起! 你沒有被授權!", "invalidCredentials": "輸入的憑據不正確。 請輸入有效憑據。", - "talawaApiUnavailable": "服務不可用。它在運行嗎?還要檢查您的網絡連接。" + "talawaApiUnavailable": "Talawa-API 服務不可用。 請檢查您的網絡連接並稍等片刻。" }, "userRegister": { "register": "登記", @@ -472,8 +472,33 @@ "alreadyhaveAnAccount": "已有帳戶?", "login": "登錄", "afterRegister": "註冊成功。 請等待管理員批准您的請求。", - "passwordNotMatch": "確認密碼和密碼不匹配。", + "passwordNotMatch": "密碼不匹配。 確認密碼並重試。", "invalidDetailsMessage": "請輸入有效的詳細信息。", - "talawaApiUnavailable": "服務不可用。它在運行嗎?還要檢查您的網絡連接。" + "talawaApiUnavailable": "Talawa-API 服務不可用。 請檢查您的網絡連接並稍等片刻。" + }, + "userNavbar": { + "talawa": "塔拉瓦", + "home": "家", + "people": "人們", + "events": "活動", + "chat": "聊天", + "donate": "捐", + "myTasks": "我的任務", + "settings": "設置", + "language": "語言", + "logout": "登出", + "close": "關閉" + }, + "userOrganizations": { + "allOrganizations":"所有組織", + "joinedOrganizations": "加入組織", + "createdOrganizations": "創建的組織", + "search": "搜索", + "nothingToShow": "這裡沒有什麼可展示的。" + }, + "userSidebar": { + "yourOrganizations": "您的組織", + "noOrganizations": "您還沒有加入任何組織。", + "viewAll": "查看全部" } } diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index efd7224e1d..f0d0d3d78c 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -430,6 +430,55 @@ export const ORGANIZATION_POST_CONNECTION_LIST = gql` } } `; + +export const USER_ORGANIZATION_CONNECTION = gql` + query organizationsConnection($first: Int, $skip: Int, $filter: String) { + organizationsConnection( + first: $first + skip: $skip + where: { name_contains: $filter } + orderBy: name_ASC + ) { + image + _id + name + image + description + isPublic + creator { + firstName + lastName + } + } + } +`; + +export const USER_JOINED_ORGANIZATIONS = gql` + query UserJoinedOrganizations($id: ID!) { + users(where: { id: $id }) { + joinedOrganizations { + _id + name + description + image + } + } + } +`; + +export const USER_CREATED_ORGANIZATIONS = gql` + query UserJoinedOrganizations($id: ID!) { + users(where: { id: $id }) { + createdOrganizations { + _id + name + description + image + } + } + } +`; + /** * @name PLUGIN_GET * @description used to fetch list of plugins diff --git a/src/components/UserPortal/Login/Login.tsx b/src/components/UserPortal/Login/Login.tsx index 412f783729..c9d5f9610a 100644 --- a/src/components/UserPortal/Login/Login.tsx +++ b/src/components/UserPortal/Login/Login.tsx @@ -4,7 +4,7 @@ import { Button, Form, InputGroup } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import EmailOutlinedIcon from '@mui/icons-material/EmailOutlined'; import { LockOutlined } from '@mui/icons-material'; -import { Link, useHistory } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import { useMutation } from '@apollo/client'; import { toast } from 'react-toastify'; @@ -32,8 +32,6 @@ export default function login(props: InterfaceLoginProps): JSX.Element { password: '', }); - const history = useHistory(); - const handleLogin = async (): Promise => { if (!(loginVariables.email && loginVariables.password)) { toast.error(t('invalidDetailsMessage')); @@ -47,12 +45,12 @@ export default function login(props: InterfaceLoginProps): JSX.Element { }); if (data.login.user.adminApproved) { - localStorage.setItem('userToken', data.login.accessToken); + localStorage.setItem('token', data.login.accessToken); localStorage.setItem('userId', data.login.user._id); navigator.clipboard.writeText(''); /* istanbul ignore next */ - history.replace('/user/organizations'); + window.location.assign('/user/organizations'); } else { toast.warn(t('notAuthorised')); } diff --git a/src/components/UserPortal/OrganizationCard/OrganizationCard.module.css b/src/components/UserPortal/OrganizationCard/OrganizationCard.module.css new file mode 100644 index 0000000000..f034cce79c --- /dev/null +++ b/src/components/UserPortal/OrganizationCard/OrganizationCard.module.css @@ -0,0 +1,16 @@ +.mainContainer { + width: 100%; + display: flex; + flex-direction: row; + padding: 10px; + cursor: pointer; + background-color: white; + border-radius: 10px; + box-shadow: 2px 2px 8px 0px #c8c8c8; +} + +.organizationDetails { + display: flex; + flex-direction: column; + justify-content: center; +} diff --git a/src/components/UserPortal/OrganizationCard/OrganizationCard.tsx b/src/components/UserPortal/OrganizationCard/OrganizationCard.tsx new file mode 100644 index 0000000000..bfc9652d72 --- /dev/null +++ b/src/components/UserPortal/OrganizationCard/OrganizationCard.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import aboutImg from 'assets/images/defaultImg.png'; +import styles from './OrganizationCard.module.css'; + +interface InterfaceOrganizationCardProps { + id: string; + name: string; + image: string; + description: string; +} + +function organizationCard(props: InterfaceOrganizationCardProps): JSX.Element { + const imageUrl = props.image ? props.image : aboutImg; + + return ( +
+ +
+ {props.name} + {props.description} +
+
+ ); +} + +export default organizationCard; diff --git a/src/components/UserPortal/UserNavbar/UserNavbar.module.css b/src/components/UserPortal/UserNavbar/UserNavbar.module.css new file mode 100644 index 0000000000..8d0bdb523c --- /dev/null +++ b/src/components/UserPortal/UserNavbar/UserNavbar.module.css @@ -0,0 +1,17 @@ +.talawaImage { + width: 40px; + height: auto; + margin-top: -5px; + border: 2px solid white; + margin-right: 10px; + background-color: white; + border-radius: 10px; +} + +.boxShadow { + box-shadow: 4px 4px 8px 4px #c8c8c8; +} + +.colorWhite { + color: white; +} diff --git a/src/components/UserPortal/UserNavbar/UserNavbar.tsx b/src/components/UserPortal/UserNavbar/UserNavbar.tsx new file mode 100644 index 0000000000..633db383bf --- /dev/null +++ b/src/components/UserPortal/UserNavbar/UserNavbar.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import styles from './UserNavbar.module.css'; +import TalawaImage from 'assets/talawa-logo-200x200.png'; +import { Container, Dropdown, Navbar } from 'react-bootstrap'; +import { languages } from 'utils/languages'; +import i18next from 'i18next'; +import cookies from 'js-cookie'; +import PermIdentityIcon from '@mui/icons-material/PermIdentity'; +import LanguageIcon from '@mui/icons-material/Language'; +import { useTranslation } from 'react-i18next'; + +function userNavbar(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'userNavbar', + }); + + const [currentLanguageCode, setCurrentLanguageCode] = React.useState( + cookies.get('i18next') || 'en' + ); + + const handleLogout = (): void => { + localStorage.clear(); + window.location.replace('/user'); + }; + + const userName = localStorage.getItem('name'); + + return ( + + + + Talawa Branding + {t('talawa')} + + + + + + + + + + {languages.map((language, index: number) => ( + => { + setCurrentLanguageCode(language.code); + await i18next.changeLanguage(language.code); + }} + disabled={currentLanguageCode === language.code} + data-testid={`changeLanguageBtn${index}`} + > + + {language.name} + + ))} + + + + + + + + + + {userName} + + {t('settings')} + {t('myTasks')} + + {t('logout')} + + + + + + + ); +} + +export default userNavbar; diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.module.css b/src/components/UserPortal/UserSidebar/UserSidebar.module.css new file mode 100644 index 0000000000..88692f7564 --- /dev/null +++ b/src/components/UserPortal/UserSidebar/UserSidebar.module.css @@ -0,0 +1,59 @@ +.mainContainer { + display: flex; + overflow: hidden; + flex-direction: column; + align-items: center; + padding: 0px 10px; + padding-top: 50px; + flex-grow: 1; +} + +.userDetails { + display: flex; + flex-direction: column; + align-items: center; + padding-top: 20px; +} + +.boxShadow { + box-shadow: 4px 4px 8px 4px #c8c8c8; +} + +.organizationsConatiner { + width: 100%; + padding-top: 50px; +} + +.heading { + text-align: center; + padding: 10px 0px; +} + +.orgName { + font-size: 16px; + font-weight: 600; + margin-top: 4px; +} + +.alignRight { + width: 100%; + text-align: right; + padding: 5px; +} + +.link { + text-decoration: none !important; + color: black; +} + +.rounded { + border-radius: 10px !important; +} + +.colorLight { + background-color: #f5f5f5; +} + +.marginTop { + margin-top: -2px; +} diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.tsx new file mode 100644 index 0000000000..fe04b2b6e2 --- /dev/null +++ b/src/components/UserPortal/UserSidebar/UserSidebar.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import AboutImg from 'assets/images/defaultImg.png'; +import styles from './UserSidebar.module.css'; +import { ListGroup } from 'react-bootstrap'; +import { Link } from 'react-router-dom'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import { useQuery } from '@apollo/client'; +import { + USER_DETAILS, + USER_JOINED_ORGANIZATIONS, +} from 'GraphQl/Queries/Queries'; +import { useTranslation } from 'react-i18next'; + +function userSidebar(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'userSidebar', + }); + + const [organizations, setOrganizations] = React.useState([]); + const [details, setDetails] = React.useState({} as any); + + const userId: string | null = localStorage.getItem('userId'); + + const { data } = useQuery(USER_JOINED_ORGANIZATIONS, { + variables: { id: userId }, + }); + + const { data: data2 } = useQuery(USER_DETAILS, { + variables: { id: userId }, + }); + + React.useEffect(() => { + if (data) { + setOrganizations(data.users[0].joinedOrganizations); + } + }, [data]); + + React.useEffect(() => { + if (data2) { + setDetails(data2.user); + } + }, [data2]); + + return ( +
+ +
+
+ {`${details.firstName} ${details.lastName}`} +
+
{details.email}
+
+
+
+ {t('yourOrganizations')} +
+ + {organizations.length ? ( + organizations.map((organization: any, _index) => { + return ( + +
+ +
{organization.name}
+
+
+ ); + }) + ) : ( +
{t('noOrganizations')}
+ )} +
+
+ + {t('viewAll')} + + +
+
+
+ ); +} + +export default userSidebar; diff --git a/src/screens/UserPortal/Organizations/Organizations.module.css b/src/screens/UserPortal/Organizations/Organizations.module.css index e69de29bb2..b5b576eba9 100644 --- a/src/screens/UserPortal/Organizations/Organizations.module.css +++ b/src/screens/UserPortal/Organizations/Organizations.module.css @@ -0,0 +1,40 @@ +.borderNone { + border: none; +} + +.colorWhite { + color: white; +} + +.maxWidth { + max-width: 300px; +} + +.colorLight { + background-color: #f5f5f5; +} + +.mainContainer { + width: 50%; + flex-grow: 3; + padding: 40px; + max-height: 100%; + overflow: auto; +} + +.content { + height: fit-content; + min-height: calc(100% - 40px); +} + +.gap { + gap: 20px; +} + +.paddingY { + padding: 30px 0px; +} + +.containerHeight { + height: calc(100vh - 61px); +} diff --git a/src/screens/UserPortal/Organizations/Organizations.tsx b/src/screens/UserPortal/Organizations/Organizations.tsx index fbbb11c865..e176b8e984 100644 --- a/src/screens/UserPortal/Organizations/Organizations.tsx +++ b/src/screens/UserPortal/Organizations/Organizations.tsx @@ -1,5 +1,198 @@ import React from 'react'; +import UserNavbar from 'components/UserPortal/UserNavbar/UserNavbar'; +import OrganizationCard from 'components/UserPortal/OrganizationCard/OrganizationCard'; +import UserSidebar from 'components/UserPortal/UserSidebar/UserSidebar'; +import { Dropdown, Form, InputGroup } from 'react-bootstrap'; +import PaginationList from 'components/PaginationList/PaginationList'; +import { useHistory } from 'react-router-dom'; +import { + USER_CREATED_ORGANIZATIONS, + USER_JOINED_ORGANIZATIONS, + USER_ORGANIZATION_CONNECTION, +} from 'GraphQl/Queries/Queries'; +import { useQuery } from '@apollo/client'; +import { SearchOutlined } from '@mui/icons-material'; +import styles from './Organizations.module.css'; +import { useTranslation } from 'react-i18next'; + +interface InterfaceOrganizationCardProps { + id: string; + name: string; + image: string; + description: string; +} export default function organizations(): JSX.Element { - return
Organizations
; + const { t } = useTranslation('translation', { + keyPrefix: 'userOrganizations', + }); + + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + const [organizations, setOrganizations] = React.useState([]); + const [filterName, setFilterName] = React.useState(''); + const [mode, setMode] = React.useState(0); + + const modes = [ + t('allOrganizations'), + t('joinedOrganizations'), + t('createdOrganizations'), + ]; + + const userId: string | null = localStorage.getItem('userId'); + + const history = useHistory(); + + const { data, refetch } = useQuery(USER_ORGANIZATION_CONNECTION, { + variables: { filter: filterName }, + }); + + const { data: data2 } = useQuery(USER_JOINED_ORGANIZATIONS, { + variables: { id: userId }, + }); + + const { data: data3 } = useQuery(USER_CREATED_ORGANIZATIONS, { + variables: { id: userId }, + }); + + /* istanbul ignore next */ + const handleChangePage = ( + _event: React.MouseEvent | null, + newPage: number + ): void => { + setPage(newPage); + }; + + /* istanbul ignore next */ + const handleChangeRowsPerPage = ( + event: React.ChangeEvent + ): void => { + const newRowsPerPage = event.target.value; + + setRowsPerPage(parseInt(newRowsPerPage, 10)); + setPage(0); + }; + + const handleSearch = ( + event: React.ChangeEvent + ): void => { + const newFilter = event.target.value; + setFilterName(newFilter); + + const filter = { + filter: newFilter, + }; + + refetch(filter); + }; + + React.useEffect(() => { + const userToken = localStorage.getItem('token'); + + /* istanbul ignore next */ + if (!userId || !userToken) { + navigator.clipboard.writeText(''); + history.replace('/user/'); + } + }, []); + + React.useEffect(() => { + if (data) { + setOrganizations(data.organizationsConnection); + } + }, [data]); + + React.useEffect(() => { + if (mode == 0) { + if (data) { + setOrganizations(data.organizationsConnection); + } + } else if (mode == 1) { + if (data2) { + setOrganizations(data2.users[0].joinedOrganizations); + } + } else if (mode == 2) { + if (data3) { + setOrganizations(data3.users[0].createdOrganizations); + } + } + }, [mode]); + + return ( + <> + +
+ +
+
+ + + + + + + + + {modes[mode]} + + + {modes.map((value, index) => { + return ( + setMode(index)} + > + {value} + + ); + })} + + +
+
+
+ {organizations.length > 0 ? ( + (rowsPerPage > 0 + ? organizations.slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ) + : organizations + ).map((organization: any, index) => { + const cardProps: InterfaceOrganizationCardProps = { + name: organization.name, + image: organization.image, + id: organization._id, + description: organization.description, + }; + return ; + }) + ) : ( + {t('nothingToShow')} + )} +
+ +
+
+
+ + ); } From 5c9f41c563ca74d1d90b407dd71779020354ad25 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Thu, 22 Jun 2023 14:16:52 +0530 Subject: [PATCH 06/29] Fix failing tests and lint code --- public/locales/en.json | 2 +- public/locales/fr.json | 2 +- public/locales/hi.json | 2 +- public/locales/sp.json | 2 +- public/locales/zh.json | 2 +- .../UserPortal/Register/Register.test.tsx | 2 +- .../Organizations/Organizations.test.tsx | 39 ------------------- 7 files changed, 6 insertions(+), 45 deletions(-) delete mode 100644 src/screens/UserPortal/Organizations/Organizations.test.tsx diff --git a/public/locales/en.json b/public/locales/en.json index a8a4daa7d6..8e61c0e64b 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -494,7 +494,7 @@ "close": "Close" }, "userOrganizations": { - "allOrganizations":"All Organizations", + "allOrganizations": "All Organizations", "joinedOrganizations": "Joined Organizations", "createdOrganizations": "Created Organizations", "search": "Search", diff --git a/public/locales/fr.json b/public/locales/fr.json index 5b8c8cadd1..48d7172a9e 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -488,7 +488,7 @@ "close": "Fermer" }, "userOrganizations": { - "allOrganizations":"Toutes les organisations", + "allOrganizations": "Toutes les organisations", "joinedOrganizations": "Organisations jointes", "createdOrganizations": "Organisations créées", "search": "Recherche", diff --git a/public/locales/hi.json b/public/locales/hi.json index e0f9ba71fd..e69cb4ca31 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -488,7 +488,7 @@ "close": "बंद करना" }, "userOrganizations": { - "allOrganizations":"सभी संगठन", + "allOrganizations": "सभी संगठन", "joinedOrganizations": "संगठन शामिल हुए", "createdOrganizations": "संगठन बनाये गये", "search": "खोज", diff --git a/public/locales/sp.json b/public/locales/sp.json index 7eb4c2849f..3d68c30f64 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -490,7 +490,7 @@ "close": "Cerca" }, "userOrganizations": { - "allOrganizations":"Todas las organizaciones", + "allOrganizations": "Todas las organizaciones", "joinedOrganizations": "Organizaciones unidas", "createdOrganizations": "Organizaciones creadas", "search": "Buscar", diff --git a/public/locales/zh.json b/public/locales/zh.json index b54b58973b..41c0a08156 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -490,7 +490,7 @@ "close": "關閉" }, "userOrganizations": { - "allOrganizations":"所有組織", + "allOrganizations": "所有組織", "joinedOrganizations": "加入組織", "createdOrganizations": "創建的組織", "search": "搜索", diff --git a/src/components/UserPortal/Register/Register.test.tsx b/src/components/UserPortal/Register/Register.test.tsx index cc2b5f8b65..f730baca73 100644 --- a/src/components/UserPortal/Register/Register.test.tsx +++ b/src/components/UserPortal/Register/Register.test.tsx @@ -225,7 +225,7 @@ describe('Testing Register Component [User Portal]', () => { userEvent.click(screen.getByTestId('registerBtn')); expect(toast.error).toBeCalledWith( - "Confirm password and password doesn't match." + "Password doesn't match. Confirm Password and try again." ); }); diff --git a/src/screens/UserPortal/Organizations/Organizations.test.tsx b/src/screens/UserPortal/Organizations/Organizations.test.tsx deleted file mode 100644 index c3cea1c4f6..0000000000 --- a/src/screens/UserPortal/Organizations/Organizations.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import { MockedProvider } from '@apollo/react-testing'; -import { act, render } from '@testing-library/react'; -import { I18nextProvider } from 'react-i18next'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import { store } from 'state/store'; -import i18nForTest from 'utils/i18nForTest'; -import { StaticMockLink } from 'utils/StaticMockLink'; - -import Organizations from './Organizations'; - -const link = new StaticMockLink([], true); - -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} - -describe('Testing Organizations Screen [User Portal]', () => { - test('Screen should be rendered properly', async () => { - render( - - - - - - - - - - ); - - await wait(); - }); -}); From 93511fd110d947ec330c2943cdc917ee0280a86f Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Thu, 22 Jun 2023 14:20:49 +0530 Subject: [PATCH 07/29] fix failing tests --- src/components/UserPortal/UserSidebar/UserSidebar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.tsx index fe04b2b6e2..c90bb9b7be 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.tsx +++ b/src/components/UserPortal/UserSidebar/UserSidebar.tsx @@ -60,10 +60,10 @@ function userSidebar(): JSX.Element { {organizations.length ? ( - organizations.map((organization: any, _index) => { + organizations.map((organization: any, index) => { return ( From 7e34d619d6f6a2636a605e7e556bd46bb6b717b4 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Tue, 27 Jun 2023 14:31:12 +0530 Subject: [PATCH 08/29] Add tests for organization screen and componenets --- src/App.tsx | 6 +- src/GraphQl/Queries/Queries.ts | 1 - .../OrganizationCard.module.css | 1 + .../OrganizationCard.test.tsx | 67 ++++++ .../SecuredRouteForUser.tsx | 15 ++ .../UserPortal/UserNavbar/UserNavbar.test.tsx | 167 +++++++++++++ .../UserPortal/UserNavbar/UserNavbar.tsx | 16 +- .../UserSidebar/UserSidebar.module.css | 6 + .../UserSidebar/UserSidebar.test.tsx | 208 +++++++++++++++++ .../UserPortal/UserSidebar/UserSidebar.tsx | 2 + .../Organizations/Organizations.test.tsx | 221 ++++++++++++++++++ .../Organizations/Organizations.tsx | 51 ++-- 12 files changed, 732 insertions(+), 29 deletions(-) create mode 100644 src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx create mode 100644 src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.tsx create mode 100644 src/components/UserPortal/UserNavbar/UserNavbar.test.tsx create mode 100644 src/components/UserPortal/UserSidebar/UserSidebar.test.tsx create mode 100644 src/screens/UserPortal/Organizations/Organizations.test.tsx diff --git a/src/App.tsx b/src/App.tsx index 9801d95b5d..0d125fe546 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import * as installedPlugins from 'components/plugins/index'; import styles from './App.module.css'; import { CHECK_AUTH } from 'GraphQl/Queries/Queries'; import SecuredRoute from 'components/SecuredRoute/SecuredRoute'; +import SecuredRouteForUser from 'components/UserPortal/SecuredRouteForUser/SecuredRouteForUser'; import LoginPage from 'screens/LoginPage/LoginPage'; import OrganizationEvents from 'screens/OrganizationEvents/OrganizationEvents'; import OrganizationPeople from 'screens/OrganizationPeople/OrganizationPeople'; @@ -105,7 +106,10 @@ function app(): JSX.Element { {/* User Portal Routes */} - + diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index f0d0d3d78c..4cc13c83f3 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -439,7 +439,6 @@ export const USER_ORGANIZATION_CONNECTION = gql` where: { name_contains: $filter } orderBy: name_ASC ) { - image _id name image diff --git a/src/components/UserPortal/OrganizationCard/OrganizationCard.module.css b/src/components/UserPortal/OrganizationCard/OrganizationCard.module.css index f034cce79c..87eee09d9a 100644 --- a/src/components/UserPortal/OrganizationCard/OrganizationCard.module.css +++ b/src/components/UserPortal/OrganizationCard/OrganizationCard.module.css @@ -7,6 +7,7 @@ background-color: white; border-radius: 10px; box-shadow: 2px 2px 8px 0px #c8c8c8; + overflow: hidden; } .organizationDetails { diff --git a/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx b/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx new file mode 100644 index 0000000000..f04c87b526 --- /dev/null +++ b/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { act, render } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; + +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import OrganizationCard from './OrganizationCard'; + +const link = new StaticMockLink([], true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +let props = { + id: '1', + name: 'organizationName', + image: '', + description: 'organizationDescription', +}; + +describe('Testing Organization Card Component [User Portal]', () => { + test('Component should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + }); + + test('Component should be rendered properly if organization Image is not undefined', async () => { + props = { + ...props, + image: 'organizationImage', + }; + + render( + + + + + + + + + + ); + + await wait(); + }); +}); diff --git a/src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.tsx b/src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.tsx new file mode 100644 index 0000000000..967e6c00a0 --- /dev/null +++ b/src/components/UserPortal/SecuredRouteForUser/SecuredRouteForUser.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Redirect, Route } from 'react-router-dom'; + +const SecuredRouteForUser = (props: any): JSX.Element => { + const isLoggedIn = localStorage.getItem('IsLoggedIn'); + return isLoggedIn === 'TRUE' ? ( + <> + + + ) : ( + + ); +}; + +export default SecuredRouteForUser; diff --git a/src/components/UserPortal/UserNavbar/UserNavbar.test.tsx b/src/components/UserPortal/UserNavbar/UserNavbar.test.tsx new file mode 100644 index 0000000000..ed51680185 --- /dev/null +++ b/src/components/UserPortal/UserNavbar/UserNavbar.test.tsx @@ -0,0 +1,167 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import { act, render, screen } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import cookies from 'js-cookie'; +import { StaticMockLink } from 'utils/StaticMockLink'; + +import UserNavbar from './UserNavbar'; +import userEvent from '@testing-library/user-event'; + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +const link = new StaticMockLink([], true); + +describe('Testing UserNavbar Component [User Portal]', () => { + afterEach(async () => { + await act(async () => { + await i18nForTest.changeLanguage('en'); + }); + }); + + test('Component should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + }); + + test('The language is switched to English', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('languageIcon')); + + userEvent.click(screen.getByTestId('changeLanguageBtn0')); + + await wait(); + + expect(cookies.get('i18next')).toBe('en'); + }); + + test('The language is switched to fr', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('languageIcon')); + + userEvent.click(screen.getByTestId('changeLanguageBtn1')); + + await wait(); + + expect(cookies.get('i18next')).toBe('fr'); + }); + + test('The language is switched to hi', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('languageIcon')); + + userEvent.click(screen.getByTestId('changeLanguageBtn2')); + + await wait(); + + expect(cookies.get('i18next')).toBe('hi'); + }); + + test('The language is switched to sp', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('languageIcon')); + + userEvent.click(screen.getByTestId('changeLanguageBtn3')); + + await wait(); + + expect(cookies.get('i18next')).toBe('sp'); + }); + + test('The language is switched to zh', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('languageIcon')); + + userEvent.click(screen.getByTestId('changeLanguageBtn4')); + + await wait(); + + expect(cookies.get('i18next')).toBe('zh'); + }); +}); diff --git a/src/components/UserPortal/UserNavbar/UserNavbar.tsx b/src/components/UserPortal/UserNavbar/UserNavbar.tsx index 633db383bf..9871f0e265 100644 --- a/src/components/UserPortal/UserNavbar/UserNavbar.tsx +++ b/src/components/UserPortal/UserNavbar/UserNavbar.tsx @@ -15,9 +15,11 @@ function userNavbar(): JSX.Element { }); const [currentLanguageCode, setCurrentLanguageCode] = React.useState( + /* istanbul ignore next */ cookies.get('i18next') || 'en' ); + /* istanbul ignore next */ const handleLogout = (): void => { localStorage.clear(); window.location.replace('/user'); @@ -48,10 +50,13 @@ function userNavbar(): JSX.Element { - + {languages.map((language, index: number) => ( @@ -80,7 +85,10 @@ function userNavbar(): JSX.Element { data-testid="logoutDropdown" className={styles.colorWhite} > - + @@ -88,7 +96,7 @@ function userNavbar(): JSX.Element { {t('settings')} {t('myTasks')} - + {t('logout')} diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.module.css b/src/components/UserPortal/UserSidebar/UserSidebar.module.css index 88692f7564..f635b0b05f 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.module.css +++ b/src/components/UserPortal/UserSidebar/UserSidebar.module.css @@ -8,6 +8,12 @@ flex-grow: 1; } +@media screen and (max-width: 700px) { + .mainContainer { + display: none; + } +} + .userDetails { display: flex; flex-direction: column; diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx new file mode 100644 index 0000000000..8a8278996a --- /dev/null +++ b/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx @@ -0,0 +1,208 @@ +import React from 'react'; +import { act, render } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; + +import { + USER_DETAILS, + USER_JOINED_ORGANIZATIONS, +} from 'GraphQl/Queries/Queries'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import UserSidebar from './UserSidebar'; + +const MOCKS = [ + { + request: { + query: USER_DETAILS, + variables: { + id: localStorage.getItem('userId'), + }, + }, + result: { + data: { + user: { + __typename: 'User', + image: null, + firstName: 'Noble', + lastName: 'Mittal', + email: 'noble@mittal.com', + role: 'SUPERADMIN', + appLanguageCode: 'en', + userType: 'SUPERADMIN', + pluginCreationAllowed: true, + adminApproved: true, + createdAt: '2023-02-18T09:22:27.969Z', + adminFor: [], + createdOrganizations: [], + joinedOrganizations: [], + organizationUserBelongsTo: null, + organizationsBlockedBy: [], + createdEvents: [], + registeredEvents: [], + eventAdmin: [], + membershipRequests: [], + }, + }, + }, + }, + { + request: { + query: USER_DETAILS, + variables: { + id: '2', + }, + }, + result: { + data: { + user: { + __typename: 'User', + image: 'adssda', + firstName: 'Noble', + lastName: 'Mittal', + email: 'noble@mittal.com', + role: 'SUPERADMIN', + appLanguageCode: 'en', + userType: 'SUPERADMIN', + pluginCreationAllowed: true, + adminApproved: true, + createdAt: '2023-02-18T09:22:27.969Z', + adminFor: [], + createdOrganizations: [], + joinedOrganizations: [], + organizationUserBelongsTo: null, + organizationsBlockedBy: [], + createdEvents: [], + registeredEvents: [], + eventAdmin: [], + membershipRequests: [], + }, + }, + }, + }, + { + request: { + query: USER_JOINED_ORGANIZATIONS, + variables: { + id: localStorage.getItem('userId'), + }, + }, + result: { + data: { + users: [ + { + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + name: 'Any Organization', + image: '', + description: 'New Desc', + }, + ], + }, + ], + }, + }, + }, + { + request: { + query: USER_JOINED_ORGANIZATIONS, + variables: { + id: '2', + }, + }, + result: { + data: { + users: [ + { + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + name: 'Any Organization', + image: 'dadsa', + description: 'New Desc', + }, + ], + }, + ], + }, + }, + }, +]; + +const link = new StaticMockLink(MOCKS, true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +describe('Testing UserSidebar Component [User Portal]', () => { + test('Component should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + }); + + test('Component should be rendered properly when userImage is not undefined', async () => { + const beforeUserId = localStorage.getItem('userId'); + + localStorage.setItem('userId', '2'); + + render( + + + + + + + + + + ); + + await wait(); + + localStorage.setItem('userId', beforeUserId!); + }); + + test('Component should be rendered properly when organizationImage is not undefined', async () => { + const beforeUserId = localStorage.getItem('userId'); + + localStorage.setItem('userId', '2'); + + render( + + + + + + + + + + ); + + await wait(); + + localStorage.setItem('userId', beforeUserId!); + }); +}); diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.tsx index c90bb9b7be..69771ce63f 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.tsx +++ b/src/components/UserPortal/UserSidebar/UserSidebar.tsx @@ -29,12 +29,14 @@ function userSidebar(): JSX.Element { variables: { id: userId }, }); + /* istanbul ignore next */ React.useEffect(() => { if (data) { setOrganizations(data.users[0].joinedOrganizations); } }, [data]); + /* istanbul ignore next */ React.useEffect(() => { if (data2) { setDetails(data2.user); diff --git a/src/screens/UserPortal/Organizations/Organizations.test.tsx b/src/screens/UserPortal/Organizations/Organizations.test.tsx new file mode 100644 index 0000000000..f12faf259b --- /dev/null +++ b/src/screens/UserPortal/Organizations/Organizations.test.tsx @@ -0,0 +1,221 @@ +import React from 'react'; +import { act, render, screen } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; + +import { + USER_CREATED_ORGANIZATIONS, + USER_JOINED_ORGANIZATIONS, + USER_ORGANIZATION_CONNECTION, +} from 'GraphQl/Queries/Queries'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import Organizations from './Organizations'; +import userEvent from '@testing-library/user-event'; + +const MOCKS = [ + { + request: { + query: USER_CREATED_ORGANIZATIONS, + variables: { + id: localStorage.getItem('userId'), + }, + }, + result: { + data: { + users: [ + { + createdOrganizations: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + name: 'createdOrganization', + image: '', + description: 'New Desc', + }, + ], + }, + ], + }, + }, + }, + { + request: { + query: USER_ORGANIZATION_CONNECTION, + variables: { + filter: '', + }, + }, + result: { + data: { + organizationsConnection: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + image: '', + name: 'anyOrganization1', + description: 'desc', + isPublic: true, + creator: { __typename: 'User', firstName: 'John', lastName: 'Doe' }, + }, + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af3', + image: '', + name: 'anyOrganization2', + description: 'desc', + isPublic: true, + creator: { __typename: 'User', firstName: 'John', lastName: 'Doe' }, + }, + ], + }, + }, + }, + { + request: { + query: USER_JOINED_ORGANIZATIONS, + variables: { + id: localStorage.getItem('userId'), + }, + }, + result: { + data: { + users: [ + { + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + name: 'joinedOrganization', + image: '', + description: 'New Desc', + }, + ], + }, + ], + }, + }, + }, + { + request: { + query: USER_ORGANIZATION_CONNECTION, + variables: { + filter: '2', + }, + }, + result: { + data: { + organizationsConnection: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af3', + image: '', + name: 'anyOrganization2', + description: 'desc', + isPublic: true, + creator: { __typename: 'User', firstName: 'John', lastName: 'Doe' }, + }, + ], + }, + }, + }, +]; + +const link = new StaticMockLink(MOCKS, true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +describe('Testing Organizations Screen [User Portal]', () => { + test('Screen should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + }); + + test('Search works properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type(screen.queryByTestId('searchInput')!, '2'); + await wait(); + + expect(screen.queryByText('anyOrganization2')).toBeInTheDocument(); + expect(screen.queryByText('anyOrganization1')).not.toBeInTheDocument(); + }); + + test('Mode is changed to joined organizations', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.queryByTestId('modeChangeBtn')!); + await wait(); + userEvent.click(screen.queryByTestId('modeBtn1')!); + await wait(); + + expect(screen.queryAllByText('joinedOrganization')).not.toBe([]); + }); + + test('Mode is changed to created organizations', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.queryByTestId('modeChangeBtn')!); + await wait(); + userEvent.click(screen.queryByTestId('modeBtn2')!); + await wait(); + + expect(screen.queryAllByText('createdOrganization')).not.toBe([]); + }); +}); diff --git a/src/screens/UserPortal/Organizations/Organizations.tsx b/src/screens/UserPortal/Organizations/Organizations.tsx index e176b8e984..53606d10a4 100644 --- a/src/screens/UserPortal/Organizations/Organizations.tsx +++ b/src/screens/UserPortal/Organizations/Organizations.tsx @@ -4,7 +4,6 @@ import OrganizationCard from 'components/UserPortal/OrganizationCard/Organizatio import UserSidebar from 'components/UserPortal/UserSidebar/UserSidebar'; import { Dropdown, Form, InputGroup } from 'react-bootstrap'; import PaginationList from 'components/PaginationList/PaginationList'; -import { useHistory } from 'react-router-dom'; import { USER_CREATED_ORGANIZATIONS, USER_JOINED_ORGANIZATIONS, @@ -41,8 +40,6 @@ export default function organizations(): JSX.Element { const userId: string | null = localStorage.getItem('userId'); - const history = useHistory(); - const { data, refetch } = useQuery(USER_ORGANIZATION_CONNECTION, { variables: { filter: filterName }, }); @@ -86,22 +83,14 @@ export default function organizations(): JSX.Element { refetch(filter); }; - React.useEffect(() => { - const userToken = localStorage.getItem('token'); - - /* istanbul ignore next */ - if (!userId || !userToken) { - navigator.clipboard.writeText(''); - history.replace('/user/'); - } - }, []); - + /* istanbul ignore next */ React.useEffect(() => { if (data) { setOrganizations(data.organizationsConnection); } }, [data]); + /* istanbul ignore next */ React.useEffect(() => { if (mode == 0) { if (data) { @@ -134,13 +123,18 @@ export default function organizations(): JSX.Element { className={styles.borderNone} value={filterName} onChange={handleSearch} + data-testid="searchInput" /> - + {modes[mode]} @@ -148,6 +142,7 @@ export default function organizations(): JSX.Element { return ( setMode(index)} > {value} @@ -163,13 +158,14 @@ export default function organizations(): JSX.Element {
- {organizations.length > 0 ? ( + {organizations && organizations.length > 0 ? ( (rowsPerPage > 0 ? organizations.slice( page * rowsPerPage, page * rowsPerPage + rowsPerPage ) - : organizations + : /* istanbul ignore next */ + organizations ).map((organization: any, index) => { const cardProps: InterfaceOrganizationCardProps = { name: organization.name, @@ -183,13 +179,22 @@ export default function organizations(): JSX.Element { {t('nothingToShow')} )}
- + + + + + + +
From d334920635d4e35c6f088b3f3ac0b39037d3da14 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Tue, 27 Jun 2023 14:40:15 +0530 Subject: [PATCH 09/29] Fix non-null assertions --- .../UserPortal/UserSidebar/UserSidebar.test.tsx | 9 ++++++--- .../UserPortal/Organizations/Organizations.test.tsx | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx index 8a8278996a..1c3e5928c3 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx +++ b/src/components/UserPortal/UserSidebar/UserSidebar.test.tsx @@ -180,8 +180,9 @@ describe('Testing UserSidebar Component [User Portal]', () => { ); await wait(); - - localStorage.setItem('userId', beforeUserId!); + if (beforeUserId) { + localStorage.setItem('userId', beforeUserId); + } }); test('Component should be rendered properly when organizationImage is not undefined', async () => { @@ -203,6 +204,8 @@ describe('Testing UserSidebar Component [User Portal]', () => { await wait(); - localStorage.setItem('userId', beforeUserId!); + if (beforeUserId) { + localStorage.setItem('userId', beforeUserId); + } }); }); diff --git a/src/screens/UserPortal/Organizations/Organizations.test.tsx b/src/screens/UserPortal/Organizations/Organizations.test.tsx index f12faf259b..7c9cede831 100644 --- a/src/screens/UserPortal/Organizations/Organizations.test.tsx +++ b/src/screens/UserPortal/Organizations/Organizations.test.tsx @@ -188,9 +188,9 @@ describe('Testing Organizations Screen [User Portal]', () => { await wait(); - userEvent.click(screen.queryByTestId('modeChangeBtn')!); + userEvent.click(screen.getByTestId('modeChangeBtn')); await wait(); - userEvent.click(screen.queryByTestId('modeBtn1')!); + userEvent.click(screen.getByTestId('modeBtn1')); await wait(); expect(screen.queryAllByText('joinedOrganization')).not.toBe([]); @@ -211,9 +211,9 @@ describe('Testing Organizations Screen [User Portal]', () => { await wait(); - userEvent.click(screen.queryByTestId('modeChangeBtn')!); + userEvent.click(screen.getByTestId('modeChangeBtn')); await wait(); - userEvent.click(screen.queryByTestId('modeBtn2')!); + userEvent.click(screen.getByTestId('modeBtn2')); await wait(); expect(screen.queryAllByText('createdOrganization')).not.toBe([]); From 08f6de7fb1811d49f36348f5946647a43e9910be Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Tue, 27 Jun 2023 14:42:16 +0530 Subject: [PATCH 10/29] Fix non-null assertions in organizations test --- src/screens/UserPortal/Organizations/Organizations.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/UserPortal/Organizations/Organizations.test.tsx b/src/screens/UserPortal/Organizations/Organizations.test.tsx index 7c9cede831..6d92831940 100644 --- a/src/screens/UserPortal/Organizations/Organizations.test.tsx +++ b/src/screens/UserPortal/Organizations/Organizations.test.tsx @@ -166,7 +166,7 @@ describe('Testing Organizations Screen [User Portal]', () => { await wait(); - userEvent.type(screen.queryByTestId('searchInput')!, '2'); + userEvent.type(screen.getByTestId('searchInput'), '2'); await wait(); expect(screen.queryByText('anyOrganization2')).toBeInTheDocument(); From ce5cbda58042b6a0a5e19c613c96e3b5aca673b3 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Thu, 29 Jun 2023 12:42:56 +0530 Subject: [PATCH 11/29] Fix bootstrap migration changes --- package-lock.json | 2 +- .../UserPortal/Login/Login.module.css | 9 ++++++++ src/components/UserPortal/Login/Login.tsx | 16 +++++++++++--- .../UserPortal/Register/Register.module.css | 4 ++++ .../UserPortal/Register/Register.tsx | 21 ++++++++++++++----- .../UserNavbar/UserNavbar.module.css | 4 ++++ .../UserPortal/UserNavbar/UserNavbar.tsx | 10 ++++----- .../Organizations/Organizations.module.css | 4 ++++ .../Organizations/Organizations.tsx | 7 +++++-- .../UserLoginPage/UserLoginPage.tsx | 2 +- 10 files changed, 61 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index e1d9b051a6..16cba5099f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39682,4 +39682,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/components/UserPortal/Login/Login.module.css b/src/components/UserPortal/Login/Login.module.css index 468498db09..98be9db02a 100644 --- a/src/components/UserPortal/Login/Login.module.css +++ b/src/components/UserPortal/Login/Login.module.css @@ -18,3 +18,12 @@ .colorWhite { color: white; } + +.colorPrimary { + background: #31bb6b; +} + +.colorPrimaryHover:hover { + background: #31bb6b; + border: none; +} diff --git a/src/components/UserPortal/Login/Login.tsx b/src/components/UserPortal/Login/Login.tsx index c9d5f9610a..d434434916 100644 --- a/src/components/UserPortal/Login/Login.tsx +++ b/src/components/UserPortal/Login/Login.tsx @@ -96,7 +96,9 @@ export default function login(props: InterfaceLoginProps): JSX.Element { value={loginVariables.email} onChange={handleEmailChange} /> - + @@ -109,7 +111,9 @@ export default function login(props: InterfaceLoginProps): JSX.Element { value={loginVariables.password} onChange={handlePasswordChange} /> - + @@ -121,11 +125,17 @@ export default function login(props: InterfaceLoginProps): JSX.Element { -
+ {likes} + {` ${t('likes')}`} + + {props.commentCount} + {` ${t('comments')}`} + + + + ); +} diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.module.css b/src/components/UserPortal/UserSidebar/UserSidebar.module.css index f635b0b05f..ef306708ed 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.module.css +++ b/src/components/UserPortal/UserSidebar/UserSidebar.module.css @@ -6,6 +6,7 @@ padding: 0px 10px; padding-top: 50px; flex-grow: 1; + width: 250px; } @media screen and (max-width: 700px) { diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.tsx index 69771ce63f..9b3ad4a07e 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.tsx +++ b/src/components/UserPortal/UserSidebar/UserSidebar.tsx @@ -10,6 +10,7 @@ import { USER_JOINED_ORGANIZATIONS, } from 'GraphQl/Queries/Queries'; import { useTranslation } from 'react-i18next'; +import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; function userSidebar(): JSX.Element { const { t } = useTranslation('translation', { @@ -21,11 +22,14 @@ function userSidebar(): JSX.Element { const userId: string | null = localStorage.getItem('userId'); - const { data } = useQuery(USER_JOINED_ORGANIZATIONS, { - variables: { id: userId }, - }); + const { data, loading: loadingJoinedOrganizations } = useQuery( + USER_JOINED_ORGANIZATIONS, + { + variables: { id: userId }, + } + ); - const { data: data2 } = useQuery(USER_DETAILS, { + const { data: data2, loading: loadingUserDetails } = useQuery(USER_DETAILS, { variables: { id: userId }, }); @@ -45,52 +49,67 @@ function userSidebar(): JSX.Element { return (
- -
-
- {`${details.firstName} ${details.lastName}`} -
-
{details.email}
-
-
-
- {t('yourOrganizations')} -
- - {organizations.length ? ( - organizations.map((organization: any, index) => { - return ( - -
- -
{organization.name}
-
-
- ); - }) - ) : ( -
{t('noOrganizations')}
- )} -
-
- - {t('viewAll')} - - -
-
+ {loadingJoinedOrganizations || loadingUserDetails ? ( + <> + Loading... + + ) : ( + <> + +
+
+ {`${details.firstName} ${details.lastName}`} +
+
{details.email}
+
+
+
+ {t('yourOrganizations')} +
+ + {organizations.length ? ( + organizations.map((organization: any, index) => { + return ( + +
+ +
+ {organization.name} +
+
+
+ ); + }) + ) : ( +
{t('noOrganizations')}
+ )} +
+
+ + {t('viewAll')} + + +
+
+ + )}
); } diff --git a/src/screens/UserPortal/Home/Home.module.css b/src/screens/UserPortal/Home/Home.module.css new file mode 100644 index 0000000000..8c11480f5c --- /dev/null +++ b/src/screens/UserPortal/Home/Home.module.css @@ -0,0 +1,65 @@ +.borderNone { + border: none; +} + +.colorWhite { + color: white; +} + +.maxWidth { + max-width: 300px; +} + +.colorLight { + background-color: #f5f5f5; +} + +.mainContainer { + width: 50%; + flex-grow: 3; + padding: 30px; + max-height: 100%; + overflow: auto; +} + +.containerHeight { + height: calc(100vh - 61px); +} + +.link { + text-decoration: none !important; + color: black; +} + +.postInputContainer { + background-color: white; + padding: 10px; + border-radius: 10px; + margin-bottom: 20px; +} + +.postActionContainer { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 10px; +} + +.postActionBtn { + background-color: white; + border: none; + color: black; +} + +.postActionBtn:hover { + background-color: ghostwhite; + border: none; + color: black; +} + +.postInput { + height: 200px !important; + resize: none; + border: none; + box-shadow: none; +} diff --git a/src/screens/UserPortal/Home/Home.tsx b/src/screens/UserPortal/Home/Home.tsx new file mode 100644 index 0000000000..17ac869594 --- /dev/null +++ b/src/screens/UserPortal/Home/Home.tsx @@ -0,0 +1,211 @@ +import React from 'react'; +import type { ChangeEvent } from 'react'; +import OrganizationNavbar from 'components/UserPortal/OrganizationNavbar/OrganizationNavbar'; +import styles from './Home.module.css'; +import UserSidebar from 'components/UserPortal/UserSidebar/UserSidebar'; +import OrganizationSidebar from 'components/UserPortal/OrganizationSidebar/OrganizationSidebar'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import { Button, FloatingLabel, Form } from 'react-bootstrap'; +import { Link } from 'react-router-dom'; +import getOrganizationId from 'utils/getOrganizationId'; +import SendIcon from '@mui/icons-material/Send'; +import PostCard from 'components/UserPortal/PostCard/PostCard'; +import { useMutation, useQuery } from '@apollo/client'; +import { ORGANIZATION_POST_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; +import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; +import { errorHandler } from 'utils/errorHandler'; +import { useTranslation } from 'react-i18next'; +import convertToBase64 from 'utils/convertToBase64'; +import { toast } from 'react-toastify'; +import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; + +interface InterfacePostCardProps { + id: string; + creator: { + firstName: string; + lastName: string; + email: string; + id: string; + }; + image: string; + video: string; + text: string; + title: string; + likeCount: number; + commentCount: number; + likedBy: { + firstName: string; + lastName: string; + id: string; + }[]; +} + +export default function home(): JSX.Element { + const { t } = useTranslation('translation', { keyPrefix: 'home' }); + + const organizationId = getOrganizationId(window.location.href); + const [posts, setPosts] = React.useState([]); + const [postContent, setPostContent] = React.useState(''); + const [postImage, setPostImage] = React.useState(''); + + const navbarProps = { + currentPage: 'home', + }; + + const { + data, + refetch, + loading: loadingPosts, + } = useQuery(ORGANIZATION_POST_CONNECTION_LIST, { + variables: { id: organizationId }, + }); + + const [create] = useMutation(CREATE_POST_MUTATION); + + const handlePost = async (): Promise => { + try { + if (!postContent) { + throw new Error("Can't create a post with an empty body."); + } + toast.info('Processing your post. Please wait.'); + + const { data } = await create({ + variables: { + title: '', + text: postContent, + organizationId: organizationId, + file: postImage, + }, + }); + /* istanbul ignore next */ + if (data) { + toast.dismiss(); + toast.success('Your post is now visible in the feed.'); + refetch(); + setPostContent(''); + setPostImage(''); + } + } catch (error: any) { + /* istanbul ignore next */ + errorHandler(t, error); + } + }; + + const handlePostInput = (e: ChangeEvent): void => { + const content = e.target.value; + + setPostContent(content); + }; + + React.useEffect(() => { + if (data) { + setPosts(data.postsByOrganizationConnection.edges); + } + }, [data]); + + return ( + <> + +
+ +
+
+ + + +
+ => { + const target = e.target as HTMLInputElement; + const file = target.files && target.files[0]; + if (file) { + const image = await convertToBase64(file); + setPostImage(image); + } + }} + /> + +
+
+
+

{t('feed')}

+
+ + {t('pinnedPosts')} + + +
+
+ {loadingPosts ? ( +
+ Loading... +
+ ) : ( + <> + {posts.map((post: any) => { + const allLikes: any = []; + post.likedBy.forEach((value: any) => { + const singleLike = { + firstName: value.firstName, + lastName: value.lastName, + id: value._id, + }; + allLikes.push(singleLike); + }); + + const cardProps: InterfacePostCardProps = { + id: post._id, + creator: { + id: post.creator._id, + firstName: post.creator.firstName, + lastName: post.creator.lastName, + email: post.creator.email, + }, + image: post.imageUrl, + video: post.videoUrl, + title: post.title, + text: post.text, + likeCount: post.likeCount, + commentCount: post.commentCount, + likedBy: allLikes, + }; + + return ; + })} + + )} +
+ +
+ + ); +} diff --git a/src/screens/UserPortal/Organizations/Organizations.tsx b/src/screens/UserPortal/Organizations/Organizations.tsx index 5d1402a94d..11497ae5c0 100644 --- a/src/screens/UserPortal/Organizations/Organizations.tsx +++ b/src/screens/UserPortal/Organizations/Organizations.tsx @@ -13,6 +13,7 @@ import { useQuery } from '@apollo/client'; import { SearchOutlined } from '@mui/icons-material'; import styles from './Organizations.module.css'; import { useTranslation } from 'react-i18next'; +import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; interface InterfaceOrganizationCardProps { id: string; @@ -40,7 +41,11 @@ export default function organizations(): JSX.Element { const userId: string | null = localStorage.getItem('userId'); - const { data, refetch } = useQuery(USER_ORGANIZATION_CONNECTION, { + const { + data, + refetch, + loading: loadingOrganizations, + } = useQuery(USER_ORGANIZATION_CONNECTION, { variables: { filter: filterName }, }); @@ -161,25 +166,34 @@ export default function organizations(): JSX.Element {
- {organizations && organizations.length > 0 ? ( - (rowsPerPage > 0 - ? organizations.slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ) - : /* istanbul ignore next */ - organizations - ).map((organization: any, index) => { - const cardProps: InterfaceOrganizationCardProps = { - name: organization.name, - image: organization.image, - id: organization._id, - description: organization.description, - }; - return ; - }) + {loadingOrganizations ? ( +
+ Loading... +
) : ( - {t('nothingToShow')} + <> + {' '} + {organizations && organizations.length > 0 ? ( + (rowsPerPage > 0 + ? organizations.slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ) + : /* istanbul ignore next */ + organizations + ).map((organization: any, index) => { + const cardProps: InterfaceOrganizationCardProps = { + name: organization.name, + image: organization.image, + id: organization._id, + description: organization.description, + }; + return ; + }) + ) : ( + {t('nothingToShow')} + )} + )}
diff --git a/src/utils/getOrganizationId.ts b/src/utils/getOrganizationId.ts new file mode 100644 index 0000000000..9cb90dd730 --- /dev/null +++ b/src/utils/getOrganizationId.ts @@ -0,0 +1,7 @@ +const getOrganizationId = (url: string): string => { + const id = url.split('=')[1]; + + return id.split('#')[0]; +}; + +export default getOrganizationId; From c4250c376f7ffe691ef0023736dfd316ea00d502 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Wed, 5 Jul 2023 14:12:33 +0530 Subject: [PATCH 13/29] Fix failing tests --- src/screens/OrgPost/OrgPost.test.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/screens/OrgPost/OrgPost.test.tsx b/src/screens/OrgPost/OrgPost.test.tsx index f263a80bd7..478095c323 100644 --- a/src/screens/OrgPost/OrgPost.test.tsx +++ b/src/screens/OrgPost/OrgPost.test.tsx @@ -41,6 +41,9 @@ const MOCKS = [ lastName: 'Shelke', email: 'adidacreator1@gmail.com', }, + likeCount: 0, + commentCount: 0, + likedBy: [], }, { _id: '6411e54835d7ba2344a78e29', @@ -54,6 +57,9 @@ const MOCKS = [ lastName: 'Shelke', email: 'adidacreator1@gmail.com', }, + likeCount: 0, + commentCount: 0, + likedBy: [], }, ], }, @@ -135,6 +141,9 @@ describe('Organisation Post Page', () => { lastName: 'Shelke', email: 'adidacreator1@gmail.com', }, + likeCount: 0, + commentCount: 0, + likedBy: [], }); }); From 1cb3ee40cae213336e4ba00421f8a6e7c0d53638 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Fri, 7 Jul 2023 13:54:35 +0530 Subject: [PATCH 14/29] Add required tests and Offcanvas navbar --- .../OrganizationNavbar.module.css | 5 + .../OrganizationNavbar.test.tsx | 224 ++++++++++++++ .../OrganizationNavbar/OrganizationNavbar.tsx | 166 ++++++----- .../OrganizationSidebar.test.tsx | 174 +++++++++++ .../OrganizationSidebar.tsx | 3 +- .../UserPortal/PostCard/PostCard.test.tsx | 280 ++++++++++++++++++ .../UserPortal/PostCard/PostCard.tsx | 1 + .../UserSidebar/UserSidebar.test.tsx | 41 +++ src/screens/UserPortal/Home/Home.test.tsx | 243 +++++++++++++++ src/screens/UserPortal/Home/Home.tsx | 19 +- src/utils/getOrganizationId.ts | 1 + 11 files changed, 1076 insertions(+), 81 deletions(-) create mode 100644 src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx create mode 100644 src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.test.tsx create mode 100644 src/components/UserPortal/PostCard/PostCard.test.tsx create mode 100644 src/screens/UserPortal/Home/Home.test.tsx diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.module.css b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.module.css index 581f745a68..e4a7898707 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.module.css +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.module.css @@ -15,3 +15,8 @@ .colorPrimary { background: #31bb6b; } + +.offcanvasContainer { + background-color: #31bb6b; + color: white; +} diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx new file mode 100644 index 0000000000..b0080147ee --- /dev/null +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.test.tsx @@ -0,0 +1,224 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import { act, render, screen } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import cookies from 'js-cookie'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import * as getOrganizationId from 'utils/getOrganizationId'; + +import OrganizationNavbar from './OrganizationNavbar'; +import userEvent from '@testing-library/user-event'; +import { USER_ORGANIZATION_CONNECTION } from 'GraphQl/Queries/Queries'; + +const MOCKS = [ + { + request: { + query: USER_ORGANIZATION_CONNECTION, + variables: { + id: '', + }, + }, + result: { + data: { + organizationsConnection: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + image: '', + name: 'anyOrganization1', + description: 'desc', + isPublic: true, + creator: { __typename: 'User', firstName: 'John', lastName: 'Doe' }, + }, + ], + }, + }, + }, +]; + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +const link = new StaticMockLink(MOCKS, true); + +const navbarProps = { + currentPage: 'home', +}; + +describe('Testing OrganizationNavbar Component [User Portal]', () => { + jest.mock('utils/getOrganizationId'); + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); + + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + afterEach(async () => { + await act(async () => { + await i18nForTest.changeLanguage('en'); + }); + }); + + test('Component should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + expect(screen.queryByText('anyOrganization1')).toBeInTheDocument(); + }); + + test('The language is switched to English', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('languageIcon')); + + userEvent.click(screen.getByTestId('changeLanguageBtn0')); + + await wait(); + + expect(cookies.get('i18next')).toBe('en'); + }); + + test('The language is switched to fr', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('languageIcon')); + + userEvent.click(screen.getByTestId('changeLanguageBtn1')); + + await wait(); + + expect(cookies.get('i18next')).toBe('fr'); + }); + + test('The language is switched to hi', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('languageIcon')); + + userEvent.click(screen.getByTestId('changeLanguageBtn2')); + + await wait(); + + expect(cookies.get('i18next')).toBe('hi'); + }); + + test('The language is switched to sp', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('languageIcon')); + + userEvent.click(screen.getByTestId('changeLanguageBtn3')); + + await wait(); + + expect(cookies.get('i18next')).toBe('sp'); + }); + + test('The language is switched to zh', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('languageIcon')); + + userEvent.click(screen.getByTestId('changeLanguageBtn4')); + + await wait(); + + expect(cookies.get('i18next')).toBe('zh'); + }); +}); diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx index 3574803835..8f704c912e 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx @@ -1,7 +1,7 @@ import React from 'react'; import styles from './OrganizationNavbar.module.css'; import TalawaImage from 'assets/images/talawa-logo-200x200.png'; -import { Container, Dropdown, Nav, Navbar } from 'react-bootstrap'; +import { Container, Dropdown, Nav, Navbar, Offcanvas } from 'react-bootstrap'; import { languages } from 'utils/languages'; import i18next from 'i18next'; import cookies from 'js-cookie'; @@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'; import { useQuery } from '@apollo/client'; import { USER_ORGANIZATION_CONNECTION } from 'GraphQl/Queries/Queries'; import getOrganizationId from 'utils/getOrganizationId'; +import type { DropDirection } from 'react-bootstrap/esm/DropdownContext'; interface InterfaceNavbarProps { currentPage: string | null; @@ -21,6 +22,8 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { keyPrefix: 'userNavbar', }); const [organizationDetails, setOrganizationDetails]: any = React.useState({}); + // const dropDirection: DropDirection = screen.width > 767 ? 'start' : 'down'; + const dropDirection: DropDirection = 'start'; const organizationId = getOrganizationId(window.location.href); @@ -47,7 +50,7 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { }, [data]); return ( - + {organizationDetails.name} - - - - - - - - - - - {languages.map((language, index: number) => ( - => { - setCurrentLanguageCode(language.code); - await i18next.changeLanguage(language.code); - }} - disabled={currentLanguageCode === language.code} - data-testid={`changeLanguageBtn${index}`} + + + + Talawa + + + + + + - {' '} - {language.name} - - ))} - - + + + + {languages.map((language, index: number) => ( + => { + setCurrentLanguageCode(language.code); + await i18next.changeLanguage(language.code); + }} + disabled={currentLanguageCode === language.code} + data-testid={`changeLanguageBtn${index}`} + > + {' '} + {language.name} + + ))} + + - - - - - - - {userName} - - {t('settings')} - {t('myTasks')} - - {t('logout')} - - - - + + + + + + + {userName} + + {t('settings')} + {t('myTasks')} + + {t('logout')} + + + + + + ); diff --git a/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.test.tsx b/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.test.tsx new file mode 100644 index 0000000000..ccfe7e4d31 --- /dev/null +++ b/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.test.tsx @@ -0,0 +1,174 @@ +import React from 'react'; +import { act, render, screen } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; + +import { + ORGANIZATIONS_MEMBER_CONNECTION_LIST, + ORGANIZATION_EVENT_CONNECTION_LIST, +} from 'GraphQl/Queries/Queries'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import OrganizationSidebar from './OrganizationSidebar'; +import * as getOrganizationId from 'utils/getOrganizationId'; + +const MOCKS = [ + { + request: { + query: ORGANIZATION_EVENT_CONNECTION_LIST, + variables: { + organization_id: 'events', + first: 3, + skip: 0, + }, + }, + result: { + data: { + eventsByOrganizationConnection: [ + { + _id: 1, + title: 'Event', + description: 'Event Test', + startDate: '', + endDate: '', + location: 'New Delhi', + startTime: '02:00', + endTime: '06:00', + allDay: false, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + ], + }, + }, + }, + { + request: { + query: ORGANIZATIONS_MEMBER_CONNECTION_LIST, + variables: { + orgId: 'members', + first: 3, + skip: 0, + }, + }, + result: { + data: { + organizationsMemberConnection: { + edges: [ + { + _id: '64001660a711c62d5b4076a2', + firstName: 'Noble', + lastName: 'Mittal', + image: null, + email: 'noble@gmail.com', + createdAt: '2023-03-02T03:22:08.101Z', + }, + { + _id: '64001660a711c62d5b4076a3', + firstName: 'Noble', + lastName: 'Mittal', + image: 'mockImage', + email: 'noble@gmail.com', + createdAt: '2023-03-02T03:22:08.101Z', + }, + ], + }, + }, + }, + }, +]; + +const link = new StaticMockLink(MOCKS, true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +describe('Testing OrganizationSidebar Component [User Portal]', () => { + jest.mock('utils/getOrganizationId'); + + test('Component should be rendered properly when members and events list is empty', async () => { + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + render( + + + + + + + + + + ); + + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + expect(screen.queryByText('No Members to show')).toBeInTheDocument(); + expect(screen.queryByText('No Events to show')).toBeInTheDocument(); + }); + + test('Component should be rendered properly when events list is not empty', async () => { + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return 'events'; + }); + + render( + + + + + + + + + + ); + + await wait(); + expect(getOrganizationIdSpy).toHaveBeenCalled(); + expect(screen.queryByText('No Members to show')).toBeInTheDocument(); + expect(screen.queryByText('No Events to show')).not.toBeInTheDocument(); + expect(screen.queryByText('Event')).toBeInTheDocument(); + }); + + test('Component should be rendered properly when members list is not empty', async () => { + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return 'members'; + }); + + render( + + + + + + + + + + ); + + await wait(); + expect(getOrganizationIdSpy).toHaveBeenCalled(); + expect(screen.queryByText('No Members to show')).not.toBeInTheDocument(); + expect(screen.queryByText('No Events to show')).toBeInTheDocument(); + }); +}); diff --git a/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.tsx b/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.tsx index b5ebc14c47..af7237189a 100644 --- a/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.tsx +++ b/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.tsx @@ -46,13 +46,14 @@ export default function organizationSidebar(): JSX.Element { } ); + /* istanbul ignore next */ useEffect(() => { if (memberData) { setMembers(memberData.organizationsMemberConnection.edges); - console.log(memberData.organizationsMemberConnection.edges); } }, [memberData]); + /* istanbul ignore next */ useEffect(() => { if (eventsData) { setEvents(eventsData.eventsByOrganizationConnection); diff --git a/src/components/UserPortal/PostCard/PostCard.test.tsx b/src/components/UserPortal/PostCard/PostCard.test.tsx new file mode 100644 index 0000000000..bb44bd3e48 --- /dev/null +++ b/src/components/UserPortal/PostCard/PostCard.test.tsx @@ -0,0 +1,280 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import { act, render, screen } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import cookies from 'js-cookie'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import * as getOrganizationId from 'utils/getOrganizationId'; + +import PostCard from './PostCard'; +import userEvent from '@testing-library/user-event'; +import { LIKE_POST, UNLIKE_POST } from 'GraphQl/Mutations/mutations'; + +const MOCKS = [ + { + request: { + query: LIKE_POST, + variables: { + postId: '', + }, + result: { + data: { + likePost: { + _id: '', + }, + }, + }, + }, + }, + { + request: { + query: UNLIKE_POST, + variables: { + post: '', + }, + result: { + data: { + unlikePost: { + _id: '', + }, + }, + }, + }, + }, +]; + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +const link = new StaticMockLink(MOCKS, true); + +describe('Testing PostCard Component [User Portal]', () => { + test('Component should be rendered properly', async () => { + const cardProps = { + id: '', + creator: { + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + id: '1', + }, + image: '', + video: '', + text: 'This is post test text', + title: 'This is post test title', + likeCount: 1, + commentCount: 0, + likedBy: [ + { + firstName: '', + lastName: '', + id: '2', + }, + ], + }; + + render( + + + + + + + + + + ); + + await wait(); + }); + + test('Component should be rendered properly if user has liked the post', async () => { + const beforeUserId = localStorage.getItem('userId'); + localStorage.setItem('userId', '2'); + + const cardProps = { + id: '', + creator: { + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + id: '1', + }, + image: '', + video: '', + text: 'This is post test text', + title: 'This is post test title', + likeCount: 1, + commentCount: 0, + likedBy: [ + { + firstName: 'test', + lastName: 'user', + id: '2', + }, + ], + }; + + render( + + + + + + + + + + ); + + await wait(); + + if (beforeUserId) { + localStorage.setItem('userId', beforeUserId); + } + }); + + test('Component should be rendered properly if user unlikes a post', async () => { + const beforeUserId = localStorage.getItem('userId'); + localStorage.setItem('userId', '2'); + + const cardProps = { + id: '', + creator: { + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + id: '1', + }, + image: '', + video: '', + text: 'This is post test text', + title: 'This is post test title', + likeCount: 1, + commentCount: 0, + likedBy: [ + { + firstName: 'test', + lastName: 'user', + id: '2', + }, + ], + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('likePostBtn')); + + if (beforeUserId) { + localStorage.setItem('userId', beforeUserId); + } + }); + + test('Component should be rendered properly if user likes a post', async () => { + const beforeUserId = localStorage.getItem('userId'); + localStorage.setItem('userId', '2'); + + const cardProps = { + id: '', + creator: { + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + id: '1', + }, + image: '', + video: '', + text: 'This is post test text', + title: 'This is post test title', + likeCount: 1, + commentCount: 0, + likedBy: [ + { + firstName: 'test', + lastName: 'user', + id: '1', + }, + ], + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('likePostBtn')); + + if (beforeUserId) { + localStorage.setItem('userId', beforeUserId); + } + }); + + test('Component should be rendered properly if post image is defined', async () => { + const cardProps = { + id: '', + creator: { + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + id: '1', + }, + image: 'testImage', + video: '', + text: 'This is post test text', + title: 'This is post test title', + likeCount: 1, + commentCount: 0, + likedBy: [ + { + firstName: 'test', + lastName: 'user', + id: '1', + }, + ], + }; + + render( + + + + + + + + + + ); + + await wait(); + }); +}); diff --git a/src/components/UserPortal/PostCard/PostCard.tsx b/src/components/UserPortal/PostCard/PostCard.tsx index 3c2b8d072f..dacc30a65b 100644 --- a/src/components/UserPortal/PostCard/PostCard.tsx +++ b/src/components/UserPortal/PostCard/PostCard.tsx @@ -104,6 +104,7 @@ export default function postCard(props: InterfacePostCardProps): JSX.Element { diff --git a/src/utils/getOrganizationId.ts b/src/utils/getOrganizationId.ts index 9cb90dd730..cee92613ab 100644 --- a/src/utils/getOrganizationId.ts +++ b/src/utils/getOrganizationId.ts @@ -1,3 +1,4 @@ +/* istanbul ignore next */ const getOrganizationId = (url: string): string => { const id = url.split('=')[1]; From 38ebe74d1e84d6b0b3215729924b6786ad510cd6 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Fri, 7 Jul 2023 13:55:32 +0530 Subject: [PATCH 15/29] Remove unused variables from tests --- src/components/UserPortal/PostCard/PostCard.test.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/UserPortal/PostCard/PostCard.test.tsx b/src/components/UserPortal/PostCard/PostCard.test.tsx index bb44bd3e48..3bdf488fb6 100644 --- a/src/components/UserPortal/PostCard/PostCard.test.tsx +++ b/src/components/UserPortal/PostCard/PostCard.test.tsx @@ -6,9 +6,7 @@ import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; -import cookies from 'js-cookie'; import { StaticMockLink } from 'utils/StaticMockLink'; -import * as getOrganizationId from 'utils/getOrganizationId'; import PostCard from './PostCard'; import userEvent from '@testing-library/user-event'; From 49b05dc94b629273c8d00ae19d93c3012baf1953 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 16 Jul 2023 17:05:30 +0530 Subject: [PATCH 16/29] Sync the Mutations with talawa-api --- src/App.tsx | 2 + src/GraphQl/Mutations/mutations.ts | 20 +- src/GraphQl/Queries/Queries.ts | 15 ++ .../AdminNavbar/AdminNavbar.test.tsx | 17 +- src/components/AdminNavbar/AdminNavbar.tsx | 117 +--------- .../AdminNavbar/AdminNavbarMocks.ts | 27 --- .../OrganizationNavbar.module.css | 5 + .../OrganizationNavbar/OrganizationNavbar.tsx | 35 ++- .../PeopleCard/PeopleCard.module.css | 17 ++ .../UserPortal/PeopleCard/PeopleCard.test.tsx | 67 ++++++ .../UserPortal/PeopleCard/PeopleCard.tsx | 26 +++ .../UserPortal/People/People.module.css | 44 ++++ src/screens/UserPortal/People/People.test.tsx | 206 +++++++++++++++++ src/screens/UserPortal/People/People.tsx | 217 ++++++++++++++++++ 14 files changed, 646 insertions(+), 169 deletions(-) create mode 100644 src/components/UserPortal/PeopleCard/PeopleCard.module.css create mode 100644 src/components/UserPortal/PeopleCard/PeopleCard.test.tsx create mode 100644 src/components/UserPortal/PeopleCard/PeopleCard.tsx create mode 100644 src/screens/UserPortal/People/People.module.css create mode 100644 src/screens/UserPortal/People/People.test.tsx create mode 100644 src/screens/UserPortal/People/People.tsx diff --git a/src/App.tsx b/src/App.tsx index e70a80d897..d3eed64de7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,6 +27,7 @@ import MemberDetail from 'screens/MemberDetail/MemberDetail'; import UserLoginPage from 'screens/UserPortal/UserLoginPage/UserLoginPage'; import Organizations from 'screens/UserPortal/Organizations/Organizations'; import Home from 'screens/UserPortal/Home/Home'; +import People from 'screens/UserPortal/People/People'; function app(): JSX.Element { /*const { updatePluginLinks, updateInstalled } = bindActionCreators( @@ -112,6 +113,7 @@ function app(): JSX.Element { component={Organizations} /> + diff --git a/src/GraphQl/Mutations/mutations.ts b/src/GraphQl/Mutations/mutations.ts index 40ec25126a..fbe9c945ca 100644 --- a/src/GraphQl/Mutations/mutations.ts +++ b/src/GraphQl/Mutations/mutations.ts @@ -352,7 +352,7 @@ export const REJECT_ADMIN_MUTATION = gql` */ export const UPDATE_INSTALL_STATUS_PLUGIN_MUTATION = gql` mutation update_install_status_plugin_mutation($id: ID!, $status: Boolean!) { - updateTempPluginStatus(id: $id, status: $status) { + updatePluginStatus(id: $id, status: $status) { _id pluginName pluginCreatedBy @@ -364,11 +364,11 @@ export const UPDATE_INSTALL_STATUS_PLUGIN_MUTATION = gql` /** * @name UPDATE_ORG_STATUS_PLUGIN_MUTATION - * @description used `updateTempPluginInstalledOrgs`to add or remove the current Organization the in the plugin list `installedOrgs` + * @description used `updatePluginInstalledOrgs`to add or remove the current Organization the in the plugin list `installedOrgs` */ export const UPDATE_ORG_STATUS_PLUGIN_MUTATION = gql` mutation update_install_status_plugin_mutation($id: ID!, $orgId: ID!) { - updateTempPluginInstalledOrgs(id: $id, orgId: $orgId) { + updatePluginInstalledOrgs(id: $id, orgId: $orgId) { _id pluginName pluginCreatedBy @@ -448,20 +448,6 @@ export const UPDATE_EVENT_MUTATION = gql` } `; -export const UPDATE_SPAM_NOTIFICATION_MUTATION = gql` - mutation UpdateSpamNotification( - $orgId: ID! - $spamId: ID! - $isReaded: Boolean - ) { - updateSpamNotification( - data: { orgId: $orgId, spamId: $spamId, isReaded: $isReaded } - ) { - _id - } - } -`; - export const LIKE_POST = gql` mutation likePost($postId: ID!) { likePost(id: $postId) { diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index d392cf154d..28d4e57d6a 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -498,6 +498,21 @@ export const USER_CREATED_ORGANIZATIONS = gql` } `; +export const ORGANIZATION_ADMINS_LIST = gql` + query Organizations($id: ID!) { + organizations(id: $id) { + _id + admins { + _id + image + firstName + lastName + email + } + } + } +`; + /** * @name PLUGIN_GET * @description used to fetch list of plugins diff --git a/src/components/AdminNavbar/AdminNavbar.test.tsx b/src/components/AdminNavbar/AdminNavbar.test.tsx index ef8028b9f8..4a034d92f0 100644 --- a/src/components/AdminNavbar/AdminNavbar.test.tsx +++ b/src/components/AdminNavbar/AdminNavbar.test.tsx @@ -1,19 +1,19 @@ -import React from 'react'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { render, screen, fireEvent, act } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { BrowserRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import 'jest-localstorage-mock'; import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; -import AdminNavbar from './AdminNavbar'; import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; import i18nForTest from 'utils/i18nForTest'; +import AdminNavbar from './AdminNavbar'; import { MOCKS } from './AdminNavbarMocks'; -import { StaticMockLink } from 'utils/StaticMockLink'; const link1 = new StaticMockLink(MOCKS, true); async function wait(ms = 100): Promise { await act(() => { @@ -89,7 +89,6 @@ describe('Testing Admin Navbar', () => { expect(screen.getByTestId('dropdownIcon')).toBeTruthy(); expect(screen.getByText('Plugin Store')).toBeInTheDocument(); expect(screen.getByTestId('logoutDropdown')).toBeTruthy(); - expect(screen.getByText('Notification')).toBeInTheDocument(); expect(screen.getByText('Settings')).toBeInTheDocument(); expect(screen.getByText('Logout')).toBeInTheDocument(); @@ -207,9 +206,7 @@ describe('Testing Admin Navbar', () => { expect(container.textContent).not.toBe('Loading data...'); await wait(); - const imageOptions = screen.getByTestId(/navbarOrgImageAbsent/i); const imageLogo = screen.getByTestId(/orgLogoAbsent/i); expect(imageLogo).toBeInTheDocument(); - expect(imageOptions).toBeInTheDocument(); }); }); diff --git a/src/components/AdminNavbar/AdminNavbar.tsx b/src/components/AdminNavbar/AdminNavbar.tsx index 839b25bf80..2a6ac7c78b 100644 --- a/src/components/AdminNavbar/AdminNavbar.tsx +++ b/src/components/AdminNavbar/AdminNavbar.tsx @@ -1,10 +1,10 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import Navbar from 'react-bootstrap/Navbar'; import Dropdown from 'react-bootstrap/Dropdown'; import Button from 'react-bootstrap/Button'; -import { Nav, Modal } from 'react-bootstrap'; +import { Nav } from 'react-bootstrap'; import { Link, NavLink } from 'react-router-dom'; -import { useMutation, useQuery } from '@apollo/client'; +import { useQuery } from '@apollo/client'; import { useTranslation } from 'react-i18next'; import Cookies from 'js-cookie'; import i18next from 'i18next'; @@ -15,9 +15,7 @@ import { ORGANIZATIONS_LIST, USER_ORGANIZATION_LIST, } from 'GraphQl/Queries/Queries'; -import { UPDATE_SPAM_NOTIFICATION_MUTATION } from 'GraphQl/Mutations/mutations'; import { languages } from 'utils/languages'; -import { errorHandler } from 'utils/errorHandler'; interface InterfaceNavbarProps { targets: { @@ -34,71 +32,18 @@ interface InterfaceNavbarProps { function adminNavbar({ targets, url1 }: InterfaceNavbarProps): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'adminNavbar' }); - - const [spamCountData, setSpamCountData] = useState([]); - const [showNotifModal, setShowNotifModal] = useState(false); - const currentUrl = window.location.href.split('=')[1]; - const { - data: orgData, - error: orgError, - refetch: orgRefetch, - } = useQuery(ORGANIZATIONS_LIST, { + const { data: orgData, error: orgError } = useQuery(ORGANIZATIONS_LIST, { variables: { id: currentUrl }, }); - const [updateSpam] = useMutation(UPDATE_SPAM_NOTIFICATION_MUTATION); + const { data: data2 } = useQuery(USER_ORGANIZATION_LIST, { variables: { id: localStorage.getItem('id') }, }); const isSuperAdmin = data2?.user.userType === 'SUPERADMIN'; - - useEffect(() => { - const handleUpdateSpam = async (): Promise => { - const spamId = localStorage.getItem('spamId'); - if (spamId) { - try { - const { data } = await updateSpam({ - variables: { - orgId: currentUrl, - spamId, - isReaded: true, - }, - }); - - /* istanbul ignore next */ - if (data) { - localStorage.removeItem('spamId'); - orgRefetch(); - } - } catch (error: any) { - /* istanbul ignore next */ - errorHandler(t, error); - } - } - }; - - handleUpdateSpam(); - }, []); - - useEffect(() => { - if (orgData && orgData?.organizations[0].spamCount) { - setSpamCountData( - orgData?.organizations[0].spamCount.filter( - (spam: any) => spam.isReaded === false - ) - ); - } - }, [orgData]); - const currentLanguageCode = Cookies.get('i18next') || 'en'; - const toggleNotifModal = (): void => setShowNotifModal(!showNotifModal); - - const handleSpamNotification = (spamId: string): void => { - localStorage.setItem('spamId', spamId); - window.location.assign(`/blockuser/id=${url1}`); - }; /* istanbul ignore next */ if (orgError) { @@ -151,7 +96,7 @@ function adminNavbar({ targets, url1 }: InterfaceNavbarProps): JSX.Element { @@ -193,25 +138,9 @@ function adminNavbar({ targets, url1 }: InterfaceNavbarProps): JSX.Element { id="dropdown-basic" data-testid="logoutDropdown" > - {orgData?.organizations[0].image ? ( - - - - ) : ( - - )} + - -   {t('notification')}{' '} - - {spamCountData.length} - -   {t('settings')} @@ -257,38 +186,6 @@ function adminNavbar({ targets, url1 }: InterfaceNavbarProps): JSX.Element { - - {/* Notification Modal */} - - -
{t('notifications')}
- -
- - {spamCountData.length > 0 ? ( - spamCountData.map((spam: any) => ( -
handleSpamNotification(spam._id)} - key={spam._id} - data-testid={`spamNotification${spam._id}`} - > - {`${spam.user.firstName} ${spam.user.lastName}`} {t('spamsThe')}{' '} - {spam.groupchat.title} {t('group')}. -
- )) - ) : ( -

{t('noNotifications')}

- )} -
- - - -
); } diff --git a/src/components/AdminNavbar/AdminNavbarMocks.ts b/src/components/AdminNavbar/AdminNavbarMocks.ts index b0d18f06de..1717807ddc 100644 --- a/src/components/AdminNavbar/AdminNavbarMocks.ts +++ b/src/components/AdminNavbar/AdminNavbarMocks.ts @@ -1,5 +1,4 @@ import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; -import { UPDATE_SPAM_NOTIFICATION_MUTATION } from 'GraphQl/Mutations/mutations'; // Has no placeholder image export const MOCKS = [ @@ -71,19 +70,6 @@ export const MOCKS = [ }, }, }, - { - request: { - query: UPDATE_SPAM_NOTIFICATION_MUTATION, - variables: { orgId: undefined, spamId: '6954', isReaded: true }, - }, - result: { - data: { - updateSpamNotification: { - _id: '900', - }, - }, - }, - }, ]; // Has a placeholder image @@ -156,17 +142,4 @@ export const MOCKS_WITH_IMAGE = [ }, }, }, - { - request: { - query: UPDATE_SPAM_NOTIFICATION_MUTATION, - variables: { orgId: undefined, spamId: '6954', isReaded: true }, - }, - result: { - data: { - updateSpamNotification: { - _id: '900', - }, - }, - }, - }, ]; diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.module.css b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.module.css index e4a7898707..761b389541 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.module.css +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.module.css @@ -20,3 +20,8 @@ background-color: #31bb6b; color: white; } + +.link { + text-decoration: none !important; + color: inherit; +} diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx index 8f704c912e..22fe578a8b 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx @@ -12,6 +12,7 @@ import { useQuery } from '@apollo/client'; import { USER_ORGANIZATION_CONNECTION } from 'GraphQl/Queries/Queries'; import getOrganizationId from 'utils/getOrganizationId'; import type { DropDirection } from 'react-bootstrap/esm/DropdownContext'; +import { useHistory } from 'react-router-dom'; interface InterfaceNavbarProps { currentPage: string | null; @@ -21,6 +22,9 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'userNavbar', }); + + const history = useHistory(); + const [organizationDetails, setOrganizationDetails]: any = React.useState({}); // const dropDirection: DropDirection = screen.width > 767 ? 'start' : 'down'; const dropDirection: DropDirection = 'start'; @@ -49,6 +53,12 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { } }, [data]); + const homeLink = `/user/organization/id=${organizationId}`; + const peopleLink = `/user/people/id=${organizationId}`; + const eventsLink = `/user/events/id=${organizationId}`; + const chatLink = `/user/chat/id=${organizationId}`; + const donationLink = `/user/donation/id=${organizationId}`; + return ( @@ -72,19 +82,34 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { diff --git a/src/components/UserPortal/PeopleCard/PeopleCard.module.css b/src/components/UserPortal/PeopleCard/PeopleCard.module.css new file mode 100644 index 0000000000..37f9e55345 --- /dev/null +++ b/src/components/UserPortal/PeopleCard/PeopleCard.module.css @@ -0,0 +1,17 @@ +.mainContainer { + width: 100%; + display: flex; + flex-direction: row; + padding: 10px; + cursor: pointer; + background-color: white; + border-radius: 10px; + box-shadow: 2px 2px 8px 0px #c8c8c8; + overflow: hidden; +} + +.personDetails { + display: flex; + flex-direction: column; + justify-content: center; +} diff --git a/src/components/UserPortal/PeopleCard/PeopleCard.test.tsx b/src/components/UserPortal/PeopleCard/PeopleCard.test.tsx new file mode 100644 index 0000000000..281cd8d84d --- /dev/null +++ b/src/components/UserPortal/PeopleCard/PeopleCard.test.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { act, render } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; + +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import PeopleCard from './PeopleCard'; + +const link = new StaticMockLink([], true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +let props = { + id: '1', + name: 'First Last', + image: '', + email: 'first@last.com', +}; + +describe('Testing Organization Card Component [User Portal]', () => { + test('Component should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + }); + + test('Component should be rendered properly if person image is not undefined', async () => { + props = { + ...props, + image: 'personImage', + }; + + render( + + + + + + + + + + ); + + await wait(); + }); +}); diff --git a/src/components/UserPortal/PeopleCard/PeopleCard.tsx b/src/components/UserPortal/PeopleCard/PeopleCard.tsx new file mode 100644 index 0000000000..9dc0ee3643 --- /dev/null +++ b/src/components/UserPortal/PeopleCard/PeopleCard.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import aboutImg from 'assets/images/defaultImg.png'; +import styles from './PeopleCard.module.css'; + +interface InterfaceOrganizationCardProps { + id: string; + name: string; + image: string; + email: string; +} + +function peopleCard(props: InterfaceOrganizationCardProps): JSX.Element { + const imageUrl = props.image ? props.image : aboutImg; + + return ( +
+ +
+ {props.name} + {props.email} +
+
+ ); +} + +export default peopleCard; diff --git a/src/screens/UserPortal/People/People.module.css b/src/screens/UserPortal/People/People.module.css new file mode 100644 index 0000000000..6f83c82d8a --- /dev/null +++ b/src/screens/UserPortal/People/People.module.css @@ -0,0 +1,44 @@ +.borderNone { + border: none; +} + +.colorWhite { + color: white; +} + +.maxWidth { + max-width: 300px; +} + +.colorLight { + background-color: #f5f5f5; +} + +.mainContainer { + width: 50%; + flex-grow: 3; + padding: 40px; + max-height: 100%; + overflow: auto; +} + +.content { + height: fit-content; + min-height: calc(100% - 40px); +} + +.gap { + gap: 20px; +} + +.paddingY { + padding: 30px 0px; +} + +.containerHeight { + height: calc(100vh - 61px); +} + +.colorPrimary { + background: #31bb6b; +} diff --git a/src/screens/UserPortal/People/People.test.tsx b/src/screens/UserPortal/People/People.test.tsx new file mode 100644 index 0000000000..b373a68e98 --- /dev/null +++ b/src/screens/UserPortal/People/People.test.tsx @@ -0,0 +1,206 @@ +import React from 'react'; +import { act, render, screen } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; + +import { + ORGANIZATIONS_MEMBER_CONNECTION_LIST, + ORGANIZATION_ADMINS_LIST, +} from 'GraphQl/Queries/Queries'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import People from './People'; +import userEvent from '@testing-library/user-event'; +import * as getOrganizationId from 'utils/getOrganizationId'; + +const MOCKS = [ + { + request: { + query: ORGANIZATIONS_MEMBER_CONNECTION_LIST, + variables: { + orgId: '', + firstName_contains: '', + }, + }, + result: { + data: { + organizationsMemberConnection: { + edges: [ + { + _id: '64001660a711c62d5b4076a2', + firstName: 'Noble', + lastName: 'Mittal', + image: null, + email: 'noble@gmail.com', + createdAt: '2023-03-02T03:22:08.101Z', + }, + { + _id: '64001660a711c62d5b4076a3', + firstName: 'Noble', + lastName: 'Mittal', + image: 'mockImage', + email: 'noble@gmail.com', + createdAt: '2023-03-02T03:22:08.101Z', + }, + ], + }, + }, + }, + }, + { + request: { + query: ORGANIZATION_ADMINS_LIST, + variables: { + id: '', + }, + }, + result: { + data: { + organizations: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af2', + admins: [ + { + _id: '64001660a711c62d5b4076a2', + firstName: 'Noble', + lastName: 'Admin', + image: null, + email: 'noble@gmail.com', + }, + ], + }, + ], + }, + }, + }, + { + request: { + query: ORGANIZATIONS_MEMBER_CONNECTION_LIST, + variables: { + orgId: '', + firstName_contains: 'j', + }, + }, + result: { + data: { + organizationsMemberConnection: { + edges: [ + { + _id: '64001660a711c62d5b4076a2', + firstName: 'John', + lastName: 'Cena', + image: null, + email: 'john@gmail.com', + createdAt: '2023-03-02T03:22:08.101Z', + }, + ], + }, + }, + }, + }, +]; + +const link = new StaticMockLink(MOCKS, true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +describe('Testing People Screen [User Portal]', () => { + jest.mock('utils/getOrganizationId'); + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); + + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + test('Screen should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + expect(screen.queryAllByText('Noble Mittal')).not.toBe([]); + }); + + test('Search works properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type(screen.getByTestId('searchInput'), 'j'); + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + expect(screen.queryByText('John Cena')).toBeInTheDocument(); + expect(screen.queryByText('Noble Mittal')).not.toBeInTheDocument(); + }); + + test('Mode is changed to Admins', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('modeChangeBtn')); + await wait(); + userEvent.click(screen.getByTestId('modeBtn1')); + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + expect(screen.queryByText('Noble Admin')).toBeInTheDocument(); + expect(screen.queryByText('Noble Mittal')).not.toBeInTheDocument(); + }); +}); diff --git a/src/screens/UserPortal/People/People.tsx b/src/screens/UserPortal/People/People.tsx new file mode 100644 index 0000000000..209441c532 --- /dev/null +++ b/src/screens/UserPortal/People/People.tsx @@ -0,0 +1,217 @@ +import React from 'react'; +import OrganizationNavbar from 'components/UserPortal/OrganizationNavbar/OrganizationNavbar'; +import OrganizationSidebar from 'components/UserPortal/OrganizationSidebar/OrganizationSidebar'; +import PeopleCard from 'components/UserPortal/PeopleCard/PeopleCard'; +import UserSidebar from 'components/UserPortal/UserSidebar/UserSidebar'; +import { Dropdown, Form, InputGroup } from 'react-bootstrap'; +import PaginationList from 'components/PaginationList/PaginationList'; +import { + ORGANIZATIONS_MEMBER_CONNECTION_LIST, + ORGANIZATION_ADMINS_LIST, +} from 'GraphQl/Queries/Queries'; +import { useQuery } from '@apollo/client'; +import { SearchOutlined } from '@mui/icons-material'; +import styles from './People.module.css'; +import { useTranslation } from 'react-i18next'; +import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; +import getOrganizationId from 'utils/getOrganizationId'; + +interface InterfaceOrganizationCardProps { + id: string; + name: string; + image: string; + email: string; +} + +export default function people(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'userOrganizations', + }); + + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + const [members, setMembers] = React.useState([]); + const [filterName, setFilterName] = React.useState(''); + const [mode, setMode] = React.useState(0); + + const organizationId = getOrganizationId(window.location.href); + + const modes = ['All Members', 'Admins']; + + const { data, loading, refetch } = useQuery( + ORGANIZATIONS_MEMBER_CONNECTION_LIST, + { + variables: { + orgId: organizationId, + firstName_contains: '', + }, + } + ); + + const { data: data2 } = useQuery(ORGANIZATION_ADMINS_LIST, { + variables: { id: organizationId }, + }); + + /* istanbul ignore next */ + const handleChangePage = ( + _event: React.MouseEvent | null, + newPage: number + ): void => { + setPage(newPage); + }; + + /* istanbul ignore next */ + const handleChangeRowsPerPage = ( + event: React.ChangeEvent + ): void => { + const newRowsPerPage = event.target.value; + + setRowsPerPage(parseInt(newRowsPerPage, 10)); + setPage(0); + }; + + const handleSearch = ( + event: React.ChangeEvent + ): void => { + const newFilter = event.target.value; + setFilterName(newFilter); + + const filter = { + firstName_contains: newFilter, + }; + + refetch(filter); + }; + + /* istanbul ignore next */ + React.useEffect(() => { + if (data) { + setMembers(data.organizationsMemberConnection.edges); + } + }, [data]); + + /* istanbul ignore next */ + React.useEffect(() => { + if (mode == 0) { + if (data) { + setMembers(data.organizationsMemberConnection.edges); + } + } else if (mode == 1) { + if (data2) { + setMembers(data2.organizations[0].admins); + } + } + }, [mode]); + + const navbarProps = { + currentPage: 'people', + }; + + return ( + <> + +
+ +
+
+ + + + + + + + + {modes[mode]} + + + {modes.map((value, index) => { + return ( + setMode(index)} + > + {value} + + ); + })} + + +
+
+
+ {loading ? ( +
+ Loading... +
+ ) : ( + <> + {members && members.length > 0 ? ( + (rowsPerPage > 0 + ? members.slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ) + : /* istanbul ignore next */ + members + ).map((member: any, index) => { + const name = `${member.firstName} ${member.lastName}`; + + const cardProps: InterfaceOrganizationCardProps = { + name, + image: member.image, + id: member._id, + email: member.email, + }; + return ; + }) + ) : ( + {t('nothingToShow')} + )} + + )} +
+
+ + + + + +
+ + + + + + ); +} From 24e139d1039d2b84bcd8de9c66a44ba664308237 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Tue, 8 Aug 2023 22:54:47 +0530 Subject: [PATCH 17/29] Add Settings and Donate Screen --- src/App.tsx | 4 + .../DonationCard/DonationCard.module.css | 22 ++ .../UserPortal/DonationCard/DonationCard.tsx | 24 +++ .../OrganizationNavbar/OrganizationNavbar.tsx | 10 +- .../OrganizationSidebar.module.css | 5 + .../OrganizationSidebar.tsx | 1 + .../PeopleCard/PeopleCard.module.css | 5 + .../UserPortal/PeopleCard/PeopleCard.tsx | 7 +- .../UserNavbar/UserNavbar.module.css | 5 + .../UserPortal/UserNavbar/UserNavbar.tsx | 7 +- .../UserSidebar/UserSidebar.module.css | 4 + .../UserPortal/UserSidebar/UserSidebar.tsx | 2 + .../UserPortal/Donate/Donate.module.css | 56 +++++ src/screens/UserPortal/Donate/Donate.test.tsx | 183 ++++++++++++++++ src/screens/UserPortal/Donate/Donate.tsx | 200 ++++++++++++++++++ src/screens/UserPortal/Home/Home.module.css | 8 +- src/screens/UserPortal/Home/Home.tsx | 1 + .../Organizations/Organizations.module.css | 2 +- .../UserPortal/People/People.module.css | 6 +- src/screens/UserPortal/People/People.tsx | 2 +- .../UserPortal/Settings/Settings.module.css | 26 +++ .../UserPortal/Settings/Settings.test.tsx | 139 ++++++++++++ src/screens/UserPortal/Settings/Settings.tsx | 144 +++++++++++++ 23 files changed, 854 insertions(+), 9 deletions(-) create mode 100644 src/components/UserPortal/DonationCard/DonationCard.module.css create mode 100644 src/components/UserPortal/DonationCard/DonationCard.tsx create mode 100644 src/screens/UserPortal/Donate/Donate.module.css create mode 100644 src/screens/UserPortal/Donate/Donate.test.tsx create mode 100644 src/screens/UserPortal/Donate/Donate.tsx create mode 100644 src/screens/UserPortal/Settings/Settings.module.css create mode 100644 src/screens/UserPortal/Settings/Settings.test.tsx create mode 100644 src/screens/UserPortal/Settings/Settings.tsx diff --git a/src/App.tsx b/src/App.tsx index b43a4adba3..10c3a40cec 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,6 +27,8 @@ import UserLoginPage from 'screens/UserPortal/UserLoginPage/UserLoginPage'; import Organizations from 'screens/UserPortal/Organizations/Organizations'; import Home from 'screens/UserPortal/Home/Home'; import People from 'screens/UserPortal/People/People'; +import Settings from 'screens/UserPortal/Settings/Settings'; +import Donate from 'screens/UserPortal/Donate/Donate'; function app(): JSX.Element { /*const { updatePluginLinks, updateInstalled } = bindActionCreators( @@ -113,6 +115,8 @@ function app(): JSX.Element { /> + + diff --git a/src/components/UserPortal/DonationCard/DonationCard.module.css b/src/components/UserPortal/DonationCard/DonationCard.module.css new file mode 100644 index 0000000000..76fcaf3b0c --- /dev/null +++ b/src/components/UserPortal/DonationCard/DonationCard.module.css @@ -0,0 +1,22 @@ +.mainContainer { + width: 100%; + display: flex; + flex-direction: row; + padding: 10px; + cursor: pointer; + background-color: white; + border-radius: 10px; + box-shadow: 2px 2px 8px 0px #c8c8c8; + overflow: hidden; +} + +.personDetails { + display: flex; + flex-direction: column; + justify-content: center; +} + +.personImage { + border-radius: 50%; + margin-right: 20px; +} diff --git a/src/components/UserPortal/DonationCard/DonationCard.tsx b/src/components/UserPortal/DonationCard/DonationCard.tsx new file mode 100644 index 0000000000..436e3049b8 --- /dev/null +++ b/src/components/UserPortal/DonationCard/DonationCard.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import styles from './DonationCard.module.css'; + +interface InterfaceDonationCardProps { + id: string; + name: string; + amount: string; + userId: string; + payPalId: string; +} + +function donationCard(props: InterfaceDonationCardProps): JSX.Element { + return ( +
+
+ {props.name} + Amount: {props.amount} + PayPal Id: {props.payPalId} +
+
+ ); +} + +export default donationCard; diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx index 22fe578a8b..3e748e2856 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx @@ -12,7 +12,7 @@ import { useQuery } from '@apollo/client'; import { USER_ORGANIZATION_CONNECTION } from 'GraphQl/Queries/Queries'; import getOrganizationId from 'utils/getOrganizationId'; import type { DropDirection } from 'react-bootstrap/esm/DropdownContext'; -import { useHistory } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; interface InterfaceNavbarProps { currentPage: string | null; @@ -57,7 +57,7 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { const peopleLink = `/user/people/id=${organizationId}`; const eventsLink = `/user/events/id=${organizationId}`; const chatLink = `/user/chat/id=${organizationId}`; - const donationLink = `/user/donation/id=${organizationId}`; + const donationLink = `/user/donate/id=${organizationId}`; return ( @@ -162,7 +162,11 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { {userName} - {t('settings')} + + + {t('settings')} + + {t('myTasks')} diff --git a/src/components/UserPortal/PeopleCard/PeopleCard.module.css b/src/components/UserPortal/PeopleCard/PeopleCard.module.css index 37f9e55345..76fcaf3b0c 100644 --- a/src/components/UserPortal/PeopleCard/PeopleCard.module.css +++ b/src/components/UserPortal/PeopleCard/PeopleCard.module.css @@ -15,3 +15,8 @@ flex-direction: column; justify-content: center; } + +.personImage { + border-radius: 50%; + margin-right: 20px; +} diff --git a/src/components/UserPortal/PeopleCard/PeopleCard.tsx b/src/components/UserPortal/PeopleCard/PeopleCard.tsx index 9dc0ee3643..578cf31194 100644 --- a/src/components/UserPortal/PeopleCard/PeopleCard.tsx +++ b/src/components/UserPortal/PeopleCard/PeopleCard.tsx @@ -14,7 +14,12 @@ function peopleCard(props: InterfaceOrganizationCardProps): JSX.Element { return (
- +
{props.name} {props.email} diff --git a/src/components/UserPortal/UserNavbar/UserNavbar.module.css b/src/components/UserPortal/UserNavbar/UserNavbar.module.css index 30eaa52187..764a24ab93 100644 --- a/src/components/UserPortal/UserNavbar/UserNavbar.module.css +++ b/src/components/UserPortal/UserNavbar/UserNavbar.module.css @@ -19,3 +19,8 @@ .colorPrimary { background: #31bb6b; } + +.link { + text-decoration: none !important; + color: inherit; +} diff --git a/src/components/UserPortal/UserNavbar/UserNavbar.tsx b/src/components/UserPortal/UserNavbar/UserNavbar.tsx index 6e3f83cc5c..ed297f03af 100644 --- a/src/components/UserPortal/UserNavbar/UserNavbar.tsx +++ b/src/components/UserPortal/UserNavbar/UserNavbar.tsx @@ -8,6 +8,7 @@ import cookies from 'js-cookie'; import PermIdentityIcon from '@mui/icons-material/PermIdentity'; import LanguageIcon from '@mui/icons-material/Language'; import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; function userNavbar(): JSX.Element { const { t } = useTranslation('translation', { @@ -92,7 +93,11 @@ function userNavbar(): JSX.Element { {userName} - {t('settings')} + + + {t('settings')} + + {t('myTasks')} {t('logout')} diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.module.css b/src/components/UserPortal/UserSidebar/UserSidebar.module.css index 930ed7c9db..00eb9b631f 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.module.css +++ b/src/components/UserPortal/UserSidebar/UserSidebar.module.css @@ -65,3 +65,7 @@ .marginTop { margin-top: -2px; } + +.personImage { + border-radius: 50%; +} diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.tsx index 9b3ad4a07e..b122b9e229 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.tsx +++ b/src/components/UserPortal/UserSidebar/UserSidebar.tsx @@ -57,6 +57,7 @@ function userSidebar(): JSX.Element { <> @@ -84,6 +85,7 @@ function userSidebar(): JSX.Element { src={ organization.image ? organization.image : AboutImg } + className={styles.personImage} width="auto" height="30px" /> diff --git a/src/screens/UserPortal/Donate/Donate.module.css b/src/screens/UserPortal/Donate/Donate.module.css new file mode 100644 index 0000000000..c137003c0e --- /dev/null +++ b/src/screens/UserPortal/Donate/Donate.module.css @@ -0,0 +1,56 @@ +.containerHeight { + height: calc(100vh - 66px); +} + +.mainContainer { + width: 50%; + flex-grow: 3; + padding: 20px; + max-height: 100%; + overflow: auto; + display: flex; + flex-direction: column; +} + +.box { + width: auto; + /* height: 200px; */ + background-color: white; + margin: 20px; + padding: 20px; + border-radius: 20px; +} + +.donationInputContainer { + display: flex; + flex-direction: row; + margin-top: 20px; +} + +.maxWidth { + width: 100%; +} + +.donateActions { + margin-top: 40px; + width: 100%; + display: flex; + flex-direction: row-reverse; +} + +.donationsContainer { + margin: 20px; + padding-top: 20px; + flex-grow: 1; + display: flex; + flex-direction: column; +} + +.colorLight { + background-color: #f5f5f5; +} + +.content { + padding-top: 10px; + flex-grow: 1; +} diff --git a/src/screens/UserPortal/Donate/Donate.test.tsx b/src/screens/UserPortal/Donate/Donate.test.tsx new file mode 100644 index 0000000000..3abb433c51 --- /dev/null +++ b/src/screens/UserPortal/Donate/Donate.test.tsx @@ -0,0 +1,183 @@ +import React from 'react'; +import { act, render, screen } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; + +import { + ORGANIZATION_DONATION_CONNECTION_LIST, + USER_ORGANIZATION_CONNECTION, +} from 'GraphQl/Queries/Queries'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import Donate from './Donate'; +import userEvent from '@testing-library/user-event'; +import * as getOrganizationId from 'utils/getOrganizationId'; + +const MOCKS = [ + { + request: { + query: ORGANIZATION_DONATION_CONNECTION_LIST, + variables: { + orgId: '', + }, + }, + result: { + data: { + getDonationByOrgIdConnection: [ + { + _id: '6391a15bcb738c181d238957', + nameOfUser: 'firstName lastName', + amount: 1, + userId: '6391a15bcb738c181d238952', + payPalId: 'payPalId', + __typename: 'Donation', + }, + ], + }, + }, + }, + { + request: { + query: USER_ORGANIZATION_CONNECTION, + variables: { + id: '', + }, + }, + result: { + data: { + organizationsConnection: [ + { + __typename: 'Organization', + _id: '6401ff65ce8e8406b8f07af3', + image: '', + name: 'anyOrganization2', + description: 'desc', + isPublic: true, + creator: { __typename: 'User', firstName: 'John', lastName: 'Doe' }, + }, + ], + }, + }, + }, +]; + +const link = new StaticMockLink(MOCKS, true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +describe('Testing Donate Screen [User Portal]', () => { + jest.mock('utils/getOrganizationId'); + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); + + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + test('Screen should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + }); + + test('Currency is swtiched to USD', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('changeCurrencyBtn')); + + userEvent.click(screen.getByTestId('currency0')); + + await wait(); + }); + + test('Currency is swtiched to INR', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('changeCurrencyBtn')); + + userEvent.click(screen.getByTestId('currency1')); + + await wait(); + }); + + test('Currency is swtiched to EUR', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('changeCurrencyBtn')); + + userEvent.click(screen.getByTestId('currency2')); + + await wait(); + }); +}); diff --git a/src/screens/UserPortal/Donate/Donate.tsx b/src/screens/UserPortal/Donate/Donate.tsx new file mode 100644 index 0000000000..bba62bc0b0 --- /dev/null +++ b/src/screens/UserPortal/Donate/Donate.tsx @@ -0,0 +1,200 @@ +import React from 'react'; +import OrganizationNavbar from 'components/UserPortal/OrganizationNavbar/OrganizationNavbar'; +import OrganizationSidebar from 'components/UserPortal/OrganizationSidebar/OrganizationSidebar'; +import UserSidebar from 'components/UserPortal/UserSidebar/UserSidebar'; +import { Button, Dropdown, Form, InputGroup } from 'react-bootstrap'; +import PaginationList from 'components/PaginationList/PaginationList'; +import { + ORGANIZATION_DONATION_CONNECTION_LIST, + USER_ORGANIZATION_CONNECTION, +} from 'GraphQl/Queries/Queries'; +import { useQuery } from '@apollo/client'; +import styles from './Donate.module.css'; +import SendIcon from '@mui/icons-material/Send'; +import getOrganizationId from 'utils/getOrganizationId'; +import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; +import DonationCard from 'components/UserPortal/DonationCard/DonationCard'; +import { useTranslation } from 'react-i18next'; + +interface InterfaceDonationCardProps { + id: string; + name: string; + amount: string; + userId: string; + payPalId: string; +} + +export default function donate(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'userOrganizations', + }); + + const organizationId = getOrganizationId(location.href); + const [organizationDetails, setOrganizationDetails]: any = React.useState({}); + const [donations, setDonations] = React.useState([]); + const [selectedCurrency, setSelectedCurrency] = React.useState(0); + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + + const currencies = ['USD', 'INR', 'EUR']; + + const { data: data2, loading } = useQuery( + ORGANIZATION_DONATION_CONNECTION_LIST, + { + variables: { orgId: organizationId }, + } + ); + + const { data } = useQuery(USER_ORGANIZATION_CONNECTION, { + variables: { id: organizationId }, + }); + + const navbarProps = { + currentPage: 'donate', + }; + + /* istanbul ignore next */ + const handleChangePage = ( + _event: React.MouseEvent | null, + newPage: number + ): void => { + setPage(newPage); + }; + + /* istanbul ignore next */ + const handleChangeRowsPerPage = ( + event: React.ChangeEvent + ): void => { + const newRowsPerPage = event.target.value; + + setRowsPerPage(parseInt(newRowsPerPage, 10)); + setPage(0); + }; + + React.useEffect(() => { + if (data) { + setOrganizationDetails(data.organizationsConnection[0]); + } + }, [data]); + + React.useEffect(() => { + if (data2) { + setDonations(data2.getDonationByOrgIdConnection); + } + }, [data2]); + + return ( + <> + +
+ +
+
+

Donate to {organizationDetails.name}

+
+ + + Amount + + + + + + {currencies[selectedCurrency]} + + + + {currencies.map((currency, index) => { + return ( + setSelectedCurrency(index)} + data-testid={`currency${index}`} + > + {currency} + + ); + })} + + + +
+
+ +
+
+
+
Your Previous Donations
+
+
+ {loading ? ( +
+ Loading... +
+ ) : ( + <> + {donations && donations.length > 0 ? ( + (rowsPerPage > 0 + ? donations.slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ) + : /* istanbul ignore next */ + donations + ).map((donation: any, index) => { + const cardProps: InterfaceDonationCardProps = { + name: donation.nameOfUser, + id: donation._id, + amount: donation.amount, + userId: donation.userId, + payPalId: donation.payPalId, + }; + return ; + }) + ) : ( + {t('nothingToShow')} + )} + + )} +
+ + + + + + +
+
+
+
+ +
+ + ); +} diff --git a/src/screens/UserPortal/Home/Home.module.css b/src/screens/UserPortal/Home/Home.module.css index 8c11480f5c..48643b3445 100644 --- a/src/screens/UserPortal/Home/Home.module.css +++ b/src/screens/UserPortal/Home/Home.module.css @@ -23,7 +23,7 @@ } .containerHeight { - height: calc(100vh - 61px); + height: calc(100vh - 66px); } .link { @@ -62,4 +62,10 @@ resize: none; border: none; box-shadow: none; + background-color: white; + margin-bottom: 10px; +} + +.imageInput { + background-color: white; } diff --git a/src/screens/UserPortal/Home/Home.tsx b/src/screens/UserPortal/Home/Home.tsx index 073d9eda4b..29792c8a22 100644 --- a/src/screens/UserPortal/Home/Home.tsx +++ b/src/screens/UserPortal/Home/Home.tsx @@ -129,6 +129,7 @@ export default function home(): JSX.Element { id="postphoto" name="photo" type="file" + className={styles.imageInput} multiple={false} onChange={ /* istanbul ignore next */ diff --git a/src/screens/UserPortal/Organizations/Organizations.module.css b/src/screens/UserPortal/Organizations/Organizations.module.css index 6f83c82d8a..76f443ca37 100644 --- a/src/screens/UserPortal/Organizations/Organizations.module.css +++ b/src/screens/UserPortal/Organizations/Organizations.module.css @@ -36,7 +36,7 @@ } .containerHeight { - height: calc(100vh - 61px); + height: calc(100vh - 66px); } .colorPrimary { diff --git a/src/screens/UserPortal/People/People.module.css b/src/screens/UserPortal/People/People.module.css index 6f83c82d8a..f67df3b23f 100644 --- a/src/screens/UserPortal/People/People.module.css +++ b/src/screens/UserPortal/People/People.module.css @@ -6,6 +6,10 @@ color: white; } +.backgroundWhite { + background-color: white; +} + .maxWidth { max-width: 300px; } @@ -36,7 +40,7 @@ } .containerHeight { - height: calc(100vh - 61px); + height: calc(100vh - 66px); } .colorPrimary { diff --git a/src/screens/UserPortal/People/People.tsx b/src/screens/UserPortal/People/People.tsx index 209441c532..f8b4d7ca3d 100644 --- a/src/screens/UserPortal/People/People.tsx +++ b/src/screens/UserPortal/People/People.tsx @@ -120,7 +120,7 @@ export default function people(): JSX.Element { { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +describe('Testing Settings Screen [User Portal]', () => { + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); + + test('Screen should be rendered properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + + expect(screen.queryAllByText('Settings')).not.toBe([]); + }); + + test('First name input works properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type(screen.getByTestId('inputFirstName'), 'Noble'); + await wait(); + }); + + test('Last name input works properly', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type(screen.getByTestId('inputLastName'), 'Mittal'); + await wait(); + }); + + test('updateUserDetails Mutation is triggered on button click', async () => { + render( + + + + + + + + + + ); + + await wait(); + + userEvent.type(screen.getByTestId('inputFirstName'), 'Noble'); + await wait(); + + userEvent.type(screen.getByTestId('inputLastName'), 'Mittal'); + await wait(); + + userEvent.click(screen.getByTestId('updateUserBtn')); + await wait(); + }); +}); diff --git a/src/screens/UserPortal/Settings/Settings.tsx b/src/screens/UserPortal/Settings/Settings.tsx new file mode 100644 index 0000000000..e7bff55744 --- /dev/null +++ b/src/screens/UserPortal/Settings/Settings.tsx @@ -0,0 +1,144 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import styles from './Settings.module.css'; +import UserSidebar from 'components/UserPortal/UserSidebar/UserSidebar'; +import UserNavbar from 'components/UserPortal/UserNavbar/UserNavbar'; +import { Button, Form } from 'react-bootstrap'; +import convertToBase64 from 'utils/convertToBase64'; +import { UPDATE_USER_MUTATION } from 'GraphQl/Mutations/mutations'; +import { useMutation, useQuery } from '@apollo/client'; +import { errorHandler } from 'utils/errorHandler'; +import { toast } from 'react-toastify'; +import { CHECK_AUTH } from 'GraphQl/Queries/Queries'; + +export default function settings(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'settings', + }); + + const { data } = useQuery(CHECK_AUTH); + const [image, setImage] = React.useState(''); + const [updateUserDetails] = useMutation(UPDATE_USER_MUTATION); + const [firstName, setFirstName] = React.useState(''); + const [lastName, setLastName] = React.useState(''); + const [email, setEmail] = React.useState(''); + + const handleUpdateUserDetails = async (): Promise => { + let variables: any = { + firstName, + lastName, + }; + + /* istanbul ignore next */ + if (image) { + variables = { + ...variables, + file: image, + }; + } + try { + const { data } = await updateUserDetails({ + variables, + }); + + /* istanbul ignore next */ + if (data) { + setImage(''); + toast.success('Your details have been updated.'); + setTimeout(() => { + window.location.reload(); + }, 500); + + const userFullName = `${firstName} ${lastName}`; + localStorage.setItem('name', userFullName); + } + } catch (error: any) { + errorHandler(t, error); + } + }; + + const handleFirstNameChange = (e: any): void => { + const { value } = e.target; + setFirstName(value); + }; + + const handleLastNameChange = (e: any): void => { + const { value } = e.target; + setLastName(value); + }; + + React.useEffect(() => { + /* istanbul ignore next */ + if (data) { + setFirstName(data.checkAuth.firstName); + setLastName(data.checkAuth.lastName); + setEmail(data.checkAuth.email); + } + }, [data]); + + return ( + <> + +
+ +
+

Profile Settings

+
+ First Name + + Last Name + + Email Address + + Update Image + => { + const target = e.target as HTMLInputElement; + const file = target.files && target.files[0]; + if (file) { + const image = await convertToBase64(file); + setImage(image); + } + } + } + /> + +
+
+
+ + ); +} From 4bc6f0016f2612f5928291dd035fe50983da0846 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Wed, 9 Aug 2023 01:29:11 +0530 Subject: [PATCH 18/29] Add multilingual support for the screens --- public/locales/en.json | 15 +++++++++++++++ public/locales/fr.json | 15 +++++++++++++++ public/locales/hi.json | 15 +++++++++++++++ public/locales/sp.json | 15 +++++++++++++++ public/locales/zh.json | 15 +++++++++++++++ src/screens/UserPortal/Donate/Donate.tsx | 12 +++++++----- src/screens/UserPortal/Settings/Settings.tsx | 12 ++++++------ 7 files changed, 88 insertions(+), 11 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 7ea43f53a7..cf05dac0cc 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -522,5 +522,20 @@ "feed": "Feed", "pinnedPosts": "View Pinned Posts", "somethingOnYourMind": "Something on your mind?" + }, + "settings": { + "profileSettings": "Profile Settings", + "firstName": "First Name", + "lastName": "Last Name", + "emailAddress": "Email Address", + "updateImage": "Update Image", + "save": "Save" + }, + "donate": { + "donateTo": "Donate to", + "amount": "Amount", + "yourPreviousDonations": "Your Previous Donations", + "donate": "Donate", + "nothingToShow": "Nothing to show here." } } diff --git a/public/locales/fr.json b/public/locales/fr.json index b9b5a712ae..768eae372e 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -516,5 +516,20 @@ "feed": "Alimentation", "pinnedPosts": "Afficher les publications épinglées", "somethingOnYourMind": "Quelque chose dans votre esprit?" + }, + "settings": { + "profileSettings": "Paramètres de profil", + "firstName": "Prénom", + "lastName": "Nom de famille", + "emailAddress": "Adresse e-mail", + "updateImage": "Mettre à jour l'image", + "save": "Sauvegarder" + }, + "donate": { + "donateTo": "Faire un don à", + "amount": "Montante", + "yourPreviousDonations": "Vos dons précédents", + "donate": "Donner", + "nothingToShow": "Rien à montrer ici." } } diff --git a/public/locales/hi.json b/public/locales/hi.json index 65a6d3653f..082ff4c094 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -516,5 +516,20 @@ "feed": "फ़ीड", "pinnedPosts": "पिन किए गए पोस्ट देखें", "somethingOnYourMind": "आपके मन में कुछ है?" + }, + "settings": { + "profileSettings": "पार्श्वचित्र समायोजन", + "firstName": "पहला नाम", + "lastName": "उपनाम", + "emailAddress": "मेल पता", + "updateImage": "छवि अद्यतन करें", + "save": "बचाना" + }, + "donate": { + "donateTo": "दान दें", + "amount": "मात्रा", + "yourPreviousDonations": "आपका पिछला दान", + "donate": "दान", + "nothingToShow": "यहां दिखाने के लिए कुछ भी नहीं है." } } diff --git a/public/locales/sp.json b/public/locales/sp.json index f00d91f38c..c6a511177b 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -518,5 +518,20 @@ "feed": "Alimentar", "pinnedPosts": "Ver publicaciones fijadas", "somethingOnYourMind": "¿Algo en tu mente?" + }, + "settings": { + "profileSettings": "Configuración de perfil", + "firstName": "Nombre de pila", + "lastName": "Apellido", + "emailAddress": "dirección de correo electrónico", + "updateImage": "Actualizar imagen", + "save": "Ahorrar" + }, + "donate": { + "donateTo": "Donar a", + "amount": "Cantidad", + "yourPreviousDonations": "Tus donaciones anteriores", + "donate": "Donar", + "nothingToShow": "Nada que mostrar aquí." } } diff --git a/public/locales/zh.json b/public/locales/zh.json index 785b1de9f0..f5dd54c2f0 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -518,5 +518,20 @@ "feed": "餵養", "pinnedPosts": "查看固定帖子", "somethingOnYourMind": "你有什麼心事嗎?" + }, + "settings": { + "profileSettings": "配置文件設置", + "firstName": "名", + "lastName": "姓", + "emailAddress": "電子郵件地址", + "updateImage": "更新圖片", + "save": "節省" + }, + "donate": { + "donateTo": "捐贈給", + "amount": "數量", + "yourPreviousDonations": "您之前的捐款", + "donate": "捐", + "nothingToShow": "這裡沒有什麼可顯示的。" } } diff --git a/src/screens/UserPortal/Donate/Donate.tsx b/src/screens/UserPortal/Donate/Donate.tsx index bba62bc0b0..cbe31f9937 100644 --- a/src/screens/UserPortal/Donate/Donate.tsx +++ b/src/screens/UserPortal/Donate/Donate.tsx @@ -26,7 +26,7 @@ interface InterfaceDonationCardProps { export default function donate(): JSX.Element { const { t } = useTranslation('translation', { - keyPrefix: 'userOrganizations', + keyPrefix: 'donate', }); const organizationId = getOrganizationId(location.href); @@ -90,13 +90,15 @@ export default function donate(): JSX.Element {
-

Donate to {organizationDetails.name}

+

+ {t('donateTo')} {organizationDetails.name} +

- Amount + {t('amount')}
-
Your Previous Donations
+
{t('yourPreviousDonations')}
diff --git a/src/screens/UserPortal/Settings/Settings.tsx b/src/screens/UserPortal/Settings/Settings.tsx index e7bff55744..35afd78790 100644 --- a/src/screens/UserPortal/Settings/Settings.tsx +++ b/src/screens/UserPortal/Settings/Settings.tsx @@ -82,9 +82,9 @@ export default function settings(): JSX.Element {
-

Profile Settings

+

{t('profileSettings')}

- First Name + {t('firstName')} - Last Name + {t('lastName')} - Email Address + {t('emailAddress')} - Update Image + {t('updateImage')} - Save + {t('save')}
From b8d4f1cd54a11974af9b604a7127468983be50e5 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Fri, 25 Aug 2023 22:52:58 +0530 Subject: [PATCH 19/29] Add events screen without calendar view --- src/App.tsx | 2 + src/GraphQl/Mutations/mutations.ts | 8 + src/GraphQl/Queries/Queries.ts | 45 ++ .../UserPortal/EventCard/EventCard.module.css | 26 + .../UserPortal/EventCard/EventCard.tsx | 120 +++++ .../OrganizationSidebar.tsx | 6 +- .../UserPortal/UserSidebar/UserSidebar.tsx | 28 +- .../UserPortal/Events/Events.module.css | 83 ++++ src/screens/UserPortal/Events/Events.tsx | 466 ++++++++++++++++++ 9 files changed, 770 insertions(+), 14 deletions(-) create mode 100644 src/components/UserPortal/EventCard/EventCard.module.css create mode 100644 src/components/UserPortal/EventCard/EventCard.tsx create mode 100644 src/screens/UserPortal/Events/Events.module.css create mode 100644 src/screens/UserPortal/Events/Events.tsx diff --git a/src/App.tsx b/src/App.tsx index 10c3a40cec..fbc99162a8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,6 +29,7 @@ import Home from 'screens/UserPortal/Home/Home'; import People from 'screens/UserPortal/People/People'; import Settings from 'screens/UserPortal/Settings/Settings'; import Donate from 'screens/UserPortal/Donate/Donate'; +import Events from 'screens/UserPortal/Events/Events'; function app(): JSX.Element { /*const { updatePluginLinks, updateInstalled } = bindActionCreators( @@ -117,6 +118,7 @@ function app(): JSX.Element { + diff --git a/src/GraphQl/Mutations/mutations.ts b/src/GraphQl/Mutations/mutations.ts index ba02fdedbd..88dc78fb46 100644 --- a/src/GraphQl/Mutations/mutations.ts +++ b/src/GraphQl/Mutations/mutations.ts @@ -456,3 +456,11 @@ export const UNLIKE_POST = gql` } } `; + +export const REGISTER_EVENT = gql` + mutation registerForEvent($eventId: ID!) { + registerForEvent(id: $eventId) { + _id + } + } +`; diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index a6c7f769f5..13a8056fbd 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -527,3 +527,48 @@ export const PLUGIN_GET = gql` } } `; + +export const ORGANIZATION_EVENTS_CONNECTION = gql` + query EventsByOrganizationConnection( + $organization_id: ID! + $title_contains: String + $description_contains: String + $location_contains: String + $first: Int + $skip: Int + ) { + eventsByOrganizationConnection( + where: { + organization_id: $organization_id + title_contains: $title_contains + description_contains: $description_contains + location_contains: $location_contains + } + first: $first + skip: $skip + ) { + _id + title + description + startDate + endDate + location + startTime + endTime + allDay + recurring + isPublic + isRegisterable + creator { + _id + firstName + lastName + } + registrants { + user { + _id + } + } + } + } +`; diff --git a/src/components/UserPortal/EventCard/EventCard.module.css b/src/components/UserPortal/EventCard/EventCard.module.css new file mode 100644 index 0000000000..28278dd5a6 --- /dev/null +++ b/src/components/UserPortal/EventCard/EventCard.module.css @@ -0,0 +1,26 @@ +.mainContainer { + width: 100%; + display: flex; + flex-direction: column; + padding: 10px; + cursor: pointer; + background-color: white; + border-radius: 10px; + box-shadow: 2px 2px 8px 0px #c8c8c8; + overflow: hidden; +} + +.eventDetails { + gap: 5px; +} + +.personImage { + border-radius: 50%; + margin-right: 20px; +} + +.eventActions { + display: flex; + flex-direction: row; + justify-content: right; +} diff --git a/src/components/UserPortal/EventCard/EventCard.tsx b/src/components/UserPortal/EventCard/EventCard.tsx new file mode 100644 index 0000000000..f688367af2 --- /dev/null +++ b/src/components/UserPortal/EventCard/EventCard.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import styles from './EventCard.module.css'; +import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; +import dayjs from 'dayjs'; +import { Button } from 'react-bootstrap'; +import { useMutation } from '@apollo/client'; +import { toast } from 'react-toastify'; +import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; + +import { REGISTER_EVENT } from 'GraphQl/Mutations/mutations'; + +interface InterfaceEventCardProps { + id: string; + title: string; + description: string; + location: string; + startDate: string; + endDate: string; + isRegisterable: boolean; + isPublic: boolean; + endTime: string; + startTime: string; + recurring: boolean; + allDay: boolean; + creator: { + firstName: string; + lastName: string; + id: string; + }; + registrants: { + id: string; + }[]; +} + +function eventCard(props: InterfaceEventCardProps): JSX.Element { + const userId = localStorage.getItem('userId'); + const creatorName = `${props.creator.firstName} ${props.creator.lastName}`; + const isInitiallyRegistered = props.registrants.some( + (registrant) => registrant.id === userId + ); + + const [registerEventMutation, { loading }] = useMutation(REGISTER_EVENT); + const [isRegistered, setIsRegistered] = React.useState(isInitiallyRegistered); + + const handleRegister = async (): Promise => { + if (!isRegistered) { + try { + const { data } = await registerEventMutation({ + variables: { + eventId: props.id, + }, + }); + /* istanbul ignore next */ + if (data) { + setIsRegistered(true); + toast.success(`Successfully registered for ${props.title}`); + } + } catch (error: any) { + /* istanbul ignore next */ + toast.error(error); + } + } + }; + + return ( +
+
+
+ {props.title} +
+
+ +
+
+ {props.description} + + {'Location '} + {props.location} + +
+ {'Starts '} + {props.startTime ? ( + {dayjs(`2015-03-04T${props.startTime}`).format('h:mm:ss A')} + ) : ( + <> + )} + {dayjs(props.startDate).format("D MMMM 'YY")} +
+
+ {'Ends '}{' '} + {props.endTime ? ( + {dayjs(`2015-03-04T${props.endTime}`).format('h:mm:ss A')} + ) : ( + <> + )}{' '} + {dayjs(props.endDate).format("D MMMM 'YY")} +
+ + {'Creator '} + {creatorName} + + +
+ {loading ? ( + + ) : isRegistered ? ( + + ) : ( + + )} +
+
+ ); +} + +export default eventCard; diff --git a/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.tsx b/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.tsx index b45a1892d5..570ea9de85 100644 --- a/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.tsx +++ b/src/components/UserPortal/OrganizationSidebar/OrganizationSidebar.tsx @@ -23,6 +23,8 @@ export default function organizationSidebar(): JSX.Element { const organizationId = getOrganizationId(window.location.href); const [members, setMembers]: any = React.useState([]); const [events, setEvents]: any = React.useState([]); + const eventsLink = `/user/events/id=${organizationId}`; + const peopleLink = `/user/people/id=${organizationId}`; const { data: memberData, loading: memberLoading } = useQuery( ORGANIZATIONS_MEMBER_CONNECTION_LIST, @@ -99,7 +101,7 @@ export default function organizationSidebar(): JSX.Element { )}
- + {t('viewAll')} @@ -145,7 +147,7 @@ export default function organizationSidebar(): JSX.Element { )}
- + {t('viewAll')} diff --git a/src/components/UserPortal/UserSidebar/UserSidebar.tsx b/src/components/UserPortal/UserSidebar/UserSidebar.tsx index b122b9e229..2e455358b0 100644 --- a/src/components/UserPortal/UserSidebar/UserSidebar.tsx +++ b/src/components/UserPortal/UserSidebar/UserSidebar.tsx @@ -74,25 +74,29 @@ function userSidebar(): JSX.Element { {organizations.length ? ( organizations.map((organization: any, index) => { + const organizationUrl = `/user/organization/id=${organization._id}`; + return ( -
- -
- {organization.name} + +
+ +
+ {organization.name} +
-
+ ); }) diff --git a/src/screens/UserPortal/Events/Events.module.css b/src/screens/UserPortal/Events/Events.module.css new file mode 100644 index 0000000000..52a305cc20 --- /dev/null +++ b/src/screens/UserPortal/Events/Events.module.css @@ -0,0 +1,83 @@ +.borderNone { + border: none; +} + +.colorWhite { + color: white; +} + +.backgroundWhite { + background-color: white; +} + +.maxWidth { + max-width: 300px; +} + +.colorLight { + background-color: #f5f5f5; +} + +.mainContainer { + width: 50%; + flex-grow: 3; + padding: 40px; + max-height: 100%; + overflow: auto; +} + +.content { + height: fit-content; + min-height: calc(100% - 40px); +} + +.gap { + gap: 20px; +} + +.paddingY { + padding: 30px 0px; +} + +.containerHeight { + height: calc(100vh - 66px); +} + +.colorPrimary { + background: #31bb6b; + color: white; +} + +.eventActionsContainer { + display: flex; + flex-direction: row; + gap: 15px; +} + +.datePicker { + border-radius: 10px; + height: 40px; + text-align: center; + background-color: #f2f2f2; + border: none; + width: 100%; +} + +.modalBody { + display: flex; + flex-direction: column; + gap: 10px; +} + +.switchContainer { + display: flex; + align-items: center; +} + +.switches { + display: flex; + flex-direction: row; + gap: 20px; + flex-wrap: wrap; + margin-top: 20px; +} diff --git a/src/screens/UserPortal/Events/Events.tsx b/src/screens/UserPortal/Events/Events.tsx new file mode 100644 index 0000000000..03f9659faa --- /dev/null +++ b/src/screens/UserPortal/Events/Events.tsx @@ -0,0 +1,466 @@ +import React from 'react'; +import OrganizationNavbar from 'components/UserPortal/OrganizationNavbar/OrganizationNavbar'; +import OrganizationSidebar from 'components/UserPortal/OrganizationSidebar/OrganizationSidebar'; +import EventCard from 'components/UserPortal/EventCard/EventCard'; +import UserSidebar from 'components/UserPortal/UserSidebar/UserSidebar'; +import { Button, Dropdown, Form, InputGroup } from 'react-bootstrap'; +import PaginationList from 'components/PaginationList/PaginationList'; +import { + ORGANIZATION_EVENTS_CONNECTION, + ORGANIZATION_ADMINS_LIST, +} from 'GraphQl/Queries/Queries'; +import { useMutation, useQuery } from '@apollo/client'; +import { SearchOutlined } from '@mui/icons-material'; +import styles from './Events.module.css'; +import { useTranslation } from 'react-i18next'; +import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; +import getOrganizationId from 'utils/getOrganizationId'; +import Modal from 'react-bootstrap/Modal'; +import ReactDatePicker from 'react-datepicker'; +import { CREATE_EVENT_MUTATION } from 'GraphQl/Mutations/mutations'; +import dayjs from 'dayjs'; +import { toast } from 'react-toastify'; +import { errorHandler } from 'utils/errorHandler'; + +interface InterfaceEventCardProps { + id: string; + title: string; + description: string; + location: string; + startDate: string; + endDate: string; + isRegisterable: boolean; + isPublic: boolean; + endTime: string; + startTime: string; + recurring: boolean; + allDay: boolean; + creator: { + firstName: string; + lastName: string; + id: string; + }; + registrants: { + id: string; + }[]; +} + +export default function events(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'userOrganizations', + }); + + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + const [events, setEvents] = React.useState([]); + const [filterName, setFilterName] = React.useState(''); + const [mode, setMode] = React.useState(0); + const [showCreateEventModal, setShowCreateEventModal] = React.useState(false); + const [eventTitle, setEventTitle] = React.useState(''); + const [eventDescription, setEventDescription] = React.useState(''); + const [eventLocation, setEventLocation] = React.useState(''); + const [startDate, setStartDate] = React.useState(new Date()); + const [endDate, setEndDate] = React.useState(new Date()); + const [isPublic, setIsPublic] = React.useState(true); + const [isRegisterable, setIsRegisterable] = React.useState(true); + const [isRecurring, setIsRecurring] = React.useState(false); + const [isAllDay, setIsAllDay] = React.useState(true); + const [startTime, setStartTime] = React.useState('08:00:00'); + const [endTime, setEndTime] = React.useState('10:00:00'); + + const organizationId = getOrganizationId(window.location.href); + + const modes = ['List View', 'Calender View']; + + const { data, loading, refetch } = useQuery(ORGANIZATION_EVENTS_CONNECTION, { + variables: { + organization_id: organizationId, + title_contains: '', + }, + }); + + const [create, { loading: loading2 }] = useMutation(CREATE_EVENT_MUTATION); + + const createEvent = async (): Promise => { + try { + const { data: createEventData } = await create({ + variables: { + title: eventTitle, + description: eventDescription, + isPublic, + recurring: isRecurring, + isRegisterable: isRegisterable, + organizationId, + startDate: dayjs(startDate).format('YYYY-MM-DD'), + endDate: dayjs(endDate).format('YYYY-MM-DD'), + allDay: isAllDay, + location: eventLocation, + startTime: !isAllDay ? startTime + 'Z' : null, + endTime: !isAllDay ? endTime + 'Z' : null, + }, + }); + + /* istanbul ignore next */ + if (createEventData) { + toast.success(t('eventCreated')); + refetch(); + setEventTitle(''); + setEventDescription(''); + setEventLocation(''); + setStartDate(new Date()); + setEndDate(new Date()); + setStartTime('08:00:00'); + setEndTime('10:00:00'); + } + } catch (error: any) { + /* istanbul ignore next */ + errorHandler(t, error); + } + }; + + /* istanbul ignore next */ + const handleChangePage = ( + _event: React.MouseEvent | null, + newPage: number + ): void => { + setPage(newPage); + }; + + const toggleCreateEventModal = (): void => + setShowCreateEventModal(!showCreateEventModal); + + /* istanbul ignore next */ + const handleChangeRowsPerPage = ( + event: React.ChangeEvent + ): void => { + const newRowsPerPage = event.target.value; + + setRowsPerPage(parseInt(newRowsPerPage, 10)); + setPage(0); + }; + + const handleSearch = ( + event: React.ChangeEvent + ): void => { + const newFilter = event.target.value; + setFilterName(newFilter); + const filter = { + title_contains: newFilter, + }; + setPage(0); + refetch(filter); + }; + + const handleEventTitleChange = ( + event: React.ChangeEvent + ): void => { + setEventTitle(event.target.value); + }; + + const handleEventLocationChange = ( + event: React.ChangeEvent + ): void => { + setEventLocation(event.target.value); + }; + + const handleEventDescriptionChange = ( + event: React.ChangeEvent + ): void => { + setEventDescription(event.target.value); + }; + + const handleStartDateChange = (newDate: any): void => { + setStartDate(newDate); + }; + + const handleEndDateChange = (newDate: any): void => { + setEndDate(newDate); + }; + + /* istanbul ignore next */ + React.useEffect(() => { + if (data) { + setEvents(data.eventsByOrganizationConnection); + } + }, [data]); + + const navbarProps = { + currentPage: 'events', + }; + + return ( + <> + +
+ +
+
+ + + + + + +
+ + + + {modes[mode]} + + + {modes.map((value, index) => { + return ( + setMode(index)} + > + {value} + + ); + })} + + +
+
+
+
+ {loading ? ( +
+ Loading... +
+ ) : ( + <> + {events && events.length > 0 ? ( + (rowsPerPage > 0 + ? events.slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ) + : /* istanbul ignore next */ + events + ).map((event: any, index) => { + const registrants: any = []; + event.registrants.forEach((registrant: any) => { + const r = { + id: registrant.user._id, + }; + + registrants.push(r); + }); + + const creator: any = {}; + creator.firstName = event.creator.firstName; + creator.lastName = event.creator.lastName; + creator.id = event.creator._id; + + const cardProps: InterfaceEventCardProps = { + id: event._id, + title: event.title, + description: event.description, + location: event.location, + startDate: event.startDate, + endDate: event.endDate, + isRegisterable: event.isRegisterable, + isPublic: event.isPublic, + endTime: event.endTime, + startTime: event.startTime, + recurring: event.recurring, + allDay: event.allDay, + registrants, + creator, + }; + + return ; + }) + ) : ( + {t('nothingToShow')} + )} + + )} +
+ + + + + + +
+
+
+ + + +
Create Event
+ +
+ + + + Event Title + + + + + + + Event Description + + + + + + + Event Location + + + +
Select Start Date
+ +
Select End Date
+ +
+
+ + setIsPublic(!isPublic)} + /> +
+ +
+ + setIsRegisterable(!isRegisterable)} + /> +
+ +
+ + setIsRecurring(!isRecurring)} + /> +
+ +
+ + setIsAllDay(!isAllDay)} + /> +
+ + {!isAllDay && ( +
+
+ + setStartTime(e.target.value)} + /> +
+
+ + setEndTime(e.target.value)} + /> +
+
+ )} +
+
+ + + + +
+
+ + ); +} From a619df2600c6fbffd7cfbcc8e14c67384be161c9 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sat, 26 Aug 2023 00:01:23 +0530 Subject: [PATCH 20/29] Add multilingual support to Events screen --- public/locales/en.json | 28 +++++++++++++ public/locales/fr.json | 28 +++++++++++++ public/locales/hi.json | 28 +++++++++++++ public/locales/sp.json | 28 +++++++++++++ public/locales/zh.json | 28 +++++++++++++ .../UserPortal/EventCard/EventCard.tsx | 16 +++++--- src/screens/UserPortal/Events/Events.tsx | 39 +++++++++---------- 7 files changed, 169 insertions(+), 26 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 3ab250aca1..3246a6210b 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -552,5 +552,33 @@ "yourPreviousDonations": "Your Previous Donations", "donate": "Donate", "nothingToShow": "Nothing to show here." + }, + "userEvents": { + "nothingToShow": "Nothing to show here.", + "search": "Search", + "createEvent": "Create Event", + "eventTitle": "Event Title", + "eventDescription": "Event Description", + "eventLocation": "Event Location", + "startDate": "Select Start Date", + "endDate": "Select End Date", + "publicEvent": "Public Event", + "registerable": "Registerable", + "recurring": "Recurring", + "startTime": "Start Time", + "endTime": "End Time", + "cancel": "Cancel", + "create": "Create", + "listView": "List View", + "calendarView": "Calendar View", + "allDay": "All Day" + }, + "userEventCard": { + "location": "Location", + "starts": "Starts", + "ends": "Ends", + "creator": "Creator", + "alreadyRegistered": "Already registered", + "register": "Register" } } diff --git a/public/locales/fr.json b/public/locales/fr.json index 82a494117c..2ccb073fee 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -548,5 +548,33 @@ "yourPreviousDonations": "Vos dons précédents", "donate": "Donner", "nothingToShow": "Rien à montrer ici." + }, + "userEvents": { + "nothingToShow": "Rien à montrer ici.", + "search": "Recherche", + "createEvent": "Créer un évènement", + "eventTitle": "Titre de l'événement", + "eventDescription": "Description de l'évenement", + "eventLocation": "Lieu de l'événement", + "startDate": "Sélectionnez la date de début", + "endDate": "Sélectionnez la date de fin", + "publicEvent": "Évennement publique", + "registerable": "Enregistrable", + "recurring": "Récurrente", + "startTime": "Heure de début", + "endTime": "Heure de fin", + "cancel": "Annuler", + "create": "Créer", + "listView": "Vue en liste", + "calendarView": "Vue du calendrier", + "allDay": "Toute la journée" + }, + "userEventCard": { + "location": "Emplacement", + "starts": "Départs", + "ends": "Prend fin", + "creator": "Créatrice", + "alreadyRegistered": "Déjà enregistré", + "register": "Registre" } } diff --git a/public/locales/hi.json b/public/locales/hi.json index 6fc8725f4a..55e9c08214 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -548,5 +548,33 @@ "yourPreviousDonations": "आपका पिछला दान", "donate": "दान", "nothingToShow": "यहां दिखाने के लिए कुछ भी नहीं है." + }, + "userEvents": { + "nothingToShow": "यहां दिखाने के लिए कुछ भी नहीं है.", + "search": "खोज", + "createEvent": "कार्यक्रम बनाएँ", + "eventTitle": "कार्यक्रम का शीर्षक", + "eventDescription": "घटना विवरण", + "eventLocation": "घटना स्थान", + "startDate": "आरंभ तिथि चुनें", + "endDate": "अंतिम तिथि चुनें", + "publicEvent": "सार्वजनिक समारोह", + "registerable": "पंजीकरण योग्य", + "recurring": "पुनरावर्ती", + "startTime": "समय शुरू", + "endTime": "अंत समय", + "cancel": "रद्द करना", + "create": "बनाएं", + "listView": "लिस्ट व्यू", + "calendarView": "कैलेंडर दृश्य", + "allDay": "पूरे दिन" + }, + "userEventCard": { + "location": "जगह", + "starts": "प्रारंभ होगा", + "ends": "समाप्त होगा", + "creator": "निर्माता", + "alreadyRegistered": "पहले से ही पंजीकृत", + "register": "पंजीकरण करवाना" } } diff --git a/public/locales/sp.json b/public/locales/sp.json index 6ee06204cb..b42f1b85d3 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -548,5 +548,33 @@ "yourPreviousDonations": "Tus donaciones anteriores", "donate": "Donar", "nothingToShow": "Nada que mostrar aquí." + }, + "userEvents": { + "nothingToShow": "No hay nada que mostrar aquí.", + "search": "Buscar", + "createEvent": "Crear evento", + "eventTitle": "Título del evento", + "eventDescription": "Descripción del evento", + "eventLocation": "Lugar del evento", + "startDate": "Seleccione la fecha de inicio", + "endDate": "Seleccionar fecha de finalización", + "publicEvent": "Evento público", + "registerable": "Registrable", + "recurring": "Periódica", + "startTime": "Hora de inicio", + "endTime": "Hora de finalización", + "cancel": "Cancelar", + "create": "Crear", + "listView": "Vista de la lista", + "calendarView": "Vista de calendario", + "allDay": "Todo el dia" + }, + "userEventCard": { + "location": "Ubicación", + "starts": "Empieza", + "ends": "Termina", + "creator": "Creadora", + "alreadyRegistered": "Ya registrado", + "register": "Registro" } } diff --git a/public/locales/zh.json b/public/locales/zh.json index b762f994b2..832936c3f2 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -548,5 +548,33 @@ "yourPreviousDonations": "您之前的捐款", "donate": "捐", "nothingToShow": "這裡沒有什麼可顯示的。" + }, + "userEvents": { + "nothingToShow": "這裡沒有什麼可顯示的。", + "search": "搜索", + "createEvent": "創建事件", + "eventTitle": "活動標題", + "eventDescription": "活動說明", + "eventLocation": "活動地點", + "startDate": "選擇開始日期", + "endDate": "選擇結束日期", + "publicEvent": "公共活動", + "registerable": "可註冊", + "recurring": "再次發生的", + "startTime": "開始時間", + "endTime": "時間結束", + "cancel": "取消", + "create": "創造", + "listView": "列表顯示", + "calendarView": "日曆視圖", + "allDay": "整天" + }, + "userEventCard": { + "location": "地點", + "starts": "開始", + "ends": "結束", + "creator": "創作者", + "alreadyRegistered": "已經註冊", + "register": "登記" } } diff --git a/src/components/UserPortal/EventCard/EventCard.tsx b/src/components/UserPortal/EventCard/EventCard.tsx index f688367af2..5aac562c06 100644 --- a/src/components/UserPortal/EventCard/EventCard.tsx +++ b/src/components/UserPortal/EventCard/EventCard.tsx @@ -8,6 +8,7 @@ import { toast } from 'react-toastify'; import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; import { REGISTER_EVENT } from 'GraphQl/Mutations/mutations'; +import { useTranslation } from 'react-i18next'; interface InterfaceEventCardProps { id: string; @@ -33,6 +34,9 @@ interface InterfaceEventCardProps { } function eventCard(props: InterfaceEventCardProps): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'userEventCard', + }); const userId = localStorage.getItem('userId'); const creatorName = `${props.creator.firstName} ${props.creator.lastName}`; const isInitiallyRegistered = props.registrants.some( @@ -74,11 +78,11 @@ function eventCard(props: InterfaceEventCardProps): JSX.Element {
{props.description} - {'Location '} + {`${t('location')} `} {props.location}
- {'Starts '} + {`${t('starts')} `} {props.startTime ? ( {dayjs(`2015-03-04T${props.startTime}`).format('h:mm:ss A')} ) : ( @@ -87,7 +91,7 @@ function eventCard(props: InterfaceEventCardProps): JSX.Element { {dayjs(props.startDate).format("D MMMM 'YY")}
- {'Ends '}{' '} + {`${t('ends')} `} {props.endTime ? ( {dayjs(`2015-03-04T${props.endTime}`).format('h:mm:ss A')} ) : ( @@ -96,7 +100,7 @@ function eventCard(props: InterfaceEventCardProps): JSX.Element { {dayjs(props.endDate).format("D MMMM 'YY")}
- {'Creator '} + {`${t('creator')} `} {creatorName} @@ -105,11 +109,11 @@ function eventCard(props: InterfaceEventCardProps): JSX.Element { ) : isRegistered ? ( ) : ( )}
diff --git a/src/screens/UserPortal/Events/Events.tsx b/src/screens/UserPortal/Events/Events.tsx index 03f9659faa..cfe7adef0d 100644 --- a/src/screens/UserPortal/Events/Events.tsx +++ b/src/screens/UserPortal/Events/Events.tsx @@ -5,10 +5,7 @@ import EventCard from 'components/UserPortal/EventCard/EventCard'; import UserSidebar from 'components/UserPortal/UserSidebar/UserSidebar'; import { Button, Dropdown, Form, InputGroup } from 'react-bootstrap'; import PaginationList from 'components/PaginationList/PaginationList'; -import { - ORGANIZATION_EVENTS_CONNECTION, - ORGANIZATION_ADMINS_LIST, -} from 'GraphQl/Queries/Queries'; +import { ORGANIZATION_EVENTS_CONNECTION } from 'GraphQl/Queries/Queries'; import { useMutation, useQuery } from '@apollo/client'; import { SearchOutlined } from '@mui/icons-material'; import styles from './Events.module.css'; @@ -47,7 +44,7 @@ interface InterfaceEventCardProps { export default function events(): JSX.Element { const { t } = useTranslation('translation', { - keyPrefix: 'userOrganizations', + keyPrefix: 'userEvents', }); const [page, setPage] = React.useState(0); @@ -70,7 +67,7 @@ export default function events(): JSX.Element { const organizationId = getOrganizationId(window.location.href); - const modes = ['List View', 'Calender View']; + const modes = [t('listView'), t('calendarView')]; const { data, loading, refetch } = useQuery(ORGANIZATION_EVENTS_CONNECTION, { variables: { @@ -79,7 +76,7 @@ export default function events(): JSX.Element { }, }); - const [create, { loading: loading2 }] = useMutation(CREATE_EVENT_MUTATION); + const [create] = useMutation(CREATE_EVENT_MUTATION); const createEvent = async (): Promise => { try { @@ -213,7 +210,9 @@ export default function events(): JSX.Element {
- + -
Create Event
+
{t('createEvent')}
@@ -330,7 +329,7 @@ export default function events(): JSX.Element { - Event Title + {t('eventTitle')} - Event Description + {t('eventDescription')} - Event Location + {t('eventLocation')} -
Select Start Date
+
{t('startDate')}
-
Select End Date
+
{t('endDate')}
- +
- +
- +
- + From 997de71523881d9d7ada264186badeb773d03890 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sat, 26 Aug 2023 16:46:55 +0530 Subject: [PATCH 21/29] Fix failing tests due to merge --- package-lock.json | 147 +++++++++++++++++++++++++++++ src/GraphQl/Mutations/mutations.ts | 6 +- 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 1795dfa860..8709de9722 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,8 @@ "react-toastify": "^9.0.3", "redux": "^4.1.1", "redux-thunk": "^2.3.0", + "typedoc": "^0.24.8", + "typedoc-plugin-markdown": "^3.15.4", "typescript": "^4.3.5", "web-vitals": "^1.0.1", "yarn": "^1.22.17" @@ -5896,6 +5898,11 @@ "node": ">=8" } }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==" + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -11465,6 +11472,34 @@ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -15686,6 +15721,11 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -16053,6 +16093,11 @@ "yallist": "^3.0.2" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -16186,6 +16231,17 @@ "node": ">=0.10.0" } }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -21707,6 +21763,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shiki": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.3.tgz", + "integrity": "sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g==", + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -23433,6 +23500,59 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typedoc": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", + "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.0", + "shiki": "^0.14.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 14.14" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x" + } + }, + "node_modules/typedoc-plugin-markdown": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.16.0.tgz", + "integrity": "sha512-eeiC78fDNGFwemPIHiwRC+mEC7W5jwt3fceUev2gJ2nFnXpVHo8eRrpC9BLWZDee6ehnz/sPmNjizbXwpfaTBw==", + "dependencies": { + "handlebars": "^4.7.7" + }, + "peerDependencies": { + "typedoc": ">=0.24.0" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -23445,6 +23565,18 @@ "node": ">=4.2.0" } }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -23902,6 +24034,16 @@ "node": ">=0.10.0" } }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -24378,6 +24520,11 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, "node_modules/workbox-background-sync": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", diff --git a/src/GraphQl/Mutations/mutations.ts b/src/GraphQl/Mutations/mutations.ts index 79ddfd2881..8287e44bad 100644 --- a/src/GraphQl/Mutations/mutations.ts +++ b/src/GraphQl/Mutations/mutations.ts @@ -460,7 +460,11 @@ export const UNLIKE_POST = gql` export const REGISTER_EVENT = gql` mutation registerForEvent($eventId: ID!) { registerForEvent(id: $eventId) { -======= + _id + } + } +`; + export const ADD_EVENT_PROJECT_MUTATION = gql` mutation AddEventProject( $title: String! From 02819f1d5c99109c46680f455154d7d080dcd434 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sat, 2 Sep 2023 00:14:23 +0530 Subject: [PATCH 22/29] Add Post Comment functionality --- public/locales/en.json | 3 +- public/locales/fr.json | 3 +- public/locales/hi.json | 3 +- public/locales/sp.json | 3 +- public/locales/zh.json | 3 +- src/GraphQl/Mutations/mutations.ts | 35 ++ src/GraphQl/Queries/Queries.ts | 14 + .../CommentCard/CommentCard.module.css | 46 ++ .../CommentCard/CommentCard.test.tsx | 229 +++++++++ .../UserPortal/CommentCard/CommentCard.tsx | 101 ++++ .../UserPortal/PostCard/PostCard.module.css | 26 + .../UserPortal/PostCard/PostCard.test.tsx | 145 +++++- .../UserPortal/PostCard/PostCard.tsx | 245 +++++++-- src/screens/OrgPost/OrgPost.test.tsx | 3 + src/screens/UserPortal/Events/Events.test.tsx | 482 ++++++++++++++++++ src/screens/UserPortal/Events/Events.tsx | 32 +- src/screens/UserPortal/Home/Home.test.tsx | 37 +- src/screens/UserPortal/Home/Home.tsx | 41 ++ 18 files changed, 1396 insertions(+), 55 deletions(-) create mode 100644 src/components/UserPortal/CommentCard/CommentCard.module.css create mode 100644 src/components/UserPortal/CommentCard/CommentCard.test.tsx create mode 100644 src/components/UserPortal/CommentCard/CommentCard.tsx create mode 100644 src/screens/UserPortal/Events/Events.test.tsx diff --git a/public/locales/en.json b/public/locales/en.json index 3246a6210b..76bdf689e8 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -571,7 +571,8 @@ "create": "Create", "listView": "List View", "calendarView": "Calendar View", - "allDay": "All Day" + "allDay": "All Day", + "eventCreated": "Event created and posted successfully." }, "userEventCard": { "location": "Location", diff --git a/public/locales/fr.json b/public/locales/fr.json index 2ccb073fee..dbe193f42e 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -567,7 +567,8 @@ "create": "Créer", "listView": "Vue en liste", "calendarView": "Vue du calendrier", - "allDay": "Toute la journée" + "allDay": "Toute la journée", + "eventCreated": "Événement créé et publié avec succès." }, "userEventCard": { "location": "Emplacement", diff --git a/public/locales/hi.json b/public/locales/hi.json index 55e9c08214..cce6789e9b 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -567,7 +567,8 @@ "create": "बनाएं", "listView": "लिस्ट व्यू", "calendarView": "कैलेंडर दृश्य", - "allDay": "पूरे दिन" + "allDay": "पूरे दिन", + "eventCreated": "ईवेंट सफलतापूर्वक बनाया और पोस्ट किया गया." }, "userEventCard": { "location": "जगह", diff --git a/public/locales/sp.json b/public/locales/sp.json index b42f1b85d3..9d30d41624 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -567,7 +567,8 @@ "create": "Crear", "listView": "Vista de la lista", "calendarView": "Vista de calendario", - "allDay": "Todo el dia" + "allDay": "Todo el dia", + "eventCreated": "Evento creado y publicado exitosamente." }, "userEventCard": { "location": "Ubicación", diff --git a/public/locales/zh.json b/public/locales/zh.json index 832936c3f2..01663b4e49 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -567,7 +567,8 @@ "create": "創造", "listView": "列表顯示", "calendarView": "日曆視圖", - "allDay": "整天" + "allDay": "整天", + "eventCreated": "活動已成功創建並發布。" }, "userEventCard": { "location": "地點", diff --git a/src/GraphQl/Mutations/mutations.ts b/src/GraphQl/Mutations/mutations.ts index 8287e44bad..bf2f537656 100644 --- a/src/GraphQl/Mutations/mutations.ts +++ b/src/GraphQl/Mutations/mutations.ts @@ -587,3 +587,38 @@ export const MARK_CHECKIN = gql` } } `; + +export const CREATE_COMMENT_POST = gql` + mutation createComment($comment: String!, $postId: ID!) { + createComment(data: { text: $comment }, postId: $postId) { + _id + creator { + _id + firstName + lastName + email + } + likeCount + likedBy { + _id + } + text + } + } +`; + +export const LIKE_COMMENT = gql` + mutation likeComment($commentId: ID!) { + likeComment(id: $commentId) { + _id + } + } +`; + +export const UNLIKE_COMMENT = gql` + mutation unlikeComment($commentId: ID!) { + unlikeComment(id: $commentId) { + _id + } + } +`; diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index 36827da8e9..ce8c190442 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -512,6 +512,20 @@ export const ORGANIZATION_POST_CONNECTION_LIST = gql` } likeCount commentCount + comments { + _id + creator { + _id + firstName + lastName + email + } + likeCount + likedBy { + _id + } + text + } likedBy { _id firstName diff --git a/src/components/UserPortal/CommentCard/CommentCard.module.css b/src/components/UserPortal/CommentCard/CommentCard.module.css new file mode 100644 index 0000000000..1124f6369d --- /dev/null +++ b/src/components/UserPortal/CommentCard/CommentCard.module.css @@ -0,0 +1,46 @@ +.mainContainer { + width: 100%; + display: flex; + flex-direction: row; + padding: 10px; + background-color: white; + border-radius: 10px; + box-shadow: 2px 2px 8px 0px #c8c8c8; + overflow: hidden; + margin-top: 10px; +} + +.personDetails { + display: flex; + flex-direction: column; + justify-content: center; +} + +.personImage { + border-radius: 50%; + margin-right: 20px; +} + +.cardActions { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; + margin-top: 10px; +} + +.cardActionBtn { + background-color: rgba(0, 0, 0, 0); + border: none; + color: black; +} + +.cardActionBtn:hover { + background-color: ghostwhite; + border: none; + color: green !important; +} + +.likeIcon { + width: 20px; +} diff --git a/src/components/UserPortal/CommentCard/CommentCard.test.tsx b/src/components/UserPortal/CommentCard/CommentCard.test.tsx new file mode 100644 index 0000000000..560e51b3df --- /dev/null +++ b/src/components/UserPortal/CommentCard/CommentCard.test.tsx @@ -0,0 +1,229 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import { act, render, screen } from '@testing-library/react'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import cookies from 'js-cookie'; +import { StaticMockLink } from 'utils/StaticMockLink'; + +import CommentCard from './CommentCard'; +import userEvent from '@testing-library/user-event'; +import { LIKE_COMMENT, UNLIKE_COMMENT } from 'GraphQl/Mutations/mutations'; + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +const MOCKS = [ + { + request: { + query: LIKE_COMMENT, + variables: { + commentId: '1', + }, + result: { + data: { + likeComment: { + _id: '1', + }, + }, + }, + }, + }, + { + request: { + query: UNLIKE_COMMENT, + variables: { + commentId: '1', + }, + result: { + data: { + unlikeComment: { + _id: '1', + }, + }, + }, + }, + }, +]; + +const link = new StaticMockLink(MOCKS, true); + +describe('Testing CommentCard Component [User Portal]', () => { + afterEach(async () => { + await act(async () => { + await i18nForTest.changeLanguage('en'); + }); + }); + + test('Component should be rendered properly if comment is already liked by the user.', async () => { + const cardProps = { + id: '1', + creator: { + id: '1', + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + }, + likeCount: 1, + likedBy: [ + { + id: '1', + }, + ], + text: 'testComment', + }; + + const beforeUserId = localStorage.getItem('userId'); + localStorage.setItem('userId', '2'); + + render( + + + + + + + + + + ); + + await wait(); + if (beforeUserId) { + localStorage.setItem('userId', beforeUserId); + } + }); + + test('Component should be rendered properly if comment is not already liked by the user.', async () => { + const cardProps = { + id: '1', + creator: { + id: '1', + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + }, + likeCount: 1, + likedBy: [ + { + id: '1', + }, + ], + text: 'testComment', + }; + + const beforeUserId = localStorage.getItem('userId'); + localStorage.setItem('userId', '1'); + + render( + + + + + + + + + + ); + + await wait(); + if (beforeUserId) { + localStorage.setItem('userId', beforeUserId); + } + }); + + test('Component renders as expected if user likes the comment.', async () => { + const cardProps = { + id: '1', + creator: { + id: '1', + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + }, + likeCount: 1, + likedBy: [ + { + id: '1', + }, + ], + text: 'testComment', + }; + + const beforeUserId = localStorage.getItem('userId'); + localStorage.setItem('userId', '2'); + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('likeCommentBtn')); + + await wait(); + + if (beforeUserId) { + localStorage.setItem('userId', beforeUserId); + } + }); + + test('Component renders as expected if user unlikes the comment.', async () => { + const cardProps = { + id: '1', + creator: { + id: '1', + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + }, + likeCount: 1, + likedBy: [ + { + id: '1', + }, + ], + text: 'testComment', + }; + + const beforeUserId = localStorage.getItem('userId'); + localStorage.setItem('userId', '1'); + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('likeCommentBtn')); + + if (beforeUserId) { + localStorage.setItem('userId', beforeUserId); + } + }); +}); diff --git a/src/components/UserPortal/CommentCard/CommentCard.tsx b/src/components/UserPortal/CommentCard/CommentCard.tsx new file mode 100644 index 0000000000..8b2d5a8fca --- /dev/null +++ b/src/components/UserPortal/CommentCard/CommentCard.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { Button } from 'react-bootstrap'; +import styles from './CommentCard.module.css'; +import ThumbUpIcon from '@mui/icons-material/ThumbUp'; +import { useMutation } from '@apollo/client'; +import { LIKE_COMMENT, UNLIKE_COMMENT } from 'GraphQl/Mutations/mutations'; +import { toast } from 'react-toastify'; +import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; +import ThumbUpOffAltIcon from '@mui/icons-material/ThumbUpOffAlt'; + +interface InterfaceCommentCardProps { + id: string; + creator: { + id: string; + firstName: string; + lastName: string; + email: string; + }; + likeCount: number; + likedBy: { + id: string; + }[]; + text: string; +} + +function commentCard(props: InterfaceCommentCardProps): JSX.Element { + const creatorName = `${props.creator.firstName} ${props.creator.lastName}`; + + const userId = localStorage.getItem('userId'); + const likedByUser = props.likedBy.some((likedBy) => likedBy.id === userId); + + const [likes, setLikes] = React.useState(props.likeCount); + const [isLikedByUser, setIsLikedByUser] = React.useState(likedByUser); + const [likeComment, { loading: likeLoading }] = useMutation(LIKE_COMMENT); + const [unlikeComment, { loading: unlikeLoading }] = + useMutation(UNLIKE_COMMENT); + + const handleToggleLike = async (): Promise => { + if (isLikedByUser) { + try { + const { data } = await unlikeComment({ + variables: { + commentId: props.id, + }, + }); + /* istanbul ignore next */ + if (data) { + setLikes((likes) => likes - 1); + setIsLikedByUser(false); + } + } catch (error: any) { + /* istanbul ignore next */ + toast.error(error); + } + } else { + try { + const { data } = await likeComment({ + variables: { + commentId: props.id, + }, + }); + /* istanbul ignore next */ + if (data) { + setLikes((likes) => likes + 1); + setIsLikedByUser(true); + } + } catch (error: any) { + /* istanbul ignore next */ + toast.error(error); + } + } + }; + + return ( +
+
+ {creatorName} + {props.text} +
+ + {`${likes} Likes`} +
+
+
+ ); +} + +export default commentCard; diff --git a/src/components/UserPortal/PostCard/PostCard.module.css b/src/components/UserPortal/PostCard/PostCard.module.css index 1cab3b2abd..56158473f9 100644 --- a/src/components/UserPortal/PostCard/PostCard.module.css +++ b/src/components/UserPortal/PostCard/PostCard.module.css @@ -27,3 +27,29 @@ gap: 10px; align-items: center; } + +.creatorNameModal { + display: flex; + flex-direction: row; + gap: 5px; + align-items: center; + margin-bottom: 10px; +} + +.modalActions { + display: flex; + flex-direction: row; + align-items: center; + gap: 1px; + margin: 5px 0px; +} + +.textModal { + margin-top: 10px; +} + +.colorPrimary { + background: #31bb6b; + color: white; + cursor: pointer; +} diff --git a/src/components/UserPortal/PostCard/PostCard.test.tsx b/src/components/UserPortal/PostCard/PostCard.test.tsx index 3bdf488fb6..acd6176b02 100644 --- a/src/components/UserPortal/PostCard/PostCard.test.tsx +++ b/src/components/UserPortal/PostCard/PostCard.test.tsx @@ -10,7 +10,11 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import PostCard from './PostCard'; import userEvent from '@testing-library/user-event'; -import { LIKE_POST, UNLIKE_POST } from 'GraphQl/Mutations/mutations'; +import { + CREATE_COMMENT_POST, + LIKE_POST, + UNLIKE_POST, +} from 'GraphQl/Mutations/mutations'; const MOCKS = [ { @@ -43,6 +47,33 @@ const MOCKS = [ }, }, }, + { + request: { + query: CREATE_COMMENT_POST, + variables: { + postId: '1', + comment: 'testComment', + }, + result: { + data: { + createComment: { + _id: '64ef885bca85de60ebe0f304', + creator: { + _id: '63d6064458fce20ee25c3bf7', + firstName: 'Noble', + lastName: 'Mittal', + email: 'test@gmail.com', + __typename: 'User', + }, + likeCount: 0, + likedBy: [], + text: 'testComment', + __typename: 'Comment', + }, + }, + }, + }, + }, ]; async function wait(ms = 100): Promise { @@ -70,7 +101,23 @@ describe('Testing PostCard Component [User Portal]', () => { text: 'This is post test text', title: 'This is post test title', likeCount: 1, - commentCount: 0, + commentCount: 1, + comments: [ + { + _id: '64eb13beca85de60ebe0ed0e', + creator: { + _id: '63d6064458fce20ee25c3bf7', + firstName: 'Noble', + lastName: 'Mittal', + email: 'test@gmail.com', + __typename: 'User', + }, + likeCount: 0, + likedBy: [], + text: 'First comment from Talawa user portal.', + __typename: 'Comment', + }, + ], likedBy: [ { firstName: '', @@ -113,6 +160,7 @@ describe('Testing PostCard Component [User Portal]', () => { title: 'This is post test title', likeCount: 1, commentCount: 0, + comments: [], likedBy: [ { firstName: 'test', @@ -159,6 +207,7 @@ describe('Testing PostCard Component [User Portal]', () => { title: 'This is post test title', likeCount: 1, commentCount: 0, + comments: [], likedBy: [ { firstName: 'test', @@ -207,6 +256,7 @@ describe('Testing PostCard Component [User Portal]', () => { title: 'This is post test title', likeCount: 1, commentCount: 0, + comments: [], likedBy: [ { firstName: 'test', @@ -252,6 +302,7 @@ describe('Testing PostCard Component [User Portal]', () => { title: 'This is post test title', likeCount: 1, commentCount: 0, + comments: [], likedBy: [ { firstName: 'test', @@ -275,4 +326,94 @@ describe('Testing PostCard Component [User Portal]', () => { await wait(); }); + + test('Comment is created successfully after create comment button is clicked.', async () => { + const cardProps = { + id: '1', + creator: { + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + id: '1', + }, + image: 'testImage', + video: '', + text: 'This is post test text', + title: 'This is post test title', + likeCount: 1, + commentCount: 0, + comments: [], + likedBy: [ + { + firstName: 'test', + lastName: 'user', + id: '1', + }, + ], + }; + + render( + + + + + + + + + + ); + + const randomComment = 'testComment'; + + userEvent.click(screen.getByTestId('showCommentsBtn')); + + userEvent.type(screen.getByTestId('commentInput'), randomComment); + userEvent.click(screen.getByTestId('createCommentBtn')); + + await wait(); + }); + + test('Comment modal pops when show comments button is clicked.', async () => { + const cardProps = { + id: '', + creator: { + firstName: 'test', + lastName: 'user', + email: 'test@user.com', + id: '1', + }, + image: 'testImage', + video: '', + text: 'This is post test text', + title: 'This is post test title', + likeCount: 1, + commentCount: 0, + comments: [], + likedBy: [ + { + firstName: 'test', + lastName: 'user', + id: '1', + }, + ], + }; + + render( + + + + + + + + + + ); + + await wait(); + + userEvent.click(screen.getByTestId('showCommentsBtn')); + expect(screen.findAllByText('Comments')).not.toBeNull(); + }); }); diff --git a/src/components/UserPortal/PostCard/PostCard.tsx b/src/components/UserPortal/PostCard/PostCard.tsx index dacc30a65b..0374858f23 100644 --- a/src/components/UserPortal/PostCard/PostCard.tsx +++ b/src/components/UserPortal/PostCard/PostCard.tsx @@ -1,15 +1,22 @@ import React from 'react'; -import { Button, Card } from 'react-bootstrap'; +import { Button, Card, Form, InputGroup, Modal } from 'react-bootstrap'; import ThumbUpIcon from '@mui/icons-material/ThumbUp'; import CommentIcon from '@mui/icons-material/Comment'; import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import styles from './PostCard.module.css'; import { useMutation } from '@apollo/client'; -import { LIKE_POST, UNLIKE_POST } from 'GraphQl/Mutations/mutations'; +import { + CREATE_COMMENT_POST, + LIKE_POST, + UNLIKE_POST, +} from 'GraphQl/Mutations/mutations'; import { toast } from 'react-toastify'; import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; import ThumbUpOffAltIcon from '@mui/icons-material/ThumbUpOffAlt'; import { useTranslation } from 'react-i18next'; +import SendIcon from '@mui/icons-material/Send'; +import { errorHandler } from 'utils/errorHandler'; +import CommentCard from '../CommentCard/CommentCard'; interface InterfacePostCardProps { id: string; @@ -25,6 +32,19 @@ interface InterfacePostCardProps { title: string; likeCount: number; commentCount: number; + comments: { + creator: { + _id: string; + firstName: string; + lastName: string; + email: string; + }; + likeCount: number; + likedBy: { + id: string; + }[]; + text: string; + }[]; likedBy: { firstName: string; lastName: string; @@ -32,6 +52,21 @@ interface InterfacePostCardProps { }[]; } +interface InterfaceCommentCardProps { + id: string; + creator: { + id: string; + firstName: string; + lastName: string; + email: string; + }; + likeCount: number; + likedBy: { + id: string; + }[]; + text: string; +} + export default function postCard(props: InterfacePostCardProps): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'postCard', @@ -39,14 +74,22 @@ export default function postCard(props: InterfacePostCardProps): JSX.Element { const userId = localStorage.getItem('userId'); const likedByUser = props.likedBy.some((likedBy) => likedBy.id === userId); + const [comments, setComments] = React.useState(props.comments); + const [numComments, setNumComments] = React.useState(props.commentCount); const [likes, setLikes] = React.useState(props.likeCount); const [isLikedByUser, setIsLikedByUser] = React.useState(likedByUser); + const [showComments, setShowComments] = React.useState(false); + const [commentInput, setCommentInput] = React.useState(''); const postCreator = `${props.creator.firstName} ${props.creator.lastName}`; const [likePost, { loading: likeLoading }] = useMutation(LIKE_POST); const [unLikePost, { loading: unlikeLoading }] = useMutation(UNLIKE_POST); + const [create, { loading: commentLoading }] = + useMutation(CREATE_COMMENT_POST); + + const toggleCommentsModal = (): void => setShowComments(!showComments); const handleToggleLike = async (): Promise => { if (isLikedByUser) { @@ -84,45 +127,165 @@ export default function postCard(props: InterfacePostCardProps): JSX.Element { } }; + const handleCommentInput = ( + event: React.ChangeEvent + ): void => { + const comment = event.target.value; + setCommentInput(comment); + }; + + const createComment = async (): Promise => { + try { + const { data: createEventData } = await create({ + variables: { + postId: props.id, + comment: commentInput, + }, + }); + + /* istanbul ignore next */ + if (createEventData) { + setCommentInput(''); + setNumComments((numComments) => numComments + 1); + + const newComment: any = { + id: createEventData.createComment._id, + creator: { + id: createEventData.createComment.creator.id, + firstName: createEventData.createComment.creator.firstName, + lastName: createEventData.createComment.creator.lastName, + email: createEventData.createComment.creator.email, + }, + likeCount: createEventData.createComment.likeCount, + likedBy: createEventData.createComment.likedBy, + text: createEventData.createComment.text, + }; + + setComments([...comments, newComment]); + } + } catch (error: any) { + /* istanbul ignore next */ + errorHandler(t, error); + } + }; + return ( - - -
- - {postCreator} -
-
- - {props.title} - {props.text} - {props.image && ( - - )} - - -
- - {likes} - {` ${t('likes')}`} - - {props.commentCount} - {` ${t('comments')}`} -
-
-
+ <> + + +
+ + {postCreator} +
+
+ + {props.title} + {props.text} + {props.image && ( + + )} + + +
+ + {likes} + {` ${t('likes')}`} + + {numComments} + {` ${t('comments')}`} +
+
+
+ + +
+ + {postCreator} +
+ {props.image && ( + + )} +
{props.text}
+
+ + {likes} + {` ${t('likes')}`} +
+

Comments

+ {numComments ? ( + comments.map((comment: any, index: any) => { + const cardProps: InterfaceCommentCardProps = { + id: comment.id, + creator: { + id: comment.creator.id, + firstName: comment.creator.firstName, + lastName: comment.creator.lastName, + email: comment.creator.email, + }, + likeCount: comment.likeCount, + likedBy: comment.likedBy, + text: comment.text, + }; + + return ; + }) + ) : ( + <>No comments to show. + )} +
+ + + + {commentLoading ? ( + + ) : ( + + )} + + +
+
+ ); } diff --git a/src/screens/OrgPost/OrgPost.test.tsx b/src/screens/OrgPost/OrgPost.test.tsx index 478095c323..1db08a4179 100644 --- a/src/screens/OrgPost/OrgPost.test.tsx +++ b/src/screens/OrgPost/OrgPost.test.tsx @@ -43,6 +43,7 @@ const MOCKS = [ }, likeCount: 0, commentCount: 0, + comments: [], likedBy: [], }, { @@ -60,6 +61,7 @@ const MOCKS = [ likeCount: 0, commentCount: 0, likedBy: [], + comments: [], }, ], }, @@ -143,6 +145,7 @@ describe('Organisation Post Page', () => { }, likeCount: 0, commentCount: 0, + comments: [], likedBy: [], }); }); diff --git a/src/screens/UserPortal/Events/Events.test.tsx b/src/screens/UserPortal/Events/Events.test.tsx new file mode 100644 index 0000000000..cc72a4031f --- /dev/null +++ b/src/screens/UserPortal/Events/Events.test.tsx @@ -0,0 +1,482 @@ +import React from 'react'; +import { act, render, screen } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import { I18nextProvider } from 'react-i18next'; + +import { ORGANIZATION_EVENTS_CONNECTION } from 'GraphQl/Queries/Queries'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import Events from './Events'; +import userEvent from '@testing-library/user-event'; +import * as getOrganizationId from 'utils/getOrganizationId'; +import { CREATE_EVENT_MUTATION } from 'GraphQl/Mutations/mutations'; +import { toast } from 'react-toastify'; +import dayjs from 'dayjs'; + +jest.mock('react-toastify', () => ({ + toast: { + error: jest.fn(), + info: jest.fn(), + success: jest.fn(), + }, +})); + +const MOCKS = [ + { + request: { + query: ORGANIZATION_EVENTS_CONNECTION, + variables: { + organization_id: '', + title_contains: '', + }, + }, + result: { + data: { + eventsByOrganizationConnection: [ + { + _id: '6404a267cc270739118e2349', + title: 'NewEvent', + description: 'sdadsasad', + startDate: '2023-03-05', + endDate: '2023-03-05', + location: 'NewLocation', + startTime: null, + endTime: null, + allDay: true, + recurring: false, + isPublic: true, + isRegisterable: false, + creator: { + _id: '63d649417ffe6e4d5174ea32', + firstName: 'Noble', + lastName: 'Mittal', + __typename: 'User', + }, + registrants: [ + { + user: { + _id: '63d649417ffe6e4d5174ea32', + __typename: 'User', + }, + __typename: 'UserAttende', + }, + { + user: { + _id: '63d6064458fce20ee25c3bf7', + __typename: 'User', + }, + __typename: 'UserAttende', + }, + ], + __typename: 'Event', + }, + { + _id: '6404e952c651df745358849d', + title: '1parti', + description: 'asddas', + startDate: '2023-03-06', + endDate: '2023-03-06', + location: 'das', + startTime: '00:40:00.000Z', + endTime: '02:40:00.000Z', + allDay: false, + recurring: false, + isPublic: true, + isRegisterable: true, + creator: { + _id: '63d649417ffe6e4d5174ea32', + firstName: 'Noble', + lastName: 'Mittal', + __typename: 'User', + }, + registrants: [ + { + user: { + _id: '63d649417ffe6e4d5174ea32', + __typename: 'User', + }, + __typename: 'UserAttende', + }, + { + user: { + _id: '63dd52bbe69f63814b0a5dd4', + __typename: 'User', + }, + __typename: 'UserAttende', + }, + { + user: { + _id: '63d6064458fce20ee25c3bf7', + __typename: 'User', + }, + __typename: 'UserAttende', + }, + ], + __typename: 'Event', + }, + ], + }, + }, + }, + { + request: { + query: ORGANIZATION_EVENTS_CONNECTION, + variables: { + organization_id: '', + title_contains: 'test', + }, + }, + result: { + data: { + eventsByOrganizationConnection: [ + { + _id: '6404a267cc270739118e2349', + title: 'NewEvent', + description: 'sdadsasad', + startDate: '2023-03-05', + endDate: '2023-03-05', + location: 'NewLocation', + startTime: null, + endTime: null, + allDay: true, + recurring: false, + isPublic: true, + isRegisterable: false, + creator: { + _id: '63d649417ffe6e4d5174ea32', + firstName: 'Noble', + lastName: 'Mittal', + __typename: 'User', + }, + registrants: [ + { + user: { + _id: '63d649417ffe6e4d5174ea32', + __typename: 'User', + }, + __typename: 'UserAttende', + }, + { + user: { + _id: '63d6064458fce20ee25c3bf7', + __typename: 'User', + }, + __typename: 'UserAttende', + }, + ], + __typename: 'Event', + }, + ], + }, + }, + }, + { + request: { + query: CREATE_EVENT_MUTATION, + variables: { + title: 'testEventTitle', + description: 'testEventDescription', + location: 'testEventLocation', + isPublic: true, + recurring: false, + isRegisterable: true, + organizationId: '', + startDate: dayjs(new Date()).format('YYYY-MM-DD'), + endDate: dayjs(new Date()).format('YYYY-MM-DD'), + allDay: false, + startTime: '08:00:00Z', + endTime: '10:00:00Z', + }, + }, + result: { + data: { + createEvent: { + _id: '2', + }, + }, + }, + }, + { + request: { + query: CREATE_EVENT_MUTATION, + variables: { + title: 'testEventTitle', + description: 'testEventDescription', + location: 'testEventLocation', + isPublic: true, + recurring: false, + isRegisterable: true, + organizationId: '', + startDate: dayjs(new Date()).format('YYYY-MM-DD'), + endDate: dayjs(new Date()).format('YYYY-MM-DD'), + allDay: true, + startTime: null, + endTime: null, + }, + }, + result: { + data: { + createEvent: { + _id: '1', + }, + }, + }, + }, +]; + +const link = new StaticMockLink(MOCKS, true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +describe('Testing Events Screen [User Portal]', () => { + jest.mock('utils/getOrganizationId'); + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); + + test('Screen should be rendered properly', async () => { + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + render( + + + + + + + + + + ); + + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + }); + + test('Events are visible as expected without search query', async () => { + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + render( + + + + + + + + + + ); + + await wait(); + + const mockEventTitle = + MOCKS[0].result?.data.eventsByOrganizationConnection![0].title; + expect(getOrganizationIdSpy).toHaveBeenCalled(); + expect(screen.queryByText(mockEventTitle!)).toBeInTheDocument(); + }); + + test('Search works as expected when user types in search input', async () => { + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + render( + + + + + + + + + + ); + + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + + const randomSearchInput = 'test'; + userEvent.type(screen.getByTestId('searchInput'), randomSearchInput); + + await wait(); + + const mockEventTitle = + MOCKS[0].result?.data.eventsByOrganizationConnection![0].title; + + const mockEventTitleAbsent = + MOCKS[0].result?.data.eventsByOrganizationConnection![1].title; + expect(screen.queryByText(mockEventTitle!)).toBeInTheDocument(); + expect(screen.queryByText(mockEventTitleAbsent!)).not.toBeInTheDocument(); + }); + + test('Create event works as expected when event is not an all day event.', async () => { + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + render( + + + + + + + + + + ); + + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + userEvent.click(screen.getByTestId('createEventModalBtn')); + + const randomEventTitle = 'testEventTitle'; + const randomEventDescription = 'testEventDescription'; + const randomEventLocation = 'testEventLocation'; + + userEvent.type(screen.getByTestId('eventTitleInput'), randomEventTitle); + userEvent.type( + screen.getByTestId('eventDescriptionInput'), + randomEventDescription + ); + userEvent.type( + screen.getByTestId('eventLocationInput'), + randomEventLocation + ); + + userEvent.click(screen.getByTestId('publicEventCheck')); + userEvent.click(screen.getByTestId('publicEventCheck')); + + userEvent.click(screen.getByTestId('registerableEventCheck')); + userEvent.click(screen.getByTestId('registerableEventCheck')); + + userEvent.click(screen.getByTestId('recurringEventCheck')); + userEvent.click(screen.getByTestId('recurringEventCheck')); + + userEvent.click(screen.getByTestId('recurringEventCheck')); + userEvent.click(screen.getByTestId('recurringEventCheck')); + + userEvent.click(screen.getByTestId('allDayEventCheck')); + + userEvent.click(screen.getByTestId('createEventBtn')); + + await wait(); + + expect(toast.success).toBeCalledWith( + 'Event created and posted successfully.' + ); + }); + + test('Create event works as expected when event is an all day event.', async () => { + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + render( + + + + + + + + + + ); + + await wait(); + + expect(getOrganizationIdSpy).toHaveBeenCalled(); + userEvent.click(screen.getByTestId('createEventModalBtn')); + + const randomEventTitle = 'testEventTitle'; + const randomEventDescription = 'testEventDescription'; + const randomEventLocation = 'testEventLocation'; + + userEvent.type(screen.getByTestId('eventTitleInput'), randomEventTitle); + userEvent.type( + screen.getByTestId('eventDescriptionInput'), + randomEventDescription + ); + userEvent.type( + screen.getByTestId('eventLocationInput'), + randomEventLocation + ); + + userEvent.click(screen.getByTestId('createEventBtn')); + + await wait(); + + expect(toast.success).toBeCalledWith( + 'Event created and posted successfully.' + ); + }); + + test('Switch to calendar view works as expected.', async () => { + const getOrganizationIdSpy = jest + .spyOn(getOrganizationId, 'default') + .mockImplementation(() => { + return ''; + }); + + render( + + + + + + + + + + ); + + await wait(); + expect(getOrganizationIdSpy).toHaveBeenCalled(); + + userEvent.click(screen.getByTestId('modeChangeBtn')); + userEvent.click(screen.getByTestId('modeBtn1')); + + await wait(); + const calenderView = 'Calendar View'; + expect(screen.queryAllByText(calenderView!)).not.toBeNull(); + }); +}); diff --git a/src/screens/UserPortal/Events/Events.tsx b/src/screens/UserPortal/Events/Events.tsx index cfe7adef0d..2f69c45a3c 100644 --- a/src/screens/UserPortal/Events/Events.tsx +++ b/src/screens/UserPortal/Events/Events.tsx @@ -166,10 +166,12 @@ export default function events(): JSX.Element { setEventDescription(event.target.value); }; + /* istanbul ignore next */ const handleStartDateChange = (newDate: any): void => { setStartDate(newDate); }; + /* istanbul ignore next */ const handleEndDateChange = (newDate: any): void => { setEndDate(newDate); }; @@ -210,7 +212,10 @@ export default function events(): JSX.Element {
- @@ -353,7 +358,7 @@ export default function events(): JSX.Element { className={styles.borderNone} value={eventDescription} onChange={handleEventDescriptionChange} - data-testid="eventTitleInput" + data-testid="eventDescriptionInput" /> @@ -369,7 +374,7 @@ export default function events(): JSX.Element { className={styles.borderNone} value={eventLocation} onChange={handleEventLocationChange} - data-testid="eventTitleInput" + data-testid="eventLocationInput" />
{t('startDate')}
@@ -402,6 +407,7 @@ export default function events(): JSX.Element { className="ms-2" type="checkbox" checked={isRegisterable} + data-testid="registerableEventCheck" onChange={(): void => setIsRegisterable(!isRegisterable)} />
@@ -412,6 +418,7 @@ export default function events(): JSX.Element { className="ms-2" type="checkbox" checked={isRecurring} + data-testid="recurringEventCheck" onChange={(): void => setIsRecurring(!isRecurring)} />
@@ -422,6 +429,7 @@ export default function events(): JSX.Element { className="ms-2" type="checkbox" checked={isAllDay} + data-testid="allDayEventCheck" onChange={(): void => setIsAllDay(!isAllDay)} />
@@ -434,7 +442,11 @@ export default function events(): JSX.Element { id="startTime" placeholder={t('startTime')} value={startTime} - onChange={(e): void => setStartTime(e.target.value)} + data-testid="startTimeEventInput" + onChange={ + /* istanbul ignore next */ + (e): void => setStartTime(e.target.value) + } />
@@ -443,7 +455,11 @@ export default function events(): JSX.Element { id="endTime" placeholder={t('endTime')} value={endTime} - onChange={(e): void => setEndTime(e.target.value)} + data-testid="endTimeEventInput" + onChange={ + /* istanbul ignore next */ + (e): void => setEndTime(e.target.value) + } />
@@ -454,7 +470,11 @@ export default function events(): JSX.Element { - diff --git a/src/screens/UserPortal/Home/Home.test.tsx b/src/screens/UserPortal/Home/Home.test.tsx index 809572073d..0c9809030a 100644 --- a/src/screens/UserPortal/Home/Home.test.tsx +++ b/src/screens/UserPortal/Home/Home.test.tsx @@ -49,6 +49,7 @@ const MOCKS = [ }, likeCount: 0, commentCount: 0, + comments: [], likedBy: [], }, { @@ -64,7 +65,41 @@ const MOCKS = [ email: 'adidacreator1@gmail.com', }, likeCount: 0, - commentCount: 0, + commentCount: 2, + comments: [ + { + _id: '64eb13beca85de60ebe0ed0e', + creator: { + _id: '63d6064458fce20ee25c3bf7', + firstName: 'Noble', + lastName: 'Mittal', + email: 'test@gmail.com', + __typename: 'User', + }, + likeCount: 1, + likedBy: [ + { + _id: 1, + }, + ], + text: 'First comment from Talawa user portal.', + __typename: 'Comment', + }, + { + _id: '64eb483aca85de60ebe0ef99', + creator: { + _id: '63d6064458fce20ee25c3bf7', + firstName: 'Noble', + lastName: 'Mittal', + email: 'test@gmail.com', + __typename: 'User', + }, + likeCount: 0, + likedBy: [], + text: 'Great View', + __typename: 'Comment', + }, + ], likedBy: [ { _id: '63d6064458fce20ee25c3bf7', diff --git a/src/screens/UserPortal/Home/Home.tsx b/src/screens/UserPortal/Home/Home.tsx index 29792c8a22..b657fc0c02 100644 --- a/src/screens/UserPortal/Home/Home.tsx +++ b/src/screens/UserPortal/Home/Home.tsx @@ -33,6 +33,19 @@ interface InterfacePostCardProps { title: string; likeCount: number; commentCount: number; + comments: { + creator: { + _id: string; + firstName: string; + lastName: string; + email: string; + }; + likeCount: number; + likedBy: { + id: string; + }[]; + text: string; + }[]; likedBy: { firstName: string; lastName: string; @@ -188,6 +201,33 @@ export default function home(): JSX.Element { allLikes.push(singleLike); }); + const postComments: any = []; + post.comments.forEach((value: any) => { + const commentLikes: any = []; + + value.likedBy.forEach((commentLike: any) => { + const singleLike = { + id: commentLike._id, + }; + commentLikes.push(singleLike); + }); + + const singleCommnet: any = { + id: value._id, + creator: { + firstName: value.creator.firstName, + lastName: value.creator.lastName, + id: value.creator._id, + email: value.creator.email, + }, + likeCount: value.likeCount, + likedBy: commentLikes, + text: value.text, + }; + + postComments.push(singleCommnet); + }); + const cardProps: InterfacePostCardProps = { id: post._id, creator: { @@ -202,6 +242,7 @@ export default function home(): JSX.Element { text: post.text, likeCount: post.likeCount, commentCount: post.commentCount, + comments: postComments, likedBy: allLikes, }; From ae076f8df51ac781ac8018254a6abf91daccba30 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sat, 2 Sep 2023 00:28:48 +0530 Subject: [PATCH 23/29] Refactor Events tests --- .../CommentCard/CommentCard.test.tsx | 1 - src/screens/UserPortal/Events/Events.test.tsx | 32 +++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/components/UserPortal/CommentCard/CommentCard.test.tsx b/src/components/UserPortal/CommentCard/CommentCard.test.tsx index 560e51b3df..c586809132 100644 --- a/src/components/UserPortal/CommentCard/CommentCard.test.tsx +++ b/src/components/UserPortal/CommentCard/CommentCard.test.tsx @@ -6,7 +6,6 @@ import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; -import cookies from 'js-cookie'; import { StaticMockLink } from 'utils/StaticMockLink'; import CommentCard from './CommentCard'; diff --git a/src/screens/UserPortal/Events/Events.test.tsx b/src/screens/UserPortal/Events/Events.test.tsx index cc72a4031f..63ef74c199 100644 --- a/src/screens/UserPortal/Events/Events.test.tsx +++ b/src/screens/UserPortal/Events/Events.test.tsx @@ -299,10 +299,14 @@ describe('Testing Events Screen [User Portal]', () => { await wait(); - const mockEventTitle = - MOCKS[0].result?.data.eventsByOrganizationConnection![0].title; + let mockEventTitle = ''; + if (MOCKS[0].result?.data.eventsByOrganizationConnection) { + mockEventTitle = + MOCKS[0].result?.data.eventsByOrganizationConnection[0].title; + } + expect(getOrganizationIdSpy).toHaveBeenCalled(); - expect(screen.queryByText(mockEventTitle!)).toBeInTheDocument(); + expect(screen.queryByText(mockEventTitle)).toBeInTheDocument(); }); test('Search works as expected when user types in search input', async () => { @@ -333,13 +337,20 @@ describe('Testing Events Screen [User Portal]', () => { await wait(); - const mockEventTitle = - MOCKS[0].result?.data.eventsByOrganizationConnection![0].title; + let mockEventTitle = ''; + if (MOCKS[0].result?.data.eventsByOrganizationConnection) { + mockEventTitle = + MOCKS[0].result?.data.eventsByOrganizationConnection[0].title; + } + + let mockEventTitleAbsent = ''; + if (MOCKS[0].result?.data.eventsByOrganizationConnection) { + mockEventTitleAbsent = + MOCKS[0].result?.data.eventsByOrganizationConnection[1].title; + } - const mockEventTitleAbsent = - MOCKS[0].result?.data.eventsByOrganizationConnection![1].title; - expect(screen.queryByText(mockEventTitle!)).toBeInTheDocument(); - expect(screen.queryByText(mockEventTitleAbsent!)).not.toBeInTheDocument(); + expect(screen.queryByText(mockEventTitle)).toBeInTheDocument(); + expect(screen.queryByText(mockEventTitleAbsent)).not.toBeInTheDocument(); }); test('Create event works as expected when event is not an all day event.', async () => { @@ -477,6 +488,7 @@ describe('Testing Events Screen [User Portal]', () => { await wait(); const calenderView = 'Calendar View'; - expect(screen.queryAllByText(calenderView!)).not.toBeNull(); + + expect(screen.queryAllByText(calenderView)).not.toBeNull(); }); }); From ed127054d2dc681301eecbe7a029c1c81230d3ca Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Mon, 18 Sep 2023 18:28:47 +0530 Subject: [PATCH 24/29] Deprecate event registrants from query --- package-lock.json | 8 +-- src/GraphQl/Queries/Queries.ts | 6 +- src/screens/UserPortal/Events/Events.test.tsx | 55 ++++++------------- src/screens/UserPortal/Events/Events.tsx | 14 ++--- 4 files changed, 30 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index d8a291c684..f2cf6800e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,7 +62,7 @@ "redux": "^4.1.1", "redux-thunk": "^2.3.0", "typedoc": "^0.24.8", - "typedoc-plugin-markdown": "^3.15.4", + "typedoc-plugin-markdown": "^3.16.0", "typescript": "^4.3.5", "web-vitals": "^1.0.1", "yarn": "^1.22.17" @@ -23521,9 +23521,9 @@ } }, "node_modules/typedoc-plugin-markdown": { - "version": "3.15.4", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.15.4.tgz", - "integrity": "sha512-KpjFL/NDrQAbY147oIoOgob2vAdEchsMcTVd6+e6H2lC1l5xhi48bhP/fMJI7qYQ8th5nubervgqw51z7gY66A==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.16.0.tgz", + "integrity": "sha512-eeiC78fDNGFwemPIHiwRC+mEC7W5jwt3fceUev2gJ2nFnXpVHo8eRrpC9BLWZDee6ehnz/sPmNjizbXwpfaTBw==", "dependencies": { "handlebars": "^4.7.7" }, diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index c9f5fd14ee..ef8aa20a2b 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -657,10 +657,8 @@ export const ORGANIZATION_EVENTS_CONNECTION = gql` firstName lastName } - registrants { - user { - _id - } + attendees { + _id } } } diff --git a/src/screens/UserPortal/Events/Events.test.tsx b/src/screens/UserPortal/Events/Events.test.tsx index 63ef74c199..4243efa8ee 100644 --- a/src/screens/UserPortal/Events/Events.test.tsx +++ b/src/screens/UserPortal/Events/Events.test.tsx @@ -55,20 +55,14 @@ const MOCKS = [ lastName: 'Mittal', __typename: 'User', }, - registrants: [ + attendees: [ { - user: { - _id: '63d649417ffe6e4d5174ea32', - __typename: 'User', - }, - __typename: 'UserAttende', + _id: '63d649417ffe6e4d5174ea32', + __typename: 'User', }, { - user: { - _id: '63d6064458fce20ee25c3bf7', - __typename: 'User', - }, - __typename: 'UserAttende', + _id: '63d6064458fce20ee25c3bf7', + __typename: 'User', }, ], __typename: 'Event', @@ -92,27 +86,18 @@ const MOCKS = [ lastName: 'Mittal', __typename: 'User', }, - registrants: [ + attendees: [ { - user: { - _id: '63d649417ffe6e4d5174ea32', - __typename: 'User', - }, - __typename: 'UserAttende', + _id: '63d649417ffe6e4d5174ea32', + __typename: 'User', }, { - user: { - _id: '63dd52bbe69f63814b0a5dd4', - __typename: 'User', - }, - __typename: 'UserAttende', + _id: '63dd52bbe69f63814b0a5dd4', + __typename: 'User', }, { - user: { - _id: '63d6064458fce20ee25c3bf7', - __typename: 'User', - }, - __typename: 'UserAttende', + _id: '63d6064458fce20ee25c3bf7', + __typename: 'User', }, ], __typename: 'Event', @@ -151,20 +136,14 @@ const MOCKS = [ lastName: 'Mittal', __typename: 'User', }, - registrants: [ + attendees: [ { - user: { - _id: '63d649417ffe6e4d5174ea32', - __typename: 'User', - }, - __typename: 'UserAttende', + _id: '63d649417ffe6e4d5174ea32', + __typename: 'User', }, { - user: { - _id: '63d6064458fce20ee25c3bf7', - __typename: 'User', - }, - __typename: 'UserAttende', + _id: '63d6064458fce20ee25c3bf7', + __typename: 'User', }, ], __typename: 'Event', diff --git a/src/screens/UserPortal/Events/Events.tsx b/src/screens/UserPortal/Events/Events.tsx index 2f69c45a3c..caf1d72fd2 100644 --- a/src/screens/UserPortal/Events/Events.tsx +++ b/src/screens/UserPortal/Events/Events.tsx @@ -263,14 +263,14 @@ export default function events(): JSX.Element { ) : /* istanbul ignore next */ events - ).map((event: any, index) => { - const registrants: any = []; - event.registrants.forEach((registrant: any) => { + ).map((event: any) => { + const attendees: any = []; + event.attendees.forEach((attendee: any) => { const r = { - id: registrant.user._id, + id: attendee._id, }; - registrants.push(r); + attendees.push(r); }); const creator: any = {}; @@ -291,11 +291,11 @@ export default function events(): JSX.Element { startTime: event.startTime, recurring: event.recurring, allDay: event.allDay, - registrants, + registrants: attendees, creator, }; - return ; + return ; }) ) : ( {t('nothingToShow')} From 57222399384a89c28dd85601e96f3516642400a2 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Mon, 2 Oct 2023 13:24:48 +0530 Subject: [PATCH 25/29] Add my tasks screen for user portal --- public/locales/en.json | 19 +- public/locales/fr.json | 19 +- public/locales/hi.json | 19 +- public/locales/sp.json | 19 +- public/locales/zh.json | 19 +- src/App.tsx | 2 + src/GraphQl/Queries/Queries.ts | 36 +++ .../OrganizationCard.test.tsx | 2 +- .../OrganizationNavbar/OrganizationNavbar.tsx | 33 ++- .../UserPortal/PeopleCard/PeopleCard.test.tsx | 2 +- .../UserPortal/TaskCard/TaskCard.module.css | 41 ++++ .../UserPortal/TaskCard/TaskCard.test.tsx | 95 ++++++++ .../UserPortal/TaskCard/TaskCard.tsx | 131 +++++++++++ .../UserPortal/UserNavbar/UserNavbar.tsx | 11 +- .../Organizations/Organizations.module.css | 4 + .../Organizations/Organizations.tsx | 5 +- src/screens/UserPortal/Tasks/Tasks.module.css | 48 ++++ src/screens/UserPortal/Tasks/Tasks.test.tsx | 211 ++++++++++++++++++ src/screens/UserPortal/Tasks/Tasks.tsx | 173 ++++++++++++++ 19 files changed, 868 insertions(+), 21 deletions(-) create mode 100644 src/components/UserPortal/TaskCard/TaskCard.module.css create mode 100644 src/components/UserPortal/TaskCard/TaskCard.test.tsx create mode 100644 src/components/UserPortal/TaskCard/TaskCard.tsx create mode 100644 src/screens/UserPortal/Tasks/Tasks.module.css create mode 100644 src/screens/UserPortal/Tasks/Tasks.test.tsx create mode 100644 src/screens/UserPortal/Tasks/Tasks.tsx diff --git a/public/locales/en.json b/public/locales/en.json index 7f6771f1e9..a8c22f0102 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -528,7 +528,8 @@ "joinedOrganizations": "Joined Organizations", "createdOrganizations": "Created Organizations", "search": "Search", - "nothingToShow": "Nothing to show here." + "nothingToShow": "Nothing to show here.", + "selectOrganization": "Select Organization" }, "userSidebar": { "yourOrganizations": "Your Organizations", @@ -594,5 +595,21 @@ "creator": "Creator", "alreadyRegistered": "Already registered", "register": "Register" + }, + "userTasks": { + "yourAssignedTasks": "Your assigned tasks", + "nothingToShow": "Nothing to show here." + }, + "userTaskCard": { + "description": "Description", + "deadline": "Deadline", + "created": "Created", + "assignees": "Assignees", + "completed": "Completed", + "incomplete": "Incomplete", + "taskCompleted": "The task has been completed", + "taskNotCompleted": "The task has not been completed yet", + "event": "Event", + "organization": "Organization" } } diff --git a/public/locales/fr.json b/public/locales/fr.json index 786c133520..39550fdb81 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -521,7 +521,8 @@ "joinedOrganizations": "Organisations jointes", "createdOrganizations": "Organisations créées", "search": "Recherche", - "nothingToShow": "Rien à montrer ici." + "nothingToShow": "Rien à montrer ici.", + "selectOrganization": "Sélectionnez une organisation" }, "userSidebar": { "yourOrganizations": "Vos organisations", @@ -587,5 +588,21 @@ "creator": "Créatrice", "alreadyRegistered": "Déjà enregistré", "register": "Registre" + }, + "userTasks": { + "yourAssignedTasks": "Vos tâches assignées", + "nothingToShow": "Rien à montrer ici." + }, + "userTaskCard": { + "description": "Description", + "deadline": "Date limite", + "created": "Créé", + "assignees": "Destinataires", + "completed": "Complété", + "incomplete": "Incomplète", + "taskCompleted": "La tâche est terminée", + "taskNotCompleted": "La tâche n'est pas encore terminée", + "event": "Événement", + "organization": "Organisation" } } diff --git a/public/locales/hi.json b/public/locales/hi.json index 5f5654b884..be82066a74 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -522,7 +522,8 @@ "joinedOrganizations": "संगठन शामिल हुए", "createdOrganizations": "संगठन बनाये गये", "search": "खोज", - "nothingToShow": "यहां दिखाने के लिए कुछ भी नहीं है." + "nothingToShow": "यहां दिखाने के लिए कुछ भी नहीं है.", + "selectOrganization": "संगठन का चयन करें" }, "userSidebar": { "yourOrganizations": "आपके संगठन", @@ -588,5 +589,21 @@ "creator": "निर्माता", "alreadyRegistered": "पहले से ही पंजीकृत", "register": "पंजीकरण करवाना" + }, + "userTasks": { + "yourAssignedTasks": "आपके असाइन किए गए कार्य", + "nothingToShow": "यहां दिखाने के लिए कुछ भी नहीं है." + }, + "userTaskCard": { + "description": "विवरण", + "deadline": "अंतिम तारीख", + "created": "बनाया", + "assignees": "असाइनी", + "completed": "पुरा", + "incomplete": "अधूरा", + "taskCompleted": "काम पूरा हो गया", + "taskNotCompleted": "कार्य अभी तक पूरा नहीं हुआ है", + "event": "आयोजन", + "organization": "संगठन" } } diff --git a/public/locales/sp.json b/public/locales/sp.json index 85f3492c02..27f0968470 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -522,7 +522,8 @@ "joinedOrganizations": "Organizaciones unidas", "createdOrganizations": "Organizaciones creadas", "search": "Buscar", - "nothingToShow": "Nada que mostrar aquí." + "nothingToShow": "Nada que mostrar aquí.", + "selectOrganization": "Seleccionar organización" }, "userSidebar": { "yourOrganizations": "Tus Organizaciones", @@ -588,5 +589,21 @@ "creator": "Creadora", "alreadyRegistered": "Ya registrado", "register": "Registro" + }, + "userTasks": { + "yourAssignedTasks": "Tus tareas asignadas", + "nothingToShow": "No hay nada que mostrar aquí." + }, + "userTaskCard": { + "description": "Descripción", + "deadline": "Fecha límite", + "created": "Creado", + "assignees": "Cesionarias", + "completed": "Terminada", + "incomplete": "Incompleta", + "taskCompleted": "La tarea ha sido completada", + "taskNotCompleted": "La tarea aún no se ha completado", + "event": "Evento", + "organization": "Organización" } } diff --git a/public/locales/zh.json b/public/locales/zh.json index 055bb709b2..d4a2a5ea7c 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -522,7 +522,8 @@ "joinedOrganizations": "加入組織", "createdOrganizations": "創建的組織", "search": "搜索", - "nothingToShow": "這裡沒有什麼可展示的。" + "nothingToShow": "這裡沒有什麼可展示的。", + "selectOrganization": "選擇組織" }, "userSidebar": { "yourOrganizations": "您的組織", @@ -588,5 +589,21 @@ "creator": "創作者", "alreadyRegistered": "已經註冊", "register": "登記" + }, + "userTasks": { + "yourAssignedTasks": "您指派的任務", + "nothingToShow": "這裡沒有什麼可顯示的。" + }, + "userTaskCard": { + "description": "描述", + "deadline": "最後期限", + "created": "已創建", + "assignees": "受讓人", + "completed": "完全的", + "incomplete": "不完整", + "taskCompleted": "任務已完成", + "taskNotCompleted": "任務還沒完成", + "event": "事件", + "organization": "組織" } } diff --git a/src/App.tsx b/src/App.tsx index 31e97bdc64..9b32d4ba59 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,6 +31,7 @@ import People from 'screens/UserPortal/People/People'; import Settings from 'screens/UserPortal/Settings/Settings'; import Donate from 'screens/UserPortal/Donate/Donate'; import Events from 'screens/UserPortal/Events/Events'; +import Tasks from 'screens/UserPortal/Tasks/Tasks'; function app(): JSX.Element { /*const { updatePluginLinks, updateInstalled } = bindActionCreators( @@ -125,6 +126,7 @@ function app(): JSX.Element { + diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index ef8aa20a2b..d426a4bfb5 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -663,3 +663,39 @@ export const ORGANIZATION_EVENTS_CONNECTION = gql` } } `; + +export const USER_TASKS_LIST = gql` + query User($id: ID!) { + user(id: $id) { + _id + assignedTasks { + _id + title + description + deadline + volunteers { + _id + firstName + lastName + email + } + createdAt + completed + event { + _id + title + organization { + _id + name + image + } + } + creator { + _id + firstName + lastName + } + } + } + } +`; diff --git a/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx b/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx index f04c87b526..fa4339e810 100644 --- a/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx +++ b/src/components/UserPortal/OrganizationCard/OrganizationCard.test.tsx @@ -27,7 +27,7 @@ let props = { description: 'organizationDescription', }; -describe('Testing Organization Card Component [User Portal]', () => { +describe('Testing OrganizationCard Component [User Portal]', () => { test('Component should be rendered properly', async () => { render( diff --git a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx index 3e748e2856..d1d72847bf 100644 --- a/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx +++ b/src/components/UserPortal/OrganizationNavbar/OrganizationNavbar.tsx @@ -81,34 +81,49 @@ function organizationNavbar(props: InterfaceNavbarProps): JSX.Element { Talawa -