diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 8a668b03d8..885b170d7b 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -172,6 +172,17 @@ "admins": "admins", "members": "members" }, + "orgPeopleOrganizationsCard": { + "manage": "Manage", + "notMember": "User is not a member", + "unblock": "UnBlock", + "block": "Block", + "blockedSuccessfully": "User blocked successfully", + "unBlockedSuccessfully": "User Un-Blocked successfully", + "blockedUser": "User is blocked", + "roleUpdated": "Role Updated.", + "memberRemoved": "The Member is removed" + }, "paginationList": { "rowsPerPage": "rows per page", "all": "All" @@ -269,7 +280,8 @@ "talawaApiUnavailable": "talawaApiUnavailable" }, "organizationPeople": { - "title": "Talawa Members", + "title": "Members", + "title_superadmin": "Users", "filterByName": "Filter by Name", "filterByLocation": "Filter by Location", "filterByEvent": "Filter by Event", @@ -863,6 +875,14 @@ "installMsg": "This feature is now enabled in your organization" }, "memberDetail": { + "overview": "Overview", + "organizations": "Organizations", + "events": "Events", + "tags": "Tags", + "title_superadmin": "User Details", + "title": "Member Details" + }, + "orgMemberDetail": { "title": "User Details", "addAdmin": "Add Admin", "alreadyIsAdmin": "Member is already an Admin", @@ -904,9 +924,11 @@ "joined": "joined", "talawaApiUnavailable": "talawaApiUnavailable" }, - "people": { - "title": "People", - "searchUsers": "Search users" + "memberOrganization": { + "sort": "Sort", + "noOrgError": "Organizations not found, please create an organization through dashboard", + "noOrgErrorTitle": "Organizations Not Found", + "noOrgErrorDescription": "Please create an organization through dashboard" }, "userLogin": { "login": "Login", @@ -922,6 +944,10 @@ "register": "register", "talawaApiUnavailable": "talawaApiUnavailable" }, + "people": { + "title": "People", + "searchUsers": "Search People" + }, "userRegister": { "enterFirstName": "Enter your first name", "enterLastName": "Enter your last name", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 6f7332057b..82ada2a3ba 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -172,6 +172,17 @@ "admins": "Administrateurs", "members": "Membres" }, + "orgPeopleOrganizationsCard": { + "manage": "Gérer", + "notMember": "L'utilisateur n'est pas un membre", + "unblock": "Débloquer", + "block": "Bloc", + "blockedSuccessfully": "Utilisateur bloqué avec succès", + "unBlockedSuccessfully": "Utilisateur débloqué avec succès", + "blockedUser": "Utilisateur bloqué", + "roleUpdated": "Rôle mis à jour.", + "memberRemoved": "Le membre est supprimé" + }, "paginationList": { "rowsPerPage": "lignes par page", "all": "Tous" @@ -269,7 +280,8 @@ "talawaApiUnavailable": "API Talawa indisponible" }, "organizationPeople": { - "title": "Membres Talawa", + "title": "Membres", + "title_superadmin": "Utilisateurs", "filterByName": "Filtrer par nom", "filterByLocation": "Filtrer par emplacement", "filterByEvent": "Filtrer par événement", @@ -863,6 +875,14 @@ "installMsg": "Cette fonctionnalité est désormais activée dans votre organisation" }, "memberDetail": { + "overview": "Aperçu", + "organizations": "Organisations", + "events": "Événements", + "tags": "Balises", + "title_superadmin": "Détails de l'utilisateur", + "title": "Détails du membre" + }, + "orgMemberDetail": { "title": "Détails de l'utilisateur", "addAdmin": "Ajouter un administrateur", "alreadyIsAdmin": "Le membre est déjà un administrateur", @@ -904,9 +924,11 @@ "joined": "Rejoint", "talawaApiUnavailable": "API Talawa non disponible" }, - "people": { - "title": "Personnes", - "searchUsers": "Rechercher des utilisateurs" + "memberOrganization": { + "sort": "Trier", + "noOrgError": "Organisations non trouvées, veuillez créer une organisation via le tableau de bord", + "noOrgErrorTitle": "Organisations non trouvées", + "noOrgErrorDescription": "Veuillez créer une organisation via le tableau de bord" }, "userLogin": { "login": "Se connecter", @@ -922,6 +944,10 @@ "register": "Inscription", "talawaApiUnavailable": "API Talawa non disponible" }, + "people": { + "title": "Personnes", + "searchUsers": "Rechercher des utilisateurs" + }, "userRegister": { "enterFirstName": "Entrez votre prénom", "enterLastName": "Entrez votre nom de famille", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 95d704daca..b8b694ae1b 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -172,6 +172,17 @@ "admins": "प्रशासक", "members": "सदस्य" }, + "orgPeopleOrganizationsCard": { + "manage": "प्रबंधित करें", + "notMember": "उपयोगकर्ता सदस्य नहीं है", + "unblock": "अनब्लॉक करें", + "block": "ब्लॉक करें", + "blockedSuccessfully": "उपयोगकर्ता को सफलतापूर्वक ब्लॉक किया गया", + "unBlockedSuccessfully": "उपयोगकर्ता को सफलतापूर्वक अनब्लॉक किया गया", + "blockedUser": "उपयोगकर्ता ब्लॉक है", + "roleUpdated": "भूमिका अद्यतन की गई।", + "memberRemoved": "सदस्य को हटा दिया गया" + }, "paginationList": { "rowsPerPage": "प्रति पृष्ठ पंक्तियाँ", "all": "सभी" @@ -269,7 +280,8 @@ "talawaApiUnavailable": "तालावा एपीआई अनुपलब्ध" }, "organizationPeople": { - "title": "तालावा सदस्य", + "title": "सदस्य", + "title_superadmin": "उपयोगकर्ता", "filterByName": "नाम से फ़िल्टर करें", "filterByLocation": "स्थान से फ़िल्टर करें", "filterByEvent": "घटना से फ़िल्टर करें", @@ -863,6 +875,14 @@ "installMsg": "यह सुविधा अब आपके संगठन में सक्षम है" }, "memberDetail": { + "overview": "अवलोकन", + "organizations": "संगठन", + "events": "घटनाएँ", + "tags": "टैग", + "title_superadmin": "उपयोगकर्ता विवरण", + "title": "सदस्य विवरण" + }, + "orgMemberDetail": { "title": "उपयोगकर्ता विवरण", "addAdmin": "व्यवस्थापक जोड़ें", "alreadyIsAdmin": "सदस्य पहले से ही एक व्यवस्थापक है", @@ -904,9 +924,11 @@ "joined": "शामिल हुए", "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, - "people": { - "title": "लोग", - "searchUsers": "उपयोगकर्ताओं को खोजें" + "memberOrganization": { + "sort": "क्रम से लगाना", + "noOrgError": "संगठन नहीं मिला, कृपया डैशबोर्ड के माध्यम से एक संगठन बनाएं", + "noOrgErrorTitle": "संगठन नहीं मिले", + "noOrgErrorDescription": "कृपया डैशबोर्ड के माध्यम से एक संगठन बनाएं" }, "userLogin": { "login": "लॉग इन करें", @@ -922,6 +944,10 @@ "register": "पंजीकरण करें", "talawaApiUnavailable": "Talawa API अनुपलब्ध" }, + "people": { + "title": "लोग", + "searchUsers": "उपयोगकर्ता खोजें" + }, "userRegister": { "enterFirstName": "अपना पहला नाम दर्ज करें", "enterLastName": "अपना अंतिम नाम दर्ज करें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index bab46846a7..655e0e430f 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -172,6 +172,17 @@ "manage": "Administrar", "sampleOrganization": "Organización de muestra" }, + "orgPeopleOrganizationsCard": { + "manage": "Gestionar", + "notMember": "El usuario no es miembro", + "unblock": "Desbloquear", + "block": "Bloquear", + "blockedSuccessfully": "Usuario bloqueado con éxito", + "unBlockedSuccessfully": "Usuario desbloqueado exitosamente", + "blockedUser": "Usuario bloqueado", + "roleUpdated": "Rol actualizado.", + "memberRemoved": "El miembro ha sido eliminado" + }, "paginationList": { "rowsPerPage": "filas por página", "all": "Todos" @@ -269,7 +280,8 @@ "talawaApiUnavailable": "El servicio Talawa-API no está disponible. ¿Está funcionando? Compruebe también la conectividad de su red." }, "organizationPeople": { - "title": "Miembros Talawa", + "title": "Miembros", + "title_superadmin": "Usuarios", "filterByName": "Filtrar por nombre", "filterByLocation": "Filtrar por Ubicación", "filterByEvent": "Filtrar por Evento", @@ -611,7 +623,6 @@ "raisedAmount": "Monto recaudado", "pledgedAmount": "Monto comprometido" }, - "orgPost": { "title": "Publicaciones de Talawa", "searchPost": "Buscar Publicación", @@ -864,6 +875,20 @@ "installMsg": "Mensaje de instalación" }, "memberDetail": { + "overview": "Resumen", + "organizations": "Organizaciones", + "events": "Eventos", + "tags": "Etiquetas", + "title_superadmin": "Detalles del usuario", + "title": "Detalles del miembro" + }, + "memberOrganization": { + "sort": "Ordenar", + "noOrgError": "Organizaciones no encontradas, por favor crea una organización a través del panel de control", + "noOrgErrorTitle": "Organizaciones no encontradas", + "noOrgErrorDescription": "Por favor, crea una organización a través del panel de control" + }, + "orgMemberDetail": { "title": "Detalles del usuario", "addAdmin": "Agregar administrador", "alreadyIsAdmin": "El Miembro ya es Administrador", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 7f41c8baaf..40a5abbfb4 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -172,6 +172,17 @@ "admins": "管理员", "members": "成员" }, + "orgPeopleOrganizationsCard": { + "manage": "管理", + "notMember": "不是会员", + "unblock": "解锁", + "block": "堵塞", + "blockedSuccessfully": "用户被成功屏蔽", + "unBlockedSuccessfully": "用户解封成功", + "blockedUser": "被阻止的用户", + "roleUpdated": "角色已更新。", + "memberRemoved": "该会员已被删除" + }, "paginationList": { "rowsPerPage": "每页行数", "all": "全部" @@ -270,6 +281,7 @@ }, "organizationPeople": { "title": "塔拉瓦会员", + "title_superadmin": "塔拉瓦用户", "filterByName": "按名称过滤", "filterByLocation": "按地点过滤", "filterByEvent": "按事件过滤", @@ -863,7 +875,15 @@ "installMsg": "您的组织现已启用此功能" }, "memberDetail": { - "title": "用户详细信息", + "overview": "概览", + "organizations": "组织", + "events": "活动", + "tags": "标签", + "title_superadmin": "用户详情", + "title": "成员详情" + }, + "orgMemberDetail": { + "title": "用户详情", "addAdmin": "添加管理员", "alreadyIsAdmin": "会员已经是管理员", "organizations": "组织机构", @@ -904,6 +924,12 @@ "joined": "已加入", "talawaApiUnavailable": "塔拉瓦 API 不可用" }, + "memberOrganization": { + "sort": "按角色搜索", + "noOrgError": "未找到组织,请通过仪表板创建组织", + "noOrgErrorTitle": "未找到组织", + "noOrgErrorDescription": "请通过仪表板创建组织" + }, "userLogin": { "login": "登录", "loginIntoYourAccount": "登录您的帐户", diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index fb7f74bb47..260de2de3f 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -98,6 +98,13 @@ export const ORGANIZATION_CONNECTION_LIST = gql` sortingCode state } + blockedUsers { + _id + firstName + lastName + email + } + description } } `; diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx b/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx index 2a0ef3815d..e01465b339 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx @@ -215,9 +215,20 @@ const MOCKS_EMPTY = [ }, ]; +const defaultScreensForSuperadmin = [ + 'Dashboard', + 'Users', + 'Events', + 'Posts', + 'Block/Unblock', + 'Plugins', + 'Settings', + 'All Organizations', +]; + const defaultScreens = [ 'Dashboard', - 'People', + 'Members', 'Events', 'Posts', 'Block/Unblock', @@ -268,7 +279,7 @@ const linkEmpty = new StaticMockLink(MOCKS_EMPTY, true); describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { test('Component should be rendered properly', async () => { setItem('UserImage', ''); - setItem('SuperAdmin', true); + setItem('SuperAdmin', false); setItem('FirstName', 'John'); setItem('LastName', 'Doe'); render( @@ -288,6 +299,28 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { }); }); + test('Component should be rendered properly for superadmin', async () => { + setItem('UserImage', ''); + setItem('SuperAdmin', true); + setItem('FirstName', 'John'); + setItem('LastName', 'Doe'); + render( + + + + + + + + + , + ); + await wait(); + defaultScreensForSuperadmin.map((screenName) => { + expect(screen.getByText(screenName)).toBeInTheDocument(); + }); + }); + test('Testing Profile Page & Organization Detail Modal', async () => { setItem('UserImage', ''); setItem('SuperAdmin', true); @@ -329,7 +362,7 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { expect(global.window.location.pathname).toContain('/orgdash/123'); }); - test('Testing when screen size is less than 820px', async () => { + test('Testing when screen size is less than 820px for superadmins', async () => { setItem('SuperAdmin', true); render( @@ -343,9 +376,10 @@ describe('Testing LeftDrawerOrg component for SUPERADMIN', () => { , ); await wait(); - resizeWindow(800); + resizeWindow(820); + expect(screen.getAllByText(/Dashboard/i)[0]).toBeInTheDocument(); - expect(screen.getAllByText(/People/i)[0]).toBeInTheDocument(); + expect(screen.getAllByText(/Users/i)[0]).toBeInTheDocument(); const peopelBtn = screen.getByTestId(/People/i); userEvent.click(peopelBtn); diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx b/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx index 3a3ba378cf..3fe6393626 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.tsx @@ -13,6 +13,7 @@ import AngleRightIcon from 'assets/svgs/angleRight.svg?react'; import TalawaLogo from 'assets/svgs/talawa.svg?react'; import styles from './LeftDrawerOrg.module.css'; import Avatar from 'components/Avatar/Avatar'; +import useLocalStorage from 'utils/useLocalstorage'; export interface InterfaceLeftDrawerProps { orgId: string; @@ -37,6 +38,9 @@ const leftDrawerOrg = ({ setHideDrawer, }: InterfaceLeftDrawerProps): JSX.Element => { const { t: tCommon } = useTranslation('common'); + const { getItem } = useLocalStorage(); + const isSuperAdmin = getItem('SuperAdmin'); + const { t: tErrors } = useTranslation('errors'); const [showDropdown, setShowDropdown] = useState(false); @@ -54,7 +58,6 @@ const leftDrawerOrg = ({ variables: { id: orgId }, }); - // Set organization data when query data is available useEffect(() => { let isMounted = true; if (data && isMounted) { @@ -165,7 +168,12 @@ const leftDrawerOrg = ({ } /> - {tCommon(name)} + + {name == 'People' + ? isSuperAdmin + ? 'Users' + : 'Members' + : tCommon(name)} )} diff --git a/src/components/MemberOrganization/MemberOrganization.module.css b/src/components/MemberOrganization/MemberOrganization.module.css new file mode 100644 index 0000000000..70acd025fe --- /dev/null +++ b/src/components/MemberOrganization/MemberOrganization.module.css @@ -0,0 +1,334 @@ +.btnsContainer { + display: flex; + margin: 2.5rem 0 2.5rem 0; +} + +.btnsContainer .btnsBlock { + display: flex; +} + +.orgCreationBtn { + width: 100%; + border: none; +} + +.enableEverythingBtn { + width: 100%; + border: none; +} + +.pluginStoreBtn { + width: 100%; + background-color: white; + color: #31bb6b; + border: 0.5px solid #31bb6b; +} + +.pluginStoreBtn:hover, +.pluginStoreBtn:focus { + background-color: #dfe1e2 !important; + color: #31bb6b !important; + border-color: #31bb6b !important; +} + +.line::before { + content: ''; + display: inline-block; + width: 100px; + border-top: 1px solid #000; + margin: 0 10px; +} + +.line::before { + left: 0; +} + +.line::after { + right: 0; +} + +.flexContainer { + display: flex; + justify-content: center; + align-items: center; + width: 100%; +} + +.orText { + display: block; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + /* top: calc(-0.7rem + 0.5rem); */ + /* left: calc(50% - 2.6rem); */ + margin: 0 auto; + padding: 0.5rem 2rem; + z-index: 100; + background: var(--bs-white); + color: var(--bs-secondary); +} +.sampleOrgSection { + display: grid; + grid-template-columns: repeat(1, 1fr); + row-gap: 1em; +} + +.sampleOrgCreationBtn { + width: 100%; + background-color: transparent; + color: #707070; + border-color: #707070; + display: flex; + justify-content: center; + align-items: center; +} + +.sampleHover:hover { + border-color: grey; + color: grey; +} + +.sampleOrgSection { + font-family: Arial, Helvetica, sans-serif; + width: 100%; + display: grid; + grid-auto-columns: repeat(1, 1fr); + justify-content: center; + flex-direction: column; + align-items: center; +} + +.sampleModalTitle { + background-color: green; +} + +.btnsContainer .btnsBlock button { + margin-left: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.btnsContainer .input { + flex: 1; + position: relative; +} + +.btnsContainer input { + outline: 1px solid var(--bs-gray-400); +} + +.btnsContainer .input button { + width: 52px; +} + +.listBox { + display: flex; + flex-wrap: wrap; + overflow: unset !important; + justify-content: space-between; +} + +.listBox .itemCard { + width: 48.77777%; +} + +.notFound { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +@media (max-width: 1440px) { + .contract { + padding-left: calc(250px + 2rem + 1.5rem); + } + + .listBox .itemCard { + width: 100%; + } +} + +@media (max-width: 1020px) { + .btnsContainer { + flex-direction: column; + margin: 1.5rem 0; + } + + .btnsContainer .btnsBlock { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } + + .btnsContainer .btnsBlock button { + margin: 0; + } + + .btnsContainer .btnsBlock div button { + margin-right: 1.5rem; + } +} + +/* For mobile devices */ + +@media (max-width: 520px) { + .btnsContainer { + margin-bottom: 0; + } + + .btnsContainer .btnsBlock { + display: block; + margin-top: 1rem; + margin-right: 0; + } + + .btnsContainer .btnsBlock div { + flex: 1; + } + + .btnsContainer .btnsBlock div[title='Sort organizations'] { + margin-right: 0.5rem; + } + + .btnsContainer .btnsBlock button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; + } +} + +/* Loading OrgList CSS */ +.itemCard .loadingWrapper { + background-color: var(--bs-white); + margin: 0.5rem; + height: calc(120px + 2rem); + padding: 1rem; + border-radius: 8px; + outline: 1px solid var(--bs-gray-200); + position: relative; +} + +.itemCard .loadingWrapper .innerContainer { + display: flex; +} + +.itemCard .loadingWrapper .innerContainer .orgImgContainer { + width: 120px; + height: 120px; + border-radius: 4px; +} + +.itemCard .loadingWrapper .innerContainer .content { + flex: 1; + display: flex; + flex-direction: column; + margin-left: 1rem; +} + +.titlemodaldialog { + color: #707070; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; +} + +form label { + font-weight: bold; + padding-bottom: 1px; + font-size: 14px; + color: #707070; +} + +form > input { + display: block; + margin-bottom: 20px; + border: 1px solid #e8e5e5; + box-shadow: 2px 1px #e8e5e5; + padding: 10px 20px; + border-radius: 5px; + background: none; + width: 100%; + transition: all 0.3s ease-in-out; + -webkit-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; +} + +.itemCard .loadingWrapper .innerContainer .content h5 { + height: 24px; + width: 60%; + margin-bottom: 0.8rem; +} + +.modalbody { + width: 50px; +} + +.pluginStoreBtnContainer { + display: flex; + gap: 1rem; +} + +.itemCard .loadingWrapper .innerContainer .content h6[title='Location'] { + display: block; + width: 45%; + height: 18px; +} + +.itemCard .loadingWrapper .innerContainer .content h6 { + display: block; + width: 30%; + height: 16px; + margin-bottom: 0.8rem; +} + +.itemCard .loadingWrapper .button { + position: absolute; + height: 48px; + width: 92px; + bottom: 1rem; + right: 1rem; + z-index: 1; +} + +@media (max-width: 450px) { + .itemCard .loadingWrapper { + height: unset; + margin: 0.5rem 0; + padding: 1.25rem 1.5rem; + } + + .itemCard .loadingWrapper .innerContainer { + flex-direction: column; + } + + .itemCard .loadingWrapper .innerContainer .orgImgContainer { + height: 200px; + width: 100%; + margin-bottom: 0.8rem; + } + + .itemCard .loadingWrapper .innerContainer .content { + margin-left: 0; + } + + .itemCard .loadingWrapper .button { + bottom: 0; + right: 0; + border-radius: 0.5rem; + position: relative; + margin-left: auto; + display: block; + } +} + +.container { + width: 100%; + padding-inline: 1rem; + justify-content: space-between; + gap: 1rem; +} diff --git a/src/components/MemberOrganization/MemberOrganization.test.tsx b/src/components/MemberOrganization/MemberOrganization.test.tsx new file mode 100644 index 0000000000..12c1d596a7 --- /dev/null +++ b/src/components/MemberOrganization/MemberOrganization.test.tsx @@ -0,0 +1,232 @@ +import React from 'react'; +import { cleanup, render, screen, waitFor } from '@testing-library/react'; +import { MockedProvider } from '@apollo/client/testing'; +import MemberOrganization from './MemberOrganization'; +import { + USER_ORGANIZATION_LIST, + ORGANIZATION_CONNECTION_LIST, +} from 'GraphQl/Queries/Queries'; +import { I18nextProvider } from 'react-i18next'; +import i18nForTest from 'utils/i18nForTest'; +import useLocalStorage from 'utils/useLocalstorage'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; + +const { getItem, setItem } = useLocalStorage(); + +afterEach(() => { + localStorage.clear(); + cleanup(); +}); + +const mocks = [ + { + request: { + query: USER_ORGANIZATION_LIST, + variables: { userId: 'testUserId' }, + }, + result: { + data: { + user: { + user: { + firstName: 'John', + lastName: 'Doe', + email: 'john@example.com', + image: '', + }, + }, + }, + }, + }, + { + request: { + query: ORGANIZATION_CONNECTION_LIST, + variables: { filter: '', first: 8, skip: 0, orderBy: 'createdAt_ASC' }, + notifyOnNetworkStatusChange: true, + }, + result: { + data: { + organizationsConnection: [ + { + _id: 'org1', + name: 'Sample Organization', + image: '', + creator: { _id: 'testUserId', firstName: 'John', lastName: 'Doe' }, + members: [{ _id: 'testUserId' }, { _id: 'testUserId1' }], + admins: [{ _id: 'testUserId' }], + createdAt: '2023-01-01T00:00:00Z', + address: { + city: 'Sample City', + countryCode: 'US', + dependentLocality: '', + line1: '123 Main St', + line2: '', + postalCode: '12345', + sortingCode: '', + state: 'Sample State', + }, + blockedUsers: [], + description: 'Sample description', + }, + { + _id: 'org2', + name: 'Sample Organization 2', + image: '', + creator: { _id: 'testUserId', firstName: 'John', lastName: 'Doe' }, + members: [{ _id: 'testUserId' }, { _id: 'testUserId1' }], + admins: [{ _id: 'testUserId1' }], + createdAt: '2024-01-01T00:00:00Z', + address: { + city: 'Sample City', + countryCode: 'US', + dependentLocality: '', + line1: '123 Main St', + line2: '', + postalCode: '12345', + sortingCode: '', + state: 'Sample State', + }, + blockedUsers: [], + description: 'Sample description', + }, + ], + }, + }, + }, +]; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ + orgId: 'org1', + }), + useRouteMatch: () => ({ url: '/members/org1' }), +})); + +describe('MemberOrganization', () => { + const link = new StaticMockLink(mocks, true); + + test('renders the member organization component for super admin', async () => { + const beforeUserId = getItem('userId'); + setItem('id', 'testUserId'); + + setItem('userId', 'testUserId'); + setItem('AdminFor', [{ _id: 'org1' }]); + setItem('SuperAdmin', true); + + render( + + + + + + + + + , + ); + + await waitFor(() => { + expect(screen.getByText('Sample Organization')).toBeInTheDocument(); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('emptyContainerForImage')).toHaveLength(2); + }); + + if (beforeUserId) { + setItem('userId', beforeUserId); + } + }); + + test('renders the member organization component for admin', async () => { + const beforeUserId = getItem('userId'); + setItem('id', 'testUserId'); + + setItem('userId', 'testUserId'); + setItem('AdminFor', [{ _id: 'org1' }]); + setItem('SuperAdmin', false); + + render( + + + + + + + + + , + ); + + await waitFor(() => { + expect(screen.getByText('Sample Organization')).toBeInTheDocument(); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('emptyContainerForImage')).toHaveLength(1); + }); + + if (beforeUserId) { + setItem('userId', beforeUserId); + } + }); + + // test('renders MemberOrganization component for Superadmin', async () => { + // const beforeUserId = getItem('userId'); + // setItem('userId', 'testUserId'); + // setItem('AdminFor', [{ _id: 'org1' }]); + // setItem('SuperAdmin', true); + + // render( + // + // + // + // + // , + // ); + + // await waitFor(() => { + // expect(screen.getByText('Sample Organization')).toBeInTheDocument(); + // }); + + // await waitFor(() => { + // expect(screen.getAllByTestId('emptyContainerForImage')).toHaveLength(2); + // }); + + // if (beforeUserId) { + // setItem('userId', beforeUserId); + // } + // }); + + // test('renders MemberOrganization component for Admin', async () => { + // const beforeUserId = getItem('userId'); + + // setItem('userId', 'testUserId'); + // setItem('AdminFor', [{ _id: 'org1' }]); + // setItem('SuperAdmin', false); + // setItem('orgId', 'org1'); + + // render( + // + // + // + // + // , + // ); + + // await waitFor(() => { + // expect(screen.getByText('Sample Organization')).toBeInTheDocument(); + // }); + + // await waitFor(() => { + // expect(screen.getAllByTestId('emptyContainerForImage')).toHaveLength(1); + // }); + + // if (beforeUserId) { + // setItem('userId', beforeUserId); + // } + // }); +}); diff --git a/src/components/MemberOrganization/MemberOrganization.tsx b/src/components/MemberOrganization/MemberOrganization.tsx new file mode 100644 index 0000000000..3f62349786 --- /dev/null +++ b/src/components/MemberOrganization/MemberOrganization.tsx @@ -0,0 +1,265 @@ +import React, { useEffect, useState } from 'react'; +import OrgPeopleOrganizationsCard from 'components/OrgPeopleOrganizationsCard/OrgPeopleOrganizationsCard'; +import type { + InterfaceMemberOrganization, + InterfaceOrgConnectionInfoType, + InterfaceOrgConnectionType, + InterfaceUserType, +} from 'utils/interfaces'; +import { useQuery } from '@apollo/client'; +import { useParams } from 'react-router-dom'; +import { + ORGANIZATION_CONNECTION_LIST, + USER_ORGANIZATION_LIST, +} from 'GraphQl/Queries/Queries'; +import useLocalStorage from 'utils/useLocalstorage'; +import styles from './MemberOrganization.module.css'; +import InfiniteScroll from 'react-infinite-scroll-component'; +import { useTranslation } from 'react-i18next'; + +/** + * MemberOrganization component displays the details of a member organization. + * + * This component: + * - Uses the `useTranslation` hook for translations. + * - Accepts props of type `InterfaceMemberOrganization`. + * - Manages state for loading, search, and pagination. + * - Retrieves `superAdmin` and `adminFor` from local storage. + * - Extracts `orgId` from URL parameters. + * - Fetches the user organization list and organization connection list using GraphQL queries. + * - Implements infinite scroll to load more data. + * + * @param props - The properties passed to the component, including organization details. + * @returns A JSX element representing the member organization details. + */ + +const MemberOrganization: React.FC = (props) => { + const { t } = useTranslation('translation', { + keyPrefix: 'memberOrganization', + }); + + const { t: tCommon } = useTranslation('common'); + + const { userId } = props; + + const perPageResult = 8; + const [isLoading, setIsLoading] = useState(true); + + const [hasMore, sethasMore] = useState(true); + const [isLoadingMore, setIsLoadingMore] = useState(false); + const [searchByName, setSearchByName] = useState(''); + + const { getItem } = useLocalStorage(); + const superAdmin = getItem('SuperAdmin'); + const adminFor = getItem('AdminFor'); + + const { orgId: currentUrl } = useParams(); + + const { + data: userData, + error: errorUser, + }: { + data: InterfaceUserType | undefined; + loading: boolean; + error?: Error | undefined; + } = useQuery(USER_ORGANIZATION_LIST, { + variables: { userId }, + context: { + headers: { authorization: `Bearer ${getItem('token')}` }, + }, + }); + + const { + data: orgsData, + loading, + error: errorList, + refetch: refetchOrgs, + fetchMore, + }: { + data: InterfaceOrgConnectionType | undefined; + loading: boolean; + error?: Error | undefined; + refetch: () => void; + fetchMore: any; + } = useQuery(ORGANIZATION_CONNECTION_LIST, { + variables: { + first: perPageResult, + skip: 0, + filter: searchByName, + orderBy: 'createdAt_ASC', + }, + notifyOnNetworkStatusChange: true, + }); + useEffect(() => { + setIsLoading(loading && isLoadingMore); + }, [loading]); + + /* istanbul ignore next */ + const isAdminForCurrentOrg = ( + currentOrg: InterfaceOrgConnectionInfoType, + ): boolean => { + if (adminFor.length === 1) { + return adminFor[0]._id === currentOrg._id; + } else { + return ( + adminFor.some( + (org: { _id: string; name: string; image: string | null }) => + org._id === currentOrg._id, + ) ?? false + ); + } + }; + + /* istanbul ignore next */ + if (errorList) { + window.location.assign('/'); + } + + /* istanbul ignore next */ + const loadMoreOrganizations = (): void => { + setIsLoadingMore(true); + fetchMore({ + variables: { + skip: orgsData?.organizationsConnection.length || 0, + }, + updateQuery: ( + prev: + | { organizationsConnection: InterfaceOrgConnectionType[] } + | undefined, + { + fetchMoreResult, + }: { + fetchMoreResult: + | { organizationsConnection: InterfaceOrgConnectionType[] } + | undefined; + }, + ): + | { organizationsConnection: InterfaceOrgConnectionType[] } + | undefined => { + setIsLoadingMore(false); + if (!fetchMoreResult) return prev; + if (fetchMoreResult.organizationsConnection.length < perPageResult) { + sethasMore(false); + } + return { + organizationsConnection: [ + ...(prev?.organizationsConnection || []), + ...(fetchMoreResult.organizationsConnection || []), + ], + }; + }, + }); + }; + + return ( +
+ {!isLoading && + (!orgsData?.organizationsConnection || + orgsData.organizationsConnection.length === 0) && + searchByName.length === 0 && + (!userData || adminFor.length === 0 || superAdmin) ? ( +
+

{t('noOrgErrorTitle')}

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

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

+
+ ) : ( +
+ + {[...Array(perPageResult)].map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} + + } + hasMore={hasMore} + className={styles.listBox} + data-testid="organizations-list" + endMessage={ +
+
{tCommon('endOfResults')}
+
+ } + > + {userData && superAdmin + ? orgsData?.organizationsConnection.map((item) => { + return ( +
+ {/* */} + +
+ ); + }) + : userData && + adminFor.length > 0 && + orgsData?.organizationsConnection.map((item) => { + if (isAdminForCurrentOrg(item) && item._id == currentUrl) { + return ( +
+ +
+ ); + } + })} + + {isLoading && ( + <> + {[...Array(perPageResult)].map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} + + )} +
+ )} +
+ ); +}; + +export default MemberOrganization; diff --git a/src/components/OrgListCard/OrgListCard.test.tsx b/src/components/OrgListCard/OrgListCard.test.tsx index 4072265ea4..be780fb402 100644 --- a/src/components/OrgListCard/OrgListCard.test.tsx +++ b/src/components/OrgListCard/OrgListCard.test.tsx @@ -71,6 +71,8 @@ const props: InterfaceOrgListCardProps = { firstName: 'John', lastName: 'Doe', }, + blockedUsers: [], + description: '', }, }; diff --git a/src/components/OrgListCard/OrgListCard.tsx b/src/components/OrgListCard/OrgListCard.tsx index 10365d2364..47440b6880 100644 --- a/src/components/OrgListCard/OrgListCard.tsx +++ b/src/components/OrgListCard/OrgListCard.tsx @@ -61,8 +61,6 @@ function orgListCard(props: InterfaceOrgListCardProps): JSX.Element { // Handle click event to navigate to the organization dashboard function handleClick(): void { const url = '/orgdash/' + _id; - - // Dont change the below two lines navigate(url); } diff --git a/src/components/OrgMemberDetail/OrgMemberDetail.module.css b/src/components/OrgMemberDetail/OrgMemberDetail.module.css new file mode 100644 index 0000000000..85e8465349 --- /dev/null +++ b/src/components/OrgMemberDetail/OrgMemberDetail.module.css @@ -0,0 +1,217 @@ +.mainpage { + display: flex; + flex-direction: row; +} + +form label { + font-weight: bold; + padding-bottom: 1px; + font-size: 14px; + color: #707070; +} + +form > input { + display: block; + margin-bottom: 20px; + border: 1px solid #e8e5e5; + box-shadow: 2px 1px #e8e5e5; + padding: 10px 20px; + border-radius: 5px; + background: none; + width: 100%; + transition: all 0.3s ease-in-out; + -webkit-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; +} + +.mainpageright > hr { + margin-top: 20px; + width: 100%; + margin-left: -15px; + margin-right: -15px; + margin-bottom: 20px; +} + +@media screen and (max-width: 1200px) { + .mainpageright { + width: 100%; + } +} + +.orgphoto { + margin-top: 5px; +} + +.orgphoto > input { + margin-top: 10px; + cursor: pointer; + margin-bottom: 5px; +} + +.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); + } +} + +.dispflex { + display: flex; +} + +.dispflex > input { + width: 20%; + border: none; + box-shadow: none; + margin-top: 5px; +} + +.userImage { + width: 180px; + height: 180px; + object-fit: cover; + border-radius: 8px; +} + +@media only screen and (max-width: 1200px) { + .userImage { + width: 100px; + height: 100px; + } +} + +.topRadius { + border-top-left-radius: 16px; + border-top-right-radius: 16px; +} + +.inputColor { + background: #f1f3f6; +} + +.width60 { + width: 60%; +} + +.maxWidth40 { + max-width: 40%; +} + +.allRound { + border-radius: 16px; +} + +.WidthFit { + width: fit-content; +} + +.datebox { + border-radius: 7px; + border-color: #e8e5e5; + outline: none; + box-shadow: none; + padding-top: 2px; + padding-bottom: 2px; + padding-right: 5px; + padding-left: 5px; + margin-right: 5px; + margin-left: 5px; +} + +.datebox > div > input { + padding: 0.5rem 0 0.5rem 0.5rem !important; /* top, right, bottom, left */ + background-color: #f1f3f6; + border-radius: var(--bs-border-radius) !important; + border: none !important; +} + +.datebox > div > div { + margin-left: 0px !important; +} + +.datebox > div > fieldset { + border: none !important; + /* background-color: #f1f3f6; */ + border-radius: var(--bs-border-radius) !important; +} + +.datebox > div { + margin: 0.5rem !important; + background-color: #f1f3f6; +} + +input::file-selector-button { + background-color: black; + color: white; +} + +.noOutline { + outline: none; +} + +.Outline { + outline: 1px solid var(--bs-gray-400); +} diff --git a/src/components/OrgMemberDetail/OrgMemberDetails.test.tsx b/src/components/OrgMemberDetail/OrgMemberDetails.test.tsx new file mode 100644 index 0000000000..fc89f2e075 --- /dev/null +++ b/src/components/OrgMemberDetail/OrgMemberDetails.test.tsx @@ -0,0 +1,606 @@ +import React from 'react'; +import { + act, + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import userEvent from '@testing-library/user-event'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import { I18nextProvider } from 'react-i18next'; +import { USER_DETAILS } from 'GraphQl/Queries/Queries'; +import i18nForTest from 'utils/i18nForTest'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import OrgMemberDetail, { + getLanguageName, + prettyDate, +} from './OrgMemberDetails'; +import { toast } from 'react-toastify'; + +const MOCKS1 = [ + { + request: { + query: USER_DETAILS, + variables: { + id: 'rishav-jha-mech', + }, + }, + result: { + data: { + user: { + __typename: 'UserData', + appUserProfile: { + _id: '1', + __typename: 'AppUserProfile', + adminFor: [ + { + __typename: 'Organization', + _id: '65e0df0906dd1228350cfd4a', + }, + { + __typename: 'Organization', + _id: '65e0e2abb92c9f3e29503d4e', + }, + ], + isSuperAdmin: false, + appLanguageCode: 'en', + createdEvents: [ + { + __typename: 'Event', + _id: '65e32a5b2a1f4288ca1f086a', + }, + ], + createdOrganizations: [ + { + __typename: 'Organization', + _id: '65e0df0906dd1228350cfd4a', + }, + { + __typename: 'Organization', + _id: '65e0e2abb92c9f3e29503d4e', + }, + ], + eventAdmin: [ + { + __typename: 'Event', + _id: '65e32a5b2a1f4288ca1f086a', + }, + ], + pluginCreationAllowed: true, + }, + user: { + _id: '1', + __typename: 'User', + createdAt: '2024-02-26T10:36:33.098Z', + email: 'adi790u@gmail.com', + firstName: 'Aditya', + image: null, + lastName: 'Agarwal', + gender: '', + birthDate: '2024-03-14', + educationGrade: '', + employmentStatus: '', + maritalStatus: '', + address: { + line1: '', + countryCode: '', + city: '', + state: '', + }, + phone: { + mobile: '', + }, + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '65e0df0906dd1228350cfd4a', + }, + { + __typename: 'Organization', + _id: '65e0e2abb92c9f3e29503d4e', + }, + ], + membershipRequests: [], + organizationsBlockedBy: [], + registeredEvents: [ + { + __typename: 'Event', + _id: '65e32a5b2a1f4288ca1f086a', + }, + ], + }, + }, + }, + }, + }, +]; + +const MOCKS2 = [ + { + request: { + query: USER_DETAILS, + variables: { + id: 'rishav-jha-mech', + }, + }, + result: { + data: { + user: { + __typename: 'UserData', + appUserProfile: { + _id: '1', + __typename: 'AppUserProfile', + adminFor: [], + isSuperAdmin: false, + appLanguageCode: 'en', + createdEvents: [ + { + __typename: 'Event', + _id: '65e32a5b2a1f4288ca1f086a', + }, + ], + createdOrganizations: [ + { + __typename: 'Organization', + _id: '65e0df0906dd1228350cfd4a', + }, + { + __typename: 'Organization', + _id: '65e0e2abb92c9f3e29503d4e', + }, + ], + eventAdmin: [ + { + __typename: 'Event', + _id: '65e32a5b2a1f4288ca1f086a', + }, + ], + pluginCreationAllowed: true, + }, + user: { + _id: '1', + __typename: 'User', + createdAt: '2024-02-26T10:36:33.098Z', + email: 'adi790u@gmail.com', + firstName: 'Aditya', + image: 'https://placeholder.com/200x200', + lastName: 'Agarwal', + gender: '', + birthDate: '2024-03-14', + educationGrade: '', + employmentStatus: '', + maritalStatus: '', + address: { + line1: '', + countryCode: '', + city: '', + state: '', + }, + phone: { + mobile: '', + }, + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '65e0df0906dd1228350cfd4a', + }, + { + __typename: 'Organization', + _id: '65e0e2abb92c9f3e29503d4e', + }, + ], + membershipRequests: [], + organizationsBlockedBy: [], + registeredEvents: [ + { + __typename: 'Event', + _id: '65e32a5b2a1f4288ca1f086a', + }, + ], + }, + }, + }, + }, + }, +]; + +const MOCKS3 = [ + { + request: { + query: USER_DETAILS, + variables: { + id: 'rishav-jha-mech', + }, + }, + result: { + data: { + user: { + __typename: 'UserData', + appUserProfile: { + _id: '1', + __typename: 'AppUserProfile', + adminFor: [], + isSuperAdmin: true, + appLanguageCode: 'en', + createdEvents: [ + { + __typename: 'Event', + _id: '65e32a5b2a1f4288ca1f086a', + }, + ], + createdOrganizations: [ + { + __typename: 'Organization', + _id: '65e0df0906dd1228350cfd4a', + }, + { + __typename: 'Organization', + _id: '65e0e2abb92c9f3e29503d4e', + }, + ], + eventAdmin: [ + { + __typename: 'Event', + _id: '65e32a5b2a1f4288ca1f086a', + }, + ], + pluginCreationAllowed: true, + }, + user: { + _id: '1', + __typename: 'User', + createdAt: '2024-02-26T10:36:33.098Z', + email: 'adi790u@gmail.com', + firstName: 'Aditya', + image: 'https://placeholder.com/200x200', + lastName: 'Agarwal', + gender: '', + birthDate: '2024-03-14', + educationGrade: '', + employmentStatus: '', + maritalStatus: '', + address: { + line1: '', + countryCode: '', + city: '', + state: '', + }, + phone: { + mobile: '', + }, + joinedOrganizations: [ + { + __typename: 'Organization', + _id: '65e0df0906dd1228350cfd4a', + }, + { + __typename: 'Organization', + _id: '65e0e2abb92c9f3e29503d4e', + }, + ], + membershipRequests: [], + organizationsBlockedBy: [], + registeredEvents: [ + { + __typename: 'Event', + _id: '65e32a5b2a1f4288ca1f086a', + }, + ], + }, + }, + }, + }, + }, +]; + +const link1 = new StaticMockLink(MOCKS1, true); +const link2 = new StaticMockLink(MOCKS2, true); +const link3 = new StaticMockLink(MOCKS3, true); + +async function wait(ms = 20): Promise { + await act(() => new Promise((resolve) => setTimeout(resolve, ms))); +} + +jest.mock('@mui/x-date-pickers/DateTimePicker', () => { + return { + DateTimePicker: jest.requireActual( + '@mui/x-date-pickers/DesktopDateTimePicker', + ).DesktopDateTimePicker, + }; +}); + +jest.mock('react-toastify'); + +describe('OrgMemberDetail', () => { + global.alert = jest.fn(); + + test('should render the elements', async () => { + const props = { + id: 'rishav-jha-mech', + }; + + render( + + + + + + + + + , + ); + + expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + await wait(); + expect(screen.getAllByText(/Email/i)).toBeTruthy(); + expect(screen.getAllByText(/First name/i)).toBeTruthy(); + expect(screen.getAllByText(/Last name/i)).toBeTruthy(); + expect(screen.getAllByText(/Language/i)).toBeTruthy(); + expect(screen.getByText(/Plugin creation allowed/i)).toBeInTheDocument(); + expect(screen.getAllByText(/Joined on/i)).toBeTruthy(); + expect(screen.getAllByText(/Joined On/i)).toHaveLength(1); + expect(screen.getAllByText(/Personal Information/i)).toHaveLength(1); + expect(screen.getAllByText(/Profile Details/i)).toHaveLength(1); + expect(screen.getAllByText(/Actions/i)).toHaveLength(1); + expect(screen.getAllByText(/Contact Information/i)).toHaveLength(1); + }); + + test('prettyDate function should work properly', () => { + // If the date is provided + const datePretty = jest.fn(prettyDate); + expect(datePretty('2023-02-18T09:22:27.969Z')).toBe( + prettyDate('2023-02-18T09:22:27.969Z'), + ); + // Handle edge cases where date input might be empty + expect(datePretty('')).toBe('Unavailable'); + }); + + test('getLanguageName function should work properly', () => { + const getLangName = jest.fn(getLanguageName); + // If the language code is provided + expect(getLangName('en')).toBe('English'); + // If the language code is not provided + expect(getLangName('')).toBe('Unavailable'); + // Test for non-existent language code + expect(getLangName('xx')).toBe('Unavailable'); + }); + + test('should render props and text elements test for the page component', async () => { + const props = { + id: '1', + }; + + const formData = { + firstName: 'Ansh', + lastName: 'Goyal', + email: 'ansh@gmail.com', + image: new File(['hello'], 'hello.png', { type: 'image/png' }), + address: 'abc', + countryCode: 'IN', + state: 'abc', + city: 'abc', + phoneNumber: '1234567890', + birthDate: '03/28/2022', + }; + render( + + + + + + + + + , + ); + expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + await wait(); + expect(screen.getAllByText(/Email/i)).toBeTruthy(); + expect(screen.getByText('User')).toBeInTheDocument(); + const birthDateDatePicker = screen.getByTestId('birthDate'); + fireEvent.change(birthDateDatePicker, { + target: { value: formData.birthDate }, + }); + + userEvent.type( + screen.getByPlaceholderText(/First Name/i), + formData.firstName, + ); + userEvent.type( + screen.getByPlaceholderText(/Last Name/i), + formData.lastName, + ); + userEvent.type(screen.getByPlaceholderText(/Address/i), formData.address); + userEvent.type( + screen.getByPlaceholderText(/Country Code/i), + formData.countryCode, + ); + userEvent.type(screen.getByPlaceholderText(/State/i), formData.state); + userEvent.type(screen.getByPlaceholderText(/City/i), formData.city); + userEvent.type(screen.getByPlaceholderText(/Email/i), formData.email); + userEvent.type(screen.getByPlaceholderText(/Phone/i), formData.phoneNumber); + userEvent.click(screen.getByPlaceholderText(/pluginCreationAllowed/i)); + userEvent.selectOptions(screen.getByTestId('applangcode'), 'Français'); + userEvent.upload(screen.getByLabelText(/Display Image:/i), formData.image); + await wait(); + + userEvent.click(screen.getByText(/Save Changes/i)); + + expect(screen.getByPlaceholderText(/First Name/i)).toHaveValue( + formData.firstName, + ); + expect(screen.getByPlaceholderText(/Last Name/i)).toHaveValue( + formData.lastName, + ); + expect(birthDateDatePicker).toHaveValue(formData.birthDate); + expect(screen.getByPlaceholderText(/Email/i)).toHaveValue(formData.email); + expect(screen.getByPlaceholderText(/First Name/i)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(/Last Name/i)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(/Email/i)).toBeInTheDocument(); + expect(screen.getByText(/Display Image/i)).toBeInTheDocument(); + }); + + test('should display warnings for blank form submission', async () => { + jest.spyOn(toast, 'warning'); + const props = { + key: '123', + id: '1', + toggleStateValue: jest.fn(), + }; + + render( + + + + + + + + + , + ); + + await wait(); + + userEvent.click(screen.getByText(/Save Changes/i)); + + expect(toast.warning).toHaveBeenCalledWith('First Name cannot be blank!'); + expect(toast.warning).toHaveBeenCalledWith('Last Name cannot be blank!'); + expect(toast.warning).toHaveBeenCalledWith('Email cannot be blank!'); + }); + test('display admin', async () => { + const props = { + id: 'rishav-jha-mech', + }; + + render( + + + + + + + + + , + ); + + expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + await wait(); + expect(screen.getByText('Admin')).toBeInTheDocument(); + }); + test('display super admin', async () => { + const props = { + id: 'rishav-jha-mech', + }; + + render( + + + + + + + + + , + ); + + expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + await wait(); + expect(screen.getByText('Super Admin')).toBeInTheDocument(); + }); + + test('Should display avatar image if image is null', async () => { + const props = { + id: 'rishav-jha-mech', + from: 'orglist', + }; + + render( + + + + + + + + + , + ); + expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + + const avatarUrl = `mocked-data-uri`; + + const userImage = await screen.findByTestId('userImageAbsent'); + expect(userImage).toBeInTheDocument(); + expect(userImage.getAttribute('src')).toBe(avatarUrl); + }); + + test('Should display image if image is present', async () => { + const props = { + id: 'rishav-jha-mech', + from: 'orglist', + }; + + render( + + + + + + + + + , + ); + + expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + + const user = MOCKS2[0].result?.data?.user?.user; + const userImage = await screen.findByTestId('userImagePresent'); + expect(userImage).toBeInTheDocument(); + expect(userImage.getAttribute('src')).toBe(user?.image); + }); + + test('should call setState with 2 when button is clicked', async () => { + const props = { + id: 'rishav-jha-mech', + }; + render( + + + + + + + + + , + ); + + expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + + waitFor(() => userEvent.click(screen.getByText(/Edit Profile/i))); + }); + + test('should be redirected to / if member id is undefined', async () => { + render( + + + + + + + + + , + ); + expect(window.location.pathname).toEqual('/'); + }); +}); diff --git a/src/components/OrgMemberDetail/OrgMemberDetails.tsx b/src/components/OrgMemberDetail/OrgMemberDetails.tsx new file mode 100644 index 0000000000..394f3c3f76 --- /dev/null +++ b/src/components/OrgMemberDetail/OrgMemberDetails.tsx @@ -0,0 +1,561 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useMutation, useQuery } from '@apollo/client'; +import Button from 'react-bootstrap/Button'; +import { useTranslation } from 'react-i18next'; +import { useLocation } from 'react-router-dom'; +import { USER_DETAILS } from 'GraphQl/Queries/Queries'; +import styles from './OrgMemberDetail.module.css'; +import { languages } from 'utils/languages'; +import { UPDATE_USER_MUTATION } from 'GraphQl/Mutations/mutations'; +import { toast } from 'react-toastify'; +import { errorHandler } from 'utils/errorHandler'; +import Loader from 'components/Loader/Loader'; +import useLocalStorage from 'utils/useLocalstorage'; +import Avatar from 'components/Avatar/Avatar'; +import { + CalendarIcon, + DatePicker, + LocalizationProvider, +} from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { Form } from 'react-bootstrap'; +import convertToBase64 from 'utils/convertToBase64'; +import sanitizeHtml from 'sanitize-html'; +import type { Dayjs } from 'dayjs'; +import dayjs from 'dayjs'; +import { + educationGradeEnum, + maritalStatusEnum, + genderEnum, + employmentStatusEnum, +} from 'utils/formEnumFields'; +import DynamicDropDown from 'components/DynamicDropDown/DynamicDropDown'; + +type OrgMemberDetailProps = { + id?: string; +}; + +/** + * OrgMemberDetails component displays detailed information about an organization member. + * + * This component: + * - Fetches user details using the `USER_DETAILS` GraphQL query. + * - Allows updating user details using the `UPDATE_USER_MUTATION` GraphQL mutation. + * - Utilizes various utility functions and components such as `useLocalStorage`, `convertToBase64`, `sanitizeHtml`, and `DynamicDropDown`. + * - Uses the `useTranslation` hook for internationalization support. + * - Manages form state for user details including first name, last name, email, language code, image, gender, birth date, education grade, employment status, marital status, phone number, address, state, city, country, and plugin creation permission. + * - Handles date changes using the `DatePicker` component from `@mui/x-date-pickers`. + * - Displays a loader while fetching user details. + * - Shows toast notifications for success and error messages. + * - Ensures component cleanup using the `useEffect` hook. + * + * @param props - The properties passed to the component, including member details. + * @returns A JSX element representing the organization member details. + * + * @example + * ```tsx + * + * ``` + */ + +const OrgMemberDetail: React.FC = ({ + id, +}): JSX.Element => { + const { t } = useTranslation('translation', { + keyPrefix: 'orgMemberDetail', + }); + + const { t: tCommon } = useTranslation('common'); + + const location = useLocation(); + const isMounted = useRef(true); + const { getItem, setItem } = useLocalStorage(); + const currentUrl = location.state?.id || getItem('id') || id; + const [formState, setFormState] = useState({ + firstName: '', + lastName: '', + email: '', + appLanguageCode: '', + image: '', + gender: '', + birthDate: '', + grade: '', + empStatus: '', + maritalStatus: '', + phoneNumber: '', + address: '', + state: '', + city: '', + country: '', + pluginCreationAllowed: false, + }); + const handleDateChange = (date: Dayjs | null): void => { + setFormState((prevState) => ({ + ...prevState, + birthDate: dayjs(date).format('YYYY-MM-DD'), + })); + }; + const [updateUser] = useMutation(UPDATE_USER_MUTATION); + const { data: user, loading: loading } = useQuery(USER_DETAILS, { + variables: { id: currentUrl }, + }); + const userData = user?.user; + + useEffect(() => { + if (userData && isMounted) { + setFormState({ + ...formState, + firstName: userData?.user?.firstName, + lastName: userData?.user?.lastName, + email: userData?.user?.email, + appLanguageCode: userData?.appUserProfile?.appLanguageCode, + gender: userData?.user?.gender, + birthDate: userData?.user?.birthDate, + grade: userData?.user?.educationGrade, + empStatus: userData?.user?.employmentStatus, + maritalStatus: userData?.user?.maritalStatus, + phoneNumber: userData?.user?.phone?.mobile, + address: userData.user?.address?.line1, + state: userData?.user?.address?.state, + city: userData?.user?.address?.city, + country: userData?.user?.address?.countryCode, + pluginCreationAllowed: userData?.appUserProfile?.pluginCreationAllowed, + image: userData?.user?.image || '', + }); + } + }, [userData, user]); + + useEffect(() => { + return () => { + isMounted.current = false; + }; + }, []); + + const handleChange = (e: React.ChangeEvent): void => { + const { name, value } = e.target; + setFormState((prevState) => ({ + ...prevState, + [name]: value, + })); + }; + + const handleToggleChange = (e: React.ChangeEvent): void => { + const { name, checked } = e.target; + setFormState((prevState) => ({ + ...prevState, + [name]: checked, + })); + }; + + const loginLink = async (): Promise => { + try { + const firstName = formState.firstName; + const lastName = formState.lastName; + const email = formState.email; + const image = formState.image; + let toSubmit = true; + if (firstName.trim().length == 0 || !firstName) { + toast.warning('First Name cannot be blank!'); + toSubmit = false; + } + if (lastName.trim().length == 0 || !lastName) { + toast.warning('Last Name cannot be blank!'); + toSubmit = false; + } + if (email.trim().length == 0 || !email) { + toast.warning('Email cannot be blank!'); + toSubmit = false; + } + if (!toSubmit) return; + try { + const { data } = await updateUser({ + variables: { + //! Currently only some fields are supported by the api + id: currentUrl, + ...formState, + }, + }); + /* istanbul ignore next */ + if (data) { + if (getItem('id') === currentUrl) { + setItem('FirstName', firstName); + setItem('LastName', lastName); + setItem('Email', email); + setItem('UserImage', image); + } + toast.success('Successful updated'); + } + } catch (error: unknown) { + if (error instanceof Error) { + errorHandler(t, error); + } + } + } catch (error: unknown) { + /* istanbul ignore next */ + if (error instanceof Error) { + errorHandler(t, error); + } + } + }; + + if (loading) { + return ; + } + + const sanitizedSrc = sanitizeHtml(formState.image, { + allowedTags: ['img'], + allowedAttributes: { + img: ['src', 'alt'], + }, + }); + + return ( + +
+
+
+ {/* Personal */} +
+
+

{t('personalInfoHeading')}

+
+
+
+

{tCommon('firstName')}

+ +
+
+

{tCommon('lastName')}

+ +
+
+

{t('gender')}

+
+ +
+
+
+

{t('birthDate')}

+
+ +
+
+
+

{t('educationGrade')}

+ +
+
+

{t('employmentStatus')}

+ +
+
+

{t('maritalStatus')}

+ +
+

+ +

+
+
+ {/* Contact Info */} +
+
+

{t('contactInfoHeading')}

+
+
+
+

{tCommon('phone')}

+ +
+
+

{tCommon('email')}

+ +
+
+

{tCommon('address')}

+ +
+
+

{t('countryCode')}

+ +
+
+

{tCommon('city')}

+ +
+
+

{tCommon('state')}

+ +
+
+
+
+
+ {/* Personal */} +
+
+

{t('personalDetailsHeading')}

+
+
+
+ {formState.image ? ( + + ) : ( + <> + + + )} +
+
+

{formState?.firstName}

+
+

+ {userData?.appUserProfile?.isSuperAdmin + ? 'Super Admin' + : userData?.appUserProfile?.adminFor.length > 0 + ? 'Admin' + : 'User'} +

+
+

{formState.email}

+

+ + Joined on {prettyDate(userData?.user?.createdAt)} +

+
+
+
+ + {/* Actions */} +
+
+

{t('actionsHeading')}

+
+
+
+
+ +

+ {`${t('pluginCreationAllowed')} (API not supported yet)`} +

+
+
+
+
+
+ +
+
+
+ + +
+
+
+
+
+ +
+
+
+
+
+ ); +}; +export const prettyDate = (param: string): string => { + const date = new Date(param); + if (date?.toDateString() === 'Invalid Date') { + return 'Unavailable'; + } + const day = date.getDate(); + const month = date.toLocaleString('default', { month: 'long' }); + const year = date.getFullYear(); + return `${day} ${month} ${year}`; +}; +export const getLanguageName = (code: string): string => { + let language = 'Unavailable'; + languages.map((data) => { + if (data.code == code) { + language = data.name; + } + }); + return language; +}; +export default OrgMemberDetail; diff --git a/src/components/OrgPeopleOrganizationsCard/OrgPeopleOrganizationsCard.module.css b/src/components/OrgPeopleOrganizationsCard/OrgPeopleOrganizationsCard.module.css new file mode 100644 index 0000000000..430ea318d6 --- /dev/null +++ b/src/components/OrgPeopleOrganizationsCard/OrgPeopleOrganizationsCard.module.css @@ -0,0 +1,141 @@ +.orgCard { + background-color: var(--bs-white); + margin: 0.5rem; + height: calc(120px + 2rem); + padding: 1rem; + border-radius: 8px; + outline: 1px solid var(--bs-gray-200); + position: relative; +} + +.orgCard .innerContainer { + display: flex; +} + +.orgCard .innerContainer .orgImgContainer { + display: flex; + justify-content: center; + align-items: center; + position: relative; + overflow: hidden; + border-radius: 4px; +} + +.orgCard .innerContainer .orgImgContainer { + width: 125px; + height: 120px; + object-fit: contain; +} + +.orgCard .innerContainer .orgImgContainer { + width: 125px; + height: 120px; + background-color: var(--bs-gray-200); +} + +.orgCard .innerContainer .content { + flex: 1; + margin-left: 1rem; + width: 70%; + margin-top: 0.7rem; +} + +.orgCard button { + position: absolute; + bottom: 1rem; + right: 1rem; + z-index: 1; +} + +.flaskIcon { + margin-top: 4px; +} + +.manageBtn { + display: flex; + justify-content: space-around; + width: 8rem; +} + +.orgName { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + font-size: 1rem; +} + +.orgdesc { + font-size: 0.9rem; + color: var(--bs-gray-600); + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 1; + line-clamp: 1; + -webkit-box-orient: vertical; + max-width: 20rem; +} + +.orgadmin { + font-size: 0.9rem; +} + +.orgmember { + font-size: 0.9rem; +} + +.address { + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 1; + line-clamp: 1; + -webkit-box-orient: vertical; + align-items: center; +} + +.address h6 { + font-size: 0.9rem; + color: var(--bs-gray-600); +} + +@media (max-width: 580px) { + .orgCard { + height: unset; + margin: 0.5rem 0; + padding: 1.25rem 1.5rem; + } + + .orgCard .innerContainer { + flex-direction: column; + } + + .orgCard .innerContainer .orgImgContainer { + margin-bottom: 0.8rem; + } + + .orgCard .innerContainer .orgImgContainer img { + height: auto; + width: 100%; + } + + .orgCard .innerContainer .content { + margin-left: 0; + } + + .orgCard button { + bottom: 0; + right: 0; + position: relative; + margin-left: auto; + display: block; + } + + .flaskIcon { + margin-bottom: 6px; + } + + .manageBtn { + display: flex; + justify-content: space-around; + width: 100%; + } +} diff --git a/src/components/OrgPeopleOrganizationsCard/OrgPeopleOrganizationsCard.test.tsx b/src/components/OrgPeopleOrganizationsCard/OrgPeopleOrganizationsCard.test.tsx new file mode 100644 index 0000000000..f525ddaf84 --- /dev/null +++ b/src/components/OrgPeopleOrganizationsCard/OrgPeopleOrganizationsCard.test.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import { act, render, screen } from '@testing-library/react'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; + +import i18nForTest from 'utils/i18nForTest'; +// import type { InterfaceOrgPeopleOrganizationCardProps } from './OrgListCard'; +// import OrgListCard from './OrgListCard'; +import type { InterfaceOrgPeopleOrganizationCardProps } from './OrgPeopleOrganizationsCard'; +import OrgPeopleOrganizationsCard from './OrgPeopleOrganizationsCard'; + +import userEvent from '@testing-library/user-event'; +import { BrowserRouter } from 'react-router-dom'; +import { IS_SAMPLE_ORGANIZATION_QUERY } from 'GraphQl/Queries/Queries'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import { MockedProvider } from '@apollo/react-testing'; +import useLocalStorage from 'utils/useLocalstorage'; + +const { setItem, removeItem } = useLocalStorage(); + +const MOCKS = [ + { + request: { + query: IS_SAMPLE_ORGANIZATION_QUERY, + variables: { + isSampleOrganizationId: 'xyz', + }, + }, + result: { + data: { + isSampleOrganization: true, + }, + }, + }, +]; + +const link = new StaticMockLink(MOCKS, true); + +async function wait(ms = 100): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +const props: InterfaceOrgPeopleOrganizationCardProps = { + data: { + _id: 'xyz', + name: 'Dogs Care', + image: 'https://api.dicebear.com/5.x/initials/svg?seed=John%20Doe', + address: { + city: 'Sample City', + countryCode: 'US', + dependentLocality: 'Sample Dependent Locality', + line1: '123 Sample Street', + line2: 'Apartment 456', + postalCode: '12345', + sortingCode: 'ABC-123', + state: 'Sample State', + }, + admins: [ + { + _id: '123', + }, + { + _id: '456', + }, + ], + members: [], + createdAt: '04/07/2019', + creator: { + _id: 'abc', + firstName: 'John', + lastName: 'Doe', + }, + blockedUsers: [], + description: '', + }, +}; + +describe('Testing the Super Dash List', () => { + test('should render props and text elements test for the page component', async () => { + removeItem('id'); + setItem('id', '123'); // Means the user is an admin + + render( + + + + + + + , + ); + await wait(); + expect(screen.getByAltText(/Dogs Care image/i)).toBeInTheDocument(); + expect(screen.getByText(/Admins:/i)).toBeInTheDocument(); + expect(screen.getByText(/Members:/i)).toBeInTheDocument(); + expect(screen.getByTestId(/manageBtn/i)).toBeInTheDocument(); + expect(screen.getByTestId(/flaskIcon/i)).toBeInTheDocument(); + userEvent.click(screen.getByTestId(/manageBtn/i)); + removeItem('id'); + }); + + test('Testing if the props data is not provided', () => { + window.location.assign('/orgdash'); + + render( + + + + + + + , + ); + + expect(window.location).toBeAt('/orgdash'); + }); + + test('Testing if component is rendered properly when image is null', () => { + const imageNullProps = { + ...props, + ...{ data: { ...props.data, ...{ image: null } } }, + }; + render( + + + + + + + , + ); + expect(screen.getByTestId(/emptyContainerForImage/i)).toBeInTheDocument(); + }); + + test('Testing if user is redirected to orgDash screen', () => { + render( + + + + + + + , + ); + userEvent.click(screen.getByTestId('manageBtn')); + }); +}); diff --git a/src/components/OrgPeopleOrganizationsCard/OrgPeopleOrganizationsCard.tsx b/src/components/OrgPeopleOrganizationsCard/OrgPeopleOrganizationsCard.tsx new file mode 100644 index 0000000000..46f91aa4c9 --- /dev/null +++ b/src/components/OrgPeopleOrganizationsCard/OrgPeopleOrganizationsCard.tsx @@ -0,0 +1,132 @@ +import React from 'react'; +import FlaskIcon from 'assets/svgs/flask.svg?react'; +import Button from 'react-bootstrap/Button'; +import { useTranslation } from 'react-i18next'; +import styles from './OrgPeopleOrganizationsCard.module.css'; +import { useNavigate } from 'react-router-dom'; +import type { + InterfaceOrgConnectionInfoType, + InterfaceQueryOrganizationsListObject, +} from 'utils/interfaces'; +import { + IS_SAMPLE_ORGANIZATION_QUERY, + ORGANIZATIONS_LIST, +} from 'GraphQl/Queries/Queries'; +import { useQuery } from '@apollo/client'; +import { Tooltip } from '@mui/material'; +import Avatar from 'components/Avatar/Avatar'; + +export interface InterfaceOrgPeopleOrganizationCardProps { + data: InterfaceOrgConnectionInfoType; +} + +/** + * OrgPeopleOrganizationsCard component displays a card with information about an organization and its people. + * + * This component is responsible for rendering a card that provides detailed information about a specific organization, + * including its name, image, number of admins, and number of members. It also includes functionality to navigate to + * the organization's dashboard and manage the organization. + * + * @param props - The properties passed to the component. + * @returns A JSX element representing the organization and people card. + * + * @example + * ```tsx + * const organizationData = { + * _id: 'org123', + * admins: ['admin1', 'admin2'], + * image: 'https://example.com/image.png', + * members: ['member1', 'member2', 'member3'], + * name: 'Example Organization', + * }; + * + * + * ``` + */ + +function OrgPeopleOrganizationsCard({ + data: { _id, admins, image, members, name }, +}: InterfaceOrgPeopleOrganizationCardProps): JSX.Element { + const { data } = useQuery(IS_SAMPLE_ORGANIZATION_QUERY, { + variables: { + isSampleOrganizationId: _id, + }, + }); + + const navigate = useNavigate(); + const { + data: userData, + }: { + data?: { + organizations: InterfaceQueryOrganizationsListObject[]; + }; + } = useQuery(ORGANIZATIONS_LIST, { + variables: { id: _id }, + }); + + function handleClick(): void { + const url = '/orgdash/' + _id; + navigate(url); + } + + const { t } = useTranslation('translation', { + keyPrefix: 'orgListCard', + }); + const { t: tCommon } = useTranslation('common'); + + return ( + <> +
+
+
+ {image ? ( + {`${name} + ) : ( + + )} +
+ +
+ +

{name}

+
+
+ {userData?.organizations[0].description} +
+ +
+ {tCommon('admins')}: {admins.length} +
+ +
+ {tCommon('members')}: {members.length} +
+
+
+ + +
+ + ); +} +export default OrgPeopleOrganizationsCard; diff --git a/src/components/OrganizationScreen/OrganizationScreen.test.tsx b/src/components/OrganizationScreen/OrganizationScreen.test.tsx index d31511ea1e..e1bb505169 100644 --- a/src/components/OrganizationScreen/OrganizationScreen.test.tsx +++ b/src/components/OrganizationScreen/OrganizationScreen.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; import 'jest-location-mock'; import { Provider } from 'react-redux'; @@ -10,13 +10,22 @@ import i18nForTest from 'utils/i18nForTest'; import OrganizationScreen from './OrganizationScreen'; import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; import { StaticMockLink } from 'utils/StaticMockLink'; +import useLocalStorage from 'utils/useLocalstorage'; let mockID: string | undefined = '123'; + jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useParams: () => ({ orgId: mockID }), })); +const { setItem } = useLocalStorage(); +async function wait(ms = 1000): Promise { + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, ms)); + }); +} + const MOCKS = [ { request: { @@ -70,6 +79,94 @@ const clickToggleMenuBtn = (toggleButton: HTMLElement): void => { }; describe('Testing LeftDrawer in OrganizationScreen', () => { + test('title should be users if user is super admin', async () => { + setItem('userId', '123'); + setItem('SuperAdmin', true); + window.location.assign('/orgpeople/orgid'); + + render( + + + + + + + + + , + ); + await wait(); + const title = screen.getByTestId('title').textContent; + expect(title).toBe('Users'); + console.log(title); + }); + + test('title should be members if members if user not is super admin', async () => { + setItem('userId', '123'); + setItem('SuperAdmin', false); + window.location.assign('/orgpeople/orgid'); + + render( + + + + + + + + + , + ); + await wait(); + const title = screen.getByTestId('title').textContent; + expect(title).toBe('Members'); + console.log(title); + }); + + test('title should be users if user is super admin for member/orgid', async () => { + setItem('userId', '123'); + setItem('SuperAdmin', true); + window.location.assign('/member/orgid'); + + render( + + + + + + + + + , + ); + await wait(); + const title = screen.getByTestId('title').textContent; + expect(title).toBe('User Details'); + console.log(title); + }); + + test('title should be members if members if user not is super admin for member/orgid', async () => { + setItem('userId', '123'); + setItem('SuperAdmin', false); + window.location.assign('/member/orgid'); + + render( + + + + + + + + + , + ); + await wait(); + const title = screen.getByTestId('title').textContent; + expect(title).toBe('Member Details'); + console.log(title); + }); + test('Testing LeftDrawer in page functionality', async () => { render( diff --git a/src/components/OrganizationScreen/OrganizationScreen.tsx b/src/components/OrganizationScreen/OrganizationScreen.tsx index 7bd2119a82..c3645a3143 100644 --- a/src/components/OrganizationScreen/OrganizationScreen.tsx +++ b/src/components/OrganizationScreen/OrganizationScreen.tsx @@ -10,6 +10,7 @@ import type { TargetsType } from 'state/reducers/routesReducer'; import styles from './OrganizationScreen.module.css'; import ProfileDropdown from 'components/ProfileDropdown/ProfileDropdown'; import { Button } from 'react-bootstrap'; +import useLocalStorage from 'utils/useLocalstorage'; import type { InterfaceMapType } from 'utils/interfaces'; /** @@ -28,6 +29,20 @@ const OrganizationScreen = (): JSX.Element => { const titleKey: string | undefined = map[location.pathname.split('/')[1]]; const { t } = useTranslation('translation', { keyPrefix: titleKey }); + const { getItem } = useLocalStorage(); + const isSuperAdmin = getItem('SuperAdmin'); + + const title = t( + isSuperAdmin && + (titleKey == 'memberDetail' || titleKey == 'organizationPeople') + ? 'title_superadmin' + : 'title', + ); + + useEffect(() => { + document.title = title; + }, [title]); + // State to manage visibility of the side drawer const [hideDrawer, setHideDrawer] = useState(null); @@ -50,7 +65,7 @@ const OrganizationScreen = (): JSX.Element => { // Update targets whenever the organization ID changes useEffect(() => { dispatch(updateTargets(orgId)); - }, [orgId]); // Added orgId to the dependency array + }, [orgId]); // Handle screen resizing to show/hide the side drawer const handleResize = (): void => { @@ -62,6 +77,7 @@ const OrganizationScreen = (): JSX.Element => { // Set up event listener for window resize useEffect(() => { handleResize(); + window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); @@ -111,7 +127,7 @@ const OrganizationScreen = (): JSX.Element => { >
-

{t('title')}

+

{title}

diff --git a/src/screens/MemberDetail/MemberDetail.module.css b/src/screens/MemberDetail/MemberDetail.module.css index 603e55d1d9..de4a1a5fd6 100644 --- a/src/screens/MemberDetail/MemberDetail.module.css +++ b/src/screens/MemberDetail/MemberDetail.module.css @@ -1,523 +1,58 @@ -.mainpage { +.topNav { display: flex; - flex-direction: row; + justify-content: flex-start; + margin-top: 2rem; + background-color: #ffffff; + padding-inline: 1.5rem; + padding-top: 2.5rem; + padding-bottom: 2.5rem; + gap: 1rem; + border-bottom: 1px solid #dddddd; } -.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: 0 2rem; - text-overflow: ellipsis; - /* overflow-x: hidden; */ -} - -/* .sidebarsticky:hover{ - overflow-x:visible; - transition: all 0.4s ease; - background-color: #707070; - -} */ -.sidebarsticky > p { - margin-top: -10px; -} - -.navitem { - padding-left: 27%; - padding-top: 12px; - padding-bottom: 12px; - cursor: pointer; -} - -.searchtitle { - color: #707070; - font-weight: 600; - font-size: 18px; - margin-top: 60px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 60%; -} - -.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; -} - -.logintitle { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 30px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 30%; -} - -.logintitleadmin { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-top: 50px; - margin-bottom: 40px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 60%; -} - -.admindetails { - display: flex; - justify-content: space-between; -} - -.admindetails > p { - margin-top: -12px; - margin-right: 30px; -} - -.mainpageright > hr { - margin-top: 20px; - width: 100%; - margin-left: -15px; - margin-right: -15px; - margin-bottom: 20px; -} - -.justifysp { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: flex-start; - /* gap : 2px; */ -} - -.flexclm { - display: flex; - flex-direction: column; -} - -.btngroup { - display: flex; - gap: 2rem; - margin-bottom: 2rem; -} -@media screen and (max-width: 1200px) { - .justifysp { - padding-left: 55px; - display: flex; - justify-content: space-evenly; - } - - .mainpageright { - width: 100%; - } - - .invitebtn { - position: relative; - right: 15px; - } +.navOverview { + border-radius: 16px; } -.invitebtn { - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - border-radius: 5px; - font-size: 16px; - height: 60%; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - transition: - transform 0.2s, - box-shadow 0.2s; - background-color: #31bb6b; - margin-right: 13px; +.navOther { + border-radius: 16px 16px 0 0; } -.flexdir { +.topNavBtn { display: flex; - flex-direction: row; justify-content: space-between; - border: none; -} - -.form_wrapper { - margin-top: 27px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - position: absolute; - display: flex; - flex-direction: column; - width: 30%; - padding: 40px 30px; - background: #ffffff; - border-color: #e8e5e5; - border-width: 5px; - border-radius: 10px; - max-height: 86vh; - overflow: auto; -} - -.form_wrapper form { - display: flex; - align-items: left; - justify-content: left; - flex-direction: column; -} - -.titlemodal { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 65%; -} - -.checkboxdiv > label { - margin-right: 50px; -} - -.checkboxdiv > label > input { - margin-left: 10px; -} - -.orgphoto { - margin-top: 5px; -} - -.orgphoto > input { - margin-top: 10px; - cursor: pointer; - margin-bottom: 5px; -} - -.cancel > i { - margin-top: 5px; - transform: scale(1.2); - cursor: pointer; - color: #707070; -} - -.modalbody { - width: 50px; -} - -.greenregbtn { - margin: 1rem 0 0; - margin-top: 10px; - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - padding: 10px 10px; - border-radius: 5px; - background-color: #31bb6b; - width: 100%; - font-size: 16px; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - transition: - transform 0.2s, - box-shadow 0.2s; - width: 100%; -} - -.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); - } -} - -.list_box { - height: 70vh; - overflow-y: auto; - width: auto; - padding-right: 50px; -} - -.dispflex { - display: flex; -} - -.dispflex > input { - width: 20%; - border: none; - box-shadow: none; - margin-top: 5px; -} - -.checkboxdiv { - display: flex; -} - -.checkboxdiv > div { - width: 50%; -} - -@media only screen and (max-width: 600px) { - .sidebar { - position: relative; - bottom: 18px; - } - - .invitebtn { - width: 135px; - position: relative; - right: 10px; - } - - .form_wrapper { - width: 90%; - } - - .searchtitle { - margin-top: 30px; - } -} - -/* User page */ - -.memberfontcreatedbtn { - border-radius: 7px; - border-color: #31bb6b; - background-color: #31bb6b; - color: white; - box-shadow: none; - height: 2.5rem; - width: max-content; - display: flex; - justify-content: center; - align-items: center; -} - -.userImage { - width: 180px; - height: 180px; - object-fit: cover; border-radius: 8px; -} - -@media only screen and (max-width: 1200px) { - .userImage { - width: 100px; - height: 100px; - } -} - -.activeBtn { - width: 100%; - display: flex; - color: #fff; - border: 1px solid #000; - background-color: #31bb6b; - transition: 0.5s; -} - -.activeBtn:hover { - color: #fff; - background: #23864c; - transition: 0.5s; -} - -.inactiveBtn { - width: 100%; - display: flex; - color: #31bb6b; - border: 1px solid #31bb6a60; - background-color: #fff; - transition: 0.5s; -} - -.inactiveBtn:hover { - color: #fff; - background: #31bb6b; - transition: 0.5s; -} - -.sidebarsticky > button { - display: flex; + column-gap: 0.5rem; align-items: center; - text-align: start; - padding: 0 1.5rem; - height: 3.25rem; - margin: 0 0 1.5rem 0; - font-weight: bold; - border-radius: 50px; -} - -.bgFill { - height: 2rem; - width: 2rem; - border-radius: 50%; - margin-right: 1rem; - display: flex; justify-content: center; - align-items: center; -} - -.activeBtn .bgFill { - background-color: #fff; + padding-inline: 1.5rem; + padding-top: 1rem; + padding-bottom: 1rem; + cursor: pointer; + text-decoration: none; + border: 1px solid #dddddd; } -.activeBtn i { - color: #31bb6b; +.topNavBtn_notSelected { + background-color: #ffffff; + color: #707070; } -.inactiveBtn .bgFill { +.topNavBtn_selected { background-color: #31bb6b; + color: #ffffff; } -.inactiveBtn:hover .bgFill { - background-color: #fff; -} - -.inactiveBtn i { - color: #fff; -} - -.inactiveBtn:hover i { - color: #31bb6b; -} - -.topRadius { - border-top-left-radius: 16px; - border-top-right-radius: 16px; -} - -.inputColor { - background: #f1f3f6; -} - -.width60 { - width: 60%; -} - -.maxWidth40 { - max-width: 40%; -} - -.allRound { - border-radius: 16px; -} - -.WidthFit { - width: fit-content; -} - -.datebox { - border-radius: 7px; - border-color: #e8e5e5; - outline: none; - box-shadow: none; - padding-top: 2px; - padding-bottom: 2px; - padding-right: 5px; - padding-left: 5px; - margin-right: 5px; - margin-left: 5px; -} - -.datebox > div > input { - padding: 0.5rem 0 0.5rem 0.5rem !important; /* top, right, bottom, left */ - background-color: #f1f3f6; - border-radius: var(--bs-border-radius) !important; - border: none !important; -} - -.datebox > div > div { - margin-left: 0px !important; -} - -.datebox > div > fieldset { - border: none !important; - /* background-color: #f1f3f6; */ - border-radius: var(--bs-border-radius) !important; -} - -.datebox > div { - margin: 0.5rem !important; - background-color: #f1f3f6; -} - -input::file-selector-button { - background-color: black; - color: white; -} - -.noOutline { - outline: none; +.tabSection { + display: flex; + justify-content: space-between; + margin-top: 0; + padding-top: 2.5rem; + padding-bottom: 2.5rem; } -.Outline { - outline: 1px solid var(--bs-gray-400); +.othertabSection { + background-color: #ffffff; + border-radius: 0 0 16px 16px; + padding-inline: 1.5rem; } diff --git a/src/screens/MemberDetail/MemberDetail.test.tsx b/src/screens/MemberDetail/MemberDetail.test.tsx index 7b3707754c..994b7f4367 100644 --- a/src/screens/MemberDetail/MemberDetail.test.tsx +++ b/src/screens/MemberDetail/MemberDetail.test.tsx @@ -1,13 +1,7 @@ import React from 'react'; -import { - act, - fireEvent, - render, - screen, - waitFor, -} from '@testing-library/react'; +import type { RenderResult } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; -import userEvent from '@testing-library/user-event'; import { BrowserRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { store } from 'state/store'; @@ -16,112 +10,15 @@ import { USER_DETAILS } from 'GraphQl/Queries/Queries'; import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; import MemberDetail, { getLanguageName, prettyDate } from './MemberDetail'; -import { toast } from 'react-toastify'; +import useLocalStorage from 'utils/useLocalstorage'; +const { setItem } = useLocalStorage(); -const MOCKS1 = [ +const MOCKS = [ { request: { query: USER_DETAILS, variables: { - id: 'rishav-jha-mech', - }, - }, - result: { - data: { - user: { - __typename: 'UserData', - appUserProfile: { - _id: '1', - __typename: 'AppUserProfile', - adminFor: [ - { - __typename: 'Organization', - _id: '65e0df0906dd1228350cfd4a', - }, - { - __typename: 'Organization', - _id: '65e0e2abb92c9f3e29503d4e', - }, - ], - isSuperAdmin: false, - appLanguageCode: 'en', - createdEvents: [ - { - __typename: 'Event', - _id: '65e32a5b2a1f4288ca1f086a', - }, - ], - createdOrganizations: [ - { - __typename: 'Organization', - _id: '65e0df0906dd1228350cfd4a', - }, - { - __typename: 'Organization', - _id: '65e0e2abb92c9f3e29503d4e', - }, - ], - eventAdmin: [ - { - __typename: 'Event', - _id: '65e32a5b2a1f4288ca1f086a', - }, - ], - pluginCreationAllowed: true, - }, - user: { - _id: '1', - __typename: 'User', - createdAt: '2024-02-26T10:36:33.098Z', - email: 'adi790u@gmail.com', - firstName: 'Aditya', - image: null, - lastName: 'Agarwal', - gender: '', - birthDate: '2024-03-14', - educationGrade: '', - employmentStatus: '', - maritalStatus: '', - address: { - line1: '', - countryCode: '', - city: '', - state: '', - }, - phone: { - mobile: '', - }, - joinedOrganizations: [ - { - __typename: 'Organization', - _id: '65e0df0906dd1228350cfd4a', - }, - { - __typename: 'Organization', - _id: '65e0e2abb92c9f3e29503d4e', - }, - ], - membershipRequests: [], - organizationsBlockedBy: [], - registeredEvents: [ - { - __typename: 'Event', - _id: '65e32a5b2a1f4288ca1f086a', - }, - ], - }, - }, - }, - }, - }, -]; - -const MOCKS2 = [ - { - request: { - query: USER_DETAILS, - variables: { - id: 'rishav-jha-mech', + id: '101', }, }, result: { @@ -204,102 +101,8 @@ const MOCKS2 = [ }, }, ]; -const MOCKS3 = [ - { - request: { - query: USER_DETAILS, - variables: { - id: 'rishav-jha-mech', - }, - }, - result: { - data: { - user: { - __typename: 'UserData', - appUserProfile: { - _id: '1', - __typename: 'AppUserProfile', - adminFor: [], - isSuperAdmin: true, - appLanguageCode: 'en', - createdEvents: [ - { - __typename: 'Event', - _id: '65e32a5b2a1f4288ca1f086a', - }, - ], - createdOrganizations: [ - { - __typename: 'Organization', - _id: '65e0df0906dd1228350cfd4a', - }, - { - __typename: 'Organization', - _id: '65e0e2abb92c9f3e29503d4e', - }, - ], - eventAdmin: [ - { - __typename: 'Event', - _id: '65e32a5b2a1f4288ca1f086a', - }, - ], - pluginCreationAllowed: true, - }, - user: { - _id: '1', - __typename: 'User', - createdAt: '2024-02-26T10:36:33.098Z', - email: 'adi790u@gmail.com', - firstName: 'Aditya', - image: 'https://placeholder.com/200x200', - lastName: 'Agarwal', - gender: '', - birthDate: '2024-03-14', - educationGrade: '', - employmentStatus: '', - maritalStatus: '', - address: { - line1: '', - countryCode: '', - city: '', - state: '', - }, - phone: { - mobile: '', - }, - joinedOrganizations: [ - { - __typename: 'Organization', - _id: '65e0df0906dd1228350cfd4a', - }, - { - __typename: 'Organization', - _id: '65e0e2abb92c9f3e29503d4e', - }, - ], - membershipRequests: [], - organizationsBlockedBy: [], - registeredEvents: [ - { - __typename: 'Event', - _id: '65e32a5b2a1f4288ca1f086a', - }, - ], - }, - }, - }, - }, - }, -]; - -const link1 = new StaticMockLink(MOCKS1, true); -const link2 = new StaticMockLink(MOCKS2, true); -const link3 = new StaticMockLink(MOCKS3, true); -async function wait(ms = 20): Promise { - await act(() => new Promise((resolve) => setTimeout(resolve, ms))); -} +const link = new StaticMockLink(MOCKS, true); jest.mock('@mui/x-date-pickers/DateTimePicker', () => { return { @@ -311,290 +114,93 @@ jest.mock('@mui/x-date-pickers/DateTimePicker', () => { jest.mock('react-toastify'); +const renderMemberDetail = (): RenderResult => { + return render( + + + + + + + + + , + ); +}; + describe('MemberDetail', () => { global.alert = jest.fn(); - test('should render the elements', async () => { - const props = { - id: 'rishav-jha-mech', - }; - - render( - - - - - - - - - , - ); - - expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); - await wait(); - expect(screen.getAllByText(/Email/i)).toBeTruthy(); - expect(screen.getAllByText(/First name/i)).toBeTruthy(); - expect(screen.getAllByText(/Last name/i)).toBeTruthy(); - expect(screen.getAllByText(/Language/i)).toBeTruthy(); - expect(screen.getByText(/Plugin creation allowed/i)).toBeInTheDocument(); - expect(screen.getAllByText(/Joined on/i)).toBeTruthy(); - expect(screen.getAllByText(/Joined On/i)).toHaveLength(1); - expect(screen.getAllByText(/Personal Information/i)).toHaveLength(1); - expect(screen.getAllByText(/Profile Details/i)).toHaveLength(1); - expect(screen.getAllByText(/Actions/i)).toHaveLength(1); - expect(screen.getAllByText(/Contact Information/i)).toHaveLength(1); - }); - - test('prettyDate function should work properly', () => { - // If the date is provided - const datePretty = jest.fn(prettyDate); - expect(datePretty('2023-02-18T09:22:27.969Z')).toBe( - prettyDate('2023-02-18T09:22:27.969Z'), - ); - // If there's some error in formatting the date - expect(datePretty('')).toBe('Unavailable'); - }); - - test('getLanguageName function should work properly', () => { - const getLangName = jest.fn(getLanguageName); - // If the language code is provided - expect(getLangName('en')).toBe('English'); - // If the language code is not provided - expect(getLangName('')).toBe('Unavailable'); - }); - - test('should render props and text elements test for the page component', async () => { - const props = { - id: '1', - }; - - const formData = { - firstName: 'Ansh', - lastName: 'Goyal', - email: 'ansh@gmail.com', - image: new File(['hello'], 'hello.png', { type: 'image/png' }), - address: 'abc', - countryCode: 'IN', - state: 'abc', - city: 'abc', - phoneNumber: '1234567890', - birthDate: '03/28/2022', - }; - render( - - - - - - - - - , - ); - expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); - await wait(); - expect(screen.getAllByText(/Email/i)).toBeTruthy(); - expect(screen.getByText('User')).toBeInTheDocument(); - const birthDateDatePicker = screen.getByTestId('birthDate'); - fireEvent.change(birthDateDatePicker, { - target: { value: formData.birthDate }, + test('Should render the elements', async () => { + await act(async () => { + renderMemberDetail(); }); - userEvent.type( - screen.getByPlaceholderText(/First Name/i), - formData.firstName, - ); - userEvent.type( - screen.getByPlaceholderText(/Last Name/i), - formData.lastName, - ); - userEvent.type(screen.getByPlaceholderText(/Address/i), formData.address); - userEvent.type( - screen.getByPlaceholderText(/Country Code/i), - formData.countryCode, - ); - userEvent.type(screen.getByPlaceholderText(/State/i), formData.state); - userEvent.type(screen.getByPlaceholderText(/City/i), formData.city); - userEvent.type(screen.getByPlaceholderText(/Email/i), formData.email); - userEvent.type(screen.getByPlaceholderText(/Phone/i), formData.phoneNumber); - userEvent.click(screen.getByPlaceholderText(/pluginCreationAllowed/i)); - userEvent.selectOptions(screen.getByTestId('applangcode'), 'Français'); - userEvent.upload(screen.getByLabelText(/Display Image:/i), formData.image); - await wait(); - - userEvent.click(screen.getByText(/Save Changes/i)); - - expect(screen.getByPlaceholderText(/First Name/i)).toHaveValue( - formData.firstName, - ); - expect(screen.getByPlaceholderText(/Last Name/i)).toHaveValue( - formData.lastName, - ); - expect(birthDateDatePicker).toHaveValue(formData.birthDate); - expect(screen.getByPlaceholderText(/Email/i)).toHaveValue(formData.email); - expect(screen.getByPlaceholderText(/First Name/i)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(/Last Name/i)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(/Email/i)).toBeInTheDocument(); - expect(screen.getByText(/Display Image/i)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByTestId('container')).toBeInTheDocument(); + }); }); - test('should display warnings for blank form submission', async () => { - jest.spyOn(toast, 'warning'); - const props = { - id: '1', - toggleStateValue: jest.fn(), - }; - - render( - - - - - - - - - , - ); - - await wait(); - - userEvent.click(screen.getByText(/Save Changes/i)); - - expect(toast.warning).toHaveBeenCalledWith('First Name cannot be blank!'); - expect(toast.warning).toHaveBeenCalledWith('Last Name cannot be blank!'); - expect(toast.warning).toHaveBeenCalledWith('Email cannot be blank!'); + test('Should return formatted date', () => { + expect(prettyDate('2024-03-14')).toBe('14 March 2024'); }); - test('display admin', async () => { - const props = { - id: 'rishav-jha-mech', - }; - - render( - - - - - - - - - , - ); - - expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); - await wait(); - expect(screen.getByText('Admin')).toBeInTheDocument(); + test('Should return Unavailable if date is invalid', () => { + expect(prettyDate('202-03-321')).toBe('Unavailable'); }); - test('display super admin', async () => { - const props = { - id: 'rishav-jha-mech', - }; - render( - - - - - - - - - , - ); - - expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); - await wait(); - expect(screen.getByText('Super Admin')).toBeInTheDocument(); + test('Should return language name', () => { + expect(getLanguageName('en')).toBe('English'); }); - test('Should display dicebear image if image is null', async () => { - const props = { - id: 'rishav-jha-mech', - from: 'orglist', - }; + test('Title should be User Details for Super Admin', async () => { + setItem('SuperAdmin', true); - render( - - - - - - - - - , - ); - expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + await act(async () => { + renderMemberDetail(); + }); - const dicebearUrl = `mocked-data-uri`; + await waitFor(() => { + expect(screen.getByTestId('container')).toBeInTheDocument(); + }); - const userImage = await screen.findByTestId('userImageAbsent'); - expect(userImage).toBeInTheDocument(); - expect(userImage.getAttribute('src')).toBe(dicebearUrl); + expect(document.title).toBe('User Details'); }); - test('Should display image if image is present', async () => { - const props = { - id: 'rishav-jha-mech', - from: 'orglist', - }; + test('Should change tab', async () => { + await act(async () => { + renderMemberDetail(); + }); - render( - - - - - - - - - , - ); + await waitFor(() => { + expect(screen.getByTestId('container')).toBeInTheDocument(); + }); - expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + await act(async () => { + const tab = screen.getByTestId('organizationsBtn'); + tab.click(); + }); - const user = MOCKS2[0].result?.data?.user?.user; - const userImage = await screen.findByTestId('userImagePresent'); - expect(userImage).toBeInTheDocument(); - expect(userImage.getAttribute('src')).toBe(user?.image); - }); + await waitFor(() => { + expect(screen.getByTestId('memberorganizationTab')).toBeInTheDocument(); + }); - test('should call setState with 2 when button is clicked', async () => { - const props = { - id: 'rishav-jha-mech', - }; - render( - - - - - - - - - , - ); + await act(async () => { + const tab1 = screen.getByTestId('eventsBtn'); + tab1.click(); + }); - expect(screen.queryByText('Loading data...')).not.toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByTestId('eventsTab')).toBeInTheDocument(); + }); - waitFor(() => userEvent.click(screen.getByText(/Edit Profile/i))); - }); + await act(async () => { + const tab2 = screen.getByTestId('tagsBtn'); + tab2.click(); + }); - test('should be redirected to / if member id is undefined', async () => { - render( - - - - - - - - - , - ); - expect(window.location.pathname).toEqual('/'); + await waitFor(() => { + expect(screen.getByTestId('tagsTab')).toBeInTheDocument(); + }); }); }); diff --git a/src/screens/MemberDetail/MemberDetail.tsx b/src/screens/MemberDetail/MemberDetail.tsx index 6d92ccbb4a..13c4a43d61 100644 --- a/src/screens/MemberDetail/MemberDetail.tsx +++ b/src/screens/MemberDetail/MemberDetail.tsx @@ -1,40 +1,50 @@ import React, { useEffect, useRef, useState } from 'react'; -import { useMutation, useQuery } from '@apollo/client'; -import Button from 'react-bootstrap/Button'; +import { useQuery } from '@apollo/client'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; import { USER_DETAILS } from 'GraphQl/Queries/Queries'; import styles from './MemberDetail.module.css'; import { languages } from 'utils/languages'; -import { UPDATE_USER_MUTATION } from 'GraphQl/Mutations/mutations'; -import { toast } from 'react-toastify'; -import { errorHandler } from 'utils/errorHandler'; import Loader from 'components/Loader/Loader'; import useLocalStorage from 'utils/useLocalstorage'; -import Avatar from 'components/Avatar/Avatar'; -import { - CalendarIcon, - DatePicker, - LocalizationProvider, -} from '@mui/x-date-pickers'; +import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { Form } from 'react-bootstrap'; -import convertToBase64 from 'utils/convertToBase64'; -import sanitizeHtml from 'sanitize-html'; -import type { Dayjs } from 'dayjs'; -import dayjs from 'dayjs'; -import { - educationGradeEnum, - maritalStatusEnum, - genderEnum, - employmentStatusEnum, -} from 'utils/formEnumFields'; -import DynamicDropDown from 'components/DynamicDropDown/DynamicDropDown'; +import DashboardOutlinedIcon from '@mui/icons-material/DashboardOutlined'; +import BusinessOutlinedIcon from '@mui/icons-material/BusinessOutlined'; +import InsertInvitationOutlinedIcon from '@mui/icons-material/InsertInvitationOutlined'; +import LocalOfferOutlinedIcon from '@mui/icons-material/LocalOfferOutlined'; +import MemberOrganization from 'components/MemberOrganization/MemberOrganization'; +import { Button } from 'react-bootstrap'; +import OrgMemberDetail from 'components/OrgMemberDetail/OrgMemberDetails'; type MemberDetailProps = { - id?: string; // This is the userId + id?: string; }; +type TabOptions = 'overview' | 'organizations' | 'events' | 'tags'; + +const topNavButtons: { + value: TabOptions; + icon: JSX.Element; +}[] = [ + { + value: 'overview', + icon: , + }, + { + value: 'organizations', + icon: , + }, + { + value: 'events', + icon: , + }, + { + value: 'tags', + icon: , + }, +]; + /** * MemberDetail component is used to display the details of a user. * It also allows the user to update the details. It uses the UPDATE_USER_MUTATION to update the user details. @@ -48,502 +58,114 @@ const MemberDetail: React.FC = ({ id }): JSX.Element => { const { t } = useTranslation('translation', { keyPrefix: 'memberDetail', }); - const { t: tCommon } = useTranslation('common'); + + const isMounted = useRef(false); + const location = useLocation(); - const isMounted = useRef(true); - const { getItem, setItem } = useLocalStorage(); + const { getItem } = useLocalStorage(); const currentUrl = location.state?.id || getItem('id') || id; - document.title = t('title'); - const [formState, setFormState] = useState({ - firstName: '', - lastName: '', - email: '', - appLanguageCode: '', - image: '', - gender: '', - birthDate: '2024-03-14', - grade: '', - empStatus: '', - maritalStatus: '', - phoneNumber: '', - address: '', - state: '', - city: '', - country: '', - pluginCreationAllowed: false, - }); - // Handle date change - const handleDateChange = (date: Dayjs | null): void => { - if (date) { - setFormState((prevState) => ({ - ...prevState, - birthDate: dayjs(date).format('YYYY-MM-DD'), // Convert Dayjs object to JavaScript Date object - })); - } - }; - const [updateUser] = useMutation(UPDATE_USER_MUTATION); - const { data: user, loading: loading } = useQuery(USER_DETAILS, { - variables: { id: currentUrl }, // For testing we are sending the id as a prop - }); - const userData = user?.user; useEffect(() => { - if (userData && isMounted) { - // console.log(userData); - setFormState({ - ...formState, - firstName: userData?.user?.firstName, - lastName: userData?.user?.lastName, - email: userData?.user?.email, - appLanguageCode: userData?.appUserProfile?.appLanguageCode, - gender: userData?.user?.gender, - birthDate: userData?.user?.birthDate || '2020-03-14', - grade: userData?.user?.educationGrade, - empStatus: userData?.user?.employmentStatus, - maritalStatus: userData?.user?.maritalStatus, - phoneNumber: userData?.user?.phone?.mobile, - address: userData.user?.address?.line1, - state: userData?.user?.address?.state, - city: userData?.user?.address?.city, - country: userData?.user?.address?.countryCode, - pluginCreationAllowed: userData?.appUserProfile?.pluginCreationAllowed, - image: userData?.user?.image || '', - }); - } - }, [userData, user]); + isMounted.current = true; + const superAdmin = getItem('SuperAdmin'); + superAdmin + ? (document.title = t('title_superadmin')) + : (document.title = t('title')); - useEffect(() => { - // check component is mounted or not return () => { isMounted.current = false; }; - }, []); + }, [getItem, t]); - const handleChange = (e: React.ChangeEvent): void => { - const { name, value } = e.target; - // setFormState({ - // ...formState, - // [name]: value, - // }); - // console.log(name, value); - setFormState((prevState) => ({ - ...prevState, - [name]: value, - })); - // console.log(formState); - }; + const { data: user, loading: loading } = useQuery(USER_DETAILS, { + variables: { id: currentUrl }, + }); + const userData = user?.user; - // const handlePhoneChange = (e: React.ChangeEvent): void => { - // const { name, value } = e.target; - // setFormState({ - // ...formState, - // phoneNumber: { - // ...formState.phoneNumber, - // [name]: value, - // }, - // }); - // // console.log(formState); - // }; + const [activeTab, setActiveTab] = useState('overview'); - const handleToggleChange = (e: React.ChangeEvent): void => { - // console.log(e.target.checked); - const { name, checked } = e.target; - setFormState((prevState) => ({ - ...prevState, - [name]: checked, - })); - // console.log(formState); - }; + const renderButton = ({ + value, + icon, + }: { + value: TabOptions; + icon: React.ReactNode; + }): JSX.Element => { + const selected = activeTab === value; + const translatedText = t(value); + const className = selected + ? `${styles.topNavBtn} ${styles.topNavBtn_selected}` + : `${styles.topNavBtn} ${styles.topNavBtn_notSelected}`; + const props = { + className, + key: value, + onClick: () => setActiveTab(value), + 'data-testid': `${value}Btn`, + }; - const loginLink = async (): Promise => { - try { - // console.log(formState); - const firstName = formState.firstName; - const lastName = formState.lastName; - const email = formState.email; - // const appLanguageCode = formState.appLanguageCode; - const image = formState.image; - // const gender = formState.gender; - let toSubmit = true; - if (firstName.trim().length == 0 || !firstName) { - toast.warning('First Name cannot be blank!'); - toSubmit = false; - } - if (lastName.trim().length == 0 || !lastName) { - toast.warning('Last Name cannot be blank!'); - toSubmit = false; - } - if (email.trim().length == 0 || !email) { - toast.warning('Email cannot be blank!'); - toSubmit = false; - } - if (!toSubmit) return; - try { - const { data } = await updateUser({ - variables: { - //! Currently only some fields are supported by the api - id: currentUrl, - ...formState, - }, - }); - /* istanbul ignore next */ - if (data) { - if (getItem('id') === currentUrl) { - setItem('FirstName', firstName); - setItem('LastName', lastName); - setItem('Email', email); - setItem('UserImage', image); - } - toast.success(tCommon('successfullyUpdated') as string); - } - } catch (error: unknown) { - if (error instanceof Error) { - errorHandler(t, error); - } - } - } catch (error: unknown) { - /* istanbul ignore next */ - if (error instanceof Error) { - errorHandler(t, error); - } - } + return ( + + ); }; if (loading) { return ; } - const sanitizedSrc = sanitizeHtml(formState.image, { - allowedTags: ['img'], - allowedAttributes: { - img: ['src', 'alt'], - }, - }); - return ( -
-
-
- {/* Personal */} -
-
-

{t('personalInfoHeading')}

-
-
-
-

{tCommon('firstName')}

- -
-
-

{tCommon('lastName')}

- -
-
-

{t('gender')}

-
- -
-
-
-

{t('birthDate')}

-
- -
-
-
-

{t('educationGrade')}

- -
-
-

{t('employmentStatus')}

- -
-
-

{t('maritalStatus')}

- -
-

- -

-
-
- {/* Contact Info */} -
-
-

{t('contactInfoHeading')}

-
-
-
-

{t('phone')}

- -
-
-

{tCommon('email')}

- -
-
-

{tCommon('address')}

- -
-
-

{t('countryCode')}

- -
-
-

{t('city')}

- -
-
-

{t('state')}

- -
-
-
-
-
- {/* Personal */} -
-
-

{t('personalDetailsHeading')}

-
-
-
- {formState.image ? ( - - ) : ( - <> - - - )} -
-
-

{formState?.firstName}

-
-

- {userData?.appUserProfile?.isSuperAdmin - ? 'Super Admin' - : userData?.appUserProfile?.adminFor.length > 0 - ? 'Admin' - : 'User'} -

-
-

{formState.email}

-

- - Joined on {prettyDate(userData?.user?.createdAt)} -

-
-
-
+
+
+ {topNavButtons.map(renderButton)} +
- {/* Actions */} -
-
-

{t('actionsHeading')}

-
-
-
-
- -

- {`${t('pluginCreationAllowed')} (API not supported yet)`} -

-
-
-
-
-
- -
-
-
- - -
+ {(() => { + switch (activeTab) { + case 'overview': + return ( +
+ +
+ ); + case 'organizations': + return ( +
+
-
-
-
- -
-
-
+ ); + case 'events': + return ( +
+ ); + case 'tags': + return ( +
+ ); + } + })()}
); diff --git a/src/screens/OrgList/OrgListMocks.ts b/src/screens/OrgList/OrgListMocks.ts index 380313ffd5..7a2d8b9757 100644 --- a/src/screens/OrgList/OrgListMocks.ts +++ b/src/screens/OrgList/OrgListMocks.ts @@ -54,6 +54,8 @@ const organizations: InterfaceOrgConnectionInfoType[] = [ sortingCode: 'ABC-123', state: 'Kingston Parish', }, + blockedUsers: [], + description: '', }, ]; @@ -88,6 +90,8 @@ for (let x = 0; x < 1; x++) { sortingCode: 'ABC-123', state: 'Kingston Parish', }, + blockedUsers: [], + description: '', }); } diff --git a/src/screens/OrganizationPeople/AddMember.tsx b/src/screens/OrganizationPeople/AddMember.tsx index 4c1f15106e..ff5811b519 100644 --- a/src/screens/OrganizationPeople/AddMember.tsx +++ b/src/screens/OrganizationPeople/AddMember.tsx @@ -26,6 +26,7 @@ import { useTranslation } from 'react-i18next'; import { Link, useParams } from 'react-router-dom'; import { toast } from 'react-toastify'; import { errorHandler } from 'utils/errorHandler'; +import { getItem } from 'utils/useLocalstorage'; import type { InterfaceQueryOrganizationsListObject, InterfaceQueryUserListItem, @@ -67,10 +68,15 @@ function AddMember(): JSX.Element { keyPrefix: 'addMember', }); + const isSuperAdmin = getItem('SuperAdmin', ''); const { t: tCommon } = useTranslation('common'); - document.title = translateOrgPeople('title'); + const updateDocumentTitle = () => { + const titleKey = isSuperAdmin ? 'title_superadmin' : 'title'; + document.title = translateOrgPeople(titleKey); + }; + updateDocumentTitle(); const [addUserModalisOpen, setAddUserModalIsOpen] = useState(false); function openAddUserModal(): void { diff --git a/src/screens/OrganizationPeople/OrganizationPeople.test.tsx b/src/screens/OrganizationPeople/OrganizationPeople.test.tsx index a840f1e1f0..d7f715a7e8 100644 --- a/src/screens/OrganizationPeople/OrganizationPeople.test.tsx +++ b/src/screens/OrganizationPeople/OrganizationPeople.test.tsx @@ -517,77 +517,7 @@ const MOCKS: TestMock[] = [ }, ]; -const EMPTYMOCKS: TestMock[] = [ - { - request: { - query: ORGANIZATIONS_LIST, - variables: { - id: 'orgid', - }, - }, - result: { - data: { - organizations: [], - }, - }, - }, - - { - //These are mocks for 1st query (member list) - request: { - query: ORGANIZATIONS_MEMBER_CONNECTION_LIST, - variables: { - orgId: 'orgid', - firstName_contains: '', - lastName_contains: '', - }, - }, - result: { - data: { - organizationsMemberConnection: { - edges: [], - }, - }, - }, - }, - - { - request: { - query: ORGANIZATIONS_MEMBER_CONNECTION_LIST, - variables: { - orgId: 'orgid', - firstName_contains: '', - lastName_contains: '', - }, - }, - result: { - data: { - organizationsMemberConnection: { - edges: [], - }, - }, - }, - }, - - { - //This is mock for user list - request: { - query: USER_LIST_FOR_TABLE, - variables: { - firstName_contains: '', - lastName_contains: '', - }, - }, - result: { - data: { - users: [], - }, - }, - }, -]; - const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink(EMPTYMOCKS, true); async function wait(ms = 2): Promise { await act(() => { return new Promise((resolve) => { @@ -605,6 +535,13 @@ jest.mock('react-router-dom', () => ({ // FOR THE FIRST TEST WHICH CAME OUT OF NOWHERE console.error = jest.fn(); +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + describe('Organization People Page', () => { const searchData = { fullNameMember: 'Aditya Memberguy', @@ -1334,12 +1271,9 @@ describe('Organization People Page', () => { await wait(); const dataGrid = screen.getByRole('grid'); expect(dataGrid).toBeInTheDocument(); - const removeButtons = screen.getAllByTestId('removeMemberModalBtn'); - userEvent.click(removeButtons[0]); }); - test('Datagrid renders with admin data', async () => { - window.location.assign('/orgpeople/orgid'); + test('Datagrid row should direct to user profile', async () => { render( @@ -1351,35 +1285,35 @@ describe('Organization People Page', () => { ); await wait(); - const dropdownToggles = screen.getAllByTestId('role'); - dropdownToggles.forEach((dropdownToggle) => { - userEvent.click(dropdownToggle); + const dataGrid = screen.getByRole('grid'); + expect(dataGrid).toBeInTheDocument(); + + const rows = screen.getAllByRole('row'); + rows.forEach((row) => { + userEvent.click(row); }); - const adminDropdownItem = screen.getByTestId('admins'); - userEvent.click(adminDropdownItem); - await wait(); - const removeButtons = screen.getAllByTestId('removeAdminModalBtn'); - userEvent.click(removeButtons[0]); }); - test('No Mock Data test', async () => { + test('Datagrid renders with admin data', async () => { window.location.assign('/orgpeople/orgid'); - render( - + - - - - - + + + , ); await wait(); - expect(window.location).toBeAt('/orgpeople/orgid'); - expect(screen.queryByText(/Nothing Found !!/i)).toBeInTheDocument(); + const dropdownToggles = screen.getAllByTestId('role'); + dropdownToggles.forEach((dropdownToggle) => { + userEvent.click(dropdownToggle); + }); + const adminDropdownItem = screen.getByTestId('admins'); + userEvent.click(adminDropdownItem); + await wait(); }); }); diff --git a/src/screens/OrganizationPeople/OrganizationPeople.tsx b/src/screens/OrganizationPeople/OrganizationPeople.tsx index 1d230ed058..9b66f87289 100644 --- a/src/screens/OrganizationPeople/OrganizationPeople.tsx +++ b/src/screens/OrganizationPeople/OrganizationPeople.tsx @@ -6,14 +6,12 @@ import { USER_LIST_FOR_TABLE, } from 'GraphQl/Queries/Queries'; import Loader from 'components/Loader/Loader'; -import OrgAdminListCard from 'components/OrgAdminListCard/OrgAdminListCard'; -import OrgPeopleListCard from 'components/OrgPeopleListCard/OrgPeopleListCard'; import dayjs from 'dayjs'; import React, { useEffect, useState } from 'react'; import { Button, Dropdown, Form } from 'react-bootstrap'; import Row from 'react-bootstrap/Row'; import { useTranslation } from 'react-i18next'; -import { Link, useLocation, useParams } from 'react-router-dom'; +import { useLocation, useParams, useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; import AddMember from './AddMember'; import styles from './OrganizationPeople.module.css'; @@ -34,7 +32,7 @@ function organizationPeople(): JSX.Element { }); const { t: tCommon } = useTranslation('common'); - document.title = t('title'); + const navigate = useNavigate(); const location = useLocation(); const role = location?.state; @@ -50,26 +48,6 @@ function organizationPeople(): JSX.Element { const [adminFilteredData, setAdminFilteredData] = useState(); const [userName, setUserName] = useState(''); - const [showRemoveModal, setShowRemoveModal] = React.useState(false); - const [selectedAdminId, setSelectedAdminId] = React.useState< - string | undefined - >(); - const [selectedMemId, setSelectedMemId] = React.useState< - string | undefined - >(); - const toggleRemoveModal = (): void => { - setShowRemoveModal((prev) => !prev); - }; - const toggleRemoveMemberModal = (id: string): void => { - setSelectedMemId(id); - setSelectedAdminId(undefined); - toggleRemoveModal(); - }; - const toggleRemoveAdminModal = (id: string): void => { - setSelectedAdminId(id); - setSelectedMemId(undefined); - toggleRemoveModal(); - }; const { data: memberData, @@ -210,15 +188,7 @@ function organizationPeople(): JSX.Element { headerClassName: `${styles.tableHeader}`, sortable: false, renderCell: (params: GridCellParams) => { - return ( - - {params.row?.firstName + ' ' + params.row?.lastName} - - ); + return
{params.row?.firstName + ' ' + params.row?.lastName}
; }, }, { @@ -244,33 +214,6 @@ function organizationPeople(): JSX.Element { return dayjs(params.row.createdAt).format('DD/MM/YYYY'); }, }, - { - field: 'action', - headerName: tCommon('action'), - flex: 1, - minWidth: 100, - align: 'center', - headerAlign: 'center', - headerClassName: `${styles.tableHeader}`, - sortable: false, - renderCell: (params: GridCellParams) => { - return state === 1 ? ( - - ) : ( - - ); - }, - }, ]; return ( <> @@ -409,21 +352,13 @@ function organizationPeople(): JSX.Element { } columns={columns} isRowSelectable={() => false} + onRowClick={(row: unknown) => { + const id = (row as { id: string }).id; + navigate(`/member/${currentUrl}`, { state: { id } }); + }} />
)} - {showRemoveModal && selectedMemId && ( - - )} - {showRemoveModal && selectedAdminId && ( - - )} ); } diff --git a/src/screens/Requests/RequestsMocks.ts b/src/screens/Requests/RequestsMocks.ts index 6dc22dd58e..08ee2352fd 100644 --- a/src/screens/Requests/RequestsMocks.ts +++ b/src/screens/Requests/RequestsMocks.ts @@ -40,6 +40,8 @@ export const EMPTY_REQUEST_MOCKS = [ sortingCode: 'ABC-123', state: 'Kingston Parish', }, + blockedUsers: [], + description: '', }, ], }, @@ -105,6 +107,8 @@ export const MOCKS = [ sortingCode: 'ABC-123', state: 'Kingston Parish', }, + blockedUsers: [], + description: '', }, ], }, @@ -189,6 +193,8 @@ export const MOCKS4 = [ sortingCode: 'ABC-123', state: 'Kingston Parish', }, + blockedUsers: [], + description: '', }, ], }, @@ -421,6 +427,8 @@ export const MOCKS2 = [ sortingCode: 'ABC-123', state: 'Kingston Parish', }, + blockedUsers: [], + description: '', }, ], }, @@ -496,6 +504,8 @@ export const MOCKS3 = [ sortingCode: 'ABC-123', state: 'Kingston Parish', }, + blockedUsers: [], + description: '', }, ], }, diff --git a/src/screens/Users/UsersMocks.ts b/src/screens/Users/UsersMocks.ts index ff346a1c97..1a7625cf12 100644 --- a/src/screens/Users/UsersMocks.ts +++ b/src/screens/Users/UsersMocks.ts @@ -232,6 +232,8 @@ export const MOCKS = [ sortingCode: 'ABC-123', state: 'Kingston Parish', }, + blockedUsers: [], + description: '', }, ], }, @@ -467,6 +469,8 @@ export const MOCKS2 = [ sortingCode: 'ABC-123', state: 'Kingston Parish', }, + blockedUsers: [], + description: '', }, ], }, diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index 495234a5a1..5d668c0038 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -79,6 +79,13 @@ export interface InterfaceMemberInfo { } export interface InterfaceOrgConnectionInfoType { + blockedUsers: { + _id: string; + firstName: string; + lastName: string; + email: string; + }[]; + description: string; _id: string; image: string | null; creator: { @@ -505,6 +512,10 @@ export interface InterfaceQueryMembershipRequestsListItem { }[]; } +export interface InterfaceMemberOrganization { + userId: string; +} + export interface InterfacePledger { _id: string; firstName: string;