From 2d7b26f4e060ec67d033adc239264a262efb1c12 Mon Sep 17 00:00:00 2001 From: Abhishek Raj <113784630+abbi4code@users.noreply.github.com> Date: Tue, 24 Dec 2024 00:36:53 +0530 Subject: [PATCH 01/28] Plugin-helper test migrated from jest to vitest (#2740) --- ...n.helper.test.ts => Plugin.helper.spec.ts} | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) rename src/components/AddOn/support/services/{Plugin.helper.test.ts => Plugin.helper.spec.ts} (65%) diff --git a/src/components/AddOn/support/services/Plugin.helper.test.ts b/src/components/AddOn/support/services/Plugin.helper.spec.ts similarity index 65% rename from src/components/AddOn/support/services/Plugin.helper.test.ts rename to src/components/AddOn/support/services/Plugin.helper.spec.ts index 39f0a5d12c..51c8ec4bc5 100644 --- a/src/components/AddOn/support/services/Plugin.helper.test.ts +++ b/src/components/AddOn/support/services/Plugin.helper.spec.ts @@ -1,14 +1,27 @@ import PluginHelper from './Plugin.helper'; +import { vi } from 'vitest'; + +/** + * This file contains unit tests for the PluginHelper component. + * + * The tests cover: + * - Verification that the class contains the required method definitions. + * - Correct functionality of the `generateLinks` method, including returning proper objects. + * - Proper behavior of the `fetchStore` method, including handling of mocked JSON responses. + * - Functionality of the `fetchInstalled` method, verifying it returns the expected JSON data. + * + * These tests use Vitest for test execution and mock the global `fetch` function for asynchronous tests. + */ describe('Testing src/components/AddOn/support/services/Plugin.helper.ts', () => { - test('Class should contain the required method definitions', () => { + it('Class should contain the required method definitions', () => { const pluginHelper = new PluginHelper(); expect(pluginHelper).toHaveProperty('fetchStore'); expect(pluginHelper).toHaveProperty('fetchInstalled'); expect(pluginHelper).toHaveProperty('generateLinks'); expect(pluginHelper).toHaveProperty('generateLinks'); }); - test('generateLinks should return proper objects', () => { + it('generateLinks should return proper objects', () => { const obj = { enabled: true, name: 'demo', @@ -28,9 +41,9 @@ describe('Testing src/components/AddOn/support/services/Plugin.helper.ts', () => }); it('fetchStore should return expected JSON', async () => { const helper = new PluginHelper(); - const spy = jest.spyOn(global, 'fetch').mockImplementation(() => { + const spy = vi.spyOn(global, 'fetch').mockImplementation(() => { const response = new Response(); - response.json = jest + response.json = vi .fn() .mockReturnValue(Promise.resolve({ data: 'mock data' })); return Promise.resolve(response); @@ -46,11 +59,11 @@ describe('Testing src/components/AddOn/support/services/Plugin.helper.ts', () => { name: 'plugin1', component: 'Component1', enabled: true }, { name: 'plugin2', component: 'Component2', enabled: false }, ]; - jest.spyOn(global, 'fetch').mockImplementation(() => { + vi.spyOn(global, 'fetch').mockImplementation(() => { const response = new Response(); - response.json = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + response.json = vi.fn().mockReturnValue(Promise.resolve(mockResponse)); return Promise.resolve(response); - }) as jest.Mock; + }); const result = await pluginHelper.fetchInstalled(); expect(result).toEqual(mockResponse); }); From 007bcb003df51160f8c0ca729350c11fbf04f422 Mon Sep 17 00:00:00 2001 From: raggettii <155475892+raggettii@users.noreply.github.com> Date: Tue, 24 Dec 2024 00:51:59 +0530 Subject: [PATCH 02/28] Feature Added: An option for users to leave the Organization (Fixes #1873) (#2629) * Fixed: issue-#1873 * Fixed: issue-#1873 * Fixed: issue-#1873 * Fixed: issue-#1873 * Not title changed to title * Ensure case-insensitive email verification for leaving organization * Null check for organization data added * Refetching of orgs added after leaving org * GraphQL error scenarios to test cases Added * GraphQL error scenarios to test cases Added 1 * Downgrade vitest to 2.1.5 to resolve peer dependency conflict * fixed parsing error 1 * TSDoc comment added in LeaveOrganization.tsx 5 * LeaveOrganization.test.tsx file updated 1 * suggested changes are made * suggested changes are made 2 * optimizing testing file 1 * optimizing testing file 2 * optimizing testing file 2-a * Fixed: issue-#1873 * Fixed: issue-#1873 * Fixed: issue-#1873 * Fixed: issue-#1873 * Not title changed to title * Ensure case-insensitive email verification for leaving organization * Null check for organization data added * Refetching of orgs added after leaving org * GraphQL error scenarios to test cases Added * GraphQL error scenarios to test cases Added 1 * Downgrade vitest to 2.1.5 to resolve peer dependency conflict * fixed parsing error 1 * TSDoc comment added in LeaveOrganization.tsx 5 * LeaveOrganization.test.tsx file updated 1 * suggested changes are made * suggested changes are made 2 * optimizing testing file 1 * optimizing testing file 2 * optimizing testing file 2-a * suggested changes are made 3 * optimizing test file 3 * optimizing test file 3-a * optimizing test file 3-b * optimizing test file 3-c * optimizing test file 3-d * optimizing test file 3-e --- public/locales/en/translation.json | 3 + public/locales/fr/translation.json | 3 + public/locales/hi/translation.json | 3 + public/locales/sp/translation.json | 3 + public/locales/zh/translation.json | 3 + src/App.tsx | 5 + .../IconComponent/IconComponent.tsx | 8 + .../LeaveOrganization.module.css | 27 + .../LeaveOrganization.test.tsx | 519 ++++++++++++++++++ .../LeaveOrganization/LeaveOrganization.tsx | 248 +++++++++ .../UserPortal/UserScreen/UserScreen.tsx | 1 + src/state/reducers/userRoutersReducer.test.ts | 12 + src/state/reducers/userRoutesReducer.ts | 5 + 13 files changed, 840 insertions(+) create mode 100644 src/screens/UserPortal/LeaveOrganization/LeaveOrganization.module.css create mode 100644 src/screens/UserPortal/LeaveOrganization/LeaveOrganization.test.tsx create mode 100644 src/screens/UserPortal/LeaveOrganization/LeaveOrganization.tsx diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 57b4c5de98..5f6d319588 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1405,6 +1405,9 @@ "userPledges": { "title": "My Pledges" }, + "leaveOrganization": { + "title": "Leave Organization" + }, "eventVolunteers": { "volunteers": "Volunteers", "volunteer": "Volunteer", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index d089aefeb5..8dc5fa888a 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -1405,6 +1405,9 @@ "userPledges": { "title": "Mes Promesses" }, + "leaveOrganization": { + "title": "Quitter l'organisation" + }, "eventVolunteers": { "volunteers": "Bénévoles", "volunteer": "Bénévole", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 2645340b23..2865e72e1f 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -1405,6 +1405,9 @@ "userPledges": { "title": "मेरी प्रतिज्ञाएँ" }, + "leaveOrganization": { + "title": "संगठन छोड़ें" + }, "eventVolunteers": { "volunteers": "स्वयंसेवक", "volunteer": "स्वयंसेवक", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 7aa1d6ffc0..da91efb41d 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -1407,6 +1407,9 @@ "userPledges": { "title": "Mis Promesas" }, + "leaveOrganization": { + "title": "Dejar la organización" + }, "eventVolunteers": { "volunteers": "Voluntarios", "volunteer": "Voluntario", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index adafbdbe45..5fbbf4b870 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -1405,6 +1405,9 @@ "userPledges": { "title": "我的承诺" }, + "leaveOrganization": { + "title": "离开组织" + }, "eventVolunteers": { "volunteers": "志愿者", "volunteer": "志愿者", diff --git a/src/App.tsx b/src/App.tsx index 37f3bc301e..4d2ca76010 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -50,6 +50,7 @@ import EventDashboardScreen from 'components/EventDashboardScreen/EventDashboard import Campaigns from 'screens/UserPortal/Campaigns/Campaigns'; import Pledges from 'screens/UserPortal/Pledges/Pledges'; import VolunteerManagement from 'screens/UserPortal/Volunteer/VolunteerManagement'; +import LeaveOrganization from 'screens/UserPortal/LeaveOrganization/LeaveOrganization'; const { setItem } = useLocalStorage(); @@ -198,6 +199,10 @@ function app(): JSX.Element { } /> } /> } /> + } + /> } diff --git a/src/components/IconComponent/IconComponent.tsx b/src/components/IconComponent/IconComponent.tsx index 8430aca131..dd104c0408 100644 --- a/src/components/IconComponent/IconComponent.tsx +++ b/src/components/IconComponent/IconComponent.tsx @@ -19,6 +19,7 @@ import PostsIcon from 'assets/svgs/posts.svg?react'; import SettingsIcon from 'assets/svgs/settings.svg?react'; import VenueIcon from 'assets/svgs/venues.svg?react'; import RequestsIcon from 'assets/svgs/requests.svg?react'; +import ExitToAppIcon from '@mui/icons-material/ExitToApp'; import { MdOutlineVolunteerActivism } from 'react-icons/md'; import React from 'react'; @@ -134,6 +135,13 @@ const iconComponent = (props: InterfaceIconComponent): JSX.Element => { stroke={props.fill} /> ); + case 'Leave Organization': + return ( + + ); case 'Volunteer': return ( ({ + toast: { success: jest.fn() }, // Mock toast function +})); + +Object.defineProperty(window, 'localStorage', { + value: { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn(), + clear: jest.fn(), + }, + writable: true, +}); + +// Mock useParams to return a test organization ID +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: jest.fn(), + useNavigate: jest.fn(), +})); + +// Mock the custom hook +jest.mock('utils/useLocalstorage', () => { + return { + getItem: jest.fn((prefix: string, key: string) => { + if (prefix === 'Talawa-admin' && key === 'email') + return 'test@example.com'; + if (prefix === 'Talawa-admin' && key === 'userId') return '12345'; + if (prefix === 'Talawa-admin-error' && key === 'user-email-error') + throw new Error(); + return null; + }), + }; +}); + +// Define mock data +const mocks = [ + { + request: { + query: ORGANIZATIONS_LIST, + variables: { id: 'test-org-id' }, + }, + result: { + data: { + organizations: [ + { + _id: 'test-org-id', + image: 'https://example.com/organization-image.png', + creator: { + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@example.com', + }, + name: 'Test Organization', + description: 'This is a test organization.', + address: { + city: 'New York', + countryCode: 'US', + dependentLocality: null, + line1: '123 Main Street', + line2: 'Suite 456', + postalCode: '10001', + sortingCode: null, + state: 'NY', + }, + userRegistrationRequired: true, + visibleInSearch: true, + members: [ + { + _id: 'member-001', + firstName: 'Alice', + lastName: 'Smith', + email: 'alice.smith@example.com', + }, + { + _id: 'member-002', + firstName: 'Bob', + lastName: 'Johnson', + email: 'bob.johnson@example.com', + }, + ], + admins: [ + { + _id: 'admin-001', + firstName: 'Jane', + lastName: 'Doe', + email: 'jane.doe@example.com', + createdAt: '2023-01-15T10:00:00Z', + }, + { + _id: 'admin-002', + firstName: 'Tom', + lastName: 'Wilson', + email: 'tom.wilson@example.com', + createdAt: '2023-02-10T12:30:00Z', + }, + ], + membershipRequests: [ + { + _id: 'req-001', + user: { + firstName: 'Emily', + lastName: 'Brown', + email: 'emily.brown@example.com', + }, + }, + ], + blockedUsers: [ + { + _id: 'blocked-001', + firstName: 'Henry', + lastName: 'Clark', + email: 'henry.clark@example.com', + }, + ], + }, + ], + }, + }, + }, + { + request: { + query: REMOVE_MEMBER_MUTATION, + variables: { orgid: 'test-org-id', userid: '12345' }, + }, + result: { + data: { + removeMember: { + _id: 'test-org-id', + success: true, + }, + }, + }, + }, + { + request: { + query: USER_ORGANIZATION_CONNECTION, + variables: { id: 'test-org-id' }, + }, + result: { + data: { + organizationsConnection: [ + { + _id: 'org123', + name: 'Tech Enthusiasts Club', + image: 'https://example.com/org-logo.png', + description: + 'A community of tech lovers who meet to share ideas and projects.', + userRegistrationRequired: true, + creator: { + firstName: 'Alice', + lastName: 'Johnson', + }, + members: [ + { _id: 'user001' }, + { _id: 'user002' }, + { _id: 'user003' }, + ], + admins: [{ _id: 'admin001' }, { _id: 'admin002' }], + createdAt: '2024-01-15T12:34:56.789Z', + address: { + city: 'San Francisco', + countryCode: 'US', + dependentLocality: null, + line1: '123 Tech Ave', + line2: 'Suite 100', + postalCode: '94105', + sortingCode: null, + state: 'California', + }, + membershipRequests: [ + { + _id: 'req001', + user: { + _id: 'user004', + }, + }, + { + _id: 'req002', + user: { + _id: 'user005', + }, + }, + ], + }, + ], + }, + }, + }, +]; + +const errorMocks = [ + { + request: { + query: ORGANIZATIONS_LIST, + variables: { id: 'test-org-id' }, + }, + error: new Error('Failed to load organization details'), + }, + { + request: { + query: REMOVE_MEMBER_MUTATION, + variables: { orgid: 'test-org-id', userid: '12345' }, + }, + error: new Error('Failed to leave organization'), + }, + { + request: { + query: USER_ORGANIZATION_CONNECTION, + variables: { id: 'test-org-id' }, + }, + error: new Error('Operation Failed'), + }, +]; + +beforeEach(() => { + localStorage.clear(); + jest.clearAllMocks(); // Clear mocks before each test + (useParams as jest.Mock).mockReturnValue({ orgId: 'test-org-id' }); +}); + +describe('LeaveOrganization Component', () => { + test('renders organization details and shows loading spinner', async () => { + render( + + + + + , + ); + const spinner = await screen.findByRole('status'); + expect(spinner).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText('Test Organization')).toBeInTheDocument(); + expect( + screen.getByText('This is a test organization.'), + ).toBeInTheDocument(); + }); + }); + + test('renders organization details and displays content correctly', async () => { + render( + + + + } + /> + + + , + ); + await waitFor(() => { + expect(screen.getByText('Test Organization')).toBeInTheDocument(); + expect( + screen.getByText('This is a test organization.'), + ).toBeInTheDocument(); + }); + }); + + test('shows error message when mutation fails', async () => { + render( + + + + } + /> + + + , + ); + await waitFor(() => { + expect( + screen.queryByText('Loading organization details...'), + ).not.toBeInTheDocument(); + }); + expect(await screen.findByText('Test Organization')).toBeInTheDocument(); + expect( + screen.getByText('This is a test organization.'), + ).toBeInTheDocument(); + const leaveButton = await screen.findByRole('button', { + name: 'Leave Organization', + }); + fireEvent.click(leaveButton); + expect(screen.queryByText(/An error occurred!/i)).not.toBeInTheDocument(); + }); + + test('logs an error when unable to access localStorage', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + const userEmail = (() => { + try { + return getItem('Talawa-admin-error', 'user-email-error') ?? ''; + } catch (e) { + console.error('Failed to access localStorage:', e); + return ''; + } + })(); + const userId = (() => { + try { + return getItem('Talawa-admin-error', 'user-email-error') ?? ''; + } catch (e) { + console.error('Failed to access localStorage:', e); + return ''; + } + })(); + expect(userEmail).toBe(''); + expect(userId).toBe(''); + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'Failed to access localStorage:', + expect.any(Error), + ); + consoleErrorSpy.mockRestore(); + }); + + test('navigates and shows toast when email matches', async () => { + const mockNavigate = jest.fn(); + (useNavigate as jest.Mock).mockReturnValue(mockNavigate); + const toastSuccessMock = jest.fn(); + toast.success = toastSuccessMock; + render( + + + + + , + ); + const leaveButton = await screen.findByRole('button', { + name: /Leave Organization/i, + }); + fireEvent.click(leaveButton); + await waitFor(() => + expect( + screen.getByText(/Are you sure you want to leave this organization?/i), + ).toBeInTheDocument(), + ); + const modal = await screen.findByRole('dialog'); + expect(modal).toBeInTheDocument(); + await screen.findByText('Continue'); + fireEvent.click(screen.getByText('Continue')); + const emailInput = screen.getByPlaceholderText(/Enter your email/i); + fireEvent.change(emailInput, { + target: { value: 'test@example.com' }, + }); + fireEvent.keyDown(emailInput, { key: 'Enter', code: 'Enter' }); + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(`/user/organizations`); + }); + await waitFor(() => { + expect(toastSuccessMock).toHaveBeenCalledWith( + 'You have successfully left the organization!', + ); + }); + }); + + test('shows error when email is missing', async () => { + render( + + + + + , + ); + const leaveButton = await screen.findByRole('button', { + name: /Leave Organization/i, + }); + fireEvent.click(leaveButton); + await waitFor(() => + expect( + screen.getByText(/Are you sure you want to leave this organization?/i), + ).toBeInTheDocument(), + ); + const modal = await screen.findByRole('dialog'); + expect(modal).toBeInTheDocument(); + await screen.findByText('Continue'); + fireEvent.click(screen.getByText('Continue')); + fireEvent.change(screen.getByPlaceholderText(/Enter your email/i), { + target: { value: '' }, + }); + fireEvent.click(screen.getByText('Confirm')); + await waitFor(() => { + expect( + screen.getByText('Verification failed: Email does not match.'), + ).toBeInTheDocument(); + }); + }); + + test('shows error when email does not match', async () => { + render( + + + + + , + ); + const leaveButton = await screen.findByRole('button', { + name: /Leave Organization/i, + }); + fireEvent.click(leaveButton); + await waitFor(() => + expect( + screen.getByText(/Are you sure you want to leave this organization?/i), + ).toBeInTheDocument(), + ); + const modal = await screen.findByRole('dialog'); + expect(modal).toBeInTheDocument(); + await screen.findByText('Continue'); + fireEvent.click(screen.getByText('Continue')); + fireEvent.change(screen.getByPlaceholderText(/Enter your email/i), { + target: { value: 'different@example.com' }, + }); + fireEvent.click(screen.getByText('Confirm')); + await waitFor(() => { + expect( + screen.getByText('Verification failed: Email does not match.'), + ).toBeInTheDocument(); + }); + }); + + test('resets state when back button pressed', async () => { + render( + + + + + , + ); + const leaveButton = await screen.findByRole('button', { + name: /Leave Organization/i, + }); + fireEvent.click(leaveButton); + await waitFor(() => + expect( + screen.getByText(/Are you sure you want to leave this organization?/i), + ).toBeInTheDocument(), + ); + const modal = await screen.findByRole('dialog'); + expect(modal).toBeInTheDocument(); + await screen.findByText('Continue'); + fireEvent.click(screen.getByText('Continue')); + const closeButton = screen.getByRole('button', { name: /Back/i }); + fireEvent.click(closeButton); + expect( + screen.queryByText(/Are you sure you want to leave this organization?/i), + ).toBeInTheDocument(); + }); + + test('resets state when modal is closed', async () => { + render( + + + + + , + ); + const leaveButton = await screen.findByRole('button', { + name: /Leave Organization/i, + }); + fireEvent.click(leaveButton); + const closeButton = screen.getByRole('button', { name: /Cancel/i }); + fireEvent.click(closeButton); + expect(screen.queryByText(/Leave Organization/i)).toBeInTheDocument(); + }); + + test('closes modal and resets state when Esc key is pressed', async () => { + const mockNavigate = jest.fn(); + (useNavigate as jest.Mock).mockReturnValue(mockNavigate); + render( + + + + + , + ); + const leaveButton = await screen.findByRole('button', { + name: /Leave Organization/i, + }); + fireEvent.click(leaveButton); + const modal = await screen.findByTestId('leave-organization-modal'); + expect(modal).toBeInTheDocument(); + fireEvent.keyDown(modal, { key: 'Escape', code: 'Escape' }); + await waitFor(() => { + expect(screen.queryByTestId('leave-organization-modal')).toBeNull(); // Modal should no longer be present + }); + expect(modal).not.toBeInTheDocument(); + }); + + test('displays an error alert when query fails', async () => { + render( + + + , + ); + const errorAlert = await screen.findByRole('alert'); + expect(errorAlert).toHaveTextContent(/Error:/i); + }); +}); diff --git a/src/screens/UserPortal/LeaveOrganization/LeaveOrganization.tsx b/src/screens/UserPortal/LeaveOrganization/LeaveOrganization.tsx new file mode 100644 index 0000000000..91dc3a54bf --- /dev/null +++ b/src/screens/UserPortal/LeaveOrganization/LeaveOrganization.tsx @@ -0,0 +1,248 @@ +import React, { useState } from 'react'; +import { useQuery, useMutation } from '@apollo/client'; +import { + ORGANIZATIONS_LIST, + USER_ORGANIZATION_CONNECTION, +} from 'GraphQl/Queries/Queries'; +import { REMOVE_MEMBER_MUTATION } from 'GraphQl/Mutations/mutations'; +import { Button, Modal, Form, Spinner, Alert } from 'react-bootstrap'; +import { useParams, useNavigate } from 'react-router-dom'; +import { getItem } from 'utils/useLocalstorage'; +import { toast } from 'react-toastify'; + +const userEmail = (() => { + try { + return getItem('Talawa-admin', 'email') ?? ''; + } catch (e) { + console.error('Failed to access localStorage:', e); + return ''; + } +})(); +const userId = (() => { + try { + return getItem('Talawa-admin', 'userId') ?? ''; + } catch (e) { + console.error('Failed to access localStorage:', e); + return ''; + } +})(); + +export { userEmail, userId }; + +const LeaveOrganization = (): JSX.Element => { + const navigate = useNavigate(); + const { orgId: organizationId } = useParams(); + const [email, setEmail] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const [showModal, setShowModal] = useState(false); + const [verificationStep, setVerificationStep] = useState(false); + + /** + * Query to fetch the organization data. + */ + const { + data: orgData, + loading: orgLoading, + error: orgError, + } = useQuery(ORGANIZATIONS_LIST, { + variables: { id: organizationId }, + }); + + /** + * Mutation to remove the member from the organization. + */ + const [removeMember] = useMutation(REMOVE_MEMBER_MUTATION, { + refetchQueries: [ + { + query: USER_ORGANIZATION_CONNECTION, + variables: { id: organizationId }, + }, + ], + onCompleted: () => { + // Use a toast notification or in-app message + setShowModal(false); + toast.success('You have successfully left the organization!'); + navigate(`/user/organizations`); + }, + onError: (err) => { + const isNetworkError = err.networkError !== null; + setError( + isNetworkError + ? 'Unable to process your request. Please check your connection.' + : 'Failed to leave organization. Please try again.', + ); + setLoading(false); + }, + }); + + /** + * Handles the process of leaving the organization. + */ + const handleLeaveOrganization = (): void => { + if (!organizationId || !userId) { + setError('Unable to process request: Missing required information.'); + setLoading(false); + return; + } + setError(''); + setLoading(true); + removeMember({ + variables: { orgid: organizationId, userid: userId }, + }); + }; + + /** + * Verifies the user's email before proceeding. + */ + const handleVerifyAndLeave = (): void => { + if (email.trim().toLowerCase() === userEmail.toLowerCase()) { + handleLeaveOrganization(); + } else { + setError('Verification failed: Email does not match.'); + } + }; + + /** + * Handles the 'Enter' key press. + */ + const handleKeyPress = ( + event: React.KeyboardEvent, + ): void => { + if (event.key === 'Enter') { + event.preventDefault(); + if (verificationStep) { + handleVerifyAndLeave(); + } else { + setVerificationStep(true); + } + } + }; + + if (orgLoading) { + return ( +
+ +

Loading organization details...

+
+ ); + } + if (orgError) + return Error: {orgError.message}; + + if (!orgData?.organizations?.length) { + return

Organization not found

; + } + + const organization = orgData?.organizations[0]; + + return ( +
+
+

{organization?.name}

+

{organization?.description}

+ + + + { + setShowModal(false); + setVerificationStep(false); + setEmail(''); + setError(''); + }} + > + + + Leave Joined Organization + + + + {!verificationStep ? ( + <> +

Are you sure you want to leave this organization?

+

+ This action cannot be undone, and you may need to request access + again if you reconsider. +

+ + ) : ( +
+ + + Enter your email to confirm: + + setEmail(e.target.value)} + onKeyDown={handleKeyPress} + aria-label="confirm-email-input" + /> + + {error && ( + + {error} + + )} +
+ )} +
+ + {!verificationStep ? ( + <> + + + + ) : ( + <> + + + + )} + +
+
+ ); +}; + +export default LeaveOrganization; diff --git a/src/screens/UserPortal/UserScreen/UserScreen.tsx b/src/screens/UserPortal/UserScreen/UserScreen.tsx index bcb1d867f3..39b422858f 100644 --- a/src/screens/UserPortal/UserScreen/UserScreen.tsx +++ b/src/screens/UserPortal/UserScreen/UserScreen.tsx @@ -20,6 +20,7 @@ const map: InterfaceMapType = { campaigns: 'userCampaigns', pledges: 'userPledges', volunteer: 'userVolunteer', + leaveorg: 'leaveOrganization', }; /** diff --git a/src/state/reducers/userRoutersReducer.test.ts b/src/state/reducers/userRoutersReducer.test.ts index e2987dcd38..67f075fd44 100644 --- a/src/state/reducers/userRoutersReducer.test.ts +++ b/src/state/reducers/userRoutersReducer.test.ts @@ -18,6 +18,7 @@ describe('Testing Routes reducer', () => { { name: 'Donate', url: 'user/donate/undefined' }, { name: 'Campaigns', url: 'user/campaigns/undefined' }, { name: 'My Pledges', url: 'user/pledges/undefined' }, + { name: 'Leave Organization', url: 'user/leaveorg/undefined' }, ], components: [ { @@ -44,6 +45,11 @@ describe('Testing Routes reducer', () => { component: 'Campaigns', }, { name: 'My Pledges', comp_id: 'pledges', component: 'Pledges' }, + { + name: 'Leave Organization', + comp_id: 'leaveorg', + component: 'LeaveOrganization', + }, ], }); }); @@ -64,6 +70,7 @@ describe('Testing Routes reducer', () => { { name: 'Donate', url: 'user/donate/orgId' }, { name: 'Campaigns', url: 'user/campaigns/orgId' }, { name: 'My Pledges', url: 'user/pledges/orgId' }, + { name: 'Leave Organization', url: 'user/leaveorg/orgId' }, ], components: [ { @@ -90,6 +97,11 @@ describe('Testing Routes reducer', () => { component: 'Campaigns', }, { name: 'My Pledges', comp_id: 'pledges', component: 'Pledges' }, + { + name: 'Leave Organization', + comp_id: 'leaveorg', + component: 'LeaveOrganization', + }, ], }); }); diff --git a/src/state/reducers/userRoutesReducer.ts b/src/state/reducers/userRoutesReducer.ts index e1bf5de0dc..3e99c51b3f 100644 --- a/src/state/reducers/userRoutesReducer.ts +++ b/src/state/reducers/userRoutesReducer.ts @@ -63,6 +63,11 @@ const components: ComponentType[] = [ component: 'Campaigns', }, { name: 'My Pledges', comp_id: 'pledges', component: 'Pledges' }, + { + name: 'Leave Organization', + comp_id: 'leaveorg', + component: 'LeaveOrganization', + }, ]; const generateRoutes = ( From df2dd67df95bef25398831b5fe13b58f2f68a10a Mon Sep 17 00:00:00 2001 From: Ramneet Singh <144323012+Ramneet04@users.noreply.github.com> Date: Tue, 24 Dec 2024 00:59:59 +0530 Subject: [PATCH 03/28] Refactor : src/components/AgendaItems/AgendaItemsCreateModal from Jest to Vitest #2489 (#2727) * file name changed * migrate jest to vitest * changes done * formated the code * fixed the type error * removed @testing-library/jest-dom * @testing-library/jest-dom removed --- ...est.tsx => AgendaItemsCreateModal.spec.tsx} | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) rename src/components/AgendaItems/{AgendaItemsCreateModal.test.tsx => AgendaItemsCreateModal.spec.tsx} (96%) diff --git a/src/components/AgendaItems/AgendaItemsCreateModal.test.tsx b/src/components/AgendaItems/AgendaItemsCreateModal.spec.tsx similarity index 96% rename from src/components/AgendaItems/AgendaItemsCreateModal.test.tsx rename to src/components/AgendaItems/AgendaItemsCreateModal.spec.tsx index 5b7339ad67..35ea8c7681 100644 --- a/src/components/AgendaItems/AgendaItemsCreateModal.test.tsx +++ b/src/components/AgendaItems/AgendaItemsCreateModal.spec.tsx @@ -19,6 +19,8 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import AgendaItemsCreateModal from './AgendaItemsCreateModal'; import { toast } from 'react-toastify'; import convertToBase64 from 'utils/convertToBase64'; +import type { MockedFunction } from 'vitest'; +import { describe, test, expect, vi } from 'vitest'; const mockFormState = { title: 'Test Title', @@ -28,9 +30,9 @@ const mockFormState = { urls: ['https://example.com'], agendaItemCategoryIds: ['category'], }; -const mockHideCreateModal = jest.fn(); -const mockSetFormState = jest.fn(); -const mockCreateAgendaItemHandler = jest.fn(); +const mockHideCreateModal = vi.fn(); +const mockSetFormState = vi.fn(); +const mockCreateAgendaItemHandler = vi.fn(); const mockT = (key: string): string => key; const mockAgendaItemCategories = [ { @@ -64,14 +66,14 @@ const mockAgendaItemCategories = [ }, }, ]; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); -jest.mock('utils/convertToBase64'); -const mockedConvertToBase64 = convertToBase64 as jest.MockedFunction< +vi.mock('utils/convertToBase64'); +const mockedConvertToBase64 = convertToBase64 as MockedFunction< typeof convertToBase64 >; From cd0b04166109fe7e95ec40961d980d1bc93ad672 Mon Sep 17 00:00:00 2001 From: Pratap Rathi Date: Tue, 24 Dec 2024 01:36:53 +0530 Subject: [PATCH 04/28] Migrated src/screens/SubTags from Jest to Vitest (#2739) * Migrated src/screens/SubTags from Jest to Vitest * fix: removed import from Jest --- .../{SubTags.test.tsx => SubTags.spec.tsx} | 39 +++++++++---------- src/screens/SubTags/SubTags.tsx | 32 ++++++++------- 2 files changed, 36 insertions(+), 35 deletions(-) rename src/screens/SubTags/{SubTags.test.tsx => SubTags.spec.tsx} (87%) diff --git a/src/screens/SubTags/SubTags.test.tsx b/src/screens/SubTags/SubTags.spec.tsx similarity index 87% rename from src/screens/SubTags/SubTags.test.tsx rename to src/screens/SubTags/SubTags.spec.tsx index 145d31109d..6db92bcab6 100644 --- a/src/screens/SubTags/SubTags.test.tsx +++ b/src/screens/SubTags/SubTags.spec.tsx @@ -11,7 +11,6 @@ import { waitForElementToBeRemoved, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; @@ -22,6 +21,7 @@ import i18n from 'utils/i18nForTest'; import SubTags from './SubTags'; import { MOCKS, MOCKS_ERROR_SUB_TAGS } from './SubTagsMocks'; import { InMemoryCache, type ApolloLink } from '@apollo/client'; +import { vi, beforeEach, afterEach, expect, it } from 'vitest'; const translations = { ...JSON.parse( @@ -44,10 +44,10 @@ async function wait(ms = 500): Promise { }); } -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); @@ -107,19 +107,18 @@ const renderSubTags = (link: ApolloLink): RenderResult => { describe('Organisation Tags Page', () => { beforeEach(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'orgId' }), + vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), })); cache.reset(); }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); cleanup(); }); - test('Component loads correctly', async () => { + it('Component loads correctly', async () => { const { getByText } = renderSubTags(link); await wait(); @@ -129,7 +128,7 @@ describe('Organisation Tags Page', () => { }); }); - test('render error component on unsuccessful subtags query', async () => { + it('render error component on unsuccessful subtags query', async () => { const { queryByText } = renderSubTags(link2); await wait(); @@ -139,7 +138,7 @@ describe('Organisation Tags Page', () => { }); }); - test('opens and closes the create tag modal', async () => { + it('opens and closes the create tag modal', async () => { renderSubTags(link); await wait(); @@ -161,7 +160,7 @@ describe('Organisation Tags Page', () => { ); }); - test('navigates to manage tag screen after clicking manage tag option', async () => { + it('navigates to manage tag screen after clicking manage tag option', async () => { renderSubTags(link); await wait(); @@ -176,7 +175,7 @@ describe('Organisation Tags Page', () => { }); }); - test('navigates to sub tags screen after clicking on a tag', async () => { + it('navigates to sub tags screen after clicking on a tag', async () => { renderSubTags(link); await wait(); @@ -191,7 +190,7 @@ describe('Organisation Tags Page', () => { }); }); - test('navigates to the different sub tag screen screen after clicking a tag in the breadcrumbs', async () => { + it('navigates to the different sub tag screen screen after clicking a tag in the breadcrumbs', async () => { renderSubTags(link); await wait(); @@ -206,7 +205,7 @@ describe('Organisation Tags Page', () => { }); }); - test('navigates to organization tags screen screen after clicking tha all tags option in the breadcrumbs', async () => { + it('navigates to organization tags screen screen after clicking tha all tags option in the breadcrumbs', async () => { renderSubTags(link); await wait(); @@ -221,7 +220,7 @@ describe('Organisation Tags Page', () => { }); }); - test('navigates to manage tags screen for the current tag after clicking tha manageCurrentTag button', async () => { + it('navigates to manage tags screen for the current tag after clicking tha manageCurrentTag button', async () => { renderSubTags(link); await wait(); @@ -236,7 +235,7 @@ describe('Organisation Tags Page', () => { }); }); - test('searchs for tags where the name matches the provided search input', async () => { + it('searchs for tags where the name matches the provided search input', async () => { renderSubTags(link); await wait(); @@ -257,7 +256,7 @@ describe('Organisation Tags Page', () => { }); }); - test('fetches the tags by the sort order, i.e. latest or oldest first', async () => { + it('fetches the tags by the sort order, i.e. latest or oldest first', async () => { renderSubTags(link); await wait(); @@ -314,7 +313,7 @@ describe('Organisation Tags Page', () => { }); }); - test('Fetches more sub tags with infinite scroll', async () => { + it('Fetches more sub tags with infinite scroll', async () => { const { getByText } = renderSubTags(link); await wait(); @@ -343,7 +342,7 @@ describe('Organisation Tags Page', () => { }); }); - test('adds a new sub tag to the current tag', async () => { + it('adds a new sub tag to the current tag', async () => { renderSubTags(link); await wait(); diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index 930232aaca..6a20e875ec 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -93,7 +93,8 @@ function SubTags(): JSX.Element { fetchMoreResult?: { getChildTags: InterfaceQueryUserTagChildTags }; }, ) => { - if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; + /* istanbul ignore next -- @preserve */ + if (!fetchMoreResult) return prevResult; return { getChildTags: { @@ -126,7 +127,7 @@ function SubTags(): JSX.Element { }, }); - /* istanbul ignore next */ + /* istanbul ignore next -- @preserve */ if (data) { toast.success(t('tagCreationSuccess') as string); subTagsRefetch(); @@ -134,7 +135,7 @@ function SubTags(): JSX.Element { setAddSubTagModalIsOpen(false); } } catch (error: unknown) { - /* istanbul ignore next */ + /* istanbul ignore next -- @preserve */ if (error instanceof Error) { toast.error(error.message); } @@ -155,8 +156,8 @@ function SubTags(): JSX.Element { } const subTagsList = - subTagsData?.getChildTags.childTags.edges.map((edge) => edge.node) ?? - /* istanbul ignore next */ []; + /* istanbul ignore next -- @preserve */ + subTagsData?.getChildTags.childTags.edges.map((edge) => edge.node) ?? []; const parentTagName = subTagsData?.getChildTags.name; @@ -394,7 +395,7 @@ function SubTags(): JSX.Element { next={loadMoreSubTags} hasMore={ subTagsData?.getChildTags.childTags.pageInfo.hasNextPage ?? - /* istanbul ignore next */ + /* istanbul ignore next -- @preserve */ false } loader={} @@ -406,15 +407,16 @@ function SubTags(): JSX.Element { hideFooter={true} getRowId={(row) => row.id} slots={{ - noRowsOverlay: /* istanbul ignore next */ () => ( - - {t('noTagsFound')} - - ), + noRowsOverlay: + /* istanbul ignore next -- @preserve */ () => ( + + {t('noTagsFound')} + + ), }} sx={dataGridStyle} getRowClassName={() => `${styles.rowBackground}`} From 47c0e3b61477c43a675c7d3ed02aa26631c0190a Mon Sep 17 00:00:00 2001 From: Pratap Rathi Date: Tue, 24 Dec 2024 01:38:50 +0530 Subject: [PATCH 05/28] Migrated src/screens/ManageTag from Jest to Vitest (#2742) * Refactor: Migrated src/screens/ManageTag from Jest to Vitest * fix: removed imports for Jest --- ...{ManageTag.test.tsx => ManageTag.spec.tsx} | 61 +++++++++---------- src/screens/ManageTag/ManageTag.tsx | 35 ++++++----- 2 files changed, 49 insertions(+), 47 deletions(-) rename src/screens/ManageTag/{ManageTag.test.tsx => ManageTag.spec.tsx} (87%) diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.spec.tsx similarity index 87% rename from src/screens/ManageTag/ManageTag.test.tsx rename to src/screens/ManageTag/ManageTag.spec.tsx index 598a15cc9a..5d86ed3c17 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.spec.tsx @@ -11,7 +11,6 @@ import { waitForElementToBeRemoved, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; @@ -22,6 +21,7 @@ import i18n from 'utils/i18nForTest'; import ManageTag from './ManageTag'; import { MOCKS, MOCKS_ERROR_ASSIGNED_MEMBERS } from './ManageTagMocks'; import { type ApolloLink } from '@apollo/client'; +import { vi, beforeEach, afterEach, expect, it } from 'vitest'; const translations = { ...JSON.parse( @@ -42,21 +42,21 @@ async function wait(ms = 500): Promise { }); } -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - info: jest.fn(), - error: jest.fn(), + success: vi.fn(), + info: vi.fn(), + error: vi.fn(), }, })); /* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ -jest.mock('../../components/AddPeopleToTag/AddPeopleToTag', () => { - return require('./ManageTagMockComponents/MockAddPeopleToTag').default; +vi.mock('../../components/AddPeopleToTag/AddPeopleToTag', async () => { + return await import('./ManageTagMockComponents/MockAddPeopleToTag'); }); -jest.mock('../../components/TagActions/TagActions', () => { - return require('./ManageTagMockComponents/MockTagActions').default; +vi.mock('../../components/TagActions/TagActions', async () => { + return await import('./ManageTagMockComponents/MockTagActions'); }); /* eslint-enable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ @@ -93,18 +93,17 @@ const renderManageTag = (link: ApolloLink): RenderResult => { describe('Manage Tag Page', () => { beforeEach(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'orgId' }), + vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), })); }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); cleanup(); }); - test('Component loads correctly', async () => { + it('Component loads correctly', async () => { const { getByText } = renderManageTag(link); await wait(); @@ -114,7 +113,7 @@ describe('Manage Tag Page', () => { }); }); - test('renders error component on unsuccessful userTag assigned members query', async () => { + it('renders error component on unsuccessful userTag assigned members query', async () => { const { queryByText } = renderManageTag(link2); await wait(); @@ -124,7 +123,7 @@ describe('Manage Tag Page', () => { }); }); - test('opens and closes the add people to tag modal', async () => { + it('opens and closes the add people to tag modal', async () => { renderManageTag(link); await waitFor(() => { @@ -146,7 +145,7 @@ describe('Manage Tag Page', () => { }); }); - test('opens and closes the unassign tag modal', async () => { + it('opens and closes the unassign tag modal', async () => { renderManageTag(link); await wait(); @@ -168,7 +167,7 @@ describe('Manage Tag Page', () => { ); }); - test('opens and closes the assignToTags modal', async () => { + it('opens and closes the assignToTags modal', async () => { renderManageTag(link); // Wait for the assignToTags button to be present @@ -193,7 +192,7 @@ describe('Manage Tag Page', () => { }); }); - test('opens and closes the removeFromTags modal', async () => { + it('opens and closes the removeFromTags modal', async () => { renderManageTag(link); // Wait for the removeFromTags button to be present @@ -218,7 +217,7 @@ describe('Manage Tag Page', () => { }); }); - test('opens and closes the edit tag modal', async () => { + it('opens and closes the edit tag modal', async () => { renderManageTag(link); await wait(); @@ -240,7 +239,7 @@ describe('Manage Tag Page', () => { ); }); - test('opens and closes the remove tag modal', async () => { + it('opens and closes the remove tag modal', async () => { renderManageTag(link); await wait(); @@ -262,7 +261,7 @@ describe('Manage Tag Page', () => { ); }); - test("navigates to the member's profile after clicking the view option", async () => { + it("navigates to the member's profile after clicking the view option", async () => { renderManageTag(link); await wait(); @@ -277,7 +276,7 @@ describe('Manage Tag Page', () => { }); }); - test('navigates to the subTags screen after clicking the subTags option', async () => { + it('navigates to the subTags screen after clicking the subTags option', async () => { renderManageTag(link); await wait(); @@ -292,7 +291,7 @@ describe('Manage Tag Page', () => { }); }); - test('navigates to the manageTag screen after clicking a tag in the breadcrumbs', async () => { + it('navigates to the manageTag screen after clicking a tag in the breadcrumbs', async () => { renderManageTag(link); await wait(); @@ -309,7 +308,7 @@ describe('Manage Tag Page', () => { }); }); - test('navigates to organization tags screen screen after clicking tha all tags option in the breadcrumbs', async () => { + it('navigates to organization tags screen screen after clicking tha all tags option in the breadcrumbs', async () => { renderManageTag(link); await wait(); @@ -324,7 +323,7 @@ describe('Manage Tag Page', () => { }); }); - test('searchs for tags where the name matches the provided search input', async () => { + it('searchs for tags where the name matches the provided search input', async () => { renderManageTag(link); await wait(); @@ -345,7 +344,7 @@ describe('Manage Tag Page', () => { }); }); - test('fetches the tags by the sort order, i.e. latest or oldest first', async () => { + it('fetches the tags by the sort order, i.e. latest or oldest first', async () => { renderManageTag(link); await wait(); @@ -402,7 +401,7 @@ describe('Manage Tag Page', () => { }); }); - test('Fetches more assigned members with infinite scroll', async () => { + it('Fetches more assigned members with infinite scroll', async () => { const { getByText } = renderManageTag(link); await wait(); @@ -433,7 +432,7 @@ describe('Manage Tag Page', () => { }); }); - test('unassigns a tag from a member', async () => { + it('unassigns a tag from a member', async () => { renderManageTag(link); await wait(); @@ -452,7 +451,7 @@ describe('Manage Tag Page', () => { }); }); - test('successfully edits the tag name', async () => { + it('successfully edits the tag name', async () => { renderManageTag(link); await wait(); @@ -482,7 +481,7 @@ describe('Manage Tag Page', () => { }); }); - test('successfully removes the tag and redirects to orgTags page', async () => { + it('successfully removes the tag and redirects to orgTags page', async () => { renderManageTag(link); await wait(); diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 428dad7981..6afec3f130 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -132,7 +132,8 @@ function ManageTag(): JSX.Element { }; }, ) => { - if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; + /* istanbul ignore next -- @preserve */ + if (!fetchMoreResult) return prevResult; return { getAssignedUsers: { @@ -174,7 +175,7 @@ function ManageTag(): JSX.Element { toggleUnassignUserTagModal(); toast.success(t('successfullyUnassigned') as string); } catch (error: unknown) { - /* istanbul ignore next */ + /* istanbul ignore next -- @preserve */ if (error instanceof Error) { toast.error(error.message); } @@ -209,13 +210,14 @@ function ManageTag(): JSX.Element { }, }); + /* istanbul ignore else -- @preserve */ if (data) { toast.success(t('tagUpdationSuccess')); userTagAssignedMembersRefetch(); setEditUserTagModalIsOpen(false); } } catch (error: unknown) { - /* istanbul ignore next */ + /* istanbul ignore next -- @preserve */ if (error instanceof Error) { toast.error(error.message); } @@ -235,7 +237,7 @@ function ManageTag(): JSX.Element { toggleRemoveUserTagModal(); toast.success(t('tagRemovalSuccess') as string); } catch (error: unknown) { - /* istanbul ignore next */ + /* istanbul ignore next -- @preserve */ if (error instanceof Error) { toast.error(error.message); } @@ -258,7 +260,7 @@ function ManageTag(): JSX.Element { const userTagAssignedMembers = userTagAssignedMembersData?.getAssignedUsers.usersAssignedTo.edges.map( (edge) => edge.node, - ) ?? /* istanbul ignore next */ []; + ) ?? /* istanbul ignore next -- @preserve */ []; // get the ancestorTags array and push the current tag in it // used for the tag breadcrumbs @@ -452,7 +454,7 @@ function ManageTag(): JSX.Element { > {tag.name} {orgUserTagAncestors.length - 1 !== index && ( - /* istanbul ignore next */ + /* istanbul ignore next -- @preserve */ )} @@ -469,7 +471,7 @@ function ManageTag(): JSX.Element { hasMore={ userTagAssignedMembersData?.getAssignedUsers .usersAssignedTo.pageInfo.hasNextPage ?? - /* istanbul ignore next */ false + /* istanbul ignore next -- @preserve */ false } loader={} scrollableTarget="manageTagScrollableDiv" @@ -480,15 +482,16 @@ function ManageTag(): JSX.Element { hideFooter={true} getRowId={(row) => row.id} slots={{ - noRowsOverlay: /* istanbul ignore next */ () => ( - - {t('noAssignedMembersFound')} - - ), + noRowsOverlay: + /* istanbul ignore next -- @preserve */ () => ( + + {t('noAssignedMembersFound')} + + ), }} sx={dataGridStyle} getRowClassName={() => `${styles.rowBackground}`} From e1b49d244a0c3fa76d41ad2387cac9c3a92db11d Mon Sep 17 00:00:00 2001 From: MANDEEP N H <146331633+mandeepnh5@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:33:21 +0530 Subject: [PATCH 06/28] Refactoring CSS files: merged CommunityProfile styles into global app.module.css (#2738) * Refactor Community Profile css * Fix linting issues * Fix explicit lint issue --- .../CommunityProfile.module.css | 41 ------------------ .../CommunityProfile/CommunityProfile.tsx | 5 ++- src/style/app.module.css | 42 +++++++++++++++++++ 3 files changed, 45 insertions(+), 43 deletions(-) delete mode 100644 src/screens/CommunityProfile/CommunityProfile.module.css diff --git a/src/screens/CommunityProfile/CommunityProfile.module.css b/src/screens/CommunityProfile/CommunityProfile.module.css deleted file mode 100644 index 1e6eac2bae..0000000000 --- a/src/screens/CommunityProfile/CommunityProfile.module.css +++ /dev/null @@ -1,41 +0,0 @@ -.card { - width: fit-content; -} - -.cardHeader { - padding: 1.25rem 1rem 1rem 1rem; - border-bottom: 1px solid var(--bs-gray-200); - display: flex; - justify-content: space-between; - align-items: center; -} - -.cardHeader .cardTitle { - font-size: 1.5rem; -} - -.formLabel { - font-weight: normal; - padding-bottom: 0; - font-size: 1rem; - color: black; -} -.cardBody { - min-height: 180px; -} - -.cardBody .textBox { - margin: 0 0 3rem 0; - color: var(--bs-secondary); -} - -.socialInput { - height: 2.5rem; -} - -@media (max-width: 520px) { - .btn { - flex-direction: column; - justify-content: center; - } -} diff --git a/src/screens/CommunityProfile/CommunityProfile.tsx b/src/screens/CommunityProfile/CommunityProfile.tsx index d96c923eb3..05f328ced0 100644 --- a/src/screens/CommunityProfile/CommunityProfile.tsx +++ b/src/screens/CommunityProfile/CommunityProfile.tsx @@ -18,7 +18,7 @@ import { SlackLogo, } from 'assets/svgs/social-icons'; import convertToBase64 from 'utils/convertToBase64'; -import styles from './CommunityProfile.module.css'; +import styles from '../../style/app.module.css'; import { errorHandler } from 'utils/errorHandler'; import UpdateSession from '../../components/UpdateSession/UpdateSession'; @@ -90,7 +90,7 @@ const CommunityProfile = (): JSX.Element => { React.useEffect(() => { const preLoginData: PreLoginImageryDataType | undefined = data?.getCommunityData; - preLoginData && + if (preLoginData) { setProfileVariable({ name: preLoginData.name ?? '', websiteLink: preLoginData.websiteLink ?? '', @@ -104,6 +104,7 @@ const CommunityProfile = (): JSX.Element => { reddit: preLoginData.socialMediaUrls.reddit ?? '', slack: preLoginData.socialMediaUrls.slack ?? '', }); + } }, [data]); /** diff --git a/src/style/app.module.css b/src/style/app.module.css index 6d6413aa86..3d161b52b1 100644 --- a/src/style/app.module.css +++ b/src/style/app.module.css @@ -831,6 +831,48 @@ hr { color: #31bb6b !important; } +.card { + width: fit-content; +} + +.cardHeader { + padding: 1.25rem 1rem 1rem 1rem; + border-bottom: 1px solid var(--bs-gray-200); + display: flex; + justify-content: space-between; + align-items: center; +} + +.cardHeader .cardTitle { + font-size: 1.5rem; +} + +.formLabel { + font-weight: normal; + padding-bottom: 0; + font-size: 1rem; + color: black; +} +.cardBody { + min-height: 180px; +} + +.cardBody .textBox { + margin: 0 0 3rem 0; + color: var(--bs-secondary); +} + +.socialInput { + height: 2.5rem; +} + +@media (max-width: 520px) { + .btn { + flex-direction: column; + justify-content: center; + } +} + @media (max-width: 1024px) { .pageNotFound h1.head { font-size: 200px; From 3b681362d935e222373555c35959801c19d18aba Mon Sep 17 00:00:00 2001 From: Gurram Karthik <167804249+gurramkarthiknetha@users.noreply.github.com> Date: Tue, 24 Dec 2024 14:46:43 +0530 Subject: [PATCH 07/28] fixed : #2516 Refactor CSS files in src/screens/OrganizationFunds (#2743) * issue-2516-fixed * test * removed commented lines * deleted file --- .../OrganizationFunds.module.css | 142 ------------------ .../OrganizationFunds/OrganizationFunds.tsx | 20 +-- src/style/app.module.css | 131 ++++++---------- 3 files changed, 56 insertions(+), 237 deletions(-) delete mode 100644 src/screens/OrganizationFunds/OrganizationFunds.module.css diff --git a/src/screens/OrganizationFunds/OrganizationFunds.module.css b/src/screens/OrganizationFunds/OrganizationFunds.module.css deleted file mode 100644 index aa9d89dfb1..0000000000 --- a/src/screens/OrganizationFunds/OrganizationFunds.module.css +++ /dev/null @@ -1,142 +0,0 @@ -.list_box { - height: auto; - overflow-y: auto; - width: 100%; -} - -.inputField { - margin-top: 10px; - margin-bottom: 10px; - background-color: white; - box-shadow: 0 1px 1px #31bb6b; -} - -.inputField > button { - padding-top: 10px; - padding-bottom: 10px; -} - -.dropdown { - background-color: white; - border: 1px solid #31bb6b; - position: relative; - display: inline-block; - color: #31bb6b; -} - -.fundName { - font-weight: 600; - cursor: pointer; -} - -.modalHeader { - border: none; - padding-bottom: 0; -} - -.label { - color: var(--bs-emphasis-color); -} - -.fundModal { - max-width: 80vw; - margin-top: 2vh; - margin-left: 13vw; -} - -.titlemodal { - color: #707070; - font-weight: 600; - font-size: 32px; - width: 65%; - margin-bottom: 0px; -} - -.noOutline input { - outline: none; -} - -.modalCloseBtn { - width: 40px; - height: 40px; - padding: 1rem; - display: flex; - justify-content: center; - align-items: center; -} - -.greenregbtn { - margin: 1rem 0 0; - margin-top: 15px; - 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%; -} - -.manageBtn { - margin: 1rem 0 0; - margin-top: 15px; - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - padding: 10px 10px; - border-radius: 5px; - font-size: 16px; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - width: 45%; - transition: - transform 0.2s, - box-shadow 0.2s; -} - -.btnsContainer { - display: flex; - margin: 2rem 0 2.5rem 0; -} - -.btnsContainer .input { - flex: 1; - min-width: 18rem; - position: relative; -} - -.btnsContainer input { - outline: 1px solid var(--bs-gray-400); -} - -.btnsContainer .input button { - width: 52px; -} - -.mainpageright > hr { - margin-top: 20px; - width: 100%; - margin-left: -15px; - margin-right: -15px; - margin-bottom: 20px; -} - -.rowBackground { - background-color: var(--bs-white); - max-height: 120px; -} - -.tableHeader { - background-color: var(--bs-primary); - color: var(--bs-white); - font-size: 1rem; -} diff --git a/src/screens/OrganizationFunds/OrganizationFunds.tsx b/src/screens/OrganizationFunds/OrganizationFunds.tsx index 2c352c1693..ec9409fbe0 100644 --- a/src/screens/OrganizationFunds/OrganizationFunds.tsx +++ b/src/screens/OrganizationFunds/OrganizationFunds.tsx @@ -160,7 +160,7 @@ const organizationFunds = (): JSX.Element => { minWidth: 100, align: 'center', headerAlign: 'center', - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, sortable: false, renderCell: (params: GridCellParams) => { return
{params.row.id}
; @@ -174,7 +174,7 @@ const organizationFunds = (): JSX.Element => { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return (
{ minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return params.row.creator.firstName + ' ' + params.row.creator.lastName; }, @@ -207,7 +207,7 @@ const organizationFunds = (): JSX.Element => { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, flex: 2, renderCell: (params: GridCellParams) => { return ( @@ -225,7 +225,7 @@ const organizationFunds = (): JSX.Element => { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return params.row.isArchived ? 'Archived' : 'Active'; }, @@ -238,7 +238,7 @@ const organizationFunds = (): JSX.Element => { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return ( <> @@ -266,7 +266,7 @@ const organizationFunds = (): JSX.Element => { minWidth: 100, headerAlign: 'center', sortable: false, - headerClassName: `${styles.tableHeader}`, + headerClassName: `${styles.tableHeaders}`, renderCell: (params: GridCellParams) => { return (
-
+
} + /> + + + + + + , + ); + await waitFor(() => { + expect(screen.getByTestId('errorMsg')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/screens/OrganizationActionItems/OrganizationActionItems.test.tsx b/src/screens/OrganizationActionItems/OrganizationActionItems.test.tsx deleted file mode 100644 index 44e11baa35..0000000000 --- a/src/screens/OrganizationActionItems/OrganizationActionItems.test.tsx +++ /dev/null @@ -1,359 +0,0 @@ -import React, { act } from 'react'; -import { MockedProvider } from '@apollo/react-testing'; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import type { RenderResult } from '@testing-library/react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { I18nextProvider } from 'react-i18next'; -import { Provider } from 'react-redux'; -import { MemoryRouter, Route, Routes } from 'react-router-dom'; -import { store } from 'state/store'; -import { StaticMockLink } from 'utils/StaticMockLink'; -import i18n from 'utils/i18nForTest'; -import OrganizationActionItems from 'screens/OrganizationActionItems/OrganizationActionItems'; -import type { ApolloLink } from '@apollo/client'; -import { - MOCKS, - MOCKS_EMPTY, - MOCKS_ERROR, -} from './OrganizationActionItem.mocks'; - -jest.mock('react-toastify', () => ({ - toast: { - success: jest.fn(), - error: jest.fn(), - }, -})); - -jest.mock('@mui/x-date-pickers/DateTimePicker', () => { - return { - DateTimePicker: jest.requireActual( - '@mui/x-date-pickers/DesktopDateTimePicker', - ).DesktopDateTimePicker, - }; -}); - -const link1 = new StaticMockLink(MOCKS); -const link2 = new StaticMockLink(MOCKS_ERROR); -const link3 = new StaticMockLink(MOCKS_EMPTY); -const t = { - ...JSON.parse( - JSON.stringify( - i18n.getDataByLanguage('en')?.translation.organizationActionItems ?? {}, - ), - ), - ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.common ?? {})), - ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), -}; - -const debounceWait = async (ms = 300): Promise => { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -}; - -const renderOrganizationActionItems = (link: ApolloLink): RenderResult => { - return render( - - - - - - - } - /> -
} - /> - - - - - - , - ); -}; - -describe('Testing Organization Action Items Screen', () => { - beforeAll(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'orgId', eventId: 'eventId' }), - })); - }); - - afterAll(() => { - jest.clearAllMocks(); - }); - - it('should redirect to fallback URL if URL params are undefined', async () => { - render( - - - - - - } - /> -
} - /> - - - - - , - ); - await waitFor(() => { - expect(screen.getByTestId('paramsError')).toBeInTheDocument(); - }); - }); - - it('should render Organization Action Items screen', async () => { - renderOrganizationActionItems(link1); - await waitFor(() => { - expect(screen.getByTestId('searchBy')).toBeInTheDocument(); - expect(screen.getAllByText('John Doe')).toHaveLength(2); - expect(screen.getAllByText('Jane Doe')).toHaveLength(2); - }); - }); - - it('Sort Action Items descending by dueDate', async () => { - renderOrganizationActionItems(link1); - - const sortBtn = await screen.findByTestId('sort'); - expect(sortBtn).toBeInTheDocument(); - - // Sort by dueDate_DESC - fireEvent.click(sortBtn); - await waitFor(() => { - expect(screen.getByTestId('dueDate_DESC')).toBeInTheDocument(); - }); - fireEvent.click(screen.getByTestId('dueDate_DESC')); - await waitFor(() => { - expect(screen.getAllByTestId('categoryName')[0]).toHaveTextContent( - 'Category 2', - ); - }); - }); - - it('Sort Action Items ascending by dueDate', async () => { - renderOrganizationActionItems(link1); - - const sortBtn = await screen.findByTestId('sort'); - expect(sortBtn).toBeInTheDocument(); - - // Sort by dueDate_ASC - fireEvent.click(sortBtn); - await waitFor(() => { - expect(screen.getByTestId('dueDate_ASC')).toBeInTheDocument(); - }); - fireEvent.click(screen.getByTestId('dueDate_ASC')); - await waitFor(() => { - expect(screen.getAllByTestId('categoryName')[0]).toHaveTextContent( - 'Category 1', - ); - }); - }); - - it('Filter Action Items by status (All/Pending)', async () => { - renderOrganizationActionItems(link1); - - const filterBtn = await screen.findByTestId('filter'); - expect(filterBtn).toBeInTheDocument(); - - // Filter by All - fireEvent.click(filterBtn); - await waitFor(() => { - expect(screen.getByTestId('statusAll')).toBeInTheDocument(); - }); - fireEvent.click(screen.getByTestId('statusAll')); - - await waitFor(() => { - expect(screen.getAllByText('Category 1')).toHaveLength(3); - expect(screen.getAllByText('Category 2')).toHaveLength(2); - }); - - // Filter by Pending - fireEvent.click(filterBtn); - await waitFor(() => { - expect(screen.getByTestId('statusPending')).toBeInTheDocument(); - }); - fireEvent.click(screen.getByTestId('statusPending')); - await waitFor(() => { - expect(screen.queryByText('Category 1')).toBeNull(); - expect(screen.getByText('Category 2')).toBeInTheDocument(); - }); - }); - - it('Filter Action Items by status (Completed)', async () => { - renderOrganizationActionItems(link1); - - const filterBtn = await screen.findByTestId('filter'); - expect(filterBtn).toBeInTheDocument(); - - fireEvent.click(filterBtn); - await waitFor(() => { - expect(screen.getByTestId('statusCompleted')).toBeInTheDocument(); - }); - fireEvent.click(screen.getByTestId('statusCompleted')); - await waitFor(() => { - expect(screen.getByText('Category 1')).toBeInTheDocument(); - expect(screen.queryByText('Category 2')).toBeNull(); - }); - }); - - it('open and close Item modal (create)', async () => { - renderOrganizationActionItems(link1); - - const addItemBtn = await screen.findByTestId('createActionItemBtn'); - expect(addItemBtn).toBeInTheDocument(); - userEvent.click(addItemBtn); - - await waitFor(() => - expect(screen.getAllByText(t.createActionItem)).toHaveLength(2), - ); - userEvent.click(screen.getByTestId('modalCloseBtn')); - await waitFor(() => - expect(screen.queryByTestId('modalCloseBtn')).toBeNull(), - ); - }); - - it('open and close Item modal (view)', async () => { - renderOrganizationActionItems(link1); - - const viewItemBtn = await screen.findByTestId('viewItemBtn1'); - expect(viewItemBtn).toBeInTheDocument(); - userEvent.click(viewItemBtn); - - await waitFor(() => - expect(screen.getByText(t.actionItemDetails)).toBeInTheDocument(), - ); - userEvent.click(screen.getByTestId('modalCloseBtn')); - await waitFor(() => - expect(screen.queryByTestId('modalCloseBtn')).toBeNull(), - ); - }); - - it('open and closes Item modal (edit)', async () => { - renderOrganizationActionItems(link1); - - const editItemBtn = await screen.findByTestId('editItemBtn1'); - await waitFor(() => expect(editItemBtn).toBeInTheDocument()); - userEvent.click(editItemBtn); - - await waitFor(() => - expect(screen.getAllByText(t.updateActionItem)).toHaveLength(2), - ); - userEvent.click(screen.getByTestId('modalCloseBtn')); - await waitFor(() => - expect(screen.queryByTestId('modalCloseBtn')).toBeNull(), - ); - }); - - it('open and closes Item modal (delete)', async () => { - renderOrganizationActionItems(link1); - - const deleteItemBtn = await screen.findByTestId('deleteItemBtn1'); - expect(deleteItemBtn).toBeInTheDocument(); - userEvent.click(deleteItemBtn); - - await waitFor(() => - expect(screen.getByText(t.deleteActionItem)).toBeInTheDocument(), - ); - userEvent.click(screen.getByTestId('modalCloseBtn')); - await waitFor(() => - expect(screen.queryByTestId('modalCloseBtn')).toBeNull(), - ); - }); - - it('open and closes Item modal (update status)', async () => { - renderOrganizationActionItems(link1); - - const statusCheckbox = await screen.findByTestId('statusCheckbox1'); - expect(statusCheckbox).toBeInTheDocument(); - userEvent.click(statusCheckbox); - - await waitFor(() => - expect(screen.getByText(t.actionItemStatus)).toBeInTheDocument(), - ); - userEvent.click(screen.getByTestId('modalCloseBtn')); - await waitFor(() => - expect(screen.queryByTestId('modalCloseBtn')).toBeNull(), - ); - }); - - it('Search action items by assignee', async () => { - renderOrganizationActionItems(link1); - - const searchByToggle = await screen.findByTestId('searchByToggle'); - expect(searchByToggle).toBeInTheDocument(); - - userEvent.click(searchByToggle); - await waitFor(() => { - expect(screen.getByTestId('assignee')).toBeInTheDocument(); - }); - - userEvent.click(screen.getByTestId('assignee')); - - const searchInput = await screen.findByTestId('searchBy'); - expect(searchInput).toBeInTheDocument(); - - userEvent.type(searchInput, 'John'); - await debounceWait(); - - await waitFor(() => { - expect(screen.getByText('Category 1')).toBeInTheDocument(); - expect(screen.queryByText('Category 2')).toBeNull(); - }); - }); - - it('Search action items by category', async () => { - renderOrganizationActionItems(link1); - - const searchByToggle = await screen.findByTestId('searchByToggle'); - expect(searchByToggle).toBeInTheDocument(); - - userEvent.click(searchByToggle); - await waitFor(() => { - expect(screen.getByTestId('category')).toBeInTheDocument(); - }); - - userEvent.click(screen.getByTestId('category')); - - const searchInput = await screen.findByTestId('searchBy'); - expect(searchInput).toBeInTheDocument(); - - userEvent.type(searchInput, 'Category 1'); - await debounceWait(); - - await waitFor(() => { - expect(screen.getByText('Category 1')).toBeInTheDocument(); - expect(screen.queryByText('Category 2')).toBeNull(); - }); - }); - - it('should render Empty Action Item Categories Screen', async () => { - renderOrganizationActionItems(link3); - await waitFor(() => { - expect(screen.getByTestId('searchBy')).toBeInTheDocument(); - expect(screen.getByText(t.noActionItems)).toBeInTheDocument(); - }); - }); - - it('should render the Action Item Categories Screen with error', async () => { - renderOrganizationActionItems(link2); - await waitFor(() => { - expect(screen.getByTestId('errorMsg')).toBeInTheDocument(); - }); - }); -}); From 7755890a13519d451ffcfdee0e3cb14f63291fe5 Mon Sep 17 00:00:00 2001 From: Nikhil Raj <125121367+hustlernik@users.noreply.github.com> Date: Tue, 24 Dec 2024 20:12:51 +0530 Subject: [PATCH 13/28] [CHORES] Refactor CSS for Org Post (#2774) * refactor: org-post css * Fix duplicate properties and class definitions * Remove duplicate keyframe definitions and optimize loader styles --- src/screens/OrgPost/OrgPost.module.css | 325 ------------------------- src/screens/OrgPost/OrgPost.tsx | 16 +- src/style/app.module.css | 187 +++++++++++++- 3 files changed, 194 insertions(+), 334 deletions(-) delete mode 100644 src/screens/OrgPost/OrgPost.module.css diff --git a/src/screens/OrgPost/OrgPost.module.css b/src/screens/OrgPost/OrgPost.module.css deleted file mode 100644 index e674efbc7a..0000000000 --- a/src/screens/OrgPost/OrgPost.module.css +++ /dev/null @@ -1,325 +0,0 @@ -.mainpage { - display: flex; - flex-direction: row; -} -.btnsContainer { - display: flex; - margin: 2.5rem 0 2.5rem 0; -} - -.btnsContainer .btnsBlock { - display: flex; -} - -.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; -} - -.preview { - display: flex; - position: relative; - width: 100%; - margin-top: 10px; - justify-content: center; -} -.preview img { - width: 400px; - height: auto; -} -.preview video { - width: 400px; - height: auto; -} -.logintitle { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 30px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 15%; -} -.logintitleadmin { - color: #707070; - font-weight: 600; - font-size: 18px; - margin-top: 50px; - margin-bottom: 40px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 30%; -} -.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; - justify-content: space-between; - align-items: center; - margin-bottom: 3rem; -} - -@media (max-width: 1120px) { - .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%; - } -} -@media screen and (max-width: 575.5px) { - .justifysp { - display: flex; - justify-content: space-between; - width: 100%; - } - - .mainpageright { - width: 98%; - } -} -.addbtn { - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - border-radius: 5px; - font-size: 16px; - height: 60%; - width: 60%; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - transition: - transform 0.2s, - box-shadow 0.2s; -} -.flexdir { - display: flex; - flex-direction: row; - justify-content: space-between; - border: none; -} -.form_wrapper { - margin-top: 27px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - position: absolute; - display: flex; - flex-direction: column; - padding: 20px 30px; - background: #ffffff; - border-color: #e8e5e5; - border-width: 5px; - border-radius: 10px; -} - -.form_wrapper form { - display: flex; - align-items: left; - justify-content: left; - flex-direction: column; -} -.logintitleinvite { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 40%; -} -.postinfo { - height: 80px; -} - -.postinfo { - height: 80px; - margin-bottom: 20px; -} -.titlemodal { - color: #707070; - font-weight: 600; - font-size: 20px; - margin-bottom: 20px; - padding-bottom: 5px; - border-bottom: 3px solid #31bb6b; - width: 65%; -} -.cancel > i { - margin-top: 5px; - transform: scale(1.2); - cursor: pointer; - color: #707070; -} -.modalbody { - width: 50px; -} - -.closeButton { - position: absolute; - top: 0px; - right: 0px; - background: transparent; - transform: scale(1.2); - cursor: pointer; - border: none; - color: #707070; - font-weight: 600; - font-size: 16px; - cursor: pointer; -} -.sidebarsticky > input { - text-decoration: none; - margin-bottom: 50px; - border: solid 1.5px #d3d3d3; - border-radius: 5px; - width: 80%; - border-radius: 7px; - padding-top: 5px; - padding-bottom: 5px; - padding-right: 10px; - padding-left: 10px; - text-decoration: none; - box-shadow: none; -} - -.sidebarsticky > input:focus { - border-color: #fff; - box-shadow: 0 0 5pt 0.5pt #d3d3d3; - outline: none; -} -button[data-testid='createPostBtn'] { - display: block; -} -.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; -} -@media only screen and (max-width: 600px) { - .form_wrapper { - width: 90%; - top: 45%; - } -} diff --git a/src/screens/OrgPost/OrgPost.tsx b/src/screens/OrgPost/OrgPost.tsx index e2c388d7b0..e9cb4d4ca2 100644 --- a/src/screens/OrgPost/OrgPost.tsx +++ b/src/screens/OrgPost/OrgPost.tsx @@ -284,9 +284,9 @@ function orgPost(): JSX.Element { return ( <> -
-
-
+
+
+
-
+