From 3090757b1d99c01af78731436ca57496bc40c672 Mon Sep 17 00:00:00 2001 From: Rishav Jha <76212518+rishav-jha-mech@users.noreply.github.com> Date: Sat, 12 Aug 2023 10:39:29 +0530 Subject: [PATCH] Merge latest adminUI-Redesign (#950) * Initial Organizations screen done * Removed yellow scrollbar * Linting fixed * Replaced images with svgs for logos * Styling done for btnsContainer * Better typechecking and readability * Animated Drawer working * Responsive page ready * OrgCard responsive * Fixed navbar issue and added webkit keyframes * LeftDrawer ready * Translations added * Added shimmer loading effect * Styling issue fixed * Failing tests fixed for OrgList * Removed unused vars * Tests done for LeftDrawer * Succesfully made component without causing any breaking change * 100% Code coverage achieved for Requests Screen * Fix alignment * Roles screen UI done * Role screen fixed with 100% test coverage * Changing screen activeness fixed * Unused vars and Typos fixed * Language support added * Linting and typos fixed * Fixed failing tests for LeftDrawer * Completed tests of AdminDashListCard with 100% code coverage * OrgListCard done * Finalised tests * Requests user search made functional again ! * Fixed loading on refetch and UX on all screens * OrgList failing errors fixed * Fixed all failing tests * Achieved 100% code coverage for OrgList.tsx * Wrote tests and mod LeftDrawer for admins * Minor ui issue fixed * Fixed failing test * UI bug dropdown * Frontend insync with Backend attempt 1 * Introspection fail fix 1 * Introspection error fix 3 * Introspection error fix another attempt * Another attempt --- package-lock.json | 4 +- public/locales/en.json | 35 +- public/locales/fr.json | 37 +- public/locales/hi.json | 37 +- public/locales/sp.json | 39 +- public/locales/zh.json | 39 +- src/GraphQl/Mutations/mutations.ts | 21 +- src/GraphQl/Queries/Queries.ts | 1 - src/assets/css/app.css | 41 ++ src/assets/scss/_general.scss | 41 ++ src/assets/svgs/icons/angleRight.svg | 3 + src/assets/svgs/icons/logout.svg | 3 + src/assets/svgs/icons/organizations.svg | 5 + src/assets/svgs/icons/requests.svg | 10 + src/assets/svgs/icons/roles.svg | 3 + src/assets/svgs/palisadoes.svg | 12 + src/assets/svgs/talawa.svg | 7 + .../AdminDashListCard.module.css | 78 --- .../AdminDashListCard.test.tsx | 98 --- .../AdminDashListCard/AdminDashListCard.tsx | 80 --- .../LeftDrawer/LeftDrawer.module.css | 241 +++++++ src/components/LeftDrawer/LeftDrawer.test.tsx | 263 +++++++ src/components/LeftDrawer/LeftDrawer.tsx | 182 +++++ .../OrgListCard/OrgListCard.module.css | 85 +++ .../OrgListCard/OrgListCard.test.tsx | 93 +++ src/components/OrgListCard/OrgListCard.tsx | 72 ++ .../OrganizationCard.module.css | 20 +- .../OrganizationCardStart.module.css | 20 +- .../SuperAdminScreen.module.css | 60 ++ .../SuperAdminScreen.test.tsx | 49 ++ .../SuperAdminScreen/SuperAdminScreen.tsx | 56 ++ .../SuperDashListCard.module.css | 83 --- .../SuperDashListCard.test.tsx | 156 ----- .../SuperDashListCard/SuperDashListCard.tsx | 96 --- src/screens/LoginPage/LoginPage.module.css | 2 + src/screens/LoginPage/LoginPage.tsx | 16 +- .../MemberDetail/MemberDetail.module.css | 22 - src/screens/OrgList/OrgList.module.css | 442 ++++-------- src/screens/OrgList/OrgList.test.tsx | 421 ++---------- src/screens/OrgList/OrgList.tsx | 645 +++++++++--------- src/screens/OrgList/OrgListMocks.ts | 146 ++++ src/screens/Requests/Requests.module.css | 150 ++-- src/screens/Requests/Requests.test.tsx | 45 +- src/screens/Requests/Requests.tsx | 343 +++++----- src/screens/Roles/Roles.module.css | 150 ++-- src/screens/Roles/Roles.test.tsx | 172 ++--- src/screens/Roles/Roles.tsx | 346 +++++----- src/utils/interfaces.ts | 36 + 48 files changed, 2644 insertions(+), 2362 deletions(-) create mode 100644 src/assets/svgs/icons/angleRight.svg create mode 100644 src/assets/svgs/icons/logout.svg create mode 100644 src/assets/svgs/icons/organizations.svg create mode 100644 src/assets/svgs/icons/requests.svg create mode 100644 src/assets/svgs/icons/roles.svg create mode 100644 src/assets/svgs/palisadoes.svg create mode 100644 src/assets/svgs/talawa.svg delete mode 100644 src/components/AdminDashListCard/AdminDashListCard.module.css delete mode 100644 src/components/AdminDashListCard/AdminDashListCard.test.tsx delete mode 100644 src/components/AdminDashListCard/AdminDashListCard.tsx create mode 100644 src/components/LeftDrawer/LeftDrawer.module.css create mode 100644 src/components/LeftDrawer/LeftDrawer.test.tsx create mode 100644 src/components/LeftDrawer/LeftDrawer.tsx create mode 100644 src/components/OrgListCard/OrgListCard.module.css create mode 100644 src/components/OrgListCard/OrgListCard.test.tsx create mode 100644 src/components/OrgListCard/OrgListCard.tsx create mode 100644 src/components/SuperAdminScreen/SuperAdminScreen.module.css create mode 100644 src/components/SuperAdminScreen/SuperAdminScreen.test.tsx create mode 100644 src/components/SuperAdminScreen/SuperAdminScreen.tsx delete mode 100644 src/components/SuperDashListCard/SuperDashListCard.module.css delete mode 100644 src/components/SuperDashListCard/SuperDashListCard.test.tsx delete mode 100644 src/components/SuperDashListCard/SuperDashListCard.tsx create mode 100644 src/screens/OrgList/OrgListMocks.ts create mode 100644 src/utils/interfaces.ts diff --git a/package-lock.json b/package-lock.json index 89054407cc..11c1eb4f57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,12 +22,12 @@ "@types/jest": "^26.0.24", "@types/jquery": "^3.5.6", "@types/node": "^12.20.16", - "@types/react-bootstrap": "^0.32.26", + "@types/react-bootstrap": "^0.32.32", "@types/react-datepicker": "^4.1.4", "@types/react-dom": "^17.0.9", "@types/react-google-recaptcha": "^2.1.5", "@types/react-modal": "^3.12.1", - "bootstrap": "^4.2.1", + "bootstrap": "^5.3.0", "dayjs": "^1.10.7", "detect-newline": "^4.0.0", "enzyme": "^3.11.0", diff --git a/public/locales/en.json b/public/locales/en.json index 7ea43f53a7..51b2bb3b72 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -34,6 +34,14 @@ "requests": "Requests", "logout": "Logout" }, + "leftDrawer": { + "talawaAdminPortal": "Talawa Admin Portal", + "menu": "Menu", + "organizations": "Organizations", + "requests": "Requests", + "roles": "Roles", + "logout": "Logout" + }, "orgList": { "title": "Talawa Organizations", "you": "You", @@ -41,7 +49,7 @@ "designation": "Designation", "email": "Email", "searchByName": "Search By Name", - "organizationList": "Organizations List", + "organizations": "Organizations", "createOrganization": "Create Organization", "description": "Description", "location": "Location", @@ -49,21 +57,18 @@ "visibleInSearch": "Visible In Search", "displayImage": "Display Image", "enterName": "Enter Name", + "sort": "Sort", + "filter": "Filter", + "cancel": "Cancel", "noOrgErrorTitle": "Organizations Not Found", - "noOrgErrorDescription": "Please create an organization through dashboard" + "noOrgErrorDescription": "Please create an organization through dashboard", + "noResultsFoundFor": "No results found for " }, - "superDashListCard": { - "created": "Created", + "orgListCard": { "admins": "Admins", "members": "Members", "manage": "Manage" }, - "adminDashListCard": { - "created": "Created", - "admins": "Admins", - "members": "Members", - "view": "View" - }, "paginationList": { "rowsPerPage": "rows per page", "all": "All" @@ -79,8 +84,13 @@ "superAdmin": "SUPERADMIN", "user": "USER", "enterName": "Enter Name", + "loadingUsers": "Loading Users...", + "noUserFound": "No User Found", + "sort": "Sort", + "filter": "Filter", "noOrgError": "Organizations not found, please create an organization through dashboard", "roleUpdated": "Role Updated.", + "noResultsFoundFor": "No results found for ", "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too." }, "requests": { @@ -92,9 +102,14 @@ "accept": "Accept", "reject": "Reject", "enterName": "Enter Name", + "loadingRequests": "Loading Requests...", + "noRequestFound": "No Request Found", + "sort": "Sort", + "filter": "Filter", "noOrgError": "Organizations not found, please create an organization through dashboard", "userApproved": "User Approved", "userRejected": "User Rejected", + "noResultsFoundFor": "No results found for ", "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too." }, "adminNavbar": { diff --git a/public/locales/fr.json b/public/locales/fr.json index b9b5a712ae..e30f8a34c8 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -34,6 +34,14 @@ "requests": "Demandes", "logout": "Se déconnecter" }, + "leftDrawer": { + "talawaAdminPortal": "Portail d'administration Talawa", + "menu": "Menu", + "organizations": "Organisations", + "requests": "Demandes", + "roles": "Rôles", + "logout": "Déconnexion" + }, "orgList": { "title": "Organisations Talawa", "you": "Tu", @@ -41,27 +49,26 @@ "designation": "La désignation", "email": "E-mail", "searchByName": "Rechercher par nom", - "organizationList": "Liste des organisations", + "organizations": "Organisations", "createOrganization": "Créer une organisation", "description": "La description", "location": "Emplacement", "isPublic": "Est publique", "visibleInSearch": "Visible dans la recherche", "displayImage": "Afficher l'image", - "enterName": "Entrez le nom" + "enterName": "Entrez le nom", + "sort": "Trier", + "filter": "Filtre", + "cancel": "Annuler", + "noOrgErrorTitle": "Organisations non trouvées", + "noOrgErrorDescription": "Veuillez créer une organisation via le tableau de bord", + "noResultsFoundFor": "Aucun résultat trouvé pour " }, - "superDashListCard": { - "created": "Établi", + "orgListCard": { "admins": "Administrateurs", "members": "Membres", "manage": "Faire en sorte" }, - "adminDashListCard": { - "view": "Voir", - "created": "Créé", - "admins": "Admins", - "members": "Membres" - }, "paginationList": { "rowsPerPage": "lignes par page", "all": "Tout" @@ -77,7 +84,12 @@ "superAdmin": "SUPERADMIN", "user": "UTILISATEUR", "enterName": "Entrez le nom", + "loadingUsers": "Chargement des utilisateurs...", + "noUserFound": "Aucun utilisateur trouvé", + "sort": "Trier", + "filter": "Filtre", "roleUpdated": "Rôle mis à jour.", + "noResultsFoundFor": "Aucun résultat trouvé pour ", "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau." }, "requests": { @@ -89,8 +101,13 @@ "accept": "Accepter", "reject": "Rejeter", "enterName": "Entrez le nom", + "loadingRequests": "Chargement des demandes...", + "noRequestFound": "Aucune demande trouvée", + "sort": "Trier", + "filter": "Filtre", "userApproved": "Approuvé par l'utilisateur", "userRejected": "Utilisateur rejeté", + "noResultsFoundFor": "Aucun résultat trouvé pour ", "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau." }, "adminNavbar": { diff --git a/public/locales/hi.json b/public/locales/hi.json index 65a6d3653f..ee96261c99 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -34,6 +34,14 @@ "requests": "अनुरोध", "logout": "लॉग आउट" }, + "leftDrawer": { + "talawaAdminPortal": "तलावा व्यवस्थापक पोर्टल", + "menu": "मेन्यू", + "organizations": "संगठन", + "requests": "अनुरोध", + "roles": "भूमिकाएँ", + "logout": "लॉग आउट" + }, "orgList": { "title": "तलवा संगठन", "you": "आप", @@ -41,27 +49,26 @@ "designation": "पद", "email": "ईमेल", "searchByName": "नाम से खोजें", - "organizationList": "संगठनों की सूची", + "organizations": "संगठन", "createOrganization": "संगठन बनाएं", "description": "विवरण", "location": "स्थान", "isPublic": "सार्वजनिक है", "visibleInSearch": "खोज में दृश्यमान", "displayImage": "प्रदर्शन छवि", - "enterName": "नाम दर्ज करें" + "enterName": "नाम दर्ज करें", + "sort": "छांटें", + "filter": "फ़िल्टर", + "cancel": "रद्द करना", + "noOrgErrorTitle": "संगठन नहीं मिला", + "noOrgErrorDescription": "कृपया डैशबोर्ड के माध्यम से एक संगठन बनाएं", + "noResultsFoundFor": "के लिए कोई परिणाम नहीं मिला " }, - "superDashListCard": { - "created": "बनाया था", + "orgListCard": { "admins": "व्यवस्थापक", "members": "सदस्य", "manage": "प्रबंधित करना" }, - "adminDashListCard": { - "view": "देखें", - "created": "बनाया गया", - "admins": "व्यवस्थापक", - "members": "सदस्य" - }, "paginationList": { "rowsPerPage": "प्रति पृष्ठ पंक्तियाँ", "all": "सभी" @@ -77,7 +84,12 @@ "superAdmin": "सुपरएडमिन", "user": "उपयोगकर्ता", "enterName": "नाम दर्ज करें", + "loadingUsers": "उपयोगकर्ता लोड हो रहा है ...", + "noUserFound": "कोई उपयोगकर्ता नहीं मिला।", + "sort": "छांटें", + "filter": "फ़िल्टर", "roleUpdated": "भूमिका अपडेट की गई।", + "noResultsFoundFor": "के लिए कोई परिणाम नहीं मिला ", "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रहा है? अपनी नेटवर्क कनेक्टिविटी भी जांचें।" }, "requests": { @@ -89,8 +101,13 @@ "accept": "स्वीकार करना", "reject": "अस्वीकार", "enterName": "नाम दर्ज करें", + "loadingRequests": "अनुरोध लोड हो रहा है ...", + "noRequestFound": "कोई अनुरोध नहीं मिला।", + "sort": "छांटें", + "filter": "फ़िल्टर", "userApproved": "उपयोगकर्ता स्वीकृत", "userRejected": "उपयोगकर्ता अस्वीकृत", + "noResultsFoundFor": "के लिए कोई परिणाम नहीं मिला ", "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रहा है? अपनी नेटवर्क कनेक्टिविटी भी जांचें।" }, "adminNavbar": { diff --git a/public/locales/sp.json b/public/locales/sp.json index f00d91f38c..9d82a31c13 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -34,6 +34,14 @@ "requests": "Peticiones", "logout": "Cerrar sesión" }, + "leftDrawer": { + "talawaAdminPortal": "Portal de administración de Talawa", + "menu": "Menú", + "organizations": "Organizaciones", + "requests": "Solicitudes", + "roles": "Roles", + "logout": "Cerrar sesión" + }, "orgList": { "title": "Organizaciones Talawa", "you": "Tú", @@ -41,27 +49,26 @@ "designation": "Designacion", "email": "Correo electrónico", "searchByName": "Buscar por nombre", - "organizationList": "Lista de organizaciones", + "organizations": "Organizaciones", "createOrganization": "Crear organización", "description": "Descripción", "location": "Ubicación", "isPublic": "Es público", "visibleInSearch": "Visible en la búsqueda", "displayImage": "Mostrar imagen", - "enterName": "Ingrese su nombre" + "enterName": "Ingrese su nombre", + "sort": "Ordenar", + "filter": "Filtrar", + "cancel": "Cancelar", + "noOrgErrorTitle": "Organizaciones no encontradas", + "noOrgErrorDescription": "Por favor, crea una organización a través del panel de control", + "noResultsFoundFor": "No se encontraron resultados para " }, - "superDashListCard": { - "created": "Creado", + "orgListCard": { "admins": "Administradores", "members": "Miembros", "manage": "Administrar" }, - "adminDashListCard": { - "view": "Ver", - "created": "Creado", - "admins": "Administradores", - "members": "Miembros" - }, "paginationList": { "rowsPerPage": "filas por página", "all": "Todos" @@ -77,7 +84,12 @@ "superAdmin": "SUPERADMIN", "user": "USUARIO", "enterName": "Ingrese su nombre", + "loadingUsers": "Cargando usuarios ...", + "noUserFound": "No se encontró ningún usuario.", + "sort": "Ordenar", + "filter": "Filtrar", "roleUpdated": "Rol actualizado.", + "noResultsFoundFor": "No se encontraron resultados para ", "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red." }, "requests": { @@ -89,8 +101,13 @@ "accept": "Aceptar", "reject": "Rechazar", "enterName": "Ingrese su nombre", + "loadingRequests": "Cargando solicitudes ...", + "noRequestFound": "No se encontró ninguna solicitud.", + "sort": "Ordenar", + "filter": "Filtrar", "userApproved": "Aprobado por el usuario", "userRejected": "Usuario rechazado", + "noResultsFoundFor": "No se encontraron resultados para ", "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red." }, "adminNavbar": { @@ -362,7 +379,6 @@ "saveChanges": "Guardar cambios", "cancel": "Cancelar" }, - "userPasswordUpdate": { "previousPassword": "Contraseña anterior", "newPassword": "Nueva contraseña", @@ -370,7 +386,6 @@ "saveChanges": "Guardar cambios", "cancel": "Cancelar" }, - "orgDelete": { "deleteOrg": "Eliminar organización" }, diff --git a/public/locales/zh.json b/public/locales/zh.json index 785b1de9f0..a01c92df83 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -34,6 +34,14 @@ "requests": "要求", "logout": "登出" }, + "leftDrawer": { + "talawaAdminPortal": "塔拉瓦管理门户", + "menu": "菜单", + "organizations": "组织", + "requests": "请求", + "roles": "角色", + "logout": "退出登录" + }, "orgList": { "title": "塔拉瓦組織", "you": "你", @@ -41,27 +49,26 @@ "designation": "指定", "email": "電子郵件", "searchByName": "按名稱搜索", - "organizationList": "組織名單", + "organizations": "组织", "createOrganization": "創建組織", "description": "描述", "location": "地點", "isPublic": "是否公開", "visibleInSearch": "在搜索中可見", "displayImage": "顯示圖像", - "enterName": "输入名字" + "enterName": "输入名字", + "sort": "排序", + "filter": "過濾", + "cancel": "取消", + "noOrgErrorTitle": "找不到组织", + "noOrgErrorDescription": "请通过仪表板创建一个组织", + "noResultsFoundFor": "未找到结果 " }, - "superDashListCard": { - "created": "創造", + "orgListCard": { "admins": "管理員", "members": "成員", "manage": "管理" }, - "adminDashListCard": { - "view": "查看", - "created": "创建于", - "admins": "管理员", - "members": "成员" - }, "paginationList": { "rowsPerPage": "每頁行數", "all": "全部" @@ -77,7 +84,12 @@ "superAdmin": "超級管理員", "user": "用戶", "enterName": "输入名字", + "loadingUsers": "正在加載用戶...", + "noUserFound": "找不到用戶。", + "sort": "排序", + "filter": "過濾", "roleUpdated": "角色已更新。", + "noResultsFoundFor": "未找到结果 ", "talawaApiUnavailable": "服務不可用。它在運行嗎?還要檢查您的網絡連接。" }, "requests": { @@ -89,8 +101,13 @@ "accept": "接受", "reject": "拒絕", "enterName": "输入名字", + "loadingRequests": "正在加載請求...", + "noRequestFound": "找不到請求。", + "sort": "排序", + "filter": "過濾", "userApproved": "用戶批准", "userRejected": "用戶被拒絕", + "noResultsFoundFor": "未找到结果 ", "talawaApiUnavailable": "服務不可用。它在運行嗎?還要檢查您的網絡連接。" }, "adminNavbar": { @@ -362,7 +379,6 @@ "saveChanges": "保存更改", "cancel": "取消" }, - "userPasswordUpdate": { "previousPassword": "以前的密碼", "newPassword": "新密碼", @@ -370,7 +386,6 @@ "saveChanges": "保存更改", "cancel": "取消" }, - "orgDelete": { "deleteOrg": "刪除組織" }, diff --git a/src/GraphQl/Mutations/mutations.ts b/src/GraphQl/Mutations/mutations.ts index fbe9c945ca..ba02fdedbd 100644 --- a/src/GraphQl/Mutations/mutations.ts +++ b/src/GraphQl/Mutations/mutations.ts @@ -334,7 +334,7 @@ export const UPDATE_USERTYPE_MUTATION = gql` } `; -export const ACCPET_ADMIN_MUTATION = gql` +export const ACCEPT_ADMIN_MUTATION = gql` mutation AcceptAdmin($id: ID!) { acceptAdmin(id: $id) } @@ -351,30 +351,29 @@ export const REJECT_ADMIN_MUTATION = gql` * @description used to toggle `installStatus` (boolean value) of a Plugin */ export const UPDATE_INSTALL_STATUS_PLUGIN_MUTATION = gql` - mutation update_install_status_plugin_mutation($id: ID!, $status: Boolean!) { - updatePluginStatus(id: $id, status: $status) { + mutation update_install_status_plugin_mutation($id: ID!, $orgId: ID!) { + updatePluginStatus(orgId: $orgId, id: $id) { _id pluginName pluginCreatedBy pluginDesc - pluginInstallStatus + uninstalledOrgs } } `; /** * @name UPDATE_ORG_STATUS_PLUGIN_MUTATION - * @description used `updatePluginInstalledOrgs`to add or remove the current Organization the in the plugin list `installedOrgs` + * @description used `updatePluginStatus`to add or remove the current Organization the in the plugin list `uninstalledOrgs` */ export const UPDATE_ORG_STATUS_PLUGIN_MUTATION = gql` mutation update_install_status_plugin_mutation($id: ID!, $orgId: ID!) { - updatePluginInstalledOrgs(id: $id, orgId: $orgId) { + updatePluginStatus(id: $id, orgId: $orgId) { _id pluginName pluginCreatedBy pluginDesc - pluginInstallStatus - installedOrgs + uninstalledOrgs } } `; @@ -388,22 +387,16 @@ export const ADD_PLUGIN_MUTATION = gql` $pluginName: String! $pluginCreatedBy: String! $pluginDesc: String! - $pluginInstallStatus: Boolean! - $installedOrgs: [ID!] ) { createPlugin( pluginName: $pluginName pluginCreatedBy: $pluginCreatedBy pluginDesc: $pluginDesc - pluginInstallStatus: $pluginInstallStatus - installedOrgs: $installedOrgs ) { _id pluginName pluginCreatedBy pluginDesc - pluginInstallStatus - installedOrgs } } `; diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index 28d4e57d6a..a6c7f769f5 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -524,7 +524,6 @@ export const PLUGIN_GET = gql` pluginName pluginCreatedBy pluginDesc - pluginInstallStatus } } `; diff --git a/src/assets/css/app.css b/src/assets/css/app.css index 288b220848..c507b04f38 100644 --- a/src/assets/css/app.css +++ b/src/assets/css/app.css @@ -12541,6 +12541,47 @@ body { background-color: #f2f7ff; } +input[type='checkbox'] { + transform: scale(1.5); +} + +.form-switch { + padding-left: 3rem; +} + +input[type='file']::file-selector-button { + background: var(--bs-gray-400); +} + +.shimmer { + animation-duration: 2.2s; + animation-fill-mode: forwards; + animation-iteration-count: infinite; + animation-name: shimmer; + animation-timing-function: linear; + background: var(--bs-gray-200); + background: linear-gradient(to right, #f6f6f6 8%, #f0f0f0 18%, #f6f6f6 33%); + background-size: 1200px 100%; +} + +@-webkit-keyframes shimmer { + 0% { + background-position: -100% 0; + } + 100% { + background-position: 100% 0; + } +} + +@keyframes shimmer { + 0% { + background-position: -1200px 0; + } + 100% { + background-position: 1200px 0; + } +} + /* 6. COLORS diff --git a/src/assets/scss/_general.scss b/src/assets/scss/_general.scss index 2bca736d92..f2309504ce 100644 --- a/src/assets/scss/_general.scss +++ b/src/assets/scss/_general.scss @@ -20,3 +20,44 @@ body { min-height: 100vh; background-color: #f2f7ff; } + +input[type='checkbox'] { + transform: scale(1.5); +} +.form-switch { + padding-left: 3rem; +} +input[type='file']::file-selector-button { + background: var(--bs-gray-400); +} + +.shimmer { + animation-duration: 2.2s; + animation-fill-mode: forwards; + animation-iteration-count: infinite; + animation-name: shimmer; + animation-timing-function: linear; + background: var(--bs-gray-200); + background: linear-gradient(to right, #f6f6f6 8%, #f0f0f0 18%, #f6f6f6 33%); + background-size: 1200px 100%; +} + +@-webkit-keyframes shimmer { + 0% { + background-position: -100% 0; + } + + 100% { + background-position: 100% 0; + } +} + +@keyframes shimmer { + 0% { + background-position: -1200px 0; + } + + 100% { + background-position: 1200px 0; + } +} diff --git a/src/assets/svgs/icons/angleRight.svg b/src/assets/svgs/icons/angleRight.svg new file mode 100644 index 0000000000..4a3a498877 --- /dev/null +++ b/src/assets/svgs/icons/angleRight.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icons/logout.svg b/src/assets/svgs/icons/logout.svg new file mode 100644 index 0000000000..e71a973f0d --- /dev/null +++ b/src/assets/svgs/icons/logout.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icons/organizations.svg b/src/assets/svgs/icons/organizations.svg new file mode 100644 index 0000000000..5c616655d2 --- /dev/null +++ b/src/assets/svgs/icons/organizations.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svgs/icons/requests.svg b/src/assets/svgs/icons/requests.svg new file mode 100644 index 0000000000..8873bce1d7 --- /dev/null +++ b/src/assets/svgs/icons/requests.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/svgs/icons/roles.svg b/src/assets/svgs/icons/roles.svg new file mode 100644 index 0000000000..bc301784f9 --- /dev/null +++ b/src/assets/svgs/icons/roles.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/palisadoes.svg b/src/assets/svgs/palisadoes.svg new file mode 100644 index 0000000000..dc57b69a42 --- /dev/null +++ b/src/assets/svgs/palisadoes.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/svgs/talawa.svg b/src/assets/svgs/talawa.svg new file mode 100644 index 0000000000..0c89afbcca --- /dev/null +++ b/src/assets/svgs/talawa.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/AdminDashListCard/AdminDashListCard.module.css b/src/components/AdminDashListCard/AdminDashListCard.module.css deleted file mode 100644 index c73fce7d17..0000000000 --- a/src/components/AdminDashListCard/AdminDashListCard.module.css +++ /dev/null @@ -1,78 +0,0 @@ -.orglist { - margin-top: -1px; - padding-left: 32px; -} - -.singledetails { - display: flex; - flex-direction: row; - justify-content: space-between; -} - -.singledetails p { - margin-bottom: -5px; -} - -.singledetails_data_left { - margin-top: 10px; - /* margin-left: 10px; */ - color: #707070; -} - -.singledetails_data_right { - justify-content: right; - margin-top: 10px; - text-align: right; - color: #707070; -} - -.orgname { - font-size: 16px; - font-weight: bold; -} - -.orgfont { - margin-top: 3px; -} - -.orgfontcreated { - margin-top: 18px; -} - -.orgfontcreatedbtn { - margin-top: 18px; - border-radius: 4px; - border-color: #31bb6b; - background-color: #31bb6b; - color: white; - padding-right: 10px; - padding-left: 10px; - box-shadow: none; - width: 100%; -} - -.orgfontcreatedbtn:hover { - color: #fff; - background-color: #1e7e34; - border-color: #1c7430; -} - -.orgCreateBtnDiv { - width: 100%; -} - -#grid_wrapper { - align-items: left; -} - -.orgimg { - width: 200px; - height: 100px; - border-radius: 7px; - margin-left: 20px; -} - -.singledetails { - margin-left: 25px; - padding-left: 0; -} diff --git a/src/components/AdminDashListCard/AdminDashListCard.test.tsx b/src/components/AdminDashListCard/AdminDashListCard.test.tsx deleted file mode 100644 index 0372a81b03..0000000000 --- a/src/components/AdminDashListCard/AdminDashListCard.test.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import Enzyme, { shallow } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; -import { I18nextProvider } from 'react-i18next'; -import 'jest-location-mock'; - -import AdminDashListCard from './AdminDashListCard'; -import i18nForTest from 'utils/i18nForTest'; - -Enzyme.configure({ adapter: new Adapter() }); - -describe('Testing the Super Dash List', () => { - test('should render props and text elements test for the page component', () => { - const props = { - key: '123', - id: '123', - orgName: 'Dogs Care', - orgLocation: 'India', - createdDate: '04/07/2019', - image: 'dummyImage', - admins: [ - { - _id: '123', - }, - { - _id: '456', - }, - ], - members: '34', - }; - - const buttonInstance = shallow(); - const clickButton = buttonInstance.find('Button'); - - render( - - - - ); - - expect(screen.getByText('Admins:')).toBeInTheDocument(); - expect(screen.getByText('Members:')).toBeInTheDocument(); - expect(screen.getByText('Dogs Care')).toBeInTheDocument(); - expect(screen.getByText('India')).toBeInTheDocument(); - expect(screen.getByText('04/07/2019')).toBeInTheDocument(); - - clickButton.simulate('click'); - }); - - test('Testing if the props data is not provided', () => { - const props = { - key: '123', - id: '123', - orgName: '', - orgLocation: 'India', - createdDate: '04/07/2019', - image: '', - admins: [ - { - _id: '123', - }, - { - _id: '456', - }, - ], - members: '34', - }; - - window.location.assign('/orgdash'); - - render( - - - - ); - - expect(window.location).toBeAt('/orgdash'); - }); -}); diff --git a/src/components/AdminDashListCard/AdminDashListCard.tsx b/src/components/AdminDashListCard/AdminDashListCard.tsx deleted file mode 100644 index 214f69a75d..0000000000 --- a/src/components/AdminDashListCard/AdminDashListCard.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import Row from 'react-bootstrap/Row'; -import Col from 'react-bootstrap/Col'; -import Button from 'react-bootstrap/Button'; -import { useTranslation } from 'react-i18next'; - -import styles from './AdminDashListCard.module.css'; -import defaultImg from 'assets/images/blank.png'; - -interface InterfaceAdminDashListCardProps { - key: string; - id: string; - orgName: string; - orgLocation: string | null; - createdDate: string; - image: string; - admins: any; - members: any; -} - -function adminDashListCard( - props: InterfaceAdminDashListCardProps -): JSX.Element { - const userId = localStorage.getItem('id'); - - function click(): void { - const url = '/orgdash/id=' + props.id; - window.location.replace(url); - } - - const { t } = useTranslation('translation', { - keyPrefix: 'adminDashListCard', - }); - - return ( - <> - - {props.image ? ( - - ) : ( - - )} - -
-

- {props.orgName ? <>{props.orgName} : <>Dogs Care} -

-

{props?.orgLocation}

-

- {t('created')}: {props.createdDate} -

-
-
-

- {t('admins')}: {props?.admins.length} -

-

- {t('members')}: {props?.members} -

-
- -
-
- -
-
- - ); -} -export {}; -export default adminDashListCard; diff --git a/src/components/LeftDrawer/LeftDrawer.module.css b/src/components/LeftDrawer/LeftDrawer.module.css new file mode 100644 index 0000000000..379633fa5a --- /dev/null +++ b/src/components/LeftDrawer/LeftDrawer.module.css @@ -0,0 +1,241 @@ +.leftDrawer { + width: calc(300px + 2rem); + position: fixed; + top: 0; + bottom: 0; + z-index: 100; + display: flex; + flex-direction: column; + padding: 1rem 1rem 0 1rem; + background-color: var(--bs-white); + transition: 0.5s; +} + +.activeDrawer { + width: calc(300px + 2rem); + position: fixed; + top: 0; + left: 0; + bottom: 0; + animation: comeToRightBigScreen 0.5s ease-in-out; +} + +.inactiveDrawer { + position: fixed; + top: 0; + left: calc(-300px - 2rem); + bottom: 0; + animation: goToLeftBigScreen 0.5s ease-in-out; +} + +.leftDrawer .closeModalBtn { + display: none; +} + +.leftDrawer .talawaLogo { + width: 100%; + height: 65px; +} + +.leftDrawer .talawaText { + font-size: 1rem; + text-align: center; +} + +.leftDrawer .titleHeader { + margin: 2rem 0 1rem 0; + font-weight: 600; +} + +.leftDrawer .optionList button { + display: flex; + align-items: center; + width: 100%; + text-align: start; + margin-bottom: 0.8rem; + border-radius: 8px; +} + +.leftDrawer .optionList button .iconWrapper { + width: 36px; +} + +.leftDrawer .profileContainer { + border: none; + width: 100%; + height: 52px; + border-radius: 8px; + background-color: var(--bs-white); + display: flex; + align-items: center; +} + +.leftDrawer .profileContainer:focus { + outline: none; + background-color: var(--bs-gray-100); +} + +.leftDrawer .imageContainer { + width: 68px; +} + +.leftDrawer .profileContainer img { + height: 52px; + width: 52px; + border-radius: 50%; +} + +.leftDrawer .profileContainer .profileText { + flex: 1; + text-align: start; +} + +.leftDrawer .profileContainer .profileText .primaryText { + font-size: 1.1rem; + font-weight: 600; +} + +.leftDrawer .profileContainer .profileText .secondaryText { + font-size: 0.8rem; + font-weight: 400; + color: var(--bs-secondary); + display: block; + text-transform: capitalize; +} + +@media (max-width: 1120px) { + .leftDrawer { + width: calc(250px + 2rem); + padding: 1rem 1rem 0 1rem; + } +} + +/* For tablets */ +@media (max-width: 820px) { + .leftDrawer { + width: 100%; + left: 0; + right: 0; + } + + .leftDrawer .closeModalBtn { + display: block; + position: absolute; + top: 1rem; + right: 1rem; + z-index: 10; + } + + /* For smaller devices .activeDrawer in real behaves like inactive */ + .activeDrawer { + opacity: 0; + left: 0; + z-index: -1; + animation: closeDrawer 0.4s ease-in-out; + } + + /* For smaller devices .inactiveDrawer in real behaves like active */ + .inactiveDrawer { + display: flex; + z-index: 100; + animation: openDrawer 0.6s ease-in-out; + } +} + +@keyframes goToLeftBigScreen { + from { + left: 0; + } + + to { + opacity: 0.1; + left: calc(-300px - 2rem); + } +} + +/* Webkit prefix for older browser compatibility */ +@-webkit-keyframes goToLeftBigScreen { + from { + left: 0; + } + + to { + opacity: 0.1; + left: calc(-300px - 2rem); + } +} + +@keyframes comeToRightBigScreen { + from { + opacity: 0.4; + left: calc(-300px - 2rem); + } + + to { + opacity: 1; + left: 0; + } +} + +/* Webkit prefix for older browser compatibility */ +@-webkit-keyframes comeToRightBigScreen { + from { + opacity: 0.4; + left: calc(-300px - 2rem); + } + + to { + opacity: 1; + left: 0; + } +} + +@keyframes closeDrawer { + from { + left: 0; + opacity: 1; + } + + to { + left: -1000px; + opacity: 0; + } +} + +/* Webkit prefix for older browser compatibility */ +@-webkit-keyframes closeDrawer { + from { + left: 0; + opacity: 1; + } + + to { + left: -1000px; + opacity: 0; + } +} + +@keyframes openDrawer { + from { + opacity: 0; + left: -1000px; + } + + to { + left: 0; + opacity: 1; + } +} + +/* Webkit prefix for older browser compatibility */ +@-webkit-keyframes openDrawer { + from { + opacity: 0; + left: -1000px; + } + + to { + left: 0; + opacity: 1; + } +} diff --git a/src/components/LeftDrawer/LeftDrawer.test.tsx b/src/components/LeftDrawer/LeftDrawer.test.tsx new file mode 100644 index 0000000000..b6bbcd5755 --- /dev/null +++ b/src/components/LeftDrawer/LeftDrawer.test.tsx @@ -0,0 +1,263 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import 'jest-localstorage-mock'; +import { I18nextProvider } from 'react-i18next'; +import { BrowserRouter } from 'react-router-dom'; + +import { toast } from 'react-toastify'; +import i18nForTest from 'utils/i18nForTest'; +import type { InterfaceLeftDrawerProps } from './LeftDrawer'; +import LeftDrawer from './LeftDrawer'; + +const props = { + data: { + user: { + firstName: 'John', + lastName: 'Doe', + image: null, + email: 'johndoe@gmail.com', + userType: 'SUPERADMIN', + adminFor: [ + { + _id: '123', + name: 'Palisadoes', + image: null, + }, + ], + }, + }, + showDrawer: true, + setShowDrawer: jest.fn(), +}; + +const propsAdmin: InterfaceLeftDrawerProps = { + data: { + user: { + firstName: 'John', + lastName: 'Doe', + image: `https://api.dicebear.com/5.x/initials/svg?seed=John%20Doe`, + email: 'johndoe@gmail.com', + userType: 'ADMIN', + adminFor: [ + { + _id: '123', + name: 'Palisadoes', + image: null, + }, + ], + }, + }, + screenName: 'Organizations', + showDrawer: true, + setShowDrawer: jest.fn(), +}; + +const propsOrg: InterfaceLeftDrawerProps = { + ...props, + screenName: 'Organizations', +}; +const propsReq: InterfaceLeftDrawerProps = { + ...props, + screenName: 'Requests', +}; +const propsRoles: InterfaceLeftDrawerProps = { + ...props, + screenName: 'Roles', + showDrawer: false, +}; + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + +afterEach(() => { + jest.clearAllMocks(); + localStorage.clear(); +}); + +describe('Testing Left Drawer component for SUPERADMIN', () => { + test('Component should be rendered properly', () => { + localStorage.setItem('UserType', 'SUPERADMIN'); + render( + + + + + + ); + + expect(screen.getByText('Organizations')).toBeInTheDocument(); + expect(screen.getByText('Requests')).toBeInTheDocument(); + expect(screen.getByText('Roles')).toBeInTheDocument(); + expect(screen.getByText('Talawa Admin Portal')).toBeInTheDocument(); + + expect(screen.getByText(/John Doe/i)).toBeInTheDocument(); + expect(screen.getByText(/Superadmin/i)).toBeInTheDocument(); + expect(screen.getByAltText(/dummy picture/i)).toBeInTheDocument(); + + const orgsBtn = screen.getByTestId(/orgsBtn/i); + const requestsBtn = screen.getByTestId(/requestsBtn/i); + const rolesBtn = screen.getByTestId(/rolesBtn/i); + + expect( + orgsBtn.className.includes('text-white btn btn-success') + ).toBeTruthy(); + expect( + requestsBtn.className.includes('text-secondary btn btn-light') + ).toBeTruthy(); + expect( + rolesBtn.className.includes('text-secondary btn btn-light') + ).toBeTruthy(); + + // Coming soon + userEvent.click(screen.getByTestId(/profileBtn/i)); + expect(toast.success).toHaveBeenCalledWith('Profile page coming soon!'); + + // Send to roles screen + userEvent.click(rolesBtn); + expect(global.window.location.pathname).toContain('/roles'); + }); + + test('Testing when user data is undefined', () => { + localStorage.setItem('UserType', 'SUPERADMIN'); + const userUndefinedProps = { + ...props, + data: undefined, + screenName: 'Organizations', + }; + render( + + + + + + ); + expect(screen.getByTestId(/loadingProfile/i)).toBeInTheDocument(); + }); + + test('Testing in requests screen', () => { + localStorage.setItem('UserType', 'SUPERADMIN'); + render( + + + + + + ); + + const orgsBtn = screen.getByTestId(/orgsBtn/i); + const requestsBtn = screen.getByTestId(/requestsBtn/i); + const rolesBtn = screen.getByTestId(/rolesBtn/i); + + expect( + requestsBtn.className.includes('text-white btn btn-success') + ).toBeTruthy(); + expect( + orgsBtn.className.includes('text-secondary btn btn-light') + ).toBeTruthy(); + expect( + rolesBtn.className.includes('text-secondary btn btn-light') + ).toBeTruthy(); + + // Send to organizations screen + userEvent.click(orgsBtn); + expect(global.window.location.pathname).toContain('/orglist'); + }); + + test('Testing in roles screen', () => { + localStorage.setItem('UserType', 'SUPERADMIN'); + render( + + + + + + ); + + const orgsBtn = screen.getByTestId(/orgsBtn/i); + const requestsBtn = screen.getByTestId(/requestsBtn/i); + const rolesBtn = screen.getByTestId(/rolesBtn/i); + + expect( + orgsBtn.className.includes('text-secondary btn btn-light') + ).toBeTruthy(); + expect( + requestsBtn.className.includes('text-secondary btn btn-light') + ).toBeTruthy(); + expect( + rolesBtn.className.includes('text-white btn btn-success') + ).toBeTruthy(); + + // Send to requests screen + userEvent.click(requestsBtn); + expect(global.window.location.pathname).toContain('/requests'); + }); + + test('Testing Drawer open close functionality', () => { + localStorage.setItem('UserType', 'SUPERADMIN'); + render( + + + + + + ); + const closeModalBtn = screen.getByTestId(/closeModalBtn/i); + userEvent.click(closeModalBtn); + }); + + test('Testing logout functionality', async () => { + render( + + + + + + ); + + userEvent.click(screen.getByTestId('logoutBtn')); + }); +}); + +describe('Testing Left Drawer component for ADMIN', () => { + test('Components should be rendered properly', () => { + localStorage.setItem('UserType', 'ADMIN'); + render( + + + + + + ); + + expect(screen.getByText('Organizations')).toBeInTheDocument(); + expect(screen.getByText('Talawa Admin Portal')).toBeInTheDocument(); + + expect(screen.getByText(/John Doe/i)).toBeInTheDocument(); + expect(screen.getAllByText(/admin/i)).toHaveLength(2); + expect(screen.getByAltText(/profile picture/i)).toBeInTheDocument(); + + const orgsBtn = screen.getByTestId(/orgsBtn/i); + + expect( + orgsBtn.className.includes('text-white btn btn-success') + ).toBeTruthy(); + + // These screens arent meant for admins so they should not be present + expect(screen.queryByTestId(/rolesBtn/i)).toBeNull(); + expect(screen.queryByTestId(/requestsBtn/i)).toBeNull(); + + // Coming soon + userEvent.click(screen.getByTestId(/profileBtn/i)); + expect(toast.success).toHaveBeenCalledWith('Profile page coming soon!'); + + // Send to roles screen + userEvent.click(orgsBtn); + expect(global.window.location.pathname).toContain('/orglist'); + }); +}); diff --git a/src/components/LeftDrawer/LeftDrawer.tsx b/src/components/LeftDrawer/LeftDrawer.tsx new file mode 100644 index 0000000000..f2f750ec85 --- /dev/null +++ b/src/components/LeftDrawer/LeftDrawer.tsx @@ -0,0 +1,182 @@ +import React from 'react'; +import Button from 'react-bootstrap/Button'; +import type { InterfaceUserType } from 'utils/interfaces'; +import { ReactComponent as AngleRightIcon } from '../../assets/svgs/icons/angleRight.svg'; +import { ReactComponent as LogoutIcon } from '../../assets/svgs/icons/logout.svg'; +import { ReactComponent as OrganizationsIcon } from '../../assets/svgs/icons/organizations.svg'; +import { ReactComponent as RequestsIcon } from '../../assets/svgs/icons/requests.svg'; +import { ReactComponent as RolesIcon } from '../../assets/svgs/icons/roles.svg'; +import { ReactComponent as TalawaLogo } from '../../assets/svgs/talawa.svg'; +import styles from './LeftDrawer.module.css'; +import { toast } from 'react-toastify'; +import { useHistory } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; + +export interface InterfaceLeftDrawerProps { + data: InterfaceUserType | undefined; + showDrawer: boolean; + setShowDrawer: React.Dispatch>; + screenName: string; +} + +const leftDrawer = ({ + data, + screenName, + showDrawer, + setShowDrawer, +}: InterfaceLeftDrawerProps): JSX.Element => { + const { t } = useTranslation('translation', { keyPrefix: 'leftDrawer' }); + + const userType = localStorage.getItem('UserType'); + + const history = useHistory(); + + const logout = (): void => { + localStorage.clear(); + history.push('/'); + }; + + return ( + <> +
+ + +

{t('talawaAdminPortal')}

+
{t('menu')}
+
+ + {userType === 'SUPERADMIN' && ( + + )} + {userType === 'SUPERADMIN' && ( + + )} +
+
+ {data === undefined ? ( +
+ ) : ( + + )} + + +
+
+ + ); +}; + +export default leftDrawer; diff --git a/src/components/OrgListCard/OrgListCard.module.css b/src/components/OrgListCard/OrgListCard.module.css new file mode 100644 index 0000000000..355b69d732 --- /dev/null +++ b/src/components/OrgListCard/OrgListCard.module.css @@ -0,0 +1,85 @@ +.orgCard { + background-color: var(--bs-white); + margin: 0.5rem; + height: calc(120px + 2rem); + padding: 1rem; + border-radius: 8px; + outline: 1px solid var(--bs-gray-200); + position: relative; +} + +.orgCard .innerContainer { + display: flex; +} + +.orgCard .innerContainer .orgImgContainer { + display: flex; + justify-content: center; + align-items: center; + position: relative; + overflow: hidden; + border-radius: 4px; +} + +.orgCard .innerContainer .orgImgContainer .overlayTheme { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: var(--bs-primary); + opacity: 0.12; + z-index: 0; +} + +.orgCard .innerContainer .orgImgContainer img { + width: 125px; + height: 120px; + z-index: 1; + object-fit: contain; +} + +.orgCard .innerContainer .content { + flex: 1; + margin-left: 1rem; +} + +.orgCard button { + position: absolute; + bottom: 1rem; + right: 1rem; + z-index: 1; +} + +@media (max-width: 450px) { + .orgCard { + height: unset; + margin: 0.5rem 0; + padding: 1.25rem 1.5rem; + } + + .orgCard .innerContainer { + flex-direction: column; + } + + .orgCard .innerContainer .orgImgContainer { + margin-bottom: 0.8rem; + } + + .orgCard .innerContainer .orgImgContainer img { + height: auto; + width: 100%; + } + + .orgCard .innerContainer .content { + margin-left: 0; + } + + .orgCard button { + bottom: 0; + right: 0; + position: relative; + margin-left: auto; + display: block; + } +} diff --git a/src/components/OrgListCard/OrgListCard.test.tsx b/src/components/OrgListCard/OrgListCard.test.tsx new file mode 100644 index 0000000000..fd1a7417b7 --- /dev/null +++ b/src/components/OrgListCard/OrgListCard.test.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; + +import i18nForTest from 'utils/i18nForTest'; +import type { InterfaceOrgListCardProps } from './OrgListCard'; +import AdminDashListCard from './OrgListCard'; +import userEvent from '@testing-library/user-event'; +import { BrowserRouter } from 'react-router-dom'; + +const props: InterfaceOrgListCardProps = { + data: { + _id: 'xyz', + name: 'Dogs Care', + image: 'https://api.dicebear.com/5.x/initials/svg?seed=John%20Doe', + location: 'India', + admins: [ + { + _id: '123', + }, + { + _id: '456', + }, + ], + members: [], + createdAt: '04/07/2019', + creator: { + _id: 'abc', + firstName: 'John', + lastName: 'Doe', + }, + }, +}; + +describe('Testing the Super Dash List', () => { + test('should render props and text elements test for the page component', () => { + localStorage.setItem('id', '123'); // Means the user is an admin + + render( + + + + + + ); + expect(screen.getByAltText(/Dogs Care image/i)).toBeInTheDocument(); + expect(screen.getByText('Admins:')).toBeInTheDocument(); + expect(screen.getByText('Members:')).toBeInTheDocument(); + expect(screen.getByText('Dogs Care')).toBeInTheDocument(); + expect(screen.getByText('India')).toBeInTheDocument(); + userEvent.click(screen.getByTestId(/manageBtn/i)); + }); + + test('Testing if the props data is not provided', () => { + window.location.assign('/orgdash'); + + render( + + + + + + ); + + expect(window.location).toBeAt('/orgdash'); + }); + + test('Testing if component is rendered properly when image is null', () => { + const imageNullProps = { + ...props, + ...{ data: { ...props.data, ...{ image: null } } }, + }; + render( + + + + ); + expect(screen.getByAltText(/default image/i)).toBeInTheDocument(); + }); + + test('Testing if user is redirected to orgDash screen', () => { + render( + + + + + + ); + userEvent.click(screen.getByTestId('manageBtn')); + expect(window.location).toBeAt('/orgdash/id=xyz'); + }); +}); diff --git a/src/components/OrgListCard/OrgListCard.tsx b/src/components/OrgListCard/OrgListCard.tsx new file mode 100644 index 0000000000..2f87e303f1 --- /dev/null +++ b/src/components/OrgListCard/OrgListCard.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import Button from 'react-bootstrap/Button'; +import { useTranslation } from 'react-i18next'; +import styles from './OrgListCard.module.css'; +import { useHistory } from 'react-router-dom'; +import AboutImg from 'assets/images/defaultImg.png'; +import type { InterfaceOrgConnectionInfoType } from 'utils/interfaces'; +import LocationOnIcon from '@mui/icons-material/LocationOn'; + +export interface InterfaceOrgListCardProps { + data: InterfaceOrgConnectionInfoType; +} + +function superDashListCard(props: InterfaceOrgListCardProps): JSX.Element { + const { _id, admins, image, location, members, name } = props.data; + + const history = useHistory(); + + function handleClick(): void { + const url = '/orgdash/id=' + _id; + + // Dont change the below two lines + window.location.replace(url); + history.push(url); + } + + const { t } = useTranslation('translation', { + keyPrefix: 'orgListCard', + }); + + return ( + <> +
+
+
+
+ {image ? ( + {`${name} + ) : ( + {`default + )} +
+
+
{name}
+
+ + {location} +
+
+ {t('admins')}: {admins.length} +
+
+ {t('members')}: {members.length} +
+
+
+ +
+ + ); +} +export default superDashListCard; diff --git a/src/components/OrganizationCard/OrganizationCard.module.css b/src/components/OrganizationCard/OrganizationCard.module.css index c72610cc1a..6c65b8258b 100644 --- a/src/components/OrganizationCard/OrganizationCard.module.css +++ b/src/components/OrganizationCard/OrganizationCard.module.css @@ -12,6 +12,7 @@ .box :hover { color: #ffbd59; } + .first_box { display: flex; flex-direction: row; @@ -43,22 +44,3 @@ width: 65vw; height: 0px !important; } -/* width */ -::-webkit-scrollbar { - width: 10px; -} - -/* Track */ -::-webkit-scrollbar-track { - box-shadow: inset 0 0 5px #dfdfdf; - border-radius: 10px; -} -/* Handle */ -::-webkit-scrollbar-thumb { - background: #ffbd59; - border-radius: 10px; -} -/* Handle on hover */ -::-webkit-scrollbar-thumb:hover { - background: #ffbd59; -} diff --git a/src/components/OrganizationCardStart/OrganizationCardStart.module.css b/src/components/OrganizationCardStart/OrganizationCardStart.module.css index c72610cc1a..6c65b8258b 100644 --- a/src/components/OrganizationCardStart/OrganizationCardStart.module.css +++ b/src/components/OrganizationCardStart/OrganizationCardStart.module.css @@ -12,6 +12,7 @@ .box :hover { color: #ffbd59; } + .first_box { display: flex; flex-direction: row; @@ -43,22 +44,3 @@ width: 65vw; height: 0px !important; } -/* width */ -::-webkit-scrollbar { - width: 10px; -} - -/* Track */ -::-webkit-scrollbar-track { - box-shadow: inset 0 0 5px #dfdfdf; - border-radius: 10px; -} -/* Handle */ -::-webkit-scrollbar-thumb { - background: #ffbd59; - border-radius: 10px; -} -/* Handle on hover */ -::-webkit-scrollbar-thumb:hover { - background: #ffbd59; -} diff --git a/src/components/SuperAdminScreen/SuperAdminScreen.module.css b/src/components/SuperAdminScreen/SuperAdminScreen.module.css new file mode 100644 index 0000000000..681ac8823d --- /dev/null +++ b/src/components/SuperAdminScreen/SuperAdminScreen.module.css @@ -0,0 +1,60 @@ +.pageContainer { + display: flex; + flex-direction: column; + min-height: 100vh; + padding: 1rem 1.5rem 0 calc(300px + 2rem + 1.5rem); +} + +.expand { + padding-left: 1.5rem; + animation: moveLeft 0.5s ease-in-out; +} + +.contract { + padding-left: calc(300px + 2rem + 1.5rem); + animation: moveRight 0.5s ease-in-out; +} + +@media (max-width: 1120px) { + .contract { + padding-left: calc(250px + 2rem + 1.5rem); + } +} + +/* For tablets */ +@media (max-width: 820px) { + .pageContainer { + padding-left: 1.5rem; + } + + .contract, + .expand { + animation: none; + } +} + +@media (max-width: 820px) { + .pageContainer { + padding: 1rem; + } +} + +@keyframes moveLeft { + from { + padding-left: calc(300px + 2rem + 1.5rem); + } + + to { + padding-left: 1.5rem; + } +} + +@keyframes moveRight { + from { + padding-left: 1.5rem; + } + + to { + padding-left: calc(300px + 2rem + 1.5rem); + } +} diff --git a/src/components/SuperAdminScreen/SuperAdminScreen.test.tsx b/src/components/SuperAdminScreen/SuperAdminScreen.test.tsx new file mode 100644 index 0000000000..1dbb787c2a --- /dev/null +++ b/src/components/SuperAdminScreen/SuperAdminScreen.test.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import { 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 { store } from 'state/store'; +import i18nForTest from 'utils/i18nForTest'; +import type { InterfaceSuperAdminScreenProps } from './SuperAdminScreen'; +import SuperAdminScreen from './SuperAdminScreen'; + +const props: InterfaceSuperAdminScreenProps = { + title: 'Organizations', + screenName: 'Organizations', + data: { + user: { + firstName: 'test', + lastName: 'test', + email: 'JohnDoe@gmail.com', + adminFor: [], + image: null, + userType: 'SUPERADMIN', + }, + }, + children:
Testing ...
, +}; + +describe('Testing LeftDrawer in SuperAdminScreen', () => { + test('Testing LeftDrawer in page functionality', async () => { + localStorage.setItem('UserType', 'SUPERADMIN'); + + render( + + + + + + + + + + ); + + userEvent.click(screen.getByTestId('menuBtn')); + }); +}); diff --git a/src/components/SuperAdminScreen/SuperAdminScreen.tsx b/src/components/SuperAdminScreen/SuperAdminScreen.tsx new file mode 100644 index 0000000000..5664e8a61c --- /dev/null +++ b/src/components/SuperAdminScreen/SuperAdminScreen.tsx @@ -0,0 +1,56 @@ +import MenuIcon from '@mui/icons-material/Menu'; +import LeftDrawer from 'components/LeftDrawer/LeftDrawer'; +import React, { useState } from 'react'; +import Button from 'react-bootstrap/Button'; +import type { InterfaceUserType } from 'utils/interfaces'; +import styles from './SuperAdminScreen.module.css'; + +export interface InterfaceSuperAdminScreenProps { + title: string; // Multilingual Page title + screenName: string; // Internal Screen name for developers + data: InterfaceUserType | undefined; + children: React.ReactNode; +} +const superAdminScreen = ({ + title, + screenName, + data, + children, +}: InterfaceSuperAdminScreenProps): JSX.Element => { + const [showDrawer, setShowDrawer] = useState(true); + + return ( + <> + +
+
+
+

{title}

+
+ +
+ {children} +
+ + ); +}; + +export default superAdminScreen; diff --git a/src/components/SuperDashListCard/SuperDashListCard.module.css b/src/components/SuperDashListCard/SuperDashListCard.module.css deleted file mode 100644 index 2f0b92fd4b..0000000000 --- a/src/components/SuperDashListCard/SuperDashListCard.module.css +++ /dev/null @@ -1,83 +0,0 @@ -.orglist { - margin-top: -1px; - padding-left: 32px; -} - -.orgImgContainer { - display: flex; - height: 100px; - width: 100%; - border-radius: 7px; - justify-content: center; -} - -.singledetails { - display: flex; - flex-direction: row; - justify-content: space-between; -} -.singledetails p { - margin-bottom: -5px; -} -.singledetails_data_left { - margin-top: 10px; - /* margin-left: 10px; */ - color: #707070; -} -.singledetails_data_right { - justify-content: right; - margin-top: 10px; - text-align: right; - color: #707070; -} -.orgname { - font-size: 16px; - font-weight: bold; -} -.orgfont { - margin-top: 3px; -} -.orgfontcreated { - margin-top: 18px; -} -.orgfontcreatedbtn { - margin-top: 18px; - border-radius: 4px; - border-color: #31bb6b; - background-color: #31bb6b; - color: white; - padding-right: 10px; - padding-left: 10px; - box-shadow: none; - width: 100%; -} -.orgfontcreatedbtn:hover { - color: #fff; - background-color: #1e7e34; - border-color: #1c7430; -} -.orgCreateBtnDiv { - width: 100%; -} -#grid_wrapper { - align-items: left; -} -.orgimg { - width: 100%; - height: 100%; - object-fit: contain; - flex: 1 1 auto; -} - -.singledetails { - margin-left: 25px; - padding-left: 0; -} - -@media screen and (min-width: 40rem) { - .orgImgContainer { - display: block; - width: inherit; - width: 200px; - } -} diff --git a/src/components/SuperDashListCard/SuperDashListCard.test.tsx b/src/components/SuperDashListCard/SuperDashListCard.test.tsx deleted file mode 100644 index d33794b801..0000000000 --- a/src/components/SuperDashListCard/SuperDashListCard.test.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import Enzyme, { shallow } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; -import { I18nextProvider } from 'react-i18next'; -import 'jest-location-mock'; - -import SuperDashListCard from './SuperDashListCard'; -import i18nForTest from 'utils/i18nForTest'; - -Enzyme.configure({ adapter: new Adapter() }); - -jest.mock( - 'react-router-dom', - (): { useHistory: () => { push: jest.Mock } } => ({ - useHistory: (): { push: jest.Mock } => ({ - push: jest.fn(), - }), - }) -); - -describe('Testing the Super Dash List', () => { - test('should render props and text elements test for the page component', () => { - const props = { - key: '123', - id: '123', - orgName: 'Dogs Care', - orgLocation: 'India', - createdDate: '04/07/2019', - image: 'dummyImage', - admins: [ - { - _id: '123', - }, - { - _id: '456', - }, - ], - members: '34', - }; - - const buttonInstance = shallow(); - const clickButton = buttonInstance.find('Button'); - - render( - - - - ); - - expect(screen.getByText('Admins:')).toBeInTheDocument(); - expect(screen.getByText('Members:')).toBeInTheDocument(); - expect(screen.getByText('Dogs Care')).toBeInTheDocument(); - expect(screen.getByText('India')).toBeInTheDocument(); - expect(screen.getByText('04/07/2019')).toBeInTheDocument(); - - clickButton.simulate('click'); - }); - - /* - WARNING! - Do not tamper with this testcase. This is a test case to check for the window.location.replace exists and is being called correctly. - Removal of this testcase will lead to unexpected breaks in the routing of the dashboard and such actions must be avoided. - */ - test('Testing if window.location.replace exists and is called', () => { - const props = { - key: '123', - id: '123', - orgName: '', - orgLocation: 'India', - createdDate: '04/07/2019', - image: '', - admins: [ - { - _id: '123', - }, - { - _id: '456', - }, - ], - members: '34', - }; - - const buttonInstance = shallow(); - const clickButton = buttonInstance.find('Button'); - - render( - - - - ); - - clickButton.simulate('click'); - expect(window.location.replace).toHaveBeenCalled(); - expect(window.location).toBeAt(`/orgdash/id=${props.id}`); - }); - - // Do not change the lines above. - - test('Testing if the props data is not provided', () => { - const props = { - key: '123', - id: '123', - orgName: '', - orgLocation: 'India', - createdDate: '04/07/2019', - image: '', - admins: [ - { - _id: '123', - }, - { - _id: '456', - }, - ], - members: '34', - }; - - window.location.assign('/orgdash'); - - render( - - - - ); - - expect(window.location).toBeAt('/orgdash'); - }); -}); diff --git a/src/components/SuperDashListCard/SuperDashListCard.tsx b/src/components/SuperDashListCard/SuperDashListCard.tsx deleted file mode 100644 index 6e84fcb15f..0000000000 --- a/src/components/SuperDashListCard/SuperDashListCard.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import Row from 'react-bootstrap/Row'; -import Col from 'react-bootstrap/Col'; -import Button from 'react-bootstrap/Button'; -import { useTranslation } from 'react-i18next'; -import styles from './SuperDashListCard.module.css'; -import { useHistory } from 'react-router-dom'; -import AboutImg from 'assets/images/defaultImg.png'; - -interface InterfaceSuperDashListCardProps { - key: string; - id: string; - orgName: string; - orgLocation: string | null; - createdDate: string; - image: string; - admins: any; - members: any; -} - -function superDashListCard( - props: InterfaceSuperDashListCardProps -): JSX.Element { - const userId = localStorage.getItem('id'); - const userType = localStorage.getItem('UserType'); - const history = useHistory(); - - function handleClick(): void { - const url = '/orgdash/id=' + props.id; - - /* - WARNING! - Please endeavor to NOT remove both the window.location.replace(url) and the history.push(url) as both are very important for routing correctly. - Removal of the window.location.replace will result to a crash on other depending routes. History.push(url) is being used to alongside window.location.replace to keep track of the browser history stack and ensure consistency with the react component life cycle. - */ - - window.location.replace(url); - history.push(url); - // Do not change the lines above. - } - - const { t } = useTranslation('translation', { - keyPrefix: 'superDashListCard', - }); - - return ( - <> - - {props.image ? ( -
- -
- ) : ( -
- -
- )} - -
-

- {props.orgName ? <>{props.orgName} : <>Dogs Care} -

-

{props?.orgLocation}

-

- {t('created')}: {props.createdDate} -

-
-
-

- {t('admins')}: {props?.admins.length} -

-

- {t('members')}: {props?.members} -

-
- -
-
- -
-
- - ); -} -export {}; -export default superDashListCard; diff --git a/src/screens/LoginPage/LoginPage.module.css b/src/screens/LoginPage/LoginPage.module.css index 0047934b7e..b66db017d8 100644 --- a/src/screens/LoginPage/LoginPage.module.css +++ b/src/screens/LoginPage/LoginPage.module.css @@ -12,6 +12,7 @@ .row .left_portion .inner .palisadoes_logo { width: 600px; + height: auto; } .row .right_portion { @@ -31,6 +32,7 @@ .row .right_portion .talawa_logo { height: 150px; + width: 150px; display: block; margin: 1rem auto; } diff --git a/src/screens/LoginPage/LoginPage.tsx b/src/screens/LoginPage/LoginPage.tsx index 83b9e30b71..fe27ee9aed 100644 --- a/src/screens/LoginPage/LoginPage.tsx +++ b/src/screens/LoginPage/LoginPage.tsx @@ -16,8 +16,8 @@ import { RECAPTCHA_MUTATION, SIGNUP_MUTATION, } from 'GraphQl/Mutations/mutations'; -import Palisadoes from 'assets/images/palisadoes_logo.png'; -import Talawa from 'assets/images/talawa-logo-200x200.png'; +import { ReactComponent as TalawaLogo } from 'assets/svgs/talawa.svg'; +import { ReactComponent as PalisadoesLogo } from 'assets/svgs/palisadoes.svg'; import ChangeLanguageDropDown from 'components/ChangeLanguageDropdown/ChangeLanguageDropDown'; import Loader from 'components/Loader/Loader'; import { errorHandler } from 'utils/errorHandler'; @@ -219,11 +219,7 @@ function loginPage(): JSX.Element {
- Palisadoes logo +

{t('fromPalisadoes')}

@@ -232,11 +228,7 @@ function loginPage(): JSX.Element { - Talawa Logo + {/* LOGIN FORM */}
p { - margin-top: -10px; +.btnsContainer .btnsBlock { + display: flex; } -.search { +.btnsContainer .btnsBlock button { + margin-left: 1rem; display: flex; - margin-bottom: 2rem; - flex-direction: column; - gap: 0.7rem; + justify-content: center; + align-items: center; } -@media screen and (min-width: 468px) { - .search { - display: flex; - flex-direction: row; - justify-content: space-between; - padding-right: 40px; - } +.btnsContainer .input { + flex: 1; + position: relative; } -.navitem { - padding-left: 27%; - padding-top: 12px; - padding-bottom: 12px; - cursor: pointer; +.btnsContainer input { + outline: 1px solid var(--bs-gray-400); } -.searchtitle { - color: #707070; - font-weight: 600; - font-size: 18px; - margin-top: 60px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 60%; +.btnsContainer .input button { + width: 52px; } -.search > input { - text-decoration: none; - border-color: #dbd7d7; - border-style: solid; - border-radius: 5px; - padding-top: 5px; - padding-bottom: 5px; - padding-right: 10px; - padding-left: 10px; - box-shadow: none; -} -.search input:focus { - border-color: #fff; - box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; - outline: none; +.listBox { + display: flex; + flex-wrap: wrap; } -.logintitle { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 30px; - padding-bottom: 5px; - width: 30%; - border-bottom: 3px solid #31bb6b; +.listBox .itemCard { + width: 50%; } -.logintitleadmin { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-top: 50px; - margin-bottom: 40px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 60%; -} -.admindetails { +.notFound { + flex: 1; display: flex; - justify-content: space-between; -} -.admindetails > p { - margin-top: -12px; - margin-right: 30px; -} -.mainpageright > hr { - margin-top: 20px; - width: 100%; - margin-left: -15px; - margin-right: -15px; - margin-bottom: 20px; -} -.justifysp { - display: flex; - justify-content: space-between; - width: 100%; -} - -.mainpageright { - width: 100%; - margin: 0 auto; - max-width: 92vw; + justify-content: center; + align-items: center; + flex-direction: column; } -@media (min-width: 468px) and (max-width: 890px) { - .logintitle { - width: 90%; +@media (max-width: 1120px) { + .contract { + padding-left: calc(250px + 2rem + 1.5rem); } - .mainpagerightContainer { - display: inline-block; - position: relative; + + .listBox .itemCard { width: 100%; } } -@media (min-width: 900px) and (max-width: 1190px) { - .mainpagerightContainer { - border-left: 1px solid #e5e5e5; - width: 70%; - position: absolute; - right: 1%; - margin-top: -2px; +@media (max-width: 1020px) { + .btnsContainer { + flex-direction: column; + margin: 1.5rem 0; } - .youheader { - width: 20%; + + .btnsContainer .btnsBlock { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } + + .btnsContainer .btnsBlock button { + margin: 0; + } + + .btnsContainer .btnsBlock div button { + margin-right: 1.5rem; } } -@media screen and (min-width: 1200px) { - .justifysp { - display: flex; - justify-content: space-between; +/* For mobile devices */ + +@media (max-width: 520px) { + .btnsContainer { + margin-bottom: 0; } - /* .invitebtn { - position: relative; - right: 15px; - } */ - .sidebarsticky { - padding-left: 30px; - text-overflow: ellipsis; - /* overflow-x: hidden; */ + + .btnsContainer .btnsBlock { + display: block; + margin-top: 1rem; + margin-right: 0; + } + + .btnsContainer .btnsBlock div { + flex: 1; + } + + .btnsContainer .btnsBlock div[title='Sort organizations'] { + margin-right: 0.5rem; + } + + .btnsContainer .btnsBlock button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; } } -.invitebtn { - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - border-radius: 5px; - font-size: 16px; - height: 60%; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; - background-color: #31bb6b; + +/* Loading OrgList CSS */ +.itemCard .loadingWrapper { + background-color: var(--bs-white); + margin: 0.5rem; + height: calc(120px + 2rem); + padding: 1rem; + border-radius: 8px; + outline: 1px solid var(--bs-gray-200); + position: relative; } -.flexdir { +.itemCard .loadingWrapper .innerContainer { display: flex; - flex-direction: row; - justify-content: space-between; - border: none; } -.form_wrapper { - margin-top: 27px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - position: absolute; - display: flex; - flex-direction: column; - padding: 40px 30px; - background: #ffffff; - border-color: #e8e5e5; - border-width: 5px; - border-radius: 10px; - max-height: 86vh; - overflow: auto; +.itemCard .loadingWrapper .innerContainer .orgImgContainer { + width: 120px; + height: 120px; + border-radius: 4px; } -.form_wrapper form { +.itemCard .loadingWrapper .innerContainer .content { + flex: 1; display: flex; - align-items: left; - justify-content: left; flex-direction: column; -} -.titlemodal { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 65%; + margin-left: 1rem; } -.checkboxdiv > label { - margin-right: 50px; -} -.checkboxdiv > label > input { - margin-left: 10px; -} -.orgphoto { - margin-top: 5px; -} -.orgphoto > input { - margin-top: 10px; - cursor: pointer; - margin-bottom: 5px; +.itemCard .loadingWrapper .innerContainer .content h5 { + height: 24px; + width: 60%; + margin-bottom: 0.8rem; } -.cancel > i { - margin-top: 5px; - transform: scale(1.2); - cursor: pointer; - color: #707070; -} -.modalbody { - width: 50px; -} -.greenregbtn { - margin: 1rem 0 0; - margin-top: 10px; - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - padding: 10px 10px; - border-radius: 5px; - background-color: #31bb6b; - width: 100%; - font-size: 16px; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; - width: 100%; +.itemCard .loadingWrapper .innerContainer .content h6[title='Location'] { + display: block; + width: 45%; + height: 18px; } -.loader, -.loader:after { - border-radius: 50%; - width: 10em; - height: 10em; -} -.loader { - margin: 60px auto; - margin-top: 35vh !important; - font-size: 10px; - position: relative; - text-indent: -9999em; - border-top: 1.1em solid rgba(255, 255, 255, 0.2); - border-right: 1.1em solid rgba(255, 255, 255, 0.2); - border-bottom: 1.1em solid rgba(255, 255, 255, 0.2); - border-left: 1.1em solid #febc59; - -webkit-transform: translateZ(0); - -ms-transform: translateZ(0); - transform: translateZ(0); - -webkit-animation: load8 1.1s infinite linear; - animation: load8 1.1s infinite linear; -} -@-webkit-keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } +.itemCard .loadingWrapper .innerContainer .content h6 { + display: block; + width: 30%; + height: 16px; + margin-bottom: 0.8rem; } -@keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); + +.itemCard .loadingWrapper .button { + position: absolute; + height: 48px; + width: 92px; + bottom: 1rem; + right: 1rem; + z-index: 1; +} + +@media (max-width: 450px) { + .itemCard .loadingWrapper { + height: unset; + margin: 0.5rem 0; + padding: 1.25rem 1.5rem; } -} -.list_box { - height: 70vh; - overflow-y: auto; - width: auto; - padding-right: 40px; - /* padding: 2rem; */ -} -/* width */ -::-webkit-scrollbar { - width: 10px; -} + .itemCard .loadingWrapper .innerContainer { + flex-direction: column; + } -/* Track */ -::-webkit-scrollbar-track { - box-shadow: inset 0 0 5px #dfdfdf; - border-radius: 10px; -} -/* Handle */ -::-webkit-scrollbar-thumb { - background: #ffbd59; - border-radius: 10px; -} -/* Handle on hover */ -::-webkit-scrollbar-thumb:hover { - background: #ffbd59; -} -.dispflex { - display: flex; -} -.dispflex > input { - width: 20%; - border: none; - box-shadow: none; - margin-top: 5px; -} -.checkboxdiv { - display: flex; -} -.checkboxdiv > div { - width: 50%; -} + .itemCard .loadingWrapper .innerContainer .orgImgContainer { + height: 200px; + width: 100%; + margin-bottom: 0.8rem; + } -@media only screen and (max-width: 600px) { - .sidebar { - position: relative; - bottom: 18px; + .itemCard .loadingWrapper .innerContainer .content { + margin-left: 0; } - /* .invitebtn { - width: 135px; + + .itemCard .loadingWrapper .button { + bottom: 0; + right: 0; + border-radius: 0.5rem; position: relative; - right: 10px; - } */ - .form_wrapper { - width: 90%; - } - .searchtitle { - margin-top: 30px; + margin-left: auto; + display: block; } } diff --git a/src/screens/OrgList/OrgList.test.tsx b/src/screens/OrgList/OrgList.test.tsx index 4c19d0ecb1..118ac52198 100644 --- a/src/screens/OrgList/OrgList.test.tsx +++ b/src/screens/OrgList/OrgList.test.tsx @@ -1,151 +1,17 @@ import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; import { act, render, screen } from '@testing-library/react'; -import { Provider } from 'react-redux'; -import 'jest-localstorage-mock'; import userEvent from '@testing-library/user-event'; -import { BrowserRouter } from 'react-router-dom'; +import 'jest-localstorage-mock'; import 'jest-location-mock'; -import OrgList from './OrgList'; -import { - ORGANIZATION_CONNECTION_LIST, - USER_ORGANIZATION_LIST, -} from 'GraphQl/Queries/Queries'; -import { store } from 'state/store'; -import i18nForTest from 'utils/i18nForTest'; import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; - -type Organization = { - _id: string; - image: string; - name: string; - creator: { - firstName: string; - lastName: string; - }; - admins: { - _id: string; - }[]; - members: { - _id: string; - }; - createdAt: string; - location: string; -}; - -const organizations: Organization[] = []; - -for (let x = 0; x < 100; x++) { - organizations.push({ - _id: 'a' + x, - image: '', - name: 'name', - creator: { - firstName: 'firstName', - lastName: 'lastName', - }, - admins: [ - { - _id: x + '1', - }, - ], - members: { - _id: x + '2', - }, - createdAt: new Date().toISOString(), - location: 'location', - }); -} - -const MOCKS = [ - { - request: { - query: ORGANIZATION_CONNECTION_LIST, - }, - result: { - data: { - organizationsConnection: [ - { - _id: 1, - creator: { firstName: 'John', lastName: 'Doe' }, - image: '', - name: 'Akatsuki', - createdAt: '02/02/2022', - admins: [ - { - _id: '123', - }, - ], - members: { - _id: '234', - }, - location: 'Washington DC', - }, - ...organizations, - ], - }, - }, - }, - { - request: { - query: USER_ORGANIZATION_LIST, - variables: { id: '123' }, - }, - result: { - data: { - user: { - firstName: 'John', - lastName: 'Doe', - image: '', - email: 'John_Does_Palasidoes@gmail.com', - userType: 'SUPERADMIN', - adminFor: { - _id: 1, - name: 'Akatsuki', - image: '', - }, - }, - }, - }, - }, -]; -const MOCKS_EMPTY = [ - { - request: { - query: ORGANIZATION_CONNECTION_LIST, - }, - result: { - data: { - organizationsConnection: [], - }, - }, - }, - { - request: { - query: USER_ORGANIZATION_LIST, - variables: { id: '123' }, - }, - result: { - data: { - user: { - firstName: 'John', - lastName: 'Doe', - image: '', - email: 'John_Does_Palasidoes@gmail.com', - userType: 'ADMIN', - adminFor: { - _id: 1, - name: 'Akatsuki', - image: '', - }, - }, - }, - }, - }, -]; -const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink(MOCKS_EMPTY, true); +import i18nForTest from 'utils/i18nForTest'; +import OrgList from './OrgList'; +import { MOCKS, MOCKS_ADMIN, MOCKS_EMPTY } from './OrgListMocks'; async function wait(ms = 100): Promise { await act(() => { @@ -159,7 +25,10 @@ afterEach(() => { localStorage.clear(); }); -describe('Organisation List Page', () => { +describe('Organisations Page testing as SuperAdmin', () => { + const link = new StaticMockLink(MOCKS, true); + const link2 = new StaticMockLink(MOCKS_EMPTY, true); + const formData = { name: 'Dummy Organization', description: 'This is a dummy organization', @@ -167,94 +36,8 @@ describe('Organisation List Page', () => { image: new File(['hello'], 'hello.png', { type: 'image/png' }), }; - test('On dynamic setting of rowsPerPage, the number of organizations rendered on the dom should be changed to the selected option', async () => { - localStorage.setItem('id', '123'); - - render( - - - - - - - - - - ); - - // Wait and confirm that the component has been rendered - await screen.findByTestId('rowsPPSelect'); - - //Get the reference to the dropdown for rows per page - const numRowsSelect: HTMLSelectElement | null = screen - .getByTestId('rowsPPSelect') - .querySelector('select'); - - if (numRowsSelect === null) { - throw new Error('numRowwsSelect is null'); - } - - // Get all possible options - const options = Array.from(numRowsSelect?.querySelectorAll('option')).slice( - 1 - ); - - // Change the number of rows to display through the dropdown - options.forEach((option) => { - //Change the selected option to the value of the current option - userEvent.selectOptions(numRowsSelect, option.value); - - // When the selected option from rowsPerPage is "All", the total number of organizations displayed - // is the number of organizations plus one (i.e an object is prepended to the list of mocked organizations) - const numOrgDisplayed = - option.textContent === 'All' - ? organizations.length + 1 - : parseInt(option.value); - - expect( - screen - .getByTestId('organizations-list') - .querySelectorAll('[data-testid="singleorg"]').length - ).toBe(numOrgDisplayed); - }); - }); - test('Testing search functionality', async () => { - const mocks = [ - { - request: { - query: ORGANIZATION_CONNECTION_LIST, - }, - result: { - data: { - organizationsConnection: [ - { - _id: 1, - image: '', - name: 'Akatsuki', - creator: { - firstName: 'John', - lastName: 'Doe', - }, - admins: [ - { - _id: '123', - }, - ], - members: { - _id: '234', - }, - createdAt: '02/02/2022', - location: 'Washington DC', - }, - ], - }, - }, - }, - ]; - - const link = new StaticMockLink(mocks, true); - + localStorage.setItem('id', '123'); render( @@ -270,25 +53,13 @@ describe('Organisation List Page', () => { // Test that the search bar filters organizations by name const searchBar = screen.getByTestId(/searchByName/i); - const search1 = 'Akatsuki'; expect(searchBar).toBeInTheDocument(); - userEvent.type(screen.getByTestId(/searchByName/i), search1); - - // Test that the search bar is case-insensitive - userEvent.clear(searchBar); - const search2 = 'aKaTsUkI'; - expect(searchBar).toBeInTheDocument(); - userEvent.type(screen.getByTestId(/searchByName/i), search2); - - // Test that the search bar filters all organization if there are is no search passed - userEvent.clear(searchBar); - const search3 = ''; - expect(searchBar).toBeInTheDocument(); - userEvent.type(screen.getByTestId(/searchByName/i), search3); + userEvent.type(searchBar, 'Dummy'); }); test('Should render no organisation warning alert when there are no organization', async () => { window.location.assign('/'); + localStorage.setItem('id', '123'); const { container } = render( @@ -303,7 +74,6 @@ describe('Organisation List Page', () => { ); await wait(); - expect(container.textContent).toMatch('Organizations Not Found'); expect(container.textContent).toMatch( 'Please create an organization through dashboard' @@ -311,97 +81,6 @@ describe('Organisation List Page', () => { expect(window.location).toBeAt('/'); }); - test('Should not render no organisation warning alert when there are no organization', async () => { - window.location.assign('/'); - - const { container } = render( - - - - - - - - - - ); - - await wait(); - - expect(container.textContent).not.toMatch('Organizations Not Found'); - expect(container.textContent).not.toMatch( - 'Please create an organization through dashboard' - ); - expect(window.location).toBeAt('/'); - }); - - test('Correct mock data should be queried', async () => { - const dataQuery1 = MOCKS[0]?.result?.data?.organizationsConnection; - - expect(dataQuery1).toEqual([ - { - _id: 1, - creator: { firstName: 'John', lastName: 'Doe' }, - image: '', - name: 'Akatsuki', - createdAt: '02/02/2022', - admins: [ - { - _id: '123', - }, - ], - members: { - _id: '234', - }, - location: 'Washington DC', - }, - ...organizations, - ]); - }); - - test('Should render props and text elements test for the screen', async () => { - window.location.assign('/'); - - const { container } = render( - - - - - - - - - - ); - - expect(container.textContent).not.toBe('Loading data...'); - - await wait(); - - expect(container.textContent).toMatch('Name:'); - expect(container.textContent).toMatch('Designation:'); - expect(container.textContent).toMatch('Email:'); - expect(window.location).toBeAt('/'); - - userEvent.type(screen.getByTestId(/searchByName/i), formData.name); - }); - - test('Testing UserType from local storage', async () => { - render( - - - - - - - - ); - - await wait(); - - expect(screen.getByTestId(/createOrganizationBtn/i)).toBeTruthy(); - }); - test('Testing Organization data is not present', async () => { render( @@ -417,25 +96,29 @@ describe('Organisation List Page', () => { }); test('Testing create organization modal', async () => { - localStorage.setItem('UserType', 'SUPERADMIN'); + localStorage.setItem('id', '123'); render( - + + + ); await wait(); - - userEvent.click(screen.getByTestId(/createOrganizationBtn/i)); + const createOrgBtn = screen.getByTestId(/createOrganizationBtn/i); + expect(createOrgBtn).toBeInTheDocument(); + userEvent.click(createOrgBtn); userEvent.click(screen.getByTestId(/closeOrganizationModal/i)); }); test('Create organization model should work properly', async () => { + localStorage.setItem('id', '123'); localStorage.setItem('UserType', 'SUPERADMIN'); await act(async () => { render( @@ -469,29 +152,49 @@ describe('Organisation List Page', () => { screen.getByPlaceholderText(/Location/i), formData.location ); - userEvent.click(screen.getByLabelText(/Is Public:/i)); - userEvent.click(screen.getByLabelText(/Visible In Search:/i)); - userEvent.upload( - screen.getByLabelText(/Display Image:/i), - formData.image - ); + userEvent.click(screen.getByTestId(/isPublic/i)); + userEvent.click(screen.getByTestId(/visibleInSearch/i)); + userEvent.upload(screen.getByLabelText(/Display Image/i), formData.image); await wait(500); + }); - expect(screen.getByTestId(/modalOrganizationName/i)).toHaveValue( - formData.name - ); - expect(screen.getByPlaceholderText(/Description/i)).toHaveValue( - formData.description - ); - expect(screen.getByPlaceholderText(/Location/i)).toHaveValue( - formData.location - ); - expect(screen.getByLabelText(/Is Public/i)).not.toBeChecked(); - expect(screen.getByLabelText(/Visible In Search:/i)).toBeChecked(); - expect(screen.getByLabelText(/Display Image:/i)).toBeTruthy(); + expect(screen.getByTestId(/modalOrganizationName/i)).toHaveValue( + formData.name + ); + expect(screen.getByPlaceholderText(/Description/i)).toHaveValue( + formData.description + ); + expect(screen.getByPlaceholderText(/Location/i)).toHaveValue( + formData.location + ); + expect(screen.getByTestId(/isPublic/i)).not.toBeChecked(); + expect(screen.getByTestId(/visibleInSearch/i)).toBeChecked(); + expect(screen.getByLabelText(/Display Image/i)).toBeTruthy(); - userEvent.click(screen.getByTestId(/submitOrganizationForm/i)); - }); + userEvent.click(screen.getByTestId(/submitOrganizationForm/i)); + }, 10000); +}); + +describe('Organisations Page testing as Admin', () => { + const link = new StaticMockLink(MOCKS_ADMIN, true); + + test('Create organization modal should not be present in the page for Admin', async () => { + localStorage.setItem('id', '123'); + + render( + + + + + + + + + + ); + + await wait(); + expect(screen.queryByText(/Create Organization/i)).toBeNull(); }); }); diff --git a/src/screens/OrgList/OrgList.tsx b/src/screens/OrgList/OrgList.tsx index 7d9c872f44..6bc6cfa1b0 100644 --- a/src/screens/OrgList/OrgList.tsx +++ b/src/screens/OrgList/OrgList.tsx @@ -1,37 +1,38 @@ -import type { ChangeEvent } from 'react'; -import React, { useState } from 'react'; -import Modal from 'react-bootstrap/Modal'; -import { Form } from 'react-bootstrap'; -import Row from 'react-bootstrap/Row'; -import Col from 'react-bootstrap/Col'; import { useMutation, useQuery } from '@apollo/client'; -import Button from 'react-bootstrap/Button'; -import dayjs from 'dayjs'; -import { toast } from 'react-toastify'; -import { useTranslation } from 'react-i18next'; - -import styles from './OrgList.module.css'; -import SuperDashListCard from 'components/SuperDashListCard/SuperDashListCard'; +import { Search } from '@mui/icons-material'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import SortIcon from '@mui/icons-material/Sort'; +import { CREATE_ORGANIZATION_MUTATION } from 'GraphQl/Mutations/mutations'; import { ORGANIZATION_CONNECTION_LIST, USER_ORGANIZATION_LIST, } from 'GraphQl/Queries/Queries'; -import { CREATE_ORGANIZATION_MUTATION } from 'GraphQl/Mutations/mutations'; -import ListNavbar from 'components/ListNavbar/ListNavbar'; -import PaginationList from 'components/PaginationList/PaginationList'; -import debounce from 'utils/debounce'; +import OrgListCard from 'components/OrgListCard/OrgListCard'; +import type { ChangeEvent } from 'react'; +import React, { useEffect, useState } from 'react'; +import { Col, Dropdown, Form, Row } from 'react-bootstrap'; +import Button from 'react-bootstrap/Button'; +import Modal from 'react-bootstrap/Modal'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; import convertToBase64 from 'utils/convertToBase64'; -import AdminDashListCard from 'components/AdminDashListCard/AdminDashListCard'; -import { Alert, AlertTitle } from '@mui/material'; +import debounce from 'utils/debounce'; import { errorHandler } from 'utils/errorHandler'; -import Loader from 'components/Loader/Loader'; +import type { + InterfaceOrgConnectionInfoType, + InterfaceOrgConnectionType, + InterfaceUserType, +} from 'utils/interfaces'; +import styles from './OrgList.module.css'; +import SuperAdminScreen from 'components/SuperAdminScreen/SuperAdminScreen'; function orgList(): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'orgList' }); document.title = t('title'); - const [showAddEventModal, setShowAddEventModal] = useState(false); + const [searchByName, setSearchByName] = useState(''); + const [showModal, setShowModal] = useState(false); const [formState, setFormState] = useState({ name: '', descrip: '', @@ -41,51 +42,66 @@ function orgList(): JSX.Element { image: '', }); - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(5); - - const isSuperAdmin = localStorage.getItem('UserType') !== 'SUPERADMIN'; + const toggleModal = (): void => setShowModal(!showModal); - const toggleAddEventModal = (): void => - setShowAddEventModal(!showAddEventModal); - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [create, { loading: loading3 }] = useMutation( - CREATE_ORGANIZATION_MUTATION - ); + const [create] = useMutation(CREATE_ORGANIZATION_MUTATION); const { - data: data2, - loading: loading2, + data: userData, error: errorUser, + }: { + data: InterfaceUserType | undefined; + loading: boolean; + error?: Error | undefined; } = useQuery(USER_ORGANIZATION_LIST, { variables: { id: localStorage.getItem('id') }, }); const { - data, + data: orgsData, loading, error: errorList, - refetch, - } = useQuery(ORGANIZATION_CONNECTION_LIST); - /*istanbul ignore next*/ - interface InterfaceUserType { - adminFor: { - _id: string; - }[]; - } - /*istanbul ignore next*/ - interface InterfaceCurrentOrgType { - _id: string; - } - /*istanbul ignore next*/ + refetch: refetchOrgs, + }: { + data: InterfaceOrgConnectionType | undefined; + loading: boolean; + error?: Error | undefined; + refetch: any; + } = useQuery(ORGANIZATION_CONNECTION_LIST, { + notifyOnNetworkStatusChange: true, + }); + + // To clear the search field and form fields on unmount + useEffect(() => { + return () => { + setSearchByName(''); + setFormState({ + name: '', + descrip: '', + ispublic: true, + visible: false, + location: '', + image: '', + }); + }; + }, []); + + /* istanbul ignore next */ const isAdminForCurrentOrg = ( - user: InterfaceUserType | undefined, - currentOrg: InterfaceCurrentOrgType + currentOrg: InterfaceOrgConnectionInfoType ): boolean => { - return ( - user?.adminFor.length === 1 && user?.adminFor[0]._id === currentOrg._id - ); + if (userData?.user?.adminFor.length === 1) { + // If user is admin for one org only then check if that org is current org + return userData?.user?.adminFor[0]._id === currentOrg._id; + } else { + // If user is admin for more than one org then check if current org is present in adminFor array + return ( + userData?.user?.adminFor.some( + (org: { _id: string; name: string; image: string | null }) => + org._id === currentOrg._id + ) ?? false + ); + } }; const createOrg = async (e: ChangeEvent): Promise => { @@ -119,7 +135,7 @@ function orgList(): JSX.Element { /* istanbul ignore next */ if (data) { toast.success('Congratulation the Organization is created'); - refetch(); + refetchOrgs(); setFormState({ name: '', descrip: '', @@ -128,7 +144,7 @@ function orgList(): JSX.Element { location: '', image: '', }); - toggleAddEventModal(); + toggleModal(); } } catch (error: any) { /* istanbul ignore next */ @@ -136,308 +152,269 @@ function orgList(): JSX.Element { } }; - if (loading || loading2 || loading3) { - return ; - } - /* istanbul ignore next */ if (errorList || errorUser) { window.location.assign('/'); } - /* istanbul ignore next */ - const handleChangePage = ( - event: React.MouseEvent | null, - newPage: number - ): void => { - setPage(newPage); - }; - - /* istanbul ignore next */ - const handleChangeRowsPerPage = ( - event: React.ChangeEvent - ): void => { - setRowsPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; - const handleSearchByName = (e: any): void => { const { value } = e.target; - refetch({ + setSearchByName(value); + refetchOrgs({ filter: value, }); }; - let dataRevOrg; + const debouncedHandleSearchByName = debounce(handleSearchByName); - if (data) { - dataRevOrg = data.organizationsConnection.slice().reverse(); - } return ( <> - - - -
-
-
- {t('you')} -
-

- {t('name')}: - - {data2?.user.firstName} {data2?.user.lastName} - -

-

- {t('designation')}: {data2?.user.userType} -

-
- {t('email')}: -

- {(data2?.user.email || '').substring( - 0, - (data2?.user.email || '').length / 2 - )} - - {data2?.user.email.substring( - data2?.user.email.length / 2, - data2?.user.email.length - )} - -

-
-
+ + {/* Buttons Container */} +
+
+ +
- - -
-
-

{t('organizationList')}

+
+
+ +
-
+ {userData && userData.user.userType === 'SUPERADMIN' && ( + )} +
+
+ {/* Organizations List */} + {!loading && + ((orgsData?.organizationsConnection.length === 0 && + searchByName.length == 0) || + (userData && + userData.user.userType === 'ADMIN' && + userData.user.adminFor.length === 0)) ? ( + // eslint-disable-next-line +
+

{t('noOrgErrorTitle')}

+
{t('noOrgErrorDescription')}
+
+ ) : !loading && + orgsData?.organizationsConnection.length == 0 && + /* istanbul ignore next */ + searchByName.length > 0 ? ( + /* istanbul ignore next */ + // eslint-disable-next-line +
+

+ {t('noResultsFoundFor')} "{searchByName}" +

+
+ ) : ( + <> + )} +
+ {loading ? ( + <> + {[...Array(8)].map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} + + ) : userData && userData.user.userType == 'SUPERADMIN' ? ( + orgsData?.organizationsConnection.map((item) => { + return ( +
+ +
+ ); + }) + ) : userData && + userData.user.userType == 'ADMIN' && + userData.user.adminFor.length > 0 ? ( + orgsData?.organizationsConnection.map((item) => { + if (isAdminForCurrentOrg(item)) { + return ( +
+ +
+ ); + } + }) + ) : null} +
+ {/* Create Organization Modal */} + + + + {t('createOrganization')} + + +
+ + {t('name')} { + setFormState({ + ...formState, + name: e.target.value, + }); }} /> -
-
- {data?.organizationsConnection.length > 0 ? ( - (rowsPerPage > 0 - ? dataRevOrg.slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ) - : data.organizationsConnection - ).map( - (datas: { - _id: string; - image: string; - name: string; - admins: any; - members: any; - createdAt: string; - location: string | null; - }) => { - if (data2 && data2.user.userType == 'SUPERADMIN') { - return ( - - ); - } else if (isAdminForCurrentOrg(data2?.user, datas)) { - /* istanbul ignore next */ - return ( - - ); - } else { - return null; - } - } - ) - ) : ( -
- - {t('noOrgErrorTitle')} - {t('noOrgErrorDescription')} - -
- )} -
-
- {t('description')} + { + setFormState({ + ...formState, + descrip: e.target.value, + }); }} - > - - {data2?.user.userType === 'SUPERADMIN' && ( - - - - )} - -
-
-
- - - - -

{t('createOrganization')}

- -
- - - - { - setFormState({ - ...formState, - name: e.target.value, - }); - }} - /> - - { - setFormState({ - ...formState, - descrip: e.target.value, - }); - }} - /> - - { - setFormState({ - ...formState, - location: e.target.value, - }); - }} - /> + /> + {t('location')} + { + setFormState({ + ...formState, + location: e.target.value, + }); + }} + /> -
-
- - - setFormState({ - ...formState, - ispublic: !formState.ispublic, - }) - } - /> -
-
- - - setFormState({ - ...formState, - visible: !formState.visible, - }) - } - /> -
-
- - +
+ + + + - -
+ + ); } diff --git a/src/screens/OrgList/OrgListMocks.ts b/src/screens/OrgList/OrgListMocks.ts new file mode 100644 index 0000000000..8958574e61 --- /dev/null +++ b/src/screens/OrgList/OrgListMocks.ts @@ -0,0 +1,146 @@ +import { + ORGANIZATION_CONNECTION_LIST, + USER_ORGANIZATION_LIST, +} from 'GraphQl/Queries/Queries'; +import 'jest-localstorage-mock'; +import 'jest-location-mock'; +import type { + InterfaceOrgConnectionInfoType, + InterfaceUserType, +} from 'utils/interfaces'; + +const superAdminUser: InterfaceUserType = { + user: { + firstName: 'John', + lastName: 'Doe', + image: '', + email: 'John_Does_Palasidoes@gmail.com', + userType: 'SUPERADMIN', + adminFor: [ + { + _id: '1', + name: 'Akatsuki', + image: '', + }, + ], + }, +}; + +const adminUser: InterfaceUserType = { + user: { ...superAdminUser.user, userType: 'ADMIN' }, +}; + +const organizations: InterfaceOrgConnectionInfoType[] = [ + { + _id: '1', + creator: { _id: 'xyz', firstName: 'John', lastName: 'Doe' }, + image: '', + name: 'Akatsuki', + createdAt: '02/02/2022', + admins: [ + { + _id: '123', + }, + ], + members: [ + { + _id: '234', + }, + ], + location: 'Washington DC', + }, +]; + +for (let x = 0; x < 100; x++) { + organizations.push({ + _id: 'a' + x, + image: '', + name: 'name', + creator: { + _id: '123', + firstName: 'firstName', + lastName: 'lastName', + }, + admins: [ + { + _id: x + '1', + }, + ], + members: [ + { + _id: x + '2', + }, + ], + createdAt: new Date().toISOString(), + location: 'location', + }); +} + +// MOCKS FOR SUPERADMIN +const MOCKS = [ + { + request: { + query: ORGANIZATION_CONNECTION_LIST, + }, + result: { + data: { + organizationsConnection: organizations, + }, + }, + }, + { + request: { + query: USER_ORGANIZATION_LIST, + variables: { id: '123' }, + }, + result: { + data: superAdminUser, + }, + }, +]; +const MOCKS_EMPTY = [ + { + request: { + query: ORGANIZATION_CONNECTION_LIST, + }, + result: { + data: { + organizationsConnection: [], + }, + }, + }, + { + request: { + query: USER_ORGANIZATION_LIST, + variables: { id: '123' }, + }, + result: { + data: superAdminUser, + }, + }, +]; + +// MOCKS FOR ADMIN +const MOCKS_ADMIN = [ + { + request: { + query: ORGANIZATION_CONNECTION_LIST, + }, + result: { + data: { + organizationsConnection: organizations, + }, + }, + }, + { + request: { + query: USER_ORGANIZATION_LIST, + variables: { id: '123' }, + }, + result: { + data: adminUser, + }, + }, +]; + +export { MOCKS, MOCKS_EMPTY, MOCKS_ADMIN }; diff --git a/src/screens/Requests/Requests.module.css b/src/screens/Requests/Requests.module.css index 06cccbba21..0750dba108 100644 --- a/src/screens/Requests/Requests.module.css +++ b/src/screens/Requests/Requests.module.css @@ -1,109 +1,95 @@ -.mainpage { +.btnsContainer { display: flex; - flex-direction: row; + margin: 2.5rem 0 2.5rem 0; } -.sidebar { - z-index: 0; - padding-top: 5px; - margin: 0; - height: 100%; -} -.sidebar:after { - content: ''; - background-color: #f7f7f7; - position: absolute; - width: 2px; - height: 600px; - top: 10px; - left: 94%; - display: block; -} -.sidebarsticky { - padding-left: 45px; + +.btnsContainer .btnsBlock { + display: flex; } -.sidebarsticky > input { - text-decoration: none; - margin-bottom: 50px; - border-color: #e8e5e5; - width: 80%; - border-radius: 7px; - padding-top: 5px; - padding-bottom: 5px; - padding-right: 10px; - padding-left: 10px; - box-shadow: none; + +.btnsContainer .btnsBlock button { + margin-left: 1rem; + display: flex; + justify-content: center; + align-items: center; } -.navitem { - padding-left: 27%; - padding-top: 12px; - padding-bottom: 12px; - cursor: pointer; +.btnsContainer .inputContainer { + flex: 1; + position: relative; +} +.btnsContainer .input { + width: 70%; + position: relative; } -.searchtitle { - color: #707070; - font-weight: 600; - font-size: 18px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 60%; +.btnsContainer input { + outline: 1px solid var(--bs-gray-400); } -.logintitle { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 30px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 15%; +.btnsContainer .inputContainer button { + width: 52px; } -.mainpageright > hr { - margin-top: 20px; +.listBox { width: 100%; - margin-left: -15px; - margin-right: -15px; - margin-bottom: 20px; + flex: 1; } -.justifysp { + +.notFound { + flex: 1; display: flex; - justify-content: space-between; + justify-content: center; + align-items: center; + flex-direction: column; } -@media screen and (max-width: 575.5px) { - .justifysp { - padding-left: 55px; - display: flex; - justify-content: space-between; +@media (max-width: 1020px) { + .btnsContainer { + flex-direction: column; + margin: 1.5rem 0; + } + .btnsContainer .input { width: 100%; } + .btnsContainer .btnsBlock { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } - .mainpageright { - width: 100%; + .btnsContainer .btnsBlock button { + margin: 0; } -} -.list_box { - height: 70vh; - overflow-y: auto; - width: auto; - padding-right: 50px; + .btnsContainer .btnsBlock div button { + margin-right: 1.5rem; + } } -@media only screen and (max-width: 600px) { - .sidebar { - position: relative; - bottom: 18px; +/* For mobile devices */ + +@media (max-width: 520px) { + .btnsContainer { + margin-bottom: 0; } - .invitebtn { - width: 135px; - position: relative; - right: 10px; + + .btnsContainer .btnsBlock { + display: block; + margin-top: 1rem; + margin-right: 0; } - .userListTable { - margin-left: 40px; + + .btnsContainer .btnsBlock div { + flex: 1; + } + + .btnsContainer .btnsBlock div[title='Sort organizations'] { + margin-right: 0.5rem; + } + + .btnsContainer .btnsBlock button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; } } diff --git a/src/screens/Requests/Requests.test.tsx b/src/screens/Requests/Requests.test.tsx index ff6183c41e..ee532f43c9 100644 --- a/src/screens/Requests/Requests.test.tsx +++ b/src/screens/Requests/Requests.test.tsx @@ -9,12 +9,13 @@ import 'jest-location-mock'; import Requests from './Requests'; import { - ACCPET_ADMIN_MUTATION, + ACCEPT_ADMIN_MUTATION, REJECT_ADMIN_MUTATION, } from 'GraphQl/Mutations/mutations'; import { ORGANIZATION_CONNECTION_LIST, USER_LIST, + USER_ORGANIZATION_LIST, } from 'GraphQl/Queries/Queries'; import { store } from 'state/store'; import userEvent from '@testing-library/user-event'; @@ -23,6 +24,28 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import { ToastContainer } from 'react-toastify'; const MOCKS = [ + { + request: { + query: USER_ORGANIZATION_LIST, + variables: { id: localStorage.getItem('id') }, + }, + result: { + data: { + user: { + userType: 'SUPERADMIN', + firstName: 'John', + lastName: 'Doe', + image: '', + email: 'John_Does_Palasidoes@gmail.com', + adminFor: { + _id: 1, + name: 'Akatsuki', + image: '', + }, + }, + }, + }, + }, { request: { query: USER_LIST, @@ -102,7 +125,7 @@ const MOCKS = [ }, { request: { - query: ACCPET_ADMIN_MUTATION, + query: ACCEPT_ADMIN_MUTATION, variables: { id: '123', userType: 'ADMIN', @@ -133,7 +156,7 @@ const MOCKS = [ const EMPTY_ORG_MOCKS = [ { request: { - query: ACCPET_ADMIN_MUTATION, + query: ACCEPT_ADMIN_MUTATION, variables: { id: '123', userType: 'ADMIN', @@ -216,9 +239,9 @@ async function wait(ms = 100): Promise { describe('Testing Request screen', () => { test('Component should be rendered properly', async () => { window.location.assign('/orglist'); - + localStorage.setItem('UserType', 'SUPERADMIN'); render( - + @@ -230,9 +253,7 @@ describe('Testing Request screen', () => { ); await wait(); - - expect(screen.getByText(/Requests/i)).toBeInTheDocument(); - expect(screen.getByText(/Search By Name/i)).toBeInTheDocument(); + expect(screen.getByTestId(/searchByName/i)).toBeInTheDocument(); expect(window.location).toBeAt('/orglist'); }); @@ -256,7 +277,7 @@ describe('Testing Request screen', () => { test('Testing seach by name functionality', async () => { render( - + @@ -274,7 +295,7 @@ describe('Testing Request screen', () => { test('Testing accept user functionality', async () => { render( - + @@ -286,13 +307,12 @@ describe('Testing Request screen', () => { ); await wait(); - userEvent.click(screen.getByTestId(/acceptUser456/i)); }); test('Testing reject user functionality', async () => { render( - + @@ -304,7 +324,6 @@ describe('Testing Request screen', () => { ); await wait(); - userEvent.click(screen.getByTestId(/rejectUser456/i)); }); diff --git a/src/screens/Requests/Requests.tsx b/src/screens/Requests/Requests.tsx index 3dc20468e9..7a40b9e60a 100644 --- a/src/screens/Requests/Requests.tsx +++ b/src/screens/Requests/Requests.tsx @@ -1,92 +1,104 @@ -import React, { useEffect, useState } from 'react'; -import { Col, Form, Row } from 'react-bootstrap'; import { useMutation, useQuery } from '@apollo/client'; -import { toast } from 'react-toastify'; -import { useTranslation } from 'react-i18next'; +import React, { useEffect, useState } from 'react'; +import { Dropdown, Form, Table } from 'react-bootstrap'; import Button from 'react-bootstrap/Button'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; -import styles from './Requests.module.css'; -import ListNavbar from 'components/ListNavbar/ListNavbar'; +import { Search } from '@mui/icons-material'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import SortIcon from '@mui/icons-material/Sort'; +import { + ACCEPT_ADMIN_MUTATION, + REJECT_ADMIN_MUTATION, +} from 'GraphQl/Mutations/mutations'; import { ORGANIZATION_CONNECTION_LIST, USER_LIST, + USER_ORGANIZATION_LIST, } from 'GraphQl/Queries/Queries'; -import { - ACCPET_ADMIN_MUTATION, - REJECT_ADMIN_MUTATION, -} from 'GraphQl/Mutations/mutations'; -import PaginationList from 'components/PaginationList/PaginationList'; +import SuperAdminScreen from 'components/SuperAdminScreen/SuperAdminScreen'; import { errorHandler } from 'utils/errorHandler'; +import type { + InterfaceOrgConnectionType, + InterfaceUserType, +} from 'utils/interfaces'; +import styles from './Requests.module.css'; +import debounce from 'utils/debounce'; const Requests = (): JSX.Element => { const { t } = useTranslation('translation', { keyPrefix: 'requests' }); document.title = t('title'); - const [componentLoader, setComponentLoader] = useState(true); const [usersData, setUsersData] = useState([]); - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); const [searchByName, setSearchByName] = useState(''); + const [acceptAdminFunc] = useMutation(ACCEPT_ADMIN_MUTATION); + const [rejectAdminFunc] = useMutation(REJECT_ADMIN_MUTATION); + const { + data: currentUserData, + }: { + data: InterfaceUserType | undefined; + loading: boolean; + error?: Error | undefined; + } = useQuery(USER_ORGANIZATION_LIST, { + variables: { id: localStorage.getItem('id') }, + }); + + const { + data: dataUsers, + loading: loadingUsers, + refetch: refetchUsers, + } = useQuery(USER_LIST, { + notifyOnNetworkStatusChange: true, + }); + + const { + data: dataOrgs, + }: { + data: InterfaceOrgConnectionType | undefined; + error?: Error; + } = useQuery(ORGANIZATION_CONNECTION_LIST); + + // To clear the search when the component is unmounted + useEffect(() => { + return () => { + setSearchByName(''); + }; + }, []); + + // If the user is not Superadmin, redirect to Organizations screen useEffect(() => { const userType = localStorage.getItem('UserType'); if (userType != 'SUPERADMIN') { window.location.assign('/orglist'); } - - setComponentLoader(false); }, []); - const { data: userData, loading: userLoading, refetch } = useQuery(USER_LIST); - - const [acceptAdminFunc] = useMutation(ACCPET_ADMIN_MUTATION); - const [rejectAdminFunc] = useMutation(REJECT_ADMIN_MUTATION); - - const { data: dataOrgs } = useQuery(ORGANIZATION_CONNECTION_LIST); - + // Check if there are no organizations then show a warning useEffect(() => { if (!dataOrgs) { return; } - if (dataOrgs.organizationsConnection.length === 0) { toast.warning(t('noOrgError')); } }, [dataOrgs]); + // Set the usersData to the users that are not approved yet after every api call useEffect(() => { - if (userData) { + if (dataUsers) { setUsersData( - userData.users.filter( + dataUsers.users.filter( (user: any) => user.userType === 'ADMIN' && user.adminApproved === false ) ); } - }, [userData]); - - if (componentLoader || userLoading) { - return
; - } - - const handleChangePage = ( - event: React.MouseEvent | null, - newPage: number - ): void => { - /* istanbul ignore next */ - setPage(newPage); - }; - - /* istanbul ignore next */ - const handleChangeRowsPerPage = ( - event: React.ChangeEvent - ): void => { - setRowsPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; + }, [dataUsers]); - const accpetAdmin = async (userId: any): Promise => { + const acceptAdmin = async (userId: any): Promise => { try { const { data } = await acceptAdminFunc({ variables: { @@ -127,126 +139,153 @@ const Requests = (): JSX.Element => { const handleSearchByName = (e: any): any => { const { value } = e.target; setSearchByName(value); - - refetch({ - filter: searchByName, + refetchUsers({ + firstName_contains: value, + lastName_contains: '', + // Later on we can add several search and filter options }); }; + const debouncedHandleSearchByName = debounce(handleSearchByName); + return ( <> - - - -
-
-
{t('searchByName')}
+ + {/* Buttons Container */} +
+
+
+
- - -
- -

{t('requests')}

-
-
-
- - - - - - - - - - - - {(rowsPerPage > 0 - ? usersData.slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ) - : usersData - ).map( - ( - user: { - _id: string; - firstName: string; - lastName: string; - email: string; - userType: string; - }, - index: number - ) => { - return ( - - - - - - - - ); - } - )} - -
#{t('name')}{t('email')}{t('accept')}{t('reject')}
{page * 10 + (index + 1)}{`${user.firstName} ${user.lastName}`}{user.email} - - - -
-
-
-
- - - - - - -
+
+
+ +
- - +
+ {loadingUsers ? ( +
+

{t('loadingRequests')}

+
+ ) : usersData.length === 0 && searchByName.length > 0 ? ( +
+

+ {t('noResultsFoundFor')} "{searchByName}" +

+
+ ) : usersData.length === 0 ? ( +
+

{t('noRequestFound')}

+
+ ) : ( +
+ + + + + + + + + + + + {usersData.map( + ( + user: { + _id: string; + firstName: string; + lastName: string; + email: string; + userType: string; + }, + index: number + ) => { + return ( + + + + + + + + ); + } + )} + +
#{t('name')}{t('email')}{t('accept')}{t('reject')}
{index + 1}{`${user.firstName} ${user.lastName}`}{user.email} + + + +
+
+ )} + ); }; diff --git a/src/screens/Roles/Roles.module.css b/src/screens/Roles/Roles.module.css index 06cccbba21..0750dba108 100644 --- a/src/screens/Roles/Roles.module.css +++ b/src/screens/Roles/Roles.module.css @@ -1,109 +1,95 @@ -.mainpage { +.btnsContainer { display: flex; - flex-direction: row; + margin: 2.5rem 0 2.5rem 0; } -.sidebar { - z-index: 0; - padding-top: 5px; - margin: 0; - height: 100%; -} -.sidebar:after { - content: ''; - background-color: #f7f7f7; - position: absolute; - width: 2px; - height: 600px; - top: 10px; - left: 94%; - display: block; -} -.sidebarsticky { - padding-left: 45px; + +.btnsContainer .btnsBlock { + display: flex; } -.sidebarsticky > input { - text-decoration: none; - margin-bottom: 50px; - border-color: #e8e5e5; - width: 80%; - border-radius: 7px; - padding-top: 5px; - padding-bottom: 5px; - padding-right: 10px; - padding-left: 10px; - box-shadow: none; + +.btnsContainer .btnsBlock button { + margin-left: 1rem; + display: flex; + justify-content: center; + align-items: center; } -.navitem { - padding-left: 27%; - padding-top: 12px; - padding-bottom: 12px; - cursor: pointer; +.btnsContainer .inputContainer { + flex: 1; + position: relative; +} +.btnsContainer .input { + width: 70%; + position: relative; } -.searchtitle { - color: #707070; - font-weight: 600; - font-size: 18px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 60%; +.btnsContainer input { + outline: 1px solid var(--bs-gray-400); } -.logintitle { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 30px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 15%; +.btnsContainer .inputContainer button { + width: 52px; } -.mainpageright > hr { - margin-top: 20px; +.listBox { width: 100%; - margin-left: -15px; - margin-right: -15px; - margin-bottom: 20px; + flex: 1; } -.justifysp { + +.notFound { + flex: 1; display: flex; - justify-content: space-between; + justify-content: center; + align-items: center; + flex-direction: column; } -@media screen and (max-width: 575.5px) { - .justifysp { - padding-left: 55px; - display: flex; - justify-content: space-between; +@media (max-width: 1020px) { + .btnsContainer { + flex-direction: column; + margin: 1.5rem 0; + } + .btnsContainer .input { width: 100%; } + .btnsContainer .btnsBlock { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } - .mainpageright { - width: 100%; + .btnsContainer .btnsBlock button { + margin: 0; } -} -.list_box { - height: 70vh; - overflow-y: auto; - width: auto; - padding-right: 50px; + .btnsContainer .btnsBlock div button { + margin-right: 1.5rem; + } } -@media only screen and (max-width: 600px) { - .sidebar { - position: relative; - bottom: 18px; +/* For mobile devices */ + +@media (max-width: 520px) { + .btnsContainer { + margin-bottom: 0; } - .invitebtn { - width: 135px; - position: relative; - right: 10px; + + .btnsContainer .btnsBlock { + display: block; + margin-top: 1rem; + margin-right: 0; } - .userListTable { - margin-left: 40px; + + .btnsContainer .btnsBlock div { + flex: 1; + } + + .btnsContainer .btnsBlock div[title='Sort organizations'] { + margin-right: 0.5rem; + } + + .btnsContainer .btnsBlock button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; } } diff --git a/src/screens/Roles/Roles.test.tsx b/src/screens/Roles/Roles.test.tsx index 50505f181e..a85ed9eda5 100644 --- a/src/screens/Roles/Roles.test.tsx +++ b/src/screens/Roles/Roles.test.tsx @@ -1,27 +1,49 @@ import React from 'react'; -import { act, fireEvent, render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; -import { BrowserRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; -import Button from 'react-bootstrap/Button'; -import { I18nextProvider } from 'react-i18next'; +import { act, render, screen } from '@testing-library/react'; 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 { ToastContainer } from 'react-toastify'; -import Roles from './Roles'; +import userEvent from '@testing-library/user-event'; import { UPDATE_USERTYPE_MUTATION } from 'GraphQl/Mutations/mutations'; import { ORGANIZATION_CONNECTION_LIST, USER_LIST, + USER_ORGANIZATION_LIST, } from 'GraphQl/Queries/Queries'; import { store } from 'state/store'; -import userEvent from '@testing-library/user-event'; -import { within } from '@testing-library/react'; -import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; +import i18nForTest from 'utils/i18nForTest'; +import Roles from './Roles'; +import { debug } from 'jest-preview'; const MOCKS = [ + { + request: { + query: USER_ORGANIZATION_LIST, + variables: { id: 'SUPERADMIN' }, + }, + result: { + data: { + user: { + userType: 'SUPERADMIN', + firstName: 'John', + lastName: 'Doe', + image: '', + email: 'John_Does_Palasidoes@gmail.com', + adminFor: { + _id: 1, + name: 'Palisadoes', + image: '', + }, + }, + }, + }, + }, { request: { query: USER_LIST, @@ -150,6 +172,11 @@ const EMPTY_MOCKS = [ request: { query: USER_LIST, }, + result: { + data: { + users: [], + }, + }, }, { request: { @@ -177,22 +204,8 @@ const EMPTY_MOCKS = [ }, ]; -const EMPTY_ORG_MOCKS = [ - { - request: { - query: ORGANIZATION_CONNECTION_LIST, - }, - result: { - data: { - organizationsConnection: [], - }, - }, - }, -]; - const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(EMPTY_MOCKS, true); -const link3 = new StaticMockLink(EMPTY_ORG_MOCKS, true); async function wait(ms = 100): Promise { await act(() => { @@ -203,7 +216,7 @@ async function wait(ms = 100): Promise { } describe('Testing Roles screen', () => { - test('Componenet should be rendered properly', async () => { + test('Component should be rendered properly', async () => { window.location.assign('/orglist'); render( @@ -219,9 +232,7 @@ describe('Testing Roles screen', () => { ); await wait(); - - expect(screen.getByText(/Users List/i)).toBeInTheDocument(); - expect(screen.getByText(/Search By Name/i)).toBeInTheDocument(); + expect(screen.getAllByText(/Users List/i)).toBeTruthy(); expect(window.location).toBeAt('/orglist'); }); @@ -279,109 +290,6 @@ describe('Testing Roles screen', () => { expect(window.location.assign).not.toHaveBeenCalled(); }); - test('Roles renders a and tests changing rowsPerPage in the select', () => { - render( - - - - - - - - - - ); - - const appHeader = screen.queryByTestId('roles-header'); - const functionWhenAppHeaderIsNotNull = (app: any): void => { - const paginationList = within(app).getByTestId('something'); - const tablePagination = - within(paginationList).getByTestId('table-pagination'); - - expect(tablePagination).toBeInTheDocument(); - - const rowsPerPageSelect = within(tablePagination).getByTestId( - 'rows-per-page-select' - ); - fireEvent.change(rowsPerPageSelect, { target: { value: '-1' } }); - expect(rowsPerPageSelect).toHaveValue('-1'); - }; - - const functionWhenAppHeaderIsNull = (): void => { - expect(appHeader).toBeNull(); - }; - - const assertion = - appHeader !== null - ? functionWhenAppHeaderIsNotNull(appHeader) - : functionWhenAppHeaderIsNull(); - - assertion; - }); - describe('handleChangePage function', () => { - test('should call setPage with the new page when button is clicked', () => { - // Arrange - const setPage = jest.fn(); - const newPage = 2; - const { getByRole } = render( - - ); - - // Act - fireEvent.click(getByRole('button')); - - // Assert - expect(setPage).toHaveBeenCalledWith(newPage); - }); - }); - - test('should update rowsPerPage when selected from menu', () => { - const { getByRole } = render( - - - - - - - - - - ); - const appHeader = screen.queryByTestId('roles-header'); - const len = MOCKS.length; - - const functionWhenAppHeaderIsNotNull = (app: any): void => { - const table = getByRole('table'); - - const rowsPerPageButton = getByRole('button', { name: /rows per page/i }); - - fireEvent.click(rowsPerPageButton); - - const rowsPerPageOption = getByRole('option', { name: /10/i }); - - fireEvent.click(rowsPerPageOption); - - const paginationList = within(app).getByTestId('something'); - const tablePagination = - within(paginationList).getByTestId('table-pagination'); - - expect(tablePagination).toHaveAttribute('rowsPerPage', '10'); - - expect(table.querySelectorAll('tbody tr')).toHaveLength(len); - }; - - const functionWhenAppHeaderIsNull = (): void => { - expect(appHeader).toBeNull(); - }; - - const assertion = - appHeader !== null - ? functionWhenAppHeaderIsNotNull(appHeader) - : functionWhenAppHeaderIsNull(); - - assertion; - }); - test('Testing seach by name functionality', async () => { render( @@ -446,11 +354,12 @@ describe('Testing Roles screen', () => { ); await wait(); + expect(screen.getByText(/No User Found/i)).toBeTruthy(); }); test('Should render warning alert when there are no organizations', async () => { const { container } = render( - + @@ -463,6 +372,7 @@ describe('Testing Roles screen', () => { ); await wait(200); + debug(); expect(container.textContent).toMatch( 'Organizations not found, please create an organization through dashboard' ); diff --git a/src/screens/Roles/Roles.tsx b/src/screens/Roles/Roles.tsx index ac08929968..0fbb60a459 100644 --- a/src/screens/Roles/Roles.tsx +++ b/src/screens/Roles/Roles.tsx @@ -1,65 +1,62 @@ -import React, { useEffect, useState } from 'react'; -import { Col, Form, Row } from 'react-bootstrap'; -import { toast } from 'react-toastify'; import { useMutation, useQuery } from '@apollo/client'; +import React, { useEffect, useState } from 'react'; +import { Dropdown, Form, Table } from 'react-bootstrap'; +import Button from 'react-bootstrap/Button'; import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; -import styles from './Roles.module.css'; -import ListNavbar from 'components/ListNavbar/ListNavbar'; +import { Search } from '@mui/icons-material'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import SortIcon from '@mui/icons-material/Sort'; import { ORGANIZATION_CONNECTION_LIST, USER_LIST, + USER_ORGANIZATION_LIST, } from 'GraphQl/Queries/Queries'; -import { UPDATE_USERTYPE_MUTATION } from 'GraphQl/Mutations/mutations'; -import PaginationList from 'components/PaginationList/PaginationList'; -import NotFound from 'components/NotFound/NotFound'; +import SuperAdminScreen from 'components/SuperAdminScreen/SuperAdminScreen'; import { errorHandler } from 'utils/errorHandler'; +import styles from './Roles.module.css'; +import type { InterfaceUserType } from 'utils/interfaces'; +import { UPDATE_USERTYPE_MUTATION } from 'GraphQl/Mutations/mutations'; +import debounce from 'utils/debounce'; const Roles = (): JSX.Element => { const { t } = useTranslation('translation', { keyPrefix: 'roles' }); document.title = t('title'); - const [componentLoader, setComponentLoader] = useState(true); - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); const [searchByName, setSearchByName] = useState(''); - const [count, setCount] = useState(0); const userType = localStorage.getItem('UserType'); const userId = localStorage.getItem('id'); - - useEffect(() => { - if (userType != 'SUPERADMIN') { - window.location.assign('/orglist'); - } - setComponentLoader(false); - }, []); - - useEffect(() => { - if (searchByName !== '') { - userRefetch({ - firstName_contains: searchByName, - }); - } else { - if (count !== 0) { - userRefetch({ - firstName_contains: searchByName, - }); - } - } - }, [count, searchByName]); - const { - loading: usersLoading, - data: userData, - refetch: userRefetch, - } = useQuery(USER_LIST); + data: currentUserData, + }: { + data: InterfaceUserType | undefined; + loading: boolean; + error?: Error | undefined; + } = useQuery(USER_ORGANIZATION_LIST, { + variables: { id: localStorage.getItem('id') }, + }); + const { + data: dataUsers, + loading: loadingUsers, + refetch: refetchUsers, + } = useQuery(USER_LIST, { + notifyOnNetworkStatusChange: true, + }); const [updateUserType] = useMutation(UPDATE_USERTYPE_MUTATION); - const { data: dataOrgs } = useQuery(ORGANIZATION_CONNECTION_LIST); + // To clear the search when the component is unmounted + useEffect(() => { + return () => { + setSearchByName(''); + }; + }, []); + + // Warn if there is no organization useEffect(() => { if (!dataOrgs) { return; @@ -70,25 +67,12 @@ const Roles = (): JSX.Element => { } }, [dataOrgs]); - if (componentLoader || usersLoading) { - return
; - } - - const handleChangePage = ( - event: React.MouseEvent | null, - newPage: number - ): void => { - /* istanbul ignore next */ - setPage(newPage); - }; - - /* istanbul ignore next */ - const handleChangeRowsPerPage = ( - event: React.ChangeEvent - ): void => { - setRowsPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; + // Send to orgList page if user is not superadmin + useEffect(() => { + if (userType != 'SUPERADMIN') { + window.location.assign('/orglist'); + } + }, []); const changeRole = async (e: any): Promise => { const { value } = e.target; @@ -106,7 +90,7 @@ const Roles = (): JSX.Element => { /* istanbul ignore next */ if (data) { toast.success(t('roleUpdated')); - userRefetch(); + refetchUsers(); } } catch (error: any) { /* istanbul ignore next */ @@ -117,131 +101,153 @@ const Roles = (): JSX.Element => { const handleSearchByName = (e: any): void => { const { value } = e.target; setSearchByName(value); - setCount((prev) => prev + 1); + refetchUsers({ + firstName_contains: value, + lastName_contains: '', + // Later on we can add several search and filter options + }); }; + const debouncedHandleSearchByName = debounce(handleSearchByName); + return ( -
- - - -
-
-
{t('searchByName')}
+ <> + + {/* Buttons Container */} +
+
+
+
- - -
- -

{t('usersList')}

-
- -
-
- - - - - - - - - - - {userData && userData.users.length > 0 ? ( - (rowsPerPage > 0 - ? userData.users.slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ) - : userData.users - ).map( - ( - user: { - _id: string; - firstName: string; - lastName: string; - email: string; - userType: string; - }, - index: number - ) => { - return ( - - - - - - - ); - } - ) - ) : ( - - - - )} - -
#{t('name')}{t('email')}{t('roles_userType')}
{page * 10 + (index + 1)}{`${user.firstName} ${user.lastName}`}{user.email} - -
- -
-
-
-
- - - - - - -
+
+
+ +
- - -
+
+ + {loadingUsers ? ( +
+

{t('loadingUsers')}

+
+ ) : dataUsers.users.length === 0 && searchByName.length > 0 ? ( +
+

+ {t('noResultsFoundFor')} "{searchByName}" +

+
+ ) : dataUsers && dataUsers.users.length === 0 ? ( +
+

{t('noUserFound')}

+
+ ) : ( +
+ + + + + + + + + + + {dataUsers && + dataUsers?.users.map( + ( + user: { + _id: string; + firstName: string; + lastName: string; + email: string; + userType: string; + }, + index: number + ) => { + return ( + + + + + + + ); + } + )} + +
#{t('name')}{t('email')}{t('roles_userType')}
{index + 1}{`${user.firstName} ${user.lastName}`}{user.email} + +
+
+ )} + + ); }; diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts new file mode 100644 index 0000000000..4e5e9f98df --- /dev/null +++ b/src/utils/interfaces.ts @@ -0,0 +1,36 @@ +export interface InterfaceUserType { + user: { + firstName: string; + lastName: string; + image: string | null; + email: string; + userType: string; + adminFor: { + _id: string; + name: string; + image: string | null; + }[]; + }; +} + +export interface InterfaceOrgConnectionInfoType { + _id: string; + image: string | null; + creator: { + _id: string; + firstName: string; + lastName: string; + }; + name: string; + members: { + _id: string; + }[]; + admins: { + _id: string; + }[]; + createdAt: string; + location: string; +} +export interface InterfaceOrgConnectionType { + organizationsConnection: InterfaceOrgConnectionInfoType[]; +}