From b1f513fc7108a4686bb59620cfacc6618c2087ae Mon Sep 17 00:00:00 2001 From: Rishav Jha <76212518+rishav-jha-mech@users.noreply.github.com> Date: Sat, 9 Sep 2023 20:16:47 +0530 Subject: [PATCH] Merge latest AdminUI Redesign into develop (#972) * 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 * Fixed Default Animation on Organizations Screen * Fixed typo * Loading data from localstorage functional * Fixed name conventions * Fixed typo * UI Fix * Changed screen name * Table Loader added * Added LeftOrg drawer and Organization screen comp to Screens * routesReducer tests fixed * Redundant adminNavbar removed from project * MemberDetail issue fixed * Achieved 100% code coverage for LeftDrawerOrg, Added Empty div in images * Fixed failing tests * Fix tests * Fixed warnings * Linting fixes * Linting issues fixed * Achieved 100% code coverage for CollapsibleDropdown * Achieved 100% CC on IconComponent and removed useless imports * Achieved 100% cc for LeftDrawer Component * Achieved 100% CC on SuperAdminScreen Component * Fixed typo * Integrated Event Dashboard * Failing tests for LeftDrawer LeftDrawerOrg OrgList screen fixed * Removed redundant code * Removed useless imports * Linting fixed * Removed LeftDrawerOrg * Dashboard screen ui almost ready * Org Dash ready * Block/Unblock screen ready * Organization settings page ready * Lang changes * Page refresh on updating org removed * OrgUpdate tests done * OrgUpdate 100% test coverage achieved * OrgSettings Tests done * Organization Dashboard Cards done * Organization Dashboard achieved 100% CC * 100% CC achieved for BlockUser screen * Finalised changes * Small change * Tests fixed * Separate OrgSettings component made * Linting fixed * Formatting fixed --- public/locales/en.json | 35 +- public/locales/fr.json | 31 +- public/locales/hi.json | 30 +- public/locales/sp.json | 30 +- public/locales/zh.json | 30 +- src/GraphQl/Queries/Queries.ts | 2 + src/assets/svgs/admin.svg | 5 + src/assets/svgs/{icons => }/angleRight.svg | 0 src/assets/svgs/{icons => }/blockUser.svg | 0 src/assets/svgs/blockedUser.svg | 3 + src/assets/svgs/{icons => }/dashboard.svg | 0 src/assets/svgs/event.svg | 3 + src/assets/svgs/{icons => }/events.svg | 0 src/assets/svgs/{icons => }/logout.svg | 0 src/assets/svgs/{icons => }/organizations.svg | 0 src/assets/svgs/{icons => }/people.svg | 0 src/assets/svgs/{icons => }/plugins.svg | 0 src/assets/svgs/post.svg | 3 + src/assets/svgs/{icons => }/posts.svg | 0 src/assets/svgs/{icons => }/requests.svg | 0 src/assets/svgs/{icons => }/roles.svg | 0 src/assets/svgs/{icons => }/settings.svg | 0 src/assets/svgs/{icons => }/tags.svg | 0 src/assets/svgs/users.svg | 3 + .../ChangeLanguageDropDown.tsx | 7 +- .../ChangeLanguageDropdown.module.css | 7 - src/components/DeleteOrg/DeleteOrg.module.css | 25 + src/components/DeleteOrg/DeleteOrg.test.tsx | 84 +++ src/components/DeleteOrg/DeleteOrg.tsx | 89 ++++ .../IconComponent/IconComponent.tsx | 16 +- src/components/LeftDrawer/LeftDrawer.tsx | 12 +- .../LeftDrawerOrg/LeftDrawerOrg.test.tsx | 10 +- .../LeftDrawerOrg/LeftDrawerOrg.tsx | 48 +- src/components/Loader/Loader.module.css | 14 +- src/components/Loader/Loader.tsx | 22 +- src/components/OrgUpdate/OrgUpdate.module.css | 113 +--- src/components/OrgUpdate/OrgUpdate.test.tsx | 278 +++++----- src/components/OrgUpdate/OrgUpdate.tsx | 274 +++++----- src/components/OrgUpdate/OrgUpdateMocks.ts | 157 ++++++ .../OrganizationDashCards/CardItem.module.css | 47 ++ .../OrganizationDashCards/CardItem.test.tsx | 42 ++ .../OrganizationDashCards/CardItem.tsx | 48 ++ .../OrganizationDashCards/CardItemLoading.tsx | 24 + .../DashboardCard.test.tsx | 18 + .../OrganizationDashCards/DashboardCard.tsx | 32 ++ .../DashboardCardLoading.tsx | 36 ++ .../Dashboardcard.module.css | 60 +++ src/screens/BlockUser/BlockUser.module.css | 196 ++----- src/screens/BlockUser/BlockUser.test.tsx | 208 +++++--- src/screens/BlockUser/BlockUser.tsx | 370 ++++++------- .../ForgotPassword/ForgotPassword.module.css | 17 - src/screens/ForgotPassword/ForgotPassword.tsx | 3 +- .../OrgSettings/OrgSettings.module.css | 189 +------ src/screens/OrgSettings/OrgSettings.test.tsx | 234 +++----- src/screens/OrgSettings/OrgSettings.tsx | 196 +------ .../OrganizationDashboard.module.css | 201 +------ .../OrganizationDashboard.test.tsx | 241 ++++----- .../OrganizationDashboard.tsx | 502 ++++++++---------- .../OrganizationDashboardMocks.ts | 323 ++++------- src/utils/interfaces.ts | 40 ++ 60 files changed, 2116 insertions(+), 2242 deletions(-) create mode 100644 src/assets/svgs/admin.svg rename src/assets/svgs/{icons => }/angleRight.svg (100%) rename src/assets/svgs/{icons => }/blockUser.svg (100%) create mode 100644 src/assets/svgs/blockedUser.svg rename src/assets/svgs/{icons => }/dashboard.svg (100%) create mode 100644 src/assets/svgs/event.svg rename src/assets/svgs/{icons => }/events.svg (100%) rename src/assets/svgs/{icons => }/logout.svg (100%) rename src/assets/svgs/{icons => }/organizations.svg (100%) rename src/assets/svgs/{icons => }/people.svg (100%) rename src/assets/svgs/{icons => }/plugins.svg (100%) create mode 100644 src/assets/svgs/post.svg rename src/assets/svgs/{icons => }/posts.svg (100%) rename src/assets/svgs/{icons => }/requests.svg (100%) rename src/assets/svgs/{icons => }/roles.svg (100%) rename src/assets/svgs/{icons => }/settings.svg (100%) rename src/assets/svgs/{icons => }/tags.svg (100%) create mode 100644 src/assets/svgs/users.svg delete mode 100644 src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.module.css create mode 100644 src/components/DeleteOrg/DeleteOrg.module.css create mode 100644 src/components/DeleteOrg/DeleteOrg.test.tsx create mode 100644 src/components/DeleteOrg/DeleteOrg.tsx create mode 100644 src/components/OrgUpdate/OrgUpdateMocks.ts create mode 100644 src/components/OrganizationDashCards/CardItem.module.css create mode 100644 src/components/OrganizationDashCards/CardItem.test.tsx create mode 100644 src/components/OrganizationDashCards/CardItem.tsx create mode 100644 src/components/OrganizationDashCards/CardItemLoading.tsx create mode 100644 src/components/OrganizationDashCards/DashboardCard.test.tsx create mode 100644 src/components/OrganizationDashCards/DashboardCard.tsx create mode 100644 src/components/OrganizationDashCards/DashboardCardLoading.tsx create mode 100644 src/components/OrganizationDashCards/Dashboardcard.module.css diff --git a/public/locales/en.json b/public/locales/en.json index 80a75b08db..6dd9156052 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -152,11 +152,7 @@ "posts": "Posts", "events": "Events", "blockedUsers": "Blocked Users", - "membershipRequests": "Membership Requests", - "deleteOrganization": "Delete Organization", - "deleteMsg": "Do you want to delete this organization?", - "no": "No", - "yes": "Yes", + "requests": "Requests", "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too." }, "organizationPeople": { @@ -304,6 +300,7 @@ }, "blockUnblockUser": { "title": "Talawa Block/Unblock User", + "pageName": "Block/Unblock", "searchByName": "Search By Name", "listOfUsers": "List of Users who spammed", "name": "Name", @@ -317,8 +314,10 @@ "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too.", "allMembers": "All Members", "blockedUsers": "Blocked Users", - "searchFirstName": "Enter First Name", - "searchLastName": "Enter Last Name" + "searchByFirstName": "Search By First Name", + "searchByLastName": "Search By Last Name", + "noResultsFoundFor": "No results found for", + "noSpammerFound": "No spammer found" }, "forgotPassword": { "title": "Talawa Forgot Password", @@ -367,13 +366,20 @@ }, "orgSettings": { "title": "Talawa Setting", - "updateYourDetails": "Update Your Details", - "updateYourPassword": "Update Your Password", + "pageName": "Settings", "updateOrganization": "Update Organization", - "deleteOrganization": "Delete Organization", "seeRequest": "See Request", "settings": "Settings", - "noData": "No data" + "noData": "No data", + "otherSettings": "Other Settings", + "changeLanguage": "Change Language" + }, + "deleteOrg": { + "deleteOrganization": "Delete Organization", + "deleteMsg": "Do you want to delete this organization?", + "no": "No", + "yes": "Yes", + "longDelOrgMsg": "By clicking on Delete organization button you will the organization will be permanently deleted along with its events, tags and all related data." }, "userUpdate": { "firstName": "First Name", @@ -388,7 +394,6 @@ "saveChanges": "Save Changes", "cancel": "Cancel" }, - "userPasswordUpdate": { "previousPassword": "Previous Password", "newPassword": "New Password", @@ -396,7 +401,6 @@ "saveChanges": "Save Changes", "cancel": "Cancel" }, - "orgDelete": { "deleteOrg": "Delete Org" }, @@ -412,10 +416,9 @@ "description": "Description", "location": "Location", "displayImage": "Display Image", - "isPublic": "Is Public", - "isRegistrable": "Is Registrable", + "isPublic": "Public", + "isVisibleInSearch": "Visible in Search", "saveChanges": "Save Changes", - "cancel": "Cancel", "enterNameOrganization": "Enter Organization Name", "successfulUpdated": "Organization updated successfully", "talawaApiUnavailable": "Talawa-API service is unavailable. Is it running? Check your network connectivity too." diff --git a/public/locales/fr.json b/public/locales/fr.json index 6d501840ac..78d3fade9b 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -145,11 +145,7 @@ "posts": "Des postes", "events": "Événements", "blockedUsers": "Utilisateurs bloqués", - "membershipRequests": "Demandes d'adhésion", - "deleteOrganization": "Supprimer l'organisation", - "deleteMsg": "Voulez-vous supprimer cette organisation ?", - "no": "Non", - "yes": "Oui", + "requests": "Demandes", "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau." }, "organizationPeople": { @@ -297,6 +293,7 @@ }, "blockUnblockUser": { "title": "Talawa Bloquer/Débloquer l'utilisateur", + "pageName": "Bloquer/Débloquer'", "searchByName": "Recherche par nom", "listOfUsers": "Liste des utilisateurs qui ont spammé", "name": "Nom", @@ -310,8 +307,10 @@ "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau.", "allMembers": "Tous les membres", "blockedUsers": "Utilisateurs bloqués", - "searchFirstName": "Entrez votre prénom", - "searchLastName": "Entrer le nom de famille" + "searchByFirstName": "Rechercher par prénom", + "searchByLastName": "Rechercher par nom de famille", + "noResultsFoundFor": "Aucun résultat trouvé pour ", + "noSpammerFound": "Aucun spammeur trouvé" }, "forgotPassword": { "title": "Mot de passe oublié Talawa", @@ -360,13 +359,22 @@ }, "orgSettings": { "title": "Paramètre Talawa", + "pageName": "Paramètres", "updateYourDetails": "Mettre à jour vos informations", "updateYourPassword": "Mettez à jour votre mot de passe", "updateOrganization": "Mettre à jour l'organisation", - "deleteOrganization": "Supprimer l'organisation", "seeRequest": "Voir demande", "settings": "Réglages", - "noData": "Pas de données" + "noData": "Pas de données", + "otherSettings": "Autres paramètres", + "changeLanguage": "Changer la langue" + }, + "deleteOrg": { + "deleteOrganization": "Supprimer l'organisation", + "deleteMsg": "Voulez-vous supprimer cette organisation ?", + "no": "Non", + "yes": "Oui", + "longDelOrgMsg": "En cliquant sur le bouton Supprimer l'organisation, l'organisation sera définitivement supprimée, ainsi que ses événements, étiquettes et toutes les données associées." }, "userUpdate": { "firstName": "Prénom", @@ -403,10 +411,9 @@ "description": "La description", "location": "emplacement", "displayImage": "Afficher l'image", - "isPublic": "Est publique", - "isRegistrable": "Est enregistrable", + "isPublic": "Public", + "isVisibleInSearch": "Visible dans la recherche", "saveChanges": "Sauvegarder les modifications", - "cancel": "Annuler", "enterNameOrganization": "Entrez le nom de l'organisation", "successfulUpdated": "Mise à jour réussie", "talawaApiUnavailable": "Le service Talawa-API n'est pas disponible. Est-il en cours d'exécution ? Vérifiez également votre connectivité réseau." diff --git a/public/locales/hi.json b/public/locales/hi.json index d376cb1107..29b449a0c9 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -145,11 +145,7 @@ "posts": "पोस्ट", "events": "आयोजन", "blockedUsers": "रोके गए उपयोगकर्ता", - "membershipRequests": "सदस्यता अनुरोध", - "deleteOrganization": "संगठन हटाएं", - "deleteMsg": "क्या आप इस संगठन को हटाना चाहते हैं?", - "no": "नहीं", - "yes": "हाँ", + "requests": "अनुरोध", "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रहा है? अपनी नेटवर्क कनेक्टिविटी भी जांचें।" }, "organizationPeople": { @@ -297,6 +293,7 @@ }, "blockUnblockUser": { "title": "तलावा ब्लॉक/अनब्लॉक यूजर", + "pageName": "ब्लॉक/अनब्लॉक", "searchByName": "नाम से खोजें", "listOfUsers": "स्पैम करने वाले उपयोगकर्ताओं की सूची", "name": "नाम", @@ -310,8 +307,10 @@ "talawaApiUnavailable": "तलवा-एपीआई सेवा उपलब्ध नहीं है। क्या यह चल रहा है? अपनी नेटवर्क कनेक्टिविटी भी जांचें।", "allMembers": "सभी सदस्य", "blockedUsers": "रोके गए उपयोगकर्ता", - "searchFirstName": "प्रथम नाम दर्ज करें", - "searchLastName": "अंतिम नाम दर्ज करो" + "searchByFirstName": "पहले नाम से खोजें", + "searchByLastName": "उपनाम से खोजें", + "noResultsFoundFor": "के लिए कोई परिणाम नहीं मिला ", + "noSpammerFound": "कोई स्पैमर नहीं मिला" }, "forgotPassword": { "title": "तलवा पासवर्ड भूल गए", @@ -360,13 +359,22 @@ }, "orgSettings": { "title": "तलावा सेटिंग", + "pageName": "सेटिंग्स", "updateYourDetails": "अपना विवरण अपडेट करें", "updateYourPassword": "अपना पासवर्ड अपडेट करें", "updateOrganization": "अद्यतन संगठन", - "deleteOrganization": "संगठन हटाएं", "seeRequest": "अनुरोध देखें", "settings": "समायोजन", - "noData": "कोई डेटा नहीं" + "noData": "कोई डेटा नहीं", + "otherSettings": "अन्य सेटिंग्स", + "changeLanguage": "भाषा बदलें" + }, + "deleteOrg": { + "deleteOrganization": "संगठन हटाएं", + "deleteMsg": "क्या आप इस संगठन को हटाना चाहते हैं?", + "no": "नहीं", + "yes": "हां", + "longDelOrgMsg": "संगठन हटाने के बटन पर क्लिक करके, संगठन को स्थायित रूप से हटा दिया जाएगा, साथ ही उसके आयोजन, टैग और सभी संबंधित डेटा भी हटा दिया जाएगा।" }, "userUpdate": { "firstName": "पहला नाम", @@ -403,8 +411,8 @@ "description": "विवरण", "location": "जगह", "displayImage": "प्रदर्शन छवि", - "isPublic": "सार्वजनिक है", - "isRegistrable": "पंजीकरण योग्य है", + "isPublic": "सार्वजनिक", + "isVisibleInSearch": "खोज में दिखाए जा सकते हैं", "saveChanges": "परिवर्तनों को सुरक्षित करें", "cancel": "रद्द करना", "enterNameOrganization": "संगठन का नाम दर्ज करें", diff --git a/public/locales/sp.json b/public/locales/sp.json index af9a7f5686..7fa6ccd04b 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -145,11 +145,7 @@ "posts": "Publicaciones", "events": "Eventos", "blockedUsers": "Usuarios bloqueados", - "membershipRequests": "Solicitudes de membresía", - "deleteOrganization": "Eliminar Organización", - "deleteMsg": "¿Desea eliminar esta organización?", - "no": "No", - "yes": "Sí", + "requests": "Solicitudes", "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red." }, "organizationPeople": { @@ -297,6 +293,7 @@ }, "blockUnblockUser": { "title": "Usuario de bloqueo/desbloqueo de Talawa", + "pageName": "Bloqueo/desbloqueo", "searchByName": "Buscar por nombre", "listOfUsers": "Lista de Usuarios que enviaron spam", "name": "Nombre", @@ -310,8 +307,10 @@ "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red.", "allMembers": "Todos los miembros", "blockedUsers": "Usuarios bloqueados", - "searchFirstName": "Ingrese el nombre", - "searchLastName": "Introduzca el apellido" + "searchByFirstName": "Buscar por nombre de pila", + "searchByLastName": "Buscar por apellido", + "noResultsFoundFor": "No se encontraron resultados para ", + "noSpammerFound": "No se encontró ningún spammer" }, "forgotPassword": { "title": "Talawa olvidó su contraseña", @@ -360,13 +359,22 @@ }, "orgSettings": { "title": "Configuración Talawa", + "pageName": "Configuración", "updateYourDetails": "Actualiza tus datos", "updateYourPassword": "Actualice su contraseña", "updateOrganization": "Actualizar Organización", - "deleteOrganization": "Eliminar Organización", "seeRequest": "Ver Solicitud", "settings": "Ajustes", - "noData": "Sin datos" + "noData": "Sin datos", + "otherSettings": "Otras Configuraciones", + "changeLanguage": "Cambiar Idioma" + }, + "deleteOrg": { + "deleteOrganization": "Eliminar organización", + "deleteMsg": "¿Desea eliminar esta organización?", + "no": "No", + "yes": "Sí", + "longDelOrgMsg": "Al hacer clic en el botón de Eliminar organización, se eliminará permanentemente la organización junto con sus eventos, etiquetas y todos los datos relacionados." }, "userUpdate": { "firstName": "Primer nombre", @@ -403,8 +411,8 @@ "description": "Descripción", "location": "ubicación", "displayImage": "Mostrar imagen", - "isPublic": "Es público", - "isRegistrable": "Es registrable", + "isPublic": "Público", + "isVisibleInSearch": "Visible en la búsqueda", "saveChanges": "Guardar cambios", "cancel": "Cancelar", "enterNameOrganization": "Ingrese el nombre de la organización", diff --git a/public/locales/zh.json b/public/locales/zh.json index e91c57c531..a4232d43cf 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -145,11 +145,7 @@ "posts": "帖子", "events": "事件", "blockedUsers": "被阻止的用戶", - "membershipRequests": "會員申請", - "deleteOrganization": "刪除組織", - "deleteMsg": "您要刪除此組織嗎?", - "no": "不", - "yes": "是的", + "requests": "请求", "talawaApiUnavailable": "服務不可用。它在運行嗎?還要檢查您的網絡連接。" }, "organizationPeople": { @@ -297,6 +293,7 @@ }, "blockUnblockUser": { "title": "塔拉瓦封鎖/解除封鎖用戶", + "pageName": "封锁/解封", "searchByName": "按名稱搜索", "listOfUsers": "發送垃圾郵件的用戶列表", "name": "姓名", @@ -310,8 +307,10 @@ "talawaApiUnavailable": "服務不可用。它在運行嗎?還要檢查您的網絡連接。", "allMembers": "所有成员", "blockedUsers": "被阻止的用户", - "searchFirstName": "输入名字", - "searchLastName": "输入姓氏" + "searchByFirstName": "按名字搜索", + "searchByLastName": "按姓氏搜索", + "noResultsFoundFor": "未找到结果 ", + "noSpammerFound": "未发现垃圾邮件发送者" }, "forgotPassword": { "title": "塔拉瓦忘記密碼", @@ -360,13 +359,22 @@ }, "orgSettings": { "title": "塔拉瓦設置", + "pageName": "设置", "updateYourDetails": "更新您的詳細信息", "updateYourPassword": "更新您的密碼", "updateOrganization": "更新組織", - "deleteOrganization": "刪除組織", "seeRequest": "查看請求", "settings": "設置", - "noData": "沒有數據" + "noData": "沒有數據", + "otherSettings": "其他设置", + "changeLanguage": "更改语言" + }, + "deleteOrg": { + "deleteOrganization": "删除组织", + "deleteMsg": "您是否要删除此组织?", + "no": "否", + "yes": "是", + "longDelOrgMsg": "点击删除组织按钮后,将永久删除该组织以及其活动、标签和所有相关数据。" }, "userUpdate": { "firstName": "名", @@ -403,8 +411,8 @@ "description": "描述", "location": "地點", "displayImage": "顯示圖像", - "isPublic": "是公開的", - "isRegistrable": "可註冊", + "isPublic": "公开", + "isVisibleInSearch": "在搜索中可见", "saveChanges": "保存更改", "cancel": "取消", "enterNameOrganization": "輸入組織名稱", diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index 1f47769e4c..6efae00ed5 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -183,6 +183,8 @@ export const ORGANIZATIONS_LIST = gql` name description location + isPublic + visibleInSearch members { _id firstName diff --git a/src/assets/svgs/admin.svg b/src/assets/svgs/admin.svg new file mode 100644 index 0000000000..8ee42f611d --- /dev/null +++ b/src/assets/svgs/admin.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svgs/icons/angleRight.svg b/src/assets/svgs/angleRight.svg similarity index 100% rename from src/assets/svgs/icons/angleRight.svg rename to src/assets/svgs/angleRight.svg diff --git a/src/assets/svgs/icons/blockUser.svg b/src/assets/svgs/blockUser.svg similarity index 100% rename from src/assets/svgs/icons/blockUser.svg rename to src/assets/svgs/blockUser.svg diff --git a/src/assets/svgs/blockedUser.svg b/src/assets/svgs/blockedUser.svg new file mode 100644 index 0000000000..bbe0a51f84 --- /dev/null +++ b/src/assets/svgs/blockedUser.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icons/dashboard.svg b/src/assets/svgs/dashboard.svg similarity index 100% rename from src/assets/svgs/icons/dashboard.svg rename to src/assets/svgs/dashboard.svg diff --git a/src/assets/svgs/event.svg b/src/assets/svgs/event.svg new file mode 100644 index 0000000000..3c73e7b04e --- /dev/null +++ b/src/assets/svgs/event.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icons/events.svg b/src/assets/svgs/events.svg similarity index 100% rename from src/assets/svgs/icons/events.svg rename to src/assets/svgs/events.svg diff --git a/src/assets/svgs/icons/logout.svg b/src/assets/svgs/logout.svg similarity index 100% rename from src/assets/svgs/icons/logout.svg rename to src/assets/svgs/logout.svg diff --git a/src/assets/svgs/icons/organizations.svg b/src/assets/svgs/organizations.svg similarity index 100% rename from src/assets/svgs/icons/organizations.svg rename to src/assets/svgs/organizations.svg diff --git a/src/assets/svgs/icons/people.svg b/src/assets/svgs/people.svg similarity index 100% rename from src/assets/svgs/icons/people.svg rename to src/assets/svgs/people.svg diff --git a/src/assets/svgs/icons/plugins.svg b/src/assets/svgs/plugins.svg similarity index 100% rename from src/assets/svgs/icons/plugins.svg rename to src/assets/svgs/plugins.svg diff --git a/src/assets/svgs/post.svg b/src/assets/svgs/post.svg new file mode 100644 index 0000000000..34e468523b --- /dev/null +++ b/src/assets/svgs/post.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svgs/icons/posts.svg b/src/assets/svgs/posts.svg similarity index 100% rename from src/assets/svgs/icons/posts.svg rename to src/assets/svgs/posts.svg diff --git a/src/assets/svgs/icons/requests.svg b/src/assets/svgs/requests.svg similarity index 100% rename from src/assets/svgs/icons/requests.svg rename to src/assets/svgs/requests.svg diff --git a/src/assets/svgs/icons/roles.svg b/src/assets/svgs/roles.svg similarity index 100% rename from src/assets/svgs/icons/roles.svg rename to src/assets/svgs/roles.svg diff --git a/src/assets/svgs/icons/settings.svg b/src/assets/svgs/settings.svg similarity index 100% rename from src/assets/svgs/icons/settings.svg rename to src/assets/svgs/settings.svg diff --git a/src/assets/svgs/icons/tags.svg b/src/assets/svgs/tags.svg similarity index 100% rename from src/assets/svgs/icons/tags.svg rename to src/assets/svgs/tags.svg diff --git a/src/assets/svgs/users.svg b/src/assets/svgs/users.svg new file mode 100644 index 0000000000..a1a474206d --- /dev/null +++ b/src/assets/svgs/users.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/ChangeLanguageDropdown/ChangeLanguageDropDown.tsx b/src/components/ChangeLanguageDropdown/ChangeLanguageDropDown.tsx index d80b8cb225..8f4fd945ed 100644 --- a/src/components/ChangeLanguageDropdown/ChangeLanguageDropDown.tsx +++ b/src/components/ChangeLanguageDropdown/ChangeLanguageDropDown.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Dropdown } from 'react-bootstrap'; import i18next from 'i18next'; -import styles from './ChangeLanguageDropdown.module.css'; import { languages } from 'utils/languages'; import cookies from 'js-cookie'; @@ -23,9 +22,7 @@ const ChangeLanguageDropDown = ( return ( ( => changeLanguage(language.code)} disabled={currentLanguageCode === language.code} data-testid={`change-language-btn-${language.code}`} diff --git a/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.module.css b/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.module.css deleted file mode 100644 index e72c604905..0000000000 --- a/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.parentContainer { - margin: 0 1rem; -} - -.dropdownItem { - font-size: 0.9rem; -} diff --git a/src/components/DeleteOrg/DeleteOrg.module.css b/src/components/DeleteOrg/DeleteOrg.module.css new file mode 100644 index 0000000000..2b15a2ac0c --- /dev/null +++ b/src/components/DeleteOrg/DeleteOrg.module.css @@ -0,0 +1,25 @@ +.settingsBody { + margin: 2.5rem 0; +} + +.cardHeader { + padding: 1.25rem 1rem 1rem 1rem; + border-bottom: 1px solid var(--bs-gray-200); + display: flex; + justify-content: space-between; + align-items: center; +} + +.cardHeader .cardTitle { + font-size: 1.2rem; + font-weight: 600; +} + +.cardBody { + min-height: 180px; +} + +.cardBody .textBox { + margin: 0 0 3rem 0; + color: var(--bs-secondary); +} diff --git a/src/components/DeleteOrg/DeleteOrg.test.tsx b/src/components/DeleteOrg/DeleteOrg.test.tsx new file mode 100644 index 0000000000..936cf44e03 --- /dev/null +++ b/src/components/DeleteOrg/DeleteOrg.test.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import { render, screen } from '@testing-library/react'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; + +import { DELETE_ORGANIZATION_MUTATION } from 'GraphQl/Mutations/mutations'; +import { act } from 'react-dom/test-utils'; +import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import i18nForTest from 'utils/i18nForTest'; +import DeleteOrg from './DeleteOrg'; + +const MOCKS = [ + { + request: { + query: DELETE_ORGANIZATION_MUTATION, + variables: { + id: 123, + }, + }, + result: { + data: { + removeOrganization: [ + { + _id: 123, + }, + ], + }, + }, + }, +]; + +const link = new StaticMockLink(MOCKS, true); + +afterEach(() => { + localStorage.clear(); +}); + +describe('Delete Organization Component', () => { + test('should be able to Toggle Delete Organization Modal', async () => { + window.location.assign('/orgsetting/id=123'); + localStorage.setItem('UserType', 'SUPERADMIN'); + render( + + + + + + + + + + ); + screen.getByTestId(/openDeleteModalBtn/i).click(); + expect(screen.getByTestId(/orgDeleteModal/i)).toBeInTheDocument(); + screen.getByTestId(/closeDelOrgModalBtn/i).click(); + await act(async () => { + expect(screen.queryByTestId(/orgDeleteModal/i)).not.toHaveFocus(); + }); + expect(window.location).toBeAt('/orgsetting/id=123'); + }); + + test('Delete organization functionality should work properly', async () => { + window.location.assign('/orgsetting/id=123'); + localStorage.setItem('UserType', 'SUPERADMIN'); + render( + + + + + + + + + + ); + screen.getByTestId(/openDeleteModalBtn/i).click(); + screen.getByTestId(/deleteOrganizationBtn/i).click(); + expect(window.location).not.toBeNull(); + }); +}); diff --git a/src/components/DeleteOrg/DeleteOrg.tsx b/src/components/DeleteOrg/DeleteOrg.tsx new file mode 100644 index 0000000000..e6442d6558 --- /dev/null +++ b/src/components/DeleteOrg/DeleteOrg.tsx @@ -0,0 +1,89 @@ +import { useMutation } from '@apollo/client'; +import { DELETE_ORGANIZATION_MUTATION } from 'GraphQl/Mutations/mutations'; +import React, { useState } from 'react'; +import { Button, Card, Modal } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import { errorHandler } from 'utils/errorHandler'; +import styles from './DeleteOrg.module.css'; + +function deleteOrg(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'deleteOrg', + }); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const currentUrl = window.location.href.split('=')[1]; + const canDelete = localStorage.getItem('UserType') === 'SUPERADMIN'; + const toggleDeleteModal = (): void => setShowDeleteModal(!showDeleteModal); + const [del] = useMutation(DELETE_ORGANIZATION_MUTATION); + + const deleteOrg = async (): Promise => { + try { + const { data } = await del({ + variables: { + id: currentUrl, + }, + }); + /* istanbul ignore next */ + if (data) { + window.location.replace('/orglist'); + } + } catch (error: any) { + /* istanbul ignore next */ + errorHandler(t, error); + } + }; + + return ( + <> + {canDelete && ( + +
+
{t('deleteOrganization')}
+
+ +
{t('longDelOrgMsg')}
+ +
+
+ )} + {/* Delete Organization Modal */} + {canDelete && ( + + +
{t('deleteOrganization')}
+
+ {t('deleteMsg')} + + + + +
+ )} + + ); +} + +export default deleteOrg; diff --git a/src/components/IconComponent/IconComponent.tsx b/src/components/IconComponent/IconComponent.tsx index 5bbbf44334..a4648a6e03 100644 --- a/src/components/IconComponent/IconComponent.tsx +++ b/src/components/IconComponent/IconComponent.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { QuestionMarkOutlined } from '@mui/icons-material'; -import { ReactComponent as BlockUserIcon } from '../../assets/svgs/icons/blockUser.svg'; -import { ReactComponent as DashboardIcon } from '../../assets/svgs/icons/dashboard.svg'; -import { ReactComponent as EventsIcon } from '../../assets/svgs/icons/events.svg'; -import { ReactComponent as OrganizationsIcon } from '../../assets/svgs/icons/organizations.svg'; -import { ReactComponent as PeopleIcon } from '../../assets/svgs/icons/people.svg'; -import { ReactComponent as PluginsIcon } from '../../assets/svgs/icons/plugins.svg'; -import { ReactComponent as PostsIcon } from '../../assets/svgs/icons/posts.svg'; -import { ReactComponent as SettingsIcon } from '../../assets/svgs/icons/settings.svg'; +import { ReactComponent as BlockUserIcon } from 'assets/svgs/blockUser.svg'; +import { ReactComponent as DashboardIcon } from 'assets/svgs/dashboard.svg'; +import { ReactComponent as EventsIcon } from 'assets/svgs/events.svg'; +import { ReactComponent as OrganizationsIcon } from 'assets/svgs/organizations.svg'; +import { ReactComponent as PeopleIcon } from 'assets/svgs/people.svg'; +import { ReactComponent as PluginsIcon } from 'assets/svgs/plugins.svg'; +import { ReactComponent as PostsIcon } from 'assets/svgs/posts.svg'; +import { ReactComponent as SettingsIcon } from 'assets/svgs/settings.svg'; export interface InterfaceIconComponent { name: string; diff --git a/src/components/LeftDrawer/LeftDrawer.tsx b/src/components/LeftDrawer/LeftDrawer.tsx index 87e37097e7..e28f40d31a 100644 --- a/src/components/LeftDrawer/LeftDrawer.tsx +++ b/src/components/LeftDrawer/LeftDrawer.tsx @@ -3,12 +3,12 @@ import Button from 'react-bootstrap/Button'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import { toast } from 'react-toastify'; -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 { ReactComponent as AngleRightIcon } from 'assets/svgs/angleRight.svg'; +import { ReactComponent as LogoutIcon } from 'assets/svgs/logout.svg'; +import { ReactComponent as OrganizationsIcon } from 'assets/svgs/organizations.svg'; +import { ReactComponent as RequestsIcon } from 'assets/svgs/requests.svg'; +import { ReactComponent as RolesIcon } from 'assets/svgs/roles.svg'; +import { ReactComponent as TalawaLogo } from 'assets/svgs/talawa.svg'; import styles from './LeftDrawer.module.css'; export interface InterfaceLeftDrawerProps { diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx b/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx index 0c108533cc..ff10c1b28b 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx @@ -83,6 +83,8 @@ const MOCKS = [ name: 'Test Organization', description: 'Testing this organization', location: 'Gotham, DC', + isPublic: true, + visibleInSearch: true, members: [ { _id: 'john123', @@ -135,6 +137,8 @@ const MOCKS_WITH_IMAGE = [ name: 'Test Organization', description: 'Testing this organization', location: 'Gotham, DC', + isPublic: true, + visibleInSearch: true, members: [ { _id: 'john123', @@ -261,11 +265,7 @@ describe('Testing Left Drawer component for SUPERADMIN', () => { ); await wait(); - // Coming soon - userEvent.click(screen.getByTestId(/orgBtn/i)); - expect(toast.success).toHaveBeenCalledWith( - 'Organization detail modal coming soon!' - ); + expect(screen.getByTestId(/orgBtn/i)).toBeInTheDocument(); userEvent.click(screen.getByTestId(/profileBtn/i)); expect(toast.success).toHaveBeenCalledWith('Profile page coming soon!'); }); diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx b/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx index 5ec1f615bd..119a90f0eb 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx @@ -3,16 +3,16 @@ import { WarningAmberOutlined } from '@mui/icons-material'; import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; import CollapsibleDropdown from 'components/CollapsibleDropdown/CollapsibleDropdown'; import IconComponent from 'components/IconComponent/IconComponent'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import Button from 'react-bootstrap/Button'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import { toast } from 'react-toastify'; import type { TargetsType } from 'state/reducers/routesReducer'; import type { InterfaceQueryOrganizationsListObject } 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 TalawaLogo } from '../../assets/svgs/talawa.svg'; +import { ReactComponent as AngleRightIcon } from 'assets/svgs/angleRight.svg'; +import { ReactComponent as LogoutIcon } from 'assets/svgs/logout.svg'; +import { ReactComponent as TalawaLogo } from 'assets/svgs/talawa.svg'; import styles from './LeftDrawerOrg.module.css'; export interface InterfaceLeftDrawerProps { @@ -31,7 +31,8 @@ const leftDrawerOrg = ({ setHideDrawer, }: InterfaceLeftDrawerProps): JSX.Element => { const { t } = useTranslation('translation', { keyPrefix: 'leftDrawerOrg' }); - + const [organization, setOrganization] = + useState(); const { data, loading, @@ -51,6 +52,17 @@ const leftDrawerOrg = ({ const history = useHistory(); + // Set organization data + useEffect(() => { + let isMounted = true; + if (data && isMounted) { + setOrganization(data?.organizations[0]); + } + return () => { + isMounted = false; + }; + }, [data]); + const logout = (): void => { localStorage.clear(); history.push('/'); @@ -95,7 +107,7 @@ const leftDrawerOrg = ({ data-testid="orgBtn" /> - ) : data && data?.organizations.length == 0 ? ( + ) : organization == undefined ? ( <> )} diff --git a/src/components/Loader/Loader.module.css b/src/components/Loader/Loader.module.css index df8c1deea6..aad512e826 100644 --- a/src/components/Loader/Loader.module.css +++ b/src/components/Loader/Loader.module.css @@ -6,8 +6,20 @@ align-items: center; } -.spinner { +.spinnerXl { width: 6rem; height: 6rem; border-width: 0.5rem; } + +.spinnerLg { + height: 4rem; + width: 4rem; + border-width: 0.3rem; +} + +.spinnerSm { + height: 2rem; + width: 2rem; + border-width: 0.2rem; +} diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx index 6dc247b4c9..f761ebd79b 100644 --- a/src/components/Loader/Loader.tsx +++ b/src/components/Loader/Loader.tsx @@ -2,12 +2,28 @@ import React from 'react'; import styles from './Loader.module.css'; import { Spinner } from 'react-bootstrap'; -const Loader = (): JSX.Element => { +interface InterfaceLoaderProps { + styles?: StyleSheet | string; + size?: 'sm' | 'lg' | 'xl'; +} + +const Loader = (props: InterfaceLoaderProps): JSX.Element => { return ( <> -
+
div { - width: 50%; - margin-right: 50px; + justify-content: center; + align-items: center; + flex-direction: column; } -.radio_buttons > input { - margin-bottom: 20px; - border: none; - box-shadow: none; - padding: 0 0; - border-radius: 5px; - background: none; - width: 50%; -} - -.whitebtn { - margin: 1rem 0 0; - margin-top: 10px; - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - padding: 10px 20px; - border-radius: 5px; - background: none; - width: 20%; - font-size: 16px; - color: #31bb6b; - outline: none; - font-weight: 600; - cursor: pointer; - float: left; - transition: transform 0.2s, box-shadow 0.2s; -} -.greenregbtn { - margin: 1rem 0 0; - margin-top: 10px; - margin-right: 30px; - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - padding: 10px 10px; - border-radius: 5px; - background-color: #31bb6b; - width: 20%; - font-size: 16px; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; -} -.radio_buttons { - width: 55%; - margin-top: 10px; - display: flex; - color: #707070; - font-weight: 600; - font-size: 14px; -} -.radio_buttons > input { - transform: scale(1.2); -} -.radio_buttons > label { - margin-top: -4px; - margin-left: 0px; - margin-right: 7px; -} -.idtitle { - width: 88%; -} -.checkboxdiv { - display: flex; - width: 100%; - margin-top: 20px; -} -.checkboxdiv > div { - display: flex; - width: 50%; -} -.checkboxdiv > div > input { - width: 30%; - border: none; - box-shadow: none; - margin-top: 5px; +.icon { + transform: scale(1.5); + color: var(--bs-danger); + margin-bottom: 1rem; } diff --git a/src/components/OrgUpdate/OrgUpdate.test.tsx b/src/components/OrgUpdate/OrgUpdate.test.tsx index 187e736c69..79b076c452 100644 --- a/src/components/OrgUpdate/OrgUpdate.test.tsx +++ b/src/components/OrgUpdate/OrgUpdate.test.tsx @@ -1,109 +1,19 @@ import React from 'react'; -import { act, render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; +import { act, fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; -import OrgUpdate from './OrgUpdate'; -import { UPDATE_ORGANIZATION_MUTATION } from 'GraphQl/Mutations/mutations'; -import i18nForTest from 'utils/i18nForTest'; -import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; import { StaticMockLink } from 'utils/StaticMockLink'; +import i18nForTest from 'utils/i18nForTest'; +import OrgUpdate from './OrgUpdate'; +import { + MOCKS, + MOCKS_ERROR_ORGLIST, + MOCKS_ERROR_UPDATE_ORGLIST, +} from './OrgUpdateMocks'; -const MOCKS = [ - { - request: { - query: ORGANIZATIONS_LIST, - }, - result: { - data: { - organizations: [ - { - _id: '123', - image: '', - name: '', - description: '', - creator: { - firstName: '', - lastName: '', - email: '', - }, - location: '', - members: { - _id: '123', - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - }, - admins: { - _id: '123', - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - }, - membershipRequests: { - _id: '456', - user: { - firstName: 'Sam', - lastName: 'Smith', - email: 'samsmith@gmail.com', - }, - }, - blockedUsers: { - _id: '789', - firstName: 'Steve', - lastName: 'Smith', - email: 'stevesmith@gmail.com', - }, - tags: ['Shelter', 'NGO', 'Open Source'], - spamCount: [ - { - _id: '6954', - user: { - _id: '878', - firstName: 'Joe', - lastName: 'Root', - email: 'joeroot@gmail.com', - }, - isReaded: false, - groupchat: { - _id: '321', - title: 'Dummy', - }, - }, - ], - }, - ], - }, - }, - }, - { - request: { - query: UPDATE_ORGANIZATION_MUTATION, - variables: { - id: '123', - name: 'Updated Organization', - description: 'This is an updated test organization', - location: 'Updated location', - image: new File(['hello'], 'hello.png', { type: 'image/png' }), - isPublic: true, - visibleInSearch: false, - }, - }, - result: { - data: { - updateOrganization: { - _id: '123', - name: 'Updated Organization', - description: 'This is an updated test organization', - location: 'Updated location', - isPublic: true, - visibleInSearch: false, - }, - }, - }, - }, -]; const link = new StaticMockLink(MOCKS, true); + async function wait(ms = 500): Promise { await act(() => { return new Promise((resolve) => { @@ -114,24 +24,22 @@ async function wait(ms = 500): Promise { describe('Testing Organization Update', () => { const props = { - id: '123', - orgid: '123', + orgId: '123', }; const formData = { - name: 'John Doe', - description: 'This is a description', - location: 'Test location', + name: 'Palisadoes Organization', + description: 'This is a updated description', + location: 'This is updated location', displayImage: new File(['hello'], 'hello.png', { type: 'image/png' }), - isPublic: true, + isPublic: false, isVisible: true, }; global.alert = jest.fn(); - test('should render props and text elements test for the page component', async () => { - //window.location.assign('/orgsetting/id=123'); - await act(async () => { + test('should render props and text elements test for the page component along with mock data', async () => { + act(() => { render( @@ -139,50 +47,128 @@ describe('Testing Organization Update', () => { ); - await wait(); - userEvent.type( - screen.getByPlaceholderText(/Enter Organization Name/i), - formData.name - ); - userEvent.type( - screen.getByPlaceholderText(/Description/i), - formData.description - ); - userEvent.type( - screen.getByPlaceholderText(/Location/i), - formData.location - ); - userEvent.upload( - screen.getByLabelText(/Display Image:/i), - formData.displayImage - ); - userEvent.click(screen.getByLabelText(/Is Public:/i)); - userEvent.click(screen.getByLabelText(/Is Registrable:/i)); + }); + await wait(); + // Check labels are present or not + expect(screen.getByText('Name')).toBeInTheDocument(); + expect(screen.getByText('Description')).toBeInTheDocument(); + expect(screen.getByText('Location')).toBeInTheDocument(); + expect(screen.getByText('Display Image:')).toBeInTheDocument(); + expect(screen.getByText('Public:')).toBeInTheDocument(); + expect(screen.getByText('Visible in Search:')).toBeInTheDocument(); - await wait(); + // Get the input fields, and btns + const name = screen.getByPlaceholderText(/Enter Organization Name/i); + const des = screen.getByPlaceholderText(/Description/i); + const location = screen.getByPlaceholderText(/Location/i); + const isPublic = screen.getByPlaceholderText(/Public/i); + const isVisible = screen.getByPlaceholderText(/Visible/i); - userEvent.click(screen.getByText(/Save Changes/i)); + // Checking if form fields got updated according to the mock data + expect(name).toHaveValue('Palisadoes'); + expect(des).toHaveValue('Equitable Access to STEM Education Jobs'); + expect(location).toHaveValue('Jamaica'); + expect(isPublic).toBeChecked(); + expect(isVisible).not.toBeChecked(); + }); - expect(screen.getByPlaceholderText(/Organization Name/i)).toHaveValue( - formData.name + test('Should Update organization properly', async () => { + await act(async () => { + render( + + + + + ); - expect(screen.getByPlaceholderText(/Description/i)).toHaveValue( - formData.description + }); + + await wait(); + + // Get the input fields, and btns + const name = screen.getByPlaceholderText(/Enter Organization Name/i); + const des = screen.getByPlaceholderText(/Description/i); + const location = screen.getByPlaceholderText(/Location/i); + const displayImage = screen.getByPlaceholderText(/Display Image/i); + const isPublic = screen.getByPlaceholderText(/Public/i); + const isVisible = screen.getByPlaceholderText(/Visible/i); + const saveChangesBtn = screen.getByText(/Save Changes/i); + + // Emptying the text fields to add updated data + fireEvent.change(name, { target: { value: '' } }); + fireEvent.change(des, { target: { value: '' } }); + fireEvent.change(location, { target: { value: '' } }); + + // Mocking filling form behaviour + userEvent.type(name, formData.name); + userEvent.type(des, formData.description); + userEvent.type(location, formData.location); + userEvent.upload(displayImage, formData.displayImage); + userEvent.click(isPublic); + userEvent.click(isVisible); + + await wait(); + userEvent.click(saveChangesBtn); + + // Checking if the form got update accordingly + expect(name).toHaveValue(formData.name); + expect(des).toHaveValue(formData.description); + expect(location).toHaveValue(formData.location); + expect(displayImage).toBeTruthy(); + expect(isPublic).not.toBeChecked(); + expect(isVisible).toBeChecked(); + }); + + test('Should render error occured text when Organization Could not be found', async () => { + act(() => { + render( + + + + + ); - expect(screen.getByPlaceholderText(/Location/i)).toHaveValue( - formData.location + }); + await wait(); + expect(screen.getByText(/Mock Graphql Error/i)).toBeInTheDocument(); + }); + + test('Should show error occured toast when Organization could not be updated', async () => { + await act(async () => { + render( + + + + + ); - expect(screen.getByLabelText(/display image:/i)).toBeTruthy(); - expect(screen.getByLabelText(/Is Public:/i)).not.toBeChecked(); - expect(screen.getByLabelText(/Is Registrable:/i)).toBeChecked(); - expect(screen.getByText(/Cancel/i)).toBeTruthy(); - - expect(screen.getByText('Name')).toBeInTheDocument(); - expect(screen.getByText('Description')).toBeInTheDocument(); - expect(screen.getByText('Location')).toBeInTheDocument(); - expect(screen.getByText('Display Image:')).toBeInTheDocument(); - expect(screen.getByText('Is Public:')).toBeInTheDocument(); - expect(screen.getByText('Is Registrable:')).toBeInTheDocument(); }); + + await wait(); + + // Get the input fields, and btns + const name = screen.getByPlaceholderText(/Enter Organization Name/i); + const des = screen.getByPlaceholderText(/Description/i); + const location = screen.getByPlaceholderText(/Location/i); + const displayImage = screen.getByPlaceholderText(/Display Image/i); + const isPublic = screen.getByPlaceholderText(/Public/i); + const isVisible = screen.getByPlaceholderText(/Visible/i); + const saveChangesBtn = screen.getByText(/Save Changes/i); + + // Emptying the text fields to add updated data + fireEvent.change(name, { target: { value: '' } }); + fireEvent.change(des, { target: { value: '' } }); + fireEvent.change(location, { target: { value: '' } }); + + // Mocking filling form behaviour + userEvent.type(name, formData.name); + userEvent.type(des, formData.description); + userEvent.type(location, formData.location); + userEvent.upload(displayImage, formData.displayImage); + userEvent.click(isPublic); + userEvent.click(isVisible); + + await wait(); + userEvent.click(saveChangesBtn); }); }); diff --git a/src/components/OrgUpdate/OrgUpdate.tsx b/src/components/OrgUpdate/OrgUpdate.tsx index b93d1ff218..16222717d6 100644 --- a/src/components/OrgUpdate/OrgUpdate.tsx +++ b/src/components/OrgUpdate/OrgUpdate.tsx @@ -1,26 +1,28 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { useMutation, useQuery } from '@apollo/client'; -import { useTranslation } from 'react-i18next'; import Button from 'react-bootstrap/Button'; +import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; +import type { ApolloError } from '@apollo/client'; +import { WarningAmberRounded } from '@mui/icons-material'; import { UPDATE_ORGANIZATION_MUTATION } from 'GraphQl/Mutations/mutations'; -import styles from './OrgUpdate.module.css'; import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; +import Loader from 'components/Loader/Loader'; +import { Col, Form, Row } from 'react-bootstrap'; import convertToBase64 from 'utils/convertToBase64'; import { errorHandler } from 'utils/errorHandler'; -import { Form } from 'react-bootstrap'; +import type { InterfaceQueryOrganizationsListObject } from 'utils/interfaces'; +import styles from './OrgUpdate.module.css'; interface InterfaceOrgUpdateProps { - id: string; - orgid: string; + orgId: string; } -// eslint-disable-next-line @typescript-eslint/no-unused-vars function orgUpdate(props: InterfaceOrgUpdateProps): JSX.Element { - const currentUrl = window.location.href.split('=')[1]; + const { orgId } = props; - const [formState, setFormState] = React.useState<{ + const [formState, setFormState] = useState<{ orgName: string; orgDescrip: string; location: string; @@ -32,7 +34,7 @@ function orgUpdate(props: InterfaceOrgUpdateProps): JSX.Element { orgImage: null, }); - const [publicchecked, setPublicChecked] = React.useState(true); + const [publicchecked, setPublicChecked] = React.useState(false); const [visiblechecked, setVisibleChecked] = React.useState(false); const [login] = useMutation(UPDATE_ORGANIZATION_MUTATION); @@ -41,30 +43,45 @@ function orgUpdate(props: InterfaceOrgUpdateProps): JSX.Element { keyPrefix: 'orgUpdate', }); - const { data, loading: loadingdata } = useQuery(ORGANIZATIONS_LIST, { - variables: { id: currentUrl }, + const { + data, + loading, + refetch, + error, + }: { + data?: { + organizations: InterfaceQueryOrganizationsListObject[]; + }; + loading: boolean; + refetch: (variables: { id: string }) => void; + error?: ApolloError; + } = useQuery(ORGANIZATIONS_LIST, { + variables: { id: orgId }, + notifyOnNetworkStatusChange: true, }); - React.useEffect(() => { - if (data) { + useEffect(() => { + let isMounted = true; + if (data && isMounted) { setFormState({ ...formState, orgName: data.organizations[0].name, orgDescrip: data.organizations[0].description, location: data.organizations[0].location, }); + setPublicChecked(data.organizations[0].isPublic); + setVisibleChecked(data.organizations[0].visibleInSearch); } - }, [data]); - - if (loadingdata) { - return
; - } + return () => { + isMounted = false; + }; + }, [data, orgId]); const onSaveChangesClicked = async (): Promise => { try { const { data } = await login({ variables: { - id: currentUrl, + id: orgId, name: formState.orgName, description: formState.orgDescrip, location: formState.location, @@ -73,144 +90,127 @@ function orgUpdate(props: InterfaceOrgUpdateProps): JSX.Element { file: formState.orgImage, }, }); - /* istanbul ignore next */ + // istanbul ignore next if (data) { - window.location.assign(`/orgdash/id=${props.orgid}`); - + refetch({ id: orgId }); toast.success(t('successfulUpdated')); } } catch (error: any) { - /* istanbul ignore next */ errorHandler(t, error); } }; - /* istanbul ignore next */ - const cancelUpdate = (): void => { - window.location.reload(); - }; + if (loading) { + return ; + } + + if (error) { + return ( +
+ +
+ Error occured while loading Organization Data +
+ {`${error.message}`} +
+
+ ); + } return ( <>
- {/*

Update Your Details

*/} -
-
- - { - setFormState({ - ...formState, - orgName: e.target.value, - }); - }} + {t('name')} + { + setFormState({ + ...formState, + orgName: e.target.value, + }); + }} + /> + {t('description')} + { + setFormState({ + ...formState, + orgDescrip: e.target.value, + }); + }} + /> + {t('location')} + { + setFormState({ + ...formState, + location: e.target.value, + }); + }} + /> + + + {t('isPublic')}: + setPublicChecked(!publicchecked)} /> -
-
- - { - setFormState({ - ...formState, - orgDescrip: e.target.value, - }); - }} + + + + {t('isVisibleInSearch')}: + + setVisibleChecked(!visiblechecked)} /> -
-
-
-
- - { - setFormState({ - ...formState, - location: e.target.value, - }); - }} - /> -
-
-
-
- -
-
-
- - setPublicChecked(!publicchecked)} - /> -
-
- - setVisibleChecked(!visiblechecked)} - /> -
-
-
-
+ + + {t('displayImage')}: + => { + const target = e.target as HTMLInputElement; + const file = target.files && target.files[0]; + /* istanbul ignore else */ + if (file) + setFormState({ + ...formState, + orgImage: await convertToBase64(file), + }); + }} + data-testid="organisationImage" + /> +
-
diff --git a/src/components/OrgUpdate/OrgUpdateMocks.ts b/src/components/OrgUpdate/OrgUpdateMocks.ts new file mode 100644 index 0000000000..cd78d37fd0 --- /dev/null +++ b/src/components/OrgUpdate/OrgUpdateMocks.ts @@ -0,0 +1,157 @@ +import { UPDATE_ORGANIZATION_MUTATION } from 'GraphQl/Mutations/mutations'; +import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; + +export const MOCKS = [ + { + request: { + query: ORGANIZATIONS_LIST, + variables: { id: '123' }, + }, + result: { + data: { + organizations: [ + { + _id: '123', + image: null, + name: 'Palisadoes', + description: 'Equitable Access to STEM Education Jobs', + location: 'Jamaica', + isPublic: true, + visibleInSearch: false, + creator: { + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@example.com', + }, + members: { + _id: '123', + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + }, + admins: [ + { + _id: '123', + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + }, + ], + membershipRequests: { + _id: '456', + user: { + firstName: 'Sam', + lastName: 'Smith', + email: 'samsmith@gmail.com', + }, + }, + blockedUsers: [], + }, + ], + }, + }, + }, + { + request: { + query: UPDATE_ORGANIZATION_MUTATION, + variables: { + id: '123', + name: 'Updated Organization', + description: 'This is an updated test organization', + location: 'Updated location', + image: new File(['hello'], 'hello.png', { type: 'image/png' }), + isPublic: true, + visibleInSearch: false, + }, + }, + result: { + data: { + updateOrganization: { + _id: '123', + name: 'Updated Organization', + description: 'This is an updated test organization', + location: 'Updated location', + isPublic: true, + visibleInSearch: false, + }, + }, + }, + }, +]; + +export const MOCKS_ERROR_ORGLIST = [ + { + request: { + query: ORGANIZATIONS_LIST, + variables: { id: '123' }, + }, + error: new Error('Mock Graphql Error'), + }, +]; + +export const MOCKS_ERROR_UPDATE_ORGLIST = [ + { + request: { + query: ORGANIZATIONS_LIST, + variables: { id: '123' }, + }, + result: { + data: { + organizations: [ + { + _id: '123', + image: null, + name: 'Palisadoes', + description: 'Equitable Access to STEM Education Jobs', + location: 'Jamaica', + isPublic: true, + visibleInSearch: false, + creator: { + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@example.com', + }, + members: { + _id: '123', + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + }, + admins: [ + { + _id: '123', + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + }, + ], + membershipRequests: { + _id: '456', + user: { + firstName: 'Sam', + lastName: 'Smith', + email: 'samsmith@gmail.com', + }, + }, + blockedUsers: [], + }, + ], + }, + }, + }, + { + request: { + query: UPDATE_ORGANIZATION_MUTATION, + variables: { + id: '123', + name: 'Updated Organization', + description: 'This is an updated test organization', + location: 'Updated location', + image: new File(['hello'], 'hello.png', { type: 'image/png' }), + isPublic: true, + visibleInSearch: false, + }, + }, + erorr: new Error('Mock Graphql Updating Organization Error'), + }, +]; diff --git a/src/components/OrganizationDashCards/CardItem.module.css b/src/components/OrganizationDashCards/CardItem.module.css new file mode 100644 index 0000000000..e90a6d3655 --- /dev/null +++ b/src/components/OrganizationDashCards/CardItem.module.css @@ -0,0 +1,47 @@ +.cardItem { + position: relative; + display: flex; + align-items: center; + padding: 0.75rem 0; +} + +.cardItem .iconWrapper { + position: relative; + height: 40px; + width: 40px; + display: flex; + justify-content: center; + align-items: center; +} + +.cardItem .iconWrapper .themeOverlay { + background: var(--bs-primary); + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + opacity: 0.12; + border-radius: 50%; +} + +.cardItem .iconWrapper .dangerOverlay { + background: var(--bs-danger); + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + opacity: 0.12; + border-radius: 50%; +} + +.cardItem .title { + font-size: 1rem; + flex: 1; +} + +.cardItem .time { + font-size: 0.9rem; + color: var(--bs-secondary); +} diff --git a/src/components/OrganizationDashCards/CardItem.test.tsx b/src/components/OrganizationDashCards/CardItem.test.tsx new file mode 100644 index 0000000000..6841fe9659 --- /dev/null +++ b/src/components/OrganizationDashCards/CardItem.test.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import CardItem from './CardItem'; +import type { InterfaceCardItem } from './CardItem'; + +describe('Testing the Organization Card', () => { + test('should render props and text elements For event card', () => { + const props: InterfaceCardItem = { + type: 'Event', + title: 'Event Title', + time: '2023-09-03', + }; + + render(); + + expect(screen.getByText(/Event Title/i)).toBeInTheDocument(); + expect(screen.getByText(/03-09-2023/i)).toBeInTheDocument(); + }); + + test('Should render props and text elements for Post card', () => { + const props: InterfaceCardItem = { + type: 'Post', + title: 'Post Title', + time: '2023-09-03', + }; + + render(); + + expect(screen.getByText(/Post Title/i)).toBeInTheDocument(); + expect(screen.getByText(/03-09-2023/i)).toBeInTheDocument(); + }); + + test('Should render props and text elements for Membership Request card', () => { + const props: InterfaceCardItem = { + type: 'MembershipRequest', + title: 'Membership Request Title', + }; + + render(); + expect(screen.getByText(/Membership Request Title/i)).toBeInTheDocument(); + }); +}); diff --git a/src/components/OrganizationDashCards/CardItem.tsx b/src/components/OrganizationDashCards/CardItem.tsx new file mode 100644 index 0000000000..4843d421c5 --- /dev/null +++ b/src/components/OrganizationDashCards/CardItem.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { ReactComponent as EventsIcon } from 'assets/svgs/events.svg'; +import { ReactComponent as PostsIcon } from 'assets/svgs/post.svg'; +import dayjs from 'dayjs'; +import styles from './CardItem.module.css'; +import { PersonAddAlt1Rounded } from '@mui/icons-material'; + +export interface InterfaceCardItem { + type: 'Event' | 'Post' | 'MembershipRequest'; + title: string; + time?: string; +} + +const cardItem = (props: InterfaceCardItem): JSX.Element => { + const { type, title, time } = props; + return ( + <> +
+
+
+ {type == 'Event' ? ( + + ) : type == 'Post' ? ( + + ) : ( + type == 'MembershipRequest' && ( + + ) + )} +
+ {`${title}`} + {time ? ( + + {dayjs(time).format('DD-MM-YYYY')} + + ) : ( + '' + )} +
+ + ); +}; + +export default cardItem; diff --git a/src/components/OrganizationDashCards/CardItemLoading.tsx b/src/components/OrganizationDashCards/CardItemLoading.tsx new file mode 100644 index 0000000000..923128c2f2 --- /dev/null +++ b/src/components/OrganizationDashCards/CardItemLoading.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import styles from './CardItem.module.css'; + +const cardItemLoading = (): JSX.Element => { + return ( + <> +
+
+
+
+ +   + +
+ + ); +}; + +export default cardItemLoading; diff --git a/src/components/OrganizationDashCards/DashboardCard.test.tsx b/src/components/OrganizationDashCards/DashboardCard.test.tsx new file mode 100644 index 0000000000..71e5e1fed0 --- /dev/null +++ b/src/components/OrganizationDashCards/DashboardCard.test.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import DashboardCard from './DashboardCard'; + +describe('Testing the Dashboard Card', () => { + test('should render props and text elements For event card', () => { + const props = { + icon: , + title: 'Example Title', + count: 100, + }; + + render(); + + expect(screen.getByText(/Example Title/i)).toBeInTheDocument(); + expect(screen.getByText(/100/i)).toBeInTheDocument(); + }); +}); diff --git a/src/components/OrganizationDashCards/DashboardCard.tsx b/src/components/OrganizationDashCards/DashboardCard.tsx new file mode 100644 index 0000000000..4ad8fe8849 --- /dev/null +++ b/src/components/OrganizationDashCards/DashboardCard.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Card, Row } from 'react-bootstrap'; +import Col from 'react-bootstrap/Col'; +import styles from './Dashboardcard.module.css'; + +const dashBoardCard = (props: { + icon: React.ReactNode; + title: string; + count?: number; +}): JSX.Element => { + const { icon, count, title } = props; + return ( + + + + +
+
+ {icon} +
+ + + {count ?? 0} + {title} + + + + + ); +}; + +export default dashBoardCard; diff --git a/src/components/OrganizationDashCards/DashboardCardLoading.tsx b/src/components/OrganizationDashCards/DashboardCardLoading.tsx new file mode 100644 index 0000000000..5b596f32b2 --- /dev/null +++ b/src/components/OrganizationDashCards/DashboardCardLoading.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Card, Row } from 'react-bootstrap'; +import Col from 'react-bootstrap/Col'; +import styles from './Dashboardcard.module.css'; + +const dashBoardCardLoading = (): JSX.Element => { + return ( + + + + +
+
+
+ + + + + + + + + ); +}; + +export default dashBoardCardLoading; diff --git a/src/components/OrganizationDashCards/Dashboardcard.module.css b/src/components/OrganizationDashCards/Dashboardcard.module.css new file mode 100644 index 0000000000..365657fb4f --- /dev/null +++ b/src/components/OrganizationDashCards/Dashboardcard.module.css @@ -0,0 +1,60 @@ +.cardBody { + padding: 1.25rem 1.5rem; +} + +.cardBody .iconWrapper { + position: relative; + height: 48px; + width: 48px; + display: flex; + justify-content: center; + align-items: center; +} + +.cardBody .iconWrapper .themeOverlay { + background: var(--bs-primary); + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + opacity: 0.12; + border-radius: 50%; +} + +.cardBody .textWrapper .primaryText { + font-size: 24px; + font-weight: bold; + display: block; +} + +.cardBody .textWrapper .secondaryText { + font-size: 14px; + display: block; + color: var(--bs-secondary); +} + +@media (max-width: 600px) { + .cardBody { + min-height: 120px; + } + + .cardBody .iconWrapper { + position: absolute; + top: 1rem; + left: 1rem; + } + + .cardBody .textWrapper { + margin-top: calc(0.5rem + 36px); + text-align: right; + } + + .cardBody .textWrapper .primaryText { + font-size: 1.5rem; + } + + .cardBody .textWrapper .secondaryText { + font-size: 1rem; + } +} diff --git a/src/screens/BlockUser/BlockUser.module.css b/src/screens/BlockUser/BlockUser.module.css index 365774b438..ed93446206 100644 --- a/src/screens/BlockUser/BlockUser.module.css +++ b/src/screens/BlockUser/BlockUser.module.css @@ -1,184 +1,102 @@ -.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%; +.btnsContainer .btnsBlock { + display: flex; } -.sidebar:after { - content: ''; - background-color: #f7f7f7; - position: absolute; - width: 2px; - height: 600px; - top: 10px; - left: 94%; - display: block; +.btnsContainer .btnsBlock button { + margin-left: 1rem; + display: flex; + justify-content: center; + align-items: center; } -.sidebarsticky { - padding-left: 45px; +.btnsContainer .inputContainer { + flex: 1; + position: relative; } -.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 .input { + width: 70%; + 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-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 60%; +.btnsContainer .inputContainer button { + width: 52px; } -.logintitle { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 30px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 30%; +.largeBtnsWrapper { + display: flex; } -.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; -} - -.radio_buttons { - color: #707070; - font-weight: 600; - font-size: 14px; -} -.radio_buttons > input { - transform: scale(1.2); -} -.radio_buttons > label { - margin-top: -4px; - margin-left: 5px; - margin-right: 15px; + justify-content: center; + align-items: center; + flex-direction: column; } -.loader { - text-align: center; -} - -@media screen and (max-width: 575.5px) { - .justifysp { - padding-left: 55px; - display: flex; - justify-content: space-between; - width: 100%; +@media (max-width: 1020px) { + .btnsContainer { + flex-direction: column; + margin: 1.5rem 0; } - .mainpageright { + .btnsContainer .input { width: 100%; } -} - -.list_box { - height: 70vh; - overflow-y: auto; - width: auto; - padding-right: 50px; -} -@media only screen and (max-width: 600px) { - .sidebar { - position: relative; - bottom: 18px; + .btnsContainer .btnsBlock { + margin: 1.5rem 0 0 0; + justify-content: space-between; } - .invitebtn { - width: 135px; - position: relative; - right: 10px; + .btnsContainer .btnsBlock button { + margin: 0; } - .userListTable { - margin-left: 40px; + .btnsContainer .btnsBlock div button { + margin-right: 1.5rem; } } -/* Loader CSS */ - -.loader, -.loader:after { - border-radius: 50%; - width: 10em; - height: 10em; -} +/* For mobile devices */ -.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; -} +@media (max-width: 520px) { + .btnsContainer { + margin-bottom: 0; + } -@-webkit-keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); + .btnsContainer .btnsBlock { + display: block; + margin-top: 1rem; + margin-right: 0; } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); + .largeBtnsWrapper { + flex-direction: column; } -} -@keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); + .btnsContainer .btnsBlock div { + flex: 1; } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); + .btnsContainer .btnsBlock button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; } } diff --git a/src/screens/BlockUser/BlockUser.test.tsx b/src/screens/BlockUser/BlockUser.test.tsx index 7d5b4fbd41..1c8be48624 100644 --- a/src/screens/BlockUser/BlockUser.test.tsx +++ b/src/screens/BlockUser/BlockUser.test.tsx @@ -1,24 +1,24 @@ import React from 'react'; -import { act, render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; -import { BrowserRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; -import { I18nextProvider } from 'react-i18next'; -import BlockUser from './BlockUser'; -import { - BLOCK_PAGE_MEMBER_LIST, - ORGANIZATIONS_LIST, -} from 'GraphQl/Queries/Queries'; +import { act, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { BLOCK_USER_MUTATION, UNBLOCK_USER_MUTATION, } from 'GraphQl/Mutations/mutations'; -import { store } from 'state/store'; -import userEvent from '@testing-library/user-event'; -import i18nForTest from 'utils/i18nForTest'; -import { StaticMockLink } from 'utils/StaticMockLink'; +import { + BLOCK_PAGE_MEMBER_LIST, + ORGANIZATIONS_LIST, +} from 'GraphQl/Queries/Queries'; 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 { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import i18nForTest from 'utils/i18nForTest'; +import BlockUser from './BlockUser'; let userQueryCalled = false; @@ -226,15 +226,87 @@ const MOCKS = [ request: { query: BLOCK_PAGE_MEMBER_LIST, variables: { - firstName_contains: 'sam', - lastName_contains: 'smith', + firstName_contains: 'Peter', + lastName_contains: '', + orgId: 'orgid', + }, + }, + result: { + data: { + organizationsMemberConnection: { + edges: [], + }, + }, + }, + }, +]; +const MOCKS_EMPTY = [ + { + request: { + query: ORGANIZATIONS_LIST, + variables: { + id: 'orgid', + }, + }, + result: { + data: { + organizations: [ + { + _id: 'orgid', + image: '', + creator: { + firstName: 'firstName', + lastName: 'lastName', + email: 'email', + }, + name: 'name', + description: 'description', + location: 'location', + members: { + _id: 'id', + firstName: 'firstName', + lastName: 'lastName', + email: 'email', + }, + admins: { + _id: 'id', + firstName: 'firstName', + lastName: 'lastName', + email: 'email', + }, + membershipRequests: { + _id: 'id', + user: { + firstName: 'firstName', + lastName: 'lastName', + email: 'email', + }, + }, + blockedUsers: { + _id: 'id', + firstName: 'firstName', + lastName: 'lastName', + email: 'email', + }, + }, + ], + }, + }, + }, + + { + request: { + query: BLOCK_PAGE_MEMBER_LIST, + variables: { + firstName_contains: 'Peter', + lastName_contains: '', orgId: 'orgid', }, }, result: { data: { organizationsMemberConnection: { - edges: [USER_UNBLOCKED], + edges: [], }, }, }, @@ -242,6 +314,7 @@ const MOCKS = [ ]; const link = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink(MOCKS_EMPTY, true); async function wait(ms = 500): Promise { await act(() => { @@ -273,7 +346,7 @@ describe('Testing Block/Unblock user screen', () => { await wait(); - expect(screen.getByText('Search By Name')).toBeInTheDocument(); + expect(screen.getByText('Search By First Name')).toBeInTheDocument(); expect(screen.getByText('List of Users who spammed')).toBeInTheDocument(); expect(window.location).toBeAt('/blockuser/id=orgid'); @@ -357,14 +430,15 @@ describe('Testing Block/Unblock user screen', () => { expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.getByText('Sam Smith')).toBeInTheDocument(); - const firstNameInput = screen.getByPlaceholderText(/Enter First Name/i); - const lastNameInput = screen.getByPlaceholderText(/Enter Last Name/i); - + const firstNameInput = screen.getByPlaceholderText(/Search by First Name/i); + // Open Dropdown + await act(async () => { + userEvent.click(screen.getByTestId('nameFilter')); + }); + // Select option and enter first name + userEvent.click(screen.getByTestId('searchByFirstName')); userEvent.type(firstNameInput, 'john'); - expect(firstNameInput).toHaveValue('john'); - expect(lastNameInput).toHaveValue(''); - await wait(700); expect(screen.getByText('John Doe')).toBeInTheDocument(); @@ -393,32 +467,31 @@ describe('Testing Block/Unblock user screen', () => { expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.getByText('Sam Smith')).toBeInTheDocument(); - const firstNameInput = screen.getByPlaceholderText(/Enter First Name/i); - const lastNameInput = screen.getByPlaceholderText(/Enter Last Name/i); - + // Open Dropdown + await act(async () => { + userEvent.click(screen.getByTestId('nameFilter')); + }); + // Select option and enter last name + userEvent.click(screen.getByTestId('searchByLastName')); + const lastNameInput = screen.getByPlaceholderText(/Search by Last Name/i); userEvent.type(lastNameInput, 'doe'); await wait(700); - expect(firstNameInput).toHaveValue(''); expect(lastNameInput).toHaveValue('doe'); - expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.queryByText('Sam Smith')).not.toBeInTheDocument(); - expect(window.location).toBeAt('/blockuser/id=orgid'); }); - test('Testing Full Name Filter', async () => { + test('Testing No Spammers Present', async () => { window.location.assign('/blockuser/id=orgid'); - render( - + - @@ -426,24 +499,7 @@ describe('Testing Block/Unblock user screen', () => { ); await wait(); - - expect(screen.getByText('John Doe')).toBeInTheDocument(); - expect(screen.getByText('Sam Smith')).toBeInTheDocument(); - - const firstNameInput = screen.getByPlaceholderText(/Enter First Name/i); - const lastNameInput = screen.getByPlaceholderText(/Enter Last Name/i); - - userEvent.type(firstNameInput, 'sam'); - userEvent.type(lastNameInput, 'smith'); - - expect(firstNameInput).toHaveValue('sam'); - expect(lastNameInput).toHaveValue('smith'); - - await wait(700); - - expect(screen.getByText('Sam Smith')).toBeInTheDocument(); - expect(screen.queryByText('John Doe')).not.toBeInTheDocument(); - + expect(screen.getByText(/No spammer found/i)).toBeInTheDocument(); expect(window.location).toBeAt('/blockuser/id=orgid'); }); @@ -462,15 +518,15 @@ describe('Testing Block/Unblock user screen', () => { ); - - await wait(); - - userEvent.click(screen.getByLabelText(/All Members/i)); await wait(); + await act(async () => { + userEvent.click(screen.getByTestId('userFilter')); + }); + userEvent.click(screen.getByTestId('showMembers')); - expect(screen.getByLabelText(/All Members/i)).toBeChecked(); - await wait(); + await wait(700); + expect(screen.getByTestId(/userFilter/i)).toHaveTextContent('All Members'); expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.getByText('Sam Smith')).toBeInTheDocument(); @@ -493,12 +549,11 @@ describe('Testing Block/Unblock user screen', () => { ); - await wait(); - - userEvent.click(screen.getByLabelText(/Blocked Users/i)); - await wait(); + await act(async () => { + userEvent.click(screen.getByTestId('userFilter')); + }); - expect(screen.getByLabelText(/Blocked Users/i)).toBeChecked(); + userEvent.click(screen.getByTestId('showBlockedMembers')); await wait(); expect(screen.getByText('John Doe')).toBeInTheDocument(); @@ -524,9 +579,34 @@ describe('Testing Block/Unblock user screen', () => { await wait(); - expect(screen.getByTestId(/blockedusers/)).toBeInTheDocument(); - expect(screen.getByTestId(/allusers/)).toBeInTheDocument(); + expect(screen.getByTestId(/userList/)).toBeInTheDocument(); + expect(screen.getAllByText('Block/Unblock')).toHaveLength(2); expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.getByText('Sam Smith')).toBeInTheDocument(); }); + + test('Testing No Results Found', async () => { + window.location.assign('/blockuser/id=orgid'); + render( + + + + + + + + + + ); + + const input = screen.getByPlaceholderText('Search By First Name'); + await act(async () => { + userEvent.type(input, 'Peter'); + }); + await wait(700); + expect( + screen.getByText(`No results found for "Peter"`) + ).toBeInTheDocument(); + expect(window.location).toBeAt('/blockuser/id=orgid'); + }); }); diff --git a/src/screens/BlockUser/BlockUser.tsx b/src/screens/BlockUser/BlockUser.tsx index 1995c2d634..2b39a00650 100644 --- a/src/screens/BlockUser/BlockUser.tsx +++ b/src/screens/BlockUser/BlockUser.tsx @@ -1,17 +1,18 @@ import { useMutation, useQuery } from '@apollo/client'; -import React, { useEffect, useRef, useState } from 'react'; -import { Col, Form, Row } from 'react-bootstrap'; +import React, { useEffect, useState } from 'react'; +import { Dropdown, Form, Table } from 'react-bootstrap'; import Button from 'react-bootstrap/Button'; import { toast } from 'react-toastify'; -import { CircularProgress } from '@mui/material'; +import { Search } from '@mui/icons-material'; +import SortIcon from '@mui/icons-material/Sort'; import { BLOCK_USER_MUTATION, UNBLOCK_USER_MUTATION, } from 'GraphQl/Mutations/mutations'; import { BLOCK_PAGE_MEMBER_LIST } from 'GraphQl/Queries/Queries'; import OrganizationScreen from 'components/OrganizationScreen/OrganizationScreen'; -import PaginationList from 'components/PaginationList/PaginationList'; +import TableLoader from 'components/TableLoader/TableLoader'; import { useTranslation } from 'react-i18next'; import debounce from 'utils/debounce'; import { errorHandler } from 'utils/errorHandler'; @@ -35,21 +36,15 @@ const Requests = (): JSX.Element => { }); document.title = t('title'); - - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = React.useState(10); - const currentUrl = window.location.href.split('=')[1]; - const [membersData, setMembersData] = useState([]); - const [state, setState] = useState(0); - - const firstNameRef = useRef(null); - const lastNameRef = useRef(null); + const [searchByFirstName, setSearchByFirstName] = useState(true); + const [searchByName, setSearchByName] = useState(''); + const [showBlockedMembers, setShowBlockedMembers] = useState(false); const { data: memberData, - loading: memberLoading, + loading: loadingMembers, error: memberError, refetch: memberRefetch, } = useQuery(BLOCK_PAGE_MEMBER_LIST, { @@ -69,33 +64,16 @@ const Requests = (): JSX.Element => { return; } - if (state === 0) { + if (showBlockedMembers == false) { setMembersData(memberData?.organizationsMemberConnection.edges); } else { const blockUsers = memberData?.organizationsMemberConnection.edges.filter( (user: InterfaceMember) => user.organizationsBlockedBy.some((org) => org._id === currentUrl) ); - setMembersData(blockUsers); } - }, [state, memberData]); - - /* 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); - }; + }, [memberData, showBlockedMembers]); const handleBlockUser = async (userId: string): Promise => { try { @@ -140,181 +118,175 @@ const Requests = (): JSX.Element => { toast.error(memberError.message); } - const handleSearch = (): void => { - const filterData = { + const handleSearch = (e: any): void => { + const { value } = e.target; + setSearchByName(value); + memberRefetch({ orgId: currentUrl, - firstName_contains: firstNameRef.current?.value ?? '', - lastName_contains: lastNameRef.current?.value ?? '', - }; - - memberRefetch(filterData); + firstName_contains: searchByFirstName ? value : '', + lastName_contains: searchByFirstName ? '' : value, + }); }; const handleSearchDebounced = debounce(handleSearch); + const headerTitles: string[] = [ + '#', + t('name'), + t('email'), + t('block_unblock'), + ]; return ( <> - - - -
-
-
{t('searchByName')}
- - - - -
- { - setState(0); - }} - /> - - - { - setState(1); - }} - /> - -
-
+ + {/* Buttons Container */} +
+
+
+ +
- - - -
- -

{t('listOfUsers')}

-
- {memberLoading ? ( -
- -
- ) : ( -
-
- - - - - - - - - - - - { - /* istanbul ignore next */ - (rowsPerPage > 0 - ? membersData.slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ) - : membersData - ).map((user, index: number) => { - return ( - - - - - - - ); - }) - } - -
#{t('name')}{t('email')} - {t('block_unblock')} -
{page * 10 + (index + 1)}{`${user.firstName} ${user.lastName}`}{user.email} - {user.organizationsBlockedBy.some( - (spam: any) => spam._id === currentUrl - ) ? ( - - ) : ( - - )} -
-
-
- )} -
- - - - - - -
-
+
+
+
+ +
- - +
+
+ {/* Table */} + {loadingMembers == false && + membersData.length === 0 && + searchByName.length > 0 ? ( +
+

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

+
+ ) : loadingMembers == false && membersData.length === 0 ? ( +
+

{t('noSpammerFound')}

+
+ ) : ( +
+ {loadingMembers ? ( + + ) : ( + + + + {headerTitles.map((title: string, index: number) => { + return ( + + ); + })} + + + + {membersData.map((user, index: number) => { + return ( + + + + + + + ); + })} + +
+ {title} +
{index + 1}{`${user.firstName} ${user.lastName}`}{user.email} + {user.organizationsBlockedBy.some( + (spam: any) => spam._id === currentUrl + ) ? ( + + ) : ( + + )} +
+ )} +
+ )} ); diff --git a/src/screens/ForgotPassword/ForgotPassword.module.css b/src/screens/ForgotPassword/ForgotPassword.module.css index bd59589a61..e69de29bb2 100644 --- a/src/screens/ForgotPassword/ForgotPassword.module.css +++ b/src/screens/ForgotPassword/ForgotPassword.module.css @@ -1,17 +0,0 @@ -.forgotPassword .border { - border-color: #31bb6b !important; -} -.forgotPassword .heading h1 { - color: #31bb6b; -} - -.talawaBackgroundColor { - background-color: #31bb6b; -} - -@media only screen and (max-width: 600px) { - .forgotPassword .border { - position: relative; - bottom: 50px; - } -} diff --git a/src/screens/ForgotPassword/ForgotPassword.tsx b/src/screens/ForgotPassword/ForgotPassword.tsx index eb75422385..eb96f70f26 100644 --- a/src/screens/ForgotPassword/ForgotPassword.tsx +++ b/src/screens/ForgotPassword/ForgotPassword.tsx @@ -14,6 +14,7 @@ import { useTranslation } from 'react-i18next'; import { errorHandler } from 'utils/errorHandler'; import Button from 'react-bootstrap/Button'; import { Form } from 'react-bootstrap'; +import Loader from 'components/Loader/Loader'; const ForgotPassword = (): JSX.Element => { const { t } = useTranslation('translation', { @@ -113,7 +114,7 @@ const ForgotPassword = (): JSX.Element => { }; if (componentLoader || otpLoading || forgotPasswordLoading) { - return
; + return ; } return ( diff --git a/src/screens/OrgSettings/OrgSettings.module.css b/src/screens/OrgSettings/OrgSettings.module.css index 88e7ab719c..2b15a2ac0c 100644 --- a/src/screens/OrgSettings/OrgSettings.module.css +++ b/src/screens/OrgSettings/OrgSettings.module.css @@ -1,184 +1,25 @@ -.navbarbg { - height: 60px; - background-color: white; - display: flex; - margin-bottom: 30px; - z-index: 1; - position: relative; - flex-direction: row; - justify-content: space-between; - box-shadow: 0px 0px 8px 2px #c8c8c8; -} -.titlemodal .logo { - color: #707070; - margin-left: 0; - display: flex; - align-items: center; - text-decoration: none; -} - -.logo img { - margin-top: 0px; - margin-left: 10px; - height: 64px; - width: 70px; -} - -.logo > strong { - line-height: 1.5rem; - margin-left: -5px; - font-family: sans-serif; - font-size: 19px; - color: #707070; -} - -.mainpage { - display: flex; - flex-direction: row; -} -.sidebar { - z-index: 0; - padding-top: 5px; - margin: 0; - height: 100%; -} -.sidebar:after { - content: ''; - background-color: #f7f7f7; - position: absolute; - width: 2px; - height: 100%; - top: 10px; - left: 94%; - display: block; -} -.sidebarsticky { - padding-left: 45px; - margin-top: 7px; -} -.sidebarsticky > p { - margin-top: -10px; +.settingsBody { + margin: 2.5rem 0; } -.navitem { - padding-left: 27%; - padding-top: 12px; - padding-bottom: 12px; - cursor: pointer; -} - -.headerDiv { +.cardHeader { + padding: 1.25rem 1rem 1rem 1rem; + border-bottom: 1px solid var(--bs-gray-200); display: flex; - flex-direction: column; + justify-content: space-between; + align-items: center; } -.logintitle { - color: #707070; - font-weight: 600; - font-size: 20px; - /* margin-bottom: 30px; */ - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - /* width: 15%; */ - margin-left: 20px; -} -.loginSubtitle { - color: #707070; - font-weight: 600; - font-size: 19px; - /* margin-bottom: 30px; */ - padding-bottom: 5px; - /* border-bottom: 3px solid #31bb6b; */ - /* width: 15%; */ - margin-left: 20px; -} -.searchtitle { - color: #707070; +.cardHeader .cardTitle { + font-size: 1.2rem; font-weight: 600; - font-size: 18px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 60%; -} -.logintitleadmin { - color: #707070; - font-weight: 600; - font-size: 18px; - margin-top: 50px; - margin-bottom: 40px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 30%; -} -.greenregbtn { - margin: 1rem 0 0; - margin-top: 15px; - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - padding: 6px 8px; - border-radius: 5px; - background-color: #31bb6b; - width: 70%; - font-size: 14px; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; -} -.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; } -.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; +.cardBody { + min-height: 180px; } -@-webkit-keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} -@keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } + +.cardBody .textBox { + margin: 0 0 3rem 0; + color: var(--bs-secondary); } diff --git a/src/screens/OrgSettings/OrgSettings.test.tsx b/src/screens/OrgSettings/OrgSettings.test.tsx index 2fe1b816ef..e722dc4a8d 100644 --- a/src/screens/OrgSettings/OrgSettings.test.tsx +++ b/src/screens/OrgSettings/OrgSettings.test.tsx @@ -1,37 +1,76 @@ import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen } from '@testing-library/react'; -import { MEMBERSHIP_REQUEST } from 'GraphQl/Queries/Queries'; +import { render, screen } from '@testing-library/react'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; -import userEvent from '@testing-library/user-event'; -import { I18nextProvider } from 'react-i18next'; -import 'jest-location-mock'; +import { DELETE_ORGANIZATION_MUTATION } from 'GraphQl/Mutations/mutations'; import { store } from 'state/store'; -import OrgSettings from './OrgSettings'; -import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; +import i18nForTest from 'utils/i18nForTest'; +import OrgSettings from './OrgSettings'; +import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; const MOCKS = [ { request: { - query: MEMBERSHIP_REQUEST, + query: ORGANIZATIONS_LIST, }, result: { data: { organizations: [ { - _id: 1, - membershipRequests: { - _id: 1, - user: { - _id: 1, + _id: '123', + image: null, + name: 'Palisadoes', + description: 'Equitable Access to STEM Education Jobs', + location: 'Jamaica', + isPublic: true, + visibleInSearch: false, + creator: { + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@example.com', + }, + members: { + _id: '123', + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + }, + admins: [ + { + _id: '123', firstName: 'John', lastName: 'Doe', email: 'johndoe@gmail.com', }, + ], + membershipRequests: { + _id: '456', + user: { + firstName: 'Sam', + lastName: 'Smith', + email: 'samsmith@gmail.com', + }, }, + blockedUsers: [], + }, + ], + }, + }, + }, + { + request: { + query: DELETE_ORGANIZATION_MUTATION, + }, + result: { + data: { + removeOrganization: [ + { + _id: 123, }, ], }, @@ -41,36 +80,24 @@ const MOCKS = [ const link = new StaticMockLink(MOCKS, true); -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} +afterEach(() => { + localStorage.clear(); +}); describe('Organisation Settings Page', () => { test('correct mock data should be queried', async () => { - const dataQuery1 = MOCKS[0]?.result?.data?.organizations[0]; - - expect(dataQuery1).toEqual({ - _id: 1, - membershipRequests: { - _id: 1, - user: { - _id: 1, - email: 'johndoe@gmail.com', - firstName: 'John', - lastName: 'Doe', - }, + const dataQuery1 = MOCKS[1]?.result?.data?.removeOrganization; + expect(dataQuery1).toEqual([ + { + _id: 123, }, - }); + ]); }); test('should render props and text elements test for the screen', async () => { - window.location.assign('/orglist'); - - const { container } = render( + window.location.assign('/orgsetting/id=123'); + localStorage.setItem('UserType', 'SUPERADMIN'); + render( @@ -81,131 +108,14 @@ describe('Organisation Settings Page', () => { ); - - await wait(); - expect(container.textContent).not.toBe('Loading data...'); - - expect(container.textContent).toMatch('Settings'); - expect(container.textContent).toMatch('Update Your Details'); - expect(container.textContent).toMatch('Update Organization'); - expect(container.textContent).toMatch('Delete Organization'); - expect(container.textContent).toMatch('See Request'); - - expect(window.location).toBeAt('/orglist'); - }); - - test('should render User update form in clicking user update button', async () => { - window.location.assign('/orglist'); - - const { container } = render( - - - - - - - - - - ); - - await wait(); - expect(container.textContent).not.toBe('Loading data...'); - await wait(); - - userEvent.click(screen.getByTestId('userUpdateBtn')); - - await wait(); - const firstNameInput = screen.getByText(/first name/i); - const lastNameInput = screen.getByText(/last name/i); - const emailInput = screen.getByText(/email/i); - const imageInput = screen.getByText(/display image:/i); - const saveBtn = screen.getByRole('button', { name: /save changes/i }); - const cancelBtn = screen.getByRole('button', { name: /cancel/i }); - - await wait(); - - expect(firstNameInput).toBeInTheDocument(); - expect(lastNameInput).toBeInTheDocument(); - expect(emailInput).toBeInTheDocument(); - expect(imageInput).toBeInTheDocument(); - expect(saveBtn).toBeInTheDocument(); - expect(cancelBtn).toBeInTheDocument(); - }); - - test('should render password update form in clicking update your password button', async () => { - window.location.assign('/orglist'); - - const { container } = render( - - - - - - - - - - ); - - expect(container.textContent).not.toBe('Loading data...'); - await wait(); - - userEvent.click(screen.getByTestId('userPasswordUpdateBtn')); - - await wait(); - const previousPasswordInput = screen.getByText(/previous password/i); - const confirmPasswordInput = screen.getByText(/confirm new password/i); - const saveBtn = screen.getByRole('button', { name: /save changes/i }); - const cancelBtn = screen.getByRole('button', { name: /cancel/i }); - - await wait(); - - expect(previousPasswordInput).toBeInTheDocument(); - expect(confirmPasswordInput).toBeInTheDocument(); - expect(saveBtn).toBeInTheDocument(); - expect(cancelBtn).toBeInTheDocument(); - }); - - test('should render update orgnization form in clicking update orgnization button', async () => { - window.location.assign('/orglist'); - - const { container } = render( - - - - - - - - - - ); - - expect(container.textContent).not.toBe('Loading data...'); - await wait(); - - userEvent.click(screen.getByTestId('orgUpdateBtn')); - - await wait(); - const nameInput = screen.getByText(/name/i); - const descriptionInput = screen.getByText(/description/i); - const locationInput = screen.getByText(/location/i); - const displayImageInput = screen.getByText(/display image:/i); - const isPublicInput = screen.getByText(/is public:/i); - const isRegistrableInput = screen.getByText(/is registrable:/i); - const saveBtn = screen.getByRole('button', { name: /save changes/i }); - const cancelBtn = screen.getByRole('button', { name: /cancel/i }); - - await wait(); - - expect(nameInput).toBeInTheDocument(); - expect(descriptionInput).toBeInTheDocument(); - expect(locationInput).toBeInTheDocument(); - expect(displayImageInput).toBeInTheDocument(); - expect(isPublicInput).toBeInTheDocument(); - expect(isRegistrableInput).toBeInTheDocument(); - expect(saveBtn).toBeInTheDocument(); - expect(cancelBtn).toBeInTheDocument(); + expect(screen.getAllByText(/Delete Organization/i)).toHaveLength(3); + expect( + screen.getByText( + /By clicking on Delete organization button you will the organization will be permanently deleted along with its events, tags and all related data/i + ) + ).toBeInTheDocument(); + expect(screen.getByText(/Other Settings/i)).toBeInTheDocument(); + expect(screen.getByText(/Change Language/i)).toBeInTheDocument(); + expect(window.location).toBeAt('/orgsetting/id=123'); }); }); diff --git a/src/screens/OrgSettings/OrgSettings.tsx b/src/screens/OrgSettings/OrgSettings.tsx index ef707bf91f..d0df92b2c6 100644 --- a/src/screens/OrgSettings/OrgSettings.tsx +++ b/src/screens/OrgSettings/OrgSettings.tsx @@ -1,15 +1,9 @@ -import { useQuery } from '@apollo/client'; -import { MEMBERSHIP_REQUEST } from 'GraphQl/Queries/Queries'; -import defaultImg from 'assets/images/blank.png'; -import Loader from 'components/Loader/Loader'; -import MemberRequestCard from 'components/MemberRequestCard/MemberRequestCard'; -import OrgDelete from 'components/OrgDelete/OrgDelete'; +import React from 'react'; +import ChangeLanguageDropDown from 'components/ChangeLanguageDropdown/ChangeLanguageDropDown'; +import DeleteOrg from 'components/DeleteOrg/DeleteOrg'; import OrgUpdate from 'components/OrgUpdate/OrgUpdate'; import OrganizationScreen from 'components/OrganizationScreen/OrganizationScreen'; -import UserPasswordUpdate from 'components/UserPasswordUpdate/UserPasswordUpdate'; -import UserUpdate from 'components/UserUpdate/UserUpdate'; -import React from 'react'; -import Button from 'react-bootstrap/Button'; +import { Card, Form } from 'react-bootstrap'; import Col from 'react-bootstrap/Col'; import Row from 'react-bootstrap/Row'; import { useTranslation } from 'react-i18next'; @@ -21,171 +15,39 @@ function orgSettings(): JSX.Element { }); document.title = t('title'); - const [screenVariable, setScreenVariable] = React.useState(0); - const [screenDisplayVariable, setDisplayScreenVariable] = React.useState(''); - - const handleClick = (number: any): void => { - if (number === 1) { - setDisplayScreenVariable('updateYourDetails'); - setScreenVariable(1); - } else if (number === 2) { - setDisplayScreenVariable('updateOrganization'); - setScreenVariable(2); - } else if (number === 3) { - setDisplayScreenVariable('deleteOrganization'); - setScreenVariable(3); - } else if (number === 4) { - setDisplayScreenVariable('seeRequest'); - setScreenVariable(4); - } else { - setDisplayScreenVariable('updateYourPassword'); - setScreenVariable(5); - } - }; - const currentUrl = window.location.href.split('=')[1]; - const { data, loading, error } = useQuery(MEMBERSHIP_REQUEST, { - variables: { id: currentUrl }, - }); - - if (loading) { - return ; - } - - /* istanbul ignore next */ - if (error) { - window.location.replace('/orglist'); - } - return ( <> - - - -
-
-
- - - - - + + + + +
+
+ {t('updateOrganization')}
-
+ + {currentUrl && } + + - -
- -
-

{t('settings')}

- {screenDisplayVariable != '' && ( -

- {t(screenDisplayVariable)} -

- )} - {/*

{t("abc")}

*/} -
- - {/*

{t('settings')}

*/} -
-
{screenVariable == 1 ? : null}
-
- {screenVariable == 5 ? : null} + + + +
+
{t('otherSettings')}
-
- {screenVariable == 2 ? ( - - ) : null} -
-
{screenVariable == 3 ? : null}
-
- {screenVariable == 4 ? ( - data?.organizations?.membershipRequests ? ( - /* istanbul ignore next */ - data.organizations.map( - /* istanbul ignore next */ - (datas: { - _id: string; - membershipRequests: { - _id: string; - user: { - _id: string; - firstName: string; - lastName: string; - email: string; - }; - }; - }) => { - /* istanbul ignore next */ - return ( - - ); - } - ) - ) : ( -
{t('noData')}
- ) - ) : null} -
-
+ +
+ + {t('changeLanguage')} + + +
+
+ diff --git a/src/screens/OrganizationDashboard/OrganizationDashboard.module.css b/src/screens/OrganizationDashboard/OrganizationDashboard.module.css index af35265ae8..485200b1ae 100644 --- a/src/screens/OrganizationDashboard/OrganizationDashboard.module.css +++ b/src/screens/OrganizationDashboard/OrganizationDashboard.module.css @@ -1,197 +1,24 @@ -.mainpage { +.cardHeader { + padding: 1.25rem 1rem 1rem 1rem; + border-bottom: 1px solid var(--bs-gray-200); display: flex; - flex-direction: row; -} - -.toporgloc { - padding-top: 8px; - font-size: 16px; -} -.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: 30px; -} -.sidebarsticky > p { - margin-top: -10px; - width: 90%; -} - -.description { - word-wrap: break-word; -} - -.titlename { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 30px; - padding-bottom: 5px; - width: 26%; -} -.tagdetailsGreen > button { - background-color: #31bb6b; - color: white; - outline: none; - cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; - border: none; - border-radius: 5px; - margin-top: -12px; - margin-bottom: 10px; - margin-right: 30px; - padding-right: 20px; - padding-left: 20px; - padding-top: 5px; - padding-bottom: 5px; -} -.mainpageright > hr { - margin-top: 20px; - width: 100%; - margin-left: -15px; - margin-right: -15px; - margin-bottom: 20px; -} -.justifysp { - display: flex; - justify-content: space-between; -} -.org_about_img { - margin-top: 0px; - margin-bottom: 30px; - border-radius: 5px; - max-width: 100%; - height: auto; - width: 90%; -} -.invitebtn { - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - border-radius: 5px; - background-color: #31bb6b; - width: 20%; - height: 40px; - font-size: 16px; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; -} -.flexdir { - display: flex; - flex-direction: row; justify-content: space-between; - border: none; + align-items: center; } -.logintitleinvite { - color: #707070; +.cardHeader .cardTitle { + font-size: 1.2rem; font-weight: 600; - font-size: 20px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 40%; -} - -.cancel > i { - margin-top: 5px; - transform: scale(1.2); - cursor: pointer; - color: #707070; -} - -.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%; -} - -.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); - } -} -@keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } } -.cardContainer { - box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); +.cardBody { + min-height: 180px; + padding-top: 0; } -.dashboardIcon { - font-size: 50px; - color: #31bb6b; -} - -.counterNumber { - font-size: 24px; - margin-bottom: 0rem !important; -} - -.counterHead { - color: #99abb4; - margin-bottom: 0rem !important; +.cardBody .emptyContainer { + display: flex; + height: 180px; + justify-content: center; + align-items: center; } diff --git a/src/screens/OrganizationDashboard/OrganizationDashboard.test.tsx b/src/screens/OrganizationDashboard/OrganizationDashboard.test.tsx index 612bbd30de..61d3eebc60 100644 --- a/src/screens/OrganizationDashboard/OrganizationDashboard.test.tsx +++ b/src/screens/OrganizationDashboard/OrganizationDashboard.test.tsx @@ -1,27 +1,19 @@ import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import type { RenderResult } from '@testing-library/react'; -import { - act, - render, - screen, - fireEvent, - waitFor, -} from '@testing-library/react'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; +import { act, fireEvent, render, screen } from '@testing-library/react'; import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; -import OrganizationDashboard from './OrganizationDashboard'; -import { - MOCKS_WITHOUT_IMAGE, - MOCKS_WITH_IMAGE, -} from './OrganizationDashboardMocks'; import { store } from 'state/store'; -import i18nForTest from 'utils/i18nForTest'; -import { USER_ORGANIZATION_LIST } from 'GraphQl/Queries/Queries'; import { StaticMockLink } from 'utils/StaticMockLink'; +import OrganizationDashboard from './OrganizationDashboard'; +import { EMPTY_MOCKS, ERROR_MOCKS, MOCKS } from './OrganizationDashboardMocks'; +import i18nForTest from 'utils/i18nForTest'; +import dayjs from 'dayjs'; +import { toast } from 'react-toastify'; +import userEvent from '@testing-library/user-event'; async function wait(ms = 100): Promise { await act(() => { @@ -30,125 +22,124 @@ async function wait(ms = 100): Promise { }); }); } -const link2 = new StaticMockLink(MOCKS_WITH_IMAGE, true); -const link3 = new StaticMockLink(MOCKS_WITHOUT_IMAGE, true); -const customRender = (userType: any): RenderResult => { - const mockedUser = { - request: { - query: USER_ORGANIZATION_LIST, - variables: { id: localStorage.getItem('id') }, - }, - result: { - data: { - user: { - userType, - firstName: 'John', - lastName: 'Doe', - image: '', - email: 'John_Does_Palasidoes@gmail.com', - adminFor: { - _id: 1, - name: 'Akatsuki', - image: '', - }, - }, - }, - }, - }; - - const mocks = [mockedUser, ...MOCKS_WITHOUT_IMAGE]; - - const link1 = new StaticMockLink(mocks, true); - - return render( - - - - - - - - - +const link1 = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink(EMPTY_MOCKS, true); +const link3 = new StaticMockLink(ERROR_MOCKS, true); + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + +beforeEach(() => { + localStorage.setItem('FirstName', 'John'); + localStorage.setItem('LastName', 'Doe'); + localStorage.setItem('UserType', 'SUPERADMIN'); + localStorage.setItem( + 'UserImage', + 'https://api.dicebear.com/5.x/initials/svg?seed=John%20Doe' ); -}; +}); + +afterEach(() => { + jest.clearAllMocks(); + localStorage.clear(); +}); describe('Organisation Dashboard Page', () => { - test('should render props and text elements test for the screen', async () => { - window.location.replace('/orglist'); - - const { container } = render( - - - - - - - - - - ); - - expect(container.textContent).not.toBe('Loading data...'); + test('Should render props and text elements test for the screen', async () => { + await act(async () => { + render( + + + + + + + + + + ); + }); + await wait(); - expect(container.textContent).toMatch('Location'); - expect(container.textContent).toMatch('About'); - expect(container.textContent).toMatch('Statistics'); - expect(window.location).toBeAt('/orglist'); - }); + expect(screen.getByText('Members')).toBeInTheDocument(); + expect(screen.getByText('Admins')).toBeInTheDocument(); + expect(screen.getAllByText('Posts')).toHaveLength(2); + expect(screen.getAllByText('Events')).toHaveLength(2); + expect(screen.getByText('Blocked Users')).toBeInTheDocument(); + expect(screen.getByText('Requests')).toBeInTheDocument(); + expect(screen.getByText('Upcoming events')).toBeInTheDocument(); + expect(screen.getByText('Latest posts')).toBeInTheDocument(); + expect(screen.getByText('Membership requests')).toBeInTheDocument(); + expect(screen.getAllByText('View all')).toHaveLength(3); - test('should display delete button for SUPERADMIN', async () => { - const { getByTestId, queryByTestId } = customRender('SUPERADMIN'); - await waitFor(() => - expect(queryByTestId('deleteClick')).toBeInTheDocument() - ); + // Checking if events are rendered + expect(screen.getByText('Event 1')).toBeInTheDocument(); + expect( + screen.getByText( + `${dayjs(new Date()).add(1, 'day').format('DD-MM-YYYY')}` + ) + ).toBeInTheDocument(); - fireEvent.click(getByTestId('deleteClick')); - fireEvent.click(getByTestId(/deleteOrganizationBtn/i)); - expect(window.location).not.toBeNull(); - }); + // Checking if posts are rendered + expect(screen.getByText('Post 1')).toBeInTheDocument(); - test('should not display delete button for non-SUPERADMIN', async () => { - const { queryByTestId } = customRender('ADMIN'); - await waitFor(() => - expect(queryByTestId('deleteClick')).not.toBeInTheDocument() - ); + // Checking if membership requests are rendered + expect(screen.getByText('Jane Doe')).toBeInTheDocument(); }); - test('Should check if organisation image is present', async () => { - const { container } = render( - - - - - - - - - - ); - - expect(container.textContent).not.toBe('Loading data...'); + test('Testing buttons and checking empty events, posts and membership requests', async () => { + await act(async () => { + render( + + + + + + + + + + ); + }); + await wait(); - const image = screen.getByTestId(/orgDashImgPresent/i); - expect(image).toBeInTheDocument(); + const viewEventsBtn = screen.getByTestId('viewAllEvents'); + const viewPostsBtn = screen.getByTestId('viewAllPosts'); + const viewMSBtn = screen.getByTestId('viewAllMembershipRequests'); + + userEvent.click(viewEventsBtn); + userEvent.click(viewPostsBtn); + fireEvent.click(viewMSBtn); + expect(toast.success).toBeCalledWith('Coming soon!'); + + expect( + screen.getByText('No membership requests present') + ).toBeInTheDocument(); + expect(screen.getByText('No upcoming events')).toBeInTheDocument(); + expect(screen.getByText('No posts present')).toBeInTheDocument(); }); - test('Should check if organisation image is not present', async () => { - const { container } = render( - - - - - - - - - - ); - - expect(container.textContent).not.toBe('Loading data...'); + + test('Testing error scenario', async () => { + await act(async () => { + render( + + + + + + + + + + ); + }); + await wait(); - const image = screen.getByTestId(/orgDashImgAbsent/i); - expect(image).toBeInTheDocument(); + expect(window.location).toBeAt('/orglist'); }); }); diff --git a/src/screens/OrganizationDashboard/OrganizationDashboard.tsx b/src/screens/OrganizationDashboard/OrganizationDashboard.tsx index 9d31a5c535..87e538a538 100644 --- a/src/screens/OrganizationDashboard/OrganizationDashboard.tsx +++ b/src/screens/OrganizationDashboard/OrganizationDashboard.tsx @@ -1,37 +1,55 @@ -import React, { useState } from 'react'; -import Row from 'react-bootstrap/Row'; +import React, { useEffect, useState } from 'react'; +import { useQuery } from '@apollo/client'; +import { Button, Card } from 'react-bootstrap'; import Col from 'react-bootstrap/Col'; -import { useMutation, useQuery } from '@apollo/client'; -import { useSelector } from 'react-redux'; -import type { RootState } from 'state/reducers'; -import { Container, Modal } from 'react-bootstrap'; +import Row from 'react-bootstrap/Row'; import { useTranslation } from 'react-i18next'; -import Button from 'react-bootstrap/Button'; -import { Link } from 'react-router-dom'; -import styles from './OrganizationDashboard.module.css'; -import AboutImg from 'assets/images/defaultImg.png'; import { ORGANIZATIONS_LIST, ORGANIZATION_EVENT_LIST, ORGANIZATION_POST_LIST, - USER_ORGANIZATION_LIST, } from 'GraphQl/Queries/Queries'; -import { DELETE_ORGANIZATION_MUTATION } from 'GraphQl/Mutations/mutations'; -import { errorHandler } from 'utils/errorHandler'; -import Loader from 'components/Loader/Loader'; +import { ReactComponent as AdminsIcon } from 'assets/svgs/admin.svg'; +import { ReactComponent as BlockedUsersIcon } from 'assets/svgs/blockedUser.svg'; +import { ReactComponent as EventsIcon } from 'assets/svgs/events.svg'; +import { ReactComponent as PostsIcon } from 'assets/svgs/post.svg'; +import { ReactComponent as UsersIcon } from 'assets/svgs/users.svg'; +import DashBoardCard from 'components/OrganizationDashCards/DashboardCard'; import OrganizationScreen from 'components/OrganizationScreen/OrganizationScreen'; +import styles from './OrganizationDashboard.module.css'; +import CardItem from 'components/OrganizationDashCards/CardItem'; +import type { ApolloError } from '@apollo/client'; +import type { + InterfaceQueryOrganizationEventListItem, + InterfaceQueryOrganizationPostListItem, + InterfaceQueryOrganizationsListObject, +} from 'utils/interfaces'; +import { toast } from 'react-toastify'; +import { useHistory } from 'react-router-dom'; +import CardItemLoading from 'components/OrganizationDashCards/CardItemLoading'; +import DashboardCardLoading from 'components/OrganizationDashCards/DashboardCardLoading'; function organizationDashboard(): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'dashboard' }); - const [showDeleteModal, setShowDeleteModal] = useState(false); document.title = t('title'); const currentUrl = window.location.href.split('=')[1]; + const history = useHistory(); + const [upcomingEvents, setUpcomingEvents] = useState< + InterfaceQueryOrganizationEventListItem[] + >([]); - const appRoutes = useSelector((state: RootState) => state.appRoutes); - const { targets } = appRoutes; - - const { data, loading, error } = useQuery(ORGANIZATIONS_LIST, { + const { + data, + loading: loadingOrgData, + error: errorOrg, + }: { + data?: { + organizations: InterfaceQueryOrganizationsListObject[]; + }; + loading: boolean; + error?: ApolloError; + } = useQuery(ORGANIZATIONS_LIST, { variables: { id: currentUrl }, }); @@ -39,6 +57,14 @@ function organizationDashboard(): JSX.Element { data: postData, loading: loadingPost, error: errorPost, + }: { + data: + | { + postsByOrganization: InterfaceQueryOrganizationPostListItem[]; + } + | undefined; + loading: boolean; + error?: ApolloError; } = useQuery(ORGANIZATION_POST_LIST, { variables: { id: currentUrl }, }); @@ -47,274 +73,218 @@ function organizationDashboard(): JSX.Element { data: eventData, loading: loadingEvent, error: errorEvent, + }: { + data: + | { + eventsByOrganization: InterfaceQueryOrganizationEventListItem[]; + } + | undefined; + loading: boolean; + error?: ApolloError; } = useQuery(ORGANIZATION_EVENT_LIST, { variables: { id: currentUrl }, }); - const { data: data2 } = useQuery(USER_ORGANIZATION_LIST, { - variables: { id: localStorage.getItem('id') }, - }); - - const canDelete = data2?.user.userType === 'SUPERADMIN'; - const toggleDeleteModal = (): void => setShowDeleteModal(!showDeleteModal); - const [del] = useMutation(DELETE_ORGANIZATION_MUTATION); - - const deleteOrg = async (): Promise => { - try { - const { data } = await del({ - variables: { - id: currentUrl, - }, + // UseEffect to update upcomingEvents array + useEffect(() => { + if (eventData && eventData?.eventsByOrganization.length > 0) { + const tempUpcomingEvents: InterfaceQueryOrganizationEventListItem[] = []; + eventData?.eventsByOrganization.map((event) => { + const startDate = new Date(event.startDate); + const now = new Date(); + if (startDate > now) { + tempUpcomingEvents.push(event); + } }); - - /* istanbul ignore next */ - if (data) { - window.location.replace('/orglist'); - } - } catch (error: any) { - /* istanbul ignore next */ - errorHandler(t, error); + setUpcomingEvents(tempUpcomingEvents); } - }; + }, [eventData?.eventsByOrganization]); - if (loading || loadingPost || loadingEvent) { - return ; - } - - /* istanbul ignore next */ - if (error || errorPost || errorEvent) { + if (errorOrg || errorPost || errorEvent) { window.location.replace('/orglist'); } - return ( <> - - -
-
-
{t('about')}
-

- {data?.organizations[0].description} -

-

- {t('location')} : {data?.organizations[0].location} -

- -

- {canDelete && ( + + + {loadingOrgData ? ( + + {[...Array(6)].map((_, index) => { + return ( + + + + ); + })} + + ) : ( + + + } + /> + + + } + /> + + + } + /> + + + } + /> + + + } + /> + + + } + /> + + + )} + + + +

+
Upcoming events
- )} -

-
-
- - - -
- -

{t('statistics')}

-
- - - { - const { name } = target; - return name == 'People'; - }) - .map((target: any) => { - return target.url; - })}`} - > -
-
- -
-
-

- {data?.organizations[0].members.length} -

-

{t('members')}

-
-
- - - - { - const { name } = target; - return name == 'People'; - }) - .map((target: any) => { - return target.url; - })}`} - > -
-
-
- -
-
-

- {data?.organizations[0].admins.length} -

-

{t('admins')}

-
-
-
- - - - { - const { name } = target; - return name == 'Posts'; - }) - .map((target: any) => { - return target.url; - })}`} - > -
-
- -
-
-

- {postData?.postsByOrganization.length} -

-

{t('posts')}

-
+
+ + {loadingEvent ? ( + [...Array(4)].map((_, index) => { + return ; + }) + ) : upcomingEvents.length == 0 ? ( +
+
No upcoming events
- - - - { - const { name } = target; - return name == 'Events'; - }) - .map((target: any) => { - return target.url; - })}`} - > -
-
- -
-
-

- {eventData?.eventsByOrganization.length} -

-

{t('events')}

-
-
- - - - { - const { name } = target; - return name == 'Block/Unblock'; - }) - .map((target: any) => { - return target.url; - })}`} + ) : ( + upcomingEvents.slice(0, 5).map((event) => { + return ( + + ); + }) + )} +
+ + + + +
+
Latest posts
+ +
+ + {loadingPost ? ( + [...Array(4)].map((_, index) => { + return ; + }) + ) : postData?.postsByOrganization?.length == 0 ? ( +
+
No posts present
-
- - + ) : ( + postData?.postsByOrganization.slice(0, 5).map((post) => { + return ( + + ); + }) + )} + + + + + + + +
+
Membership requests
+
-
+ + {loadingOrgData ? ( + [...Array(4)].map((_, index) => { + return ; + }) + ) : data?.organizations[0].membershipRequests.length == 0 ? ( +
+
No membership requests present
+
+ ) : ( + data?.organizations[0]?.membershipRequests + .slice(0, 8) + .map((request) => { + return ( + + ); + }) + )} +
+ - - -
{t('deleteOrganization')}
- -
- {t('deleteMsg')} - - - - -
); diff --git a/src/screens/OrganizationDashboard/OrganizationDashboardMocks.ts b/src/screens/OrganizationDashboard/OrganizationDashboardMocks.ts index 8335e55f91..f1672c03ca 100644 --- a/src/screens/OrganizationDashboard/OrganizationDashboardMocks.ts +++ b/src/screens/OrganizationDashboard/OrganizationDashboardMocks.ts @@ -1,11 +1,11 @@ -import { DELETE_ORGANIZATION_MUTATION } from 'GraphQl/Mutations/mutations'; import { ORGANIZATIONS_LIST, ORGANIZATION_EVENT_LIST, ORGANIZATION_POST_LIST, } from 'GraphQl/Queries/Queries'; +import dayjs from 'dayjs'; -export const MOCKS_WITHOUT_IMAGE = [ +export const MOCKS = [ { request: { query: ORGANIZATIONS_LIST, @@ -14,58 +14,53 @@ export const MOCKS_WITHOUT_IMAGE = [ data: { organizations: [ { - _id: 1, + _id: 123, image: '', name: 'Dummy Organization', description: 'This is a Dummy Organization', + location: 'New Delhi', + isPublic: true, + visibleInSearch: false, creator: { firstName: '', lastName: '', email: '', }, - location: 'New Delhi', - members: { - _id: '123', - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - }, - admins: { - _id: '123', - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - }, - membershipRequests: { - _id: '456', - user: { - firstName: 'Sam', - lastName: 'Smith', - email: 'samsmith@gmail.com', + + members: [ + { + _id: '123', + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', }, - }, - blockedUsers: { - _id: '789', - firstName: 'Steve', - lastName: 'Smith', - email: 'stevesmith@gmail.com', - }, - spamCount: [ + ], + admins: [ + { + _id: '123', + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + }, + ], + membershipRequests: [ { - _id: '6954', + _id: '456', user: { - _id: '878', - firstName: 'Joe', - lastName: 'Root', - email: 'joeroot@gmail.com', - }, - isReaded: false, - groupchat: { - _id: '321', - title: 'Dummy', + firstName: 'Jane', + lastName: 'Doe', + email: 'janedoe@gmail.com', }, }, ], + blockedUsers: [ + { + _id: '789', + firstName: 'Steve', + lastName: 'Smith', + email: 'stevesmith@gmail.com', + }, + ], }, ], }, @@ -80,8 +75,8 @@ export const MOCKS_WITHOUT_IMAGE = [ postsByOrganization: [ { _id: 1, - title: 'Akatsuki', - text: 'Capture Jinchuriki', + title: 'Post 1', + text: 'Test Post', imageUrl: '', videoUrl: '', creator: { @@ -95,20 +90,6 @@ export const MOCKS_WITHOUT_IMAGE = [ }, }, }, - { - request: { - query: DELETE_ORGANIZATION_MUTATION, - }, - result: { - data: { - removeOrganization: [ - { - _id: 1, - }, - ], - }, - }, - }, { request: { query: ORGANIZATION_EVENT_LIST, @@ -118,14 +99,28 @@ export const MOCKS_WITHOUT_IMAGE = [ eventsByOrganization: [ { _id: 1, - title: 'Event', + title: 'Event 1', description: 'Event Test', - startDate: '', - endDate: '', + startDate: dayjs(new Date()).add(1, 'day'), + endDate: dayjs(new Date()).add(3, 'day'), location: 'New Delhi', - startTime: '02:00', - endTime: '06:00', - allDay: false, + startTime: '', + endTime: '', + allDay: true, + recurring: false, + isPublic: true, + isRegisterable: true, + }, + { + _id: 2, + title: 'Event 2', + description: 'Event Test', + startDate: dayjs(new Date()), + endDate: dayjs(new Date()).add(1, 'day'), + location: 'Jamaica', + startTime: '', + endTime: '', + allDay: true, recurring: false, isPublic: true, isRegisterable: true, @@ -136,7 +131,7 @@ export const MOCKS_WITHOUT_IMAGE = [ }, ]; -export const MOCKS_NO_TAGS = [ +export const EMPTY_MOCKS = [ { request: { query: ORGANIZATIONS_LIST, @@ -145,56 +140,41 @@ export const MOCKS_NO_TAGS = [ data: { organizations: [ { - _id: 1, + _id: 123, image: '', name: 'Dummy Organization', description: 'This is a Dummy Organization', - creator: { - firstName: '', - lastName: '', - email: '', - }, location: 'New Delhi', - members: { - _id: '123', - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - }, - admins: { - _id: '123', + isPublic: true, + visibleInSearch: false, + creator: { firstName: 'John', lastName: 'Doe', email: 'johndoe@gmail.com', }, - membershipRequests: { - _id: '456', - user: { - firstName: 'Sam', - lastName: 'Smith', - email: 'samsmith@gmail.com', + members: [ + { + _id: '123', + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', }, - }, - blockedUsers: { - _id: '789', - firstName: 'Steve', - lastName: 'Smith', - email: 'stevesmith@gmail.com', - }, - spamCount: [ + ], + admins: [ { - _id: '6954', - user: { - _id: '878', - firstName: 'Joe', - lastName: 'Root', - email: 'joeroot@gmail.com', - }, - isReaded: false, - groupchat: { - _id: '321', - title: 'Dummy', - }, + _id: '123', + firstName: 'John', + lastName: 'Doe', + email: 'johndoe@gmail.com', + }, + ], + membershipRequests: [], + blockedUsers: [ + { + _id: '789', + firstName: 'Steve', + lastName: 'Smith', + email: 'stevesmith@gmail.com', }, ], }, @@ -208,21 +188,7 @@ export const MOCKS_NO_TAGS = [ }, result: { data: { - postsByOrganization: [ - { - _id: 1, - title: 'Akatsuki', - text: 'Capture Jinchuriki', - imageUrl: '', - videoUrl: '', - creator: { - _id: '583', - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - }, - }, - ], + postsByOrganization: [], }, }, }, @@ -232,140 +198,29 @@ export const MOCKS_NO_TAGS = [ }, result: { data: { - eventsByOrganization: [ - { - _id: 1, - title: 'Event', - description: 'Event Test', - startDate: '', - endDate: '', - location: 'New Delhi', - startTime: '02:00', - endTime: '06:00', - allDay: false, - recurring: false, - isPublic: true, - isRegisterable: true, - }, - ], + eventsByOrganization: [], }, }, }, ]; -export const MOCKS_WITH_IMAGE = [ +export const ERROR_MOCKS = [ { request: { query: ORGANIZATIONS_LIST, }, - result: { - data: { - organizations: [ - { - _id: 1, - image: 'https://via.placeholder.com/200x200', - name: 'Dummy Organization', - description: 'This is a Dummy Organization', - creator: { - firstName: '', - lastName: '', - email: '', - }, - location: 'New Delhi', - members: { - _id: '123', - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - }, - admins: { - _id: '123', - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - }, - membershipRequests: { - _id: '456', - user: { - firstName: 'Sam', - lastName: 'Smith', - email: 'samsmith@gmail.com', - }, - }, - blockedUsers: { - _id: '789', - firstName: 'Steve', - lastName: 'Smith', - email: 'stevesmith@gmail.com', - }, - spamCount: [ - { - _id: '6954', - user: { - _id: '878', - firstName: 'Joe', - lastName: 'Root', - email: 'joeroot@gmail.com', - }, - isReaded: false, - groupchat: { - _id: '321', - title: 'Dummy', - }, - }, - ], - }, - ], - }, - }, + error: new Error('Mock Graphql ORGANIZATIONS_LIST Error'), }, { request: { query: ORGANIZATION_POST_LIST, }, - result: { - data: { - postsByOrganization: [ - { - _id: 1, - title: 'Akatsuki', - text: 'Capture Jinchuriki', - imageUrl: '', - videoUrl: '', - creator: { - _id: '583', - firstName: 'John', - lastName: 'Doe', - email: 'johndoe@gmail.com', - }, - }, - ], - }, - }, + error: new Error('Mock Graphql ORGANIZATION_POST_LIST Error'), }, { request: { query: ORGANIZATION_EVENT_LIST, }, - result: { - data: { - eventsByOrganization: [ - { - _id: 1, - title: 'Event', - description: 'Event Test', - startDate: '', - endDate: '', - location: 'New Delhi', - startTime: '02:00', - endTime: '06:00', - allDay: false, - recurring: false, - isPublic: true, - isRegisterable: true, - }, - ], - }, - }, + error: new Error('Mock Graphql ORGANIZATION_EVENT_LIST Error'), }, ]; diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index 44994640f8..304fc36715 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -46,6 +46,8 @@ export interface InterfaceQueryOrganizationsListObject { name: string; description: string; location: string; + isPublic: boolean; + visibleInSearch: boolean; members: { _id: string; firstName: string; @@ -73,3 +75,41 @@ export interface InterfaceQueryOrganizationsListObject { email: string; }[]; } + +export interface InterfaceQueryOrganizationPostListItem { + _id: string; + title: string; + text: string; + imageUrl: null; + videoUrl: null; + creator: { + _id: string; + firstName: string; + lastName: string; + email: string; + }[]; +} +export interface InterfaceQueryOrganizationEventListItem { + _id: string; + title: string; + description: string; + startDate: string; + endDate: string; + location: string; + startTime: string; + endTime: string; + allDay: boolean; + recurring: boolean; + isPublic: boolean; + isRegisterable: boolean; +} + +export interface InterfaceQueryBlockPageMemberListItem { + _id: string; + firstName: string; + lastName: string; + email: string; + organizationsBlockedBy: { + _id: string; + }[]; +}