Skip to content

Commit

Permalink
Merge pull request #558 from Real-Dev-Squad/develop
Browse files Browse the repository at this point in the history
dev to main sync
  • Loading branch information
iamitprakash authored Sep 20, 2023
2 parents e1ba3bb + 5fb7a14 commit 2d5f1ff
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/components/member-profile/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ const Profile = (props) => {
</Modal>
)}
{isSuperUser && (
<div id="memberRoleUpdateModal">
<div id="memberRoleUpdateModal" data-testid="memberRoleUpdateModal">
{showMemberRoleUpdateModal && <MemberRoleUpdate />}
</div>
)}
Expand Down
96 changes: 82 additions & 14 deletions src/components/member-role-update/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import Spinner from '@components/UI/spinner';
import MemberTagAssign from '@components/member-tag-assign';
import { BASE_API_URL } from '@constants/AppConstants';
import useFetch from '@custom-hooks/useFetch';
import { useRouter } from 'next/router';
import classNames from './member-role-update.module.scss';
import { memberRoleUpdate } from '../../helper-functions/action-handlers';

const MemberRoleUpdate = () => {
const { query } = useRouter() || { query: { dev: false } };
const { dev } = query;
const isDev = Boolean(dev); // convert string to boolean
const {
showMemberRoleUpdateModal,
setShowMemberRoleUpdateModal,
Expand All @@ -18,7 +22,8 @@ const MemberRoleUpdate = () => {

const [isUpdating, setIsUpdating] = useState(false);
const [updateStatus, setUpdateStatus] = useState('');

const [validateError, setvalidateError] = useState('');
const [reasonText, setReasonText] = useState('');
const { data: userData } = useFetch(
`${BASE_API_URL}/users/${selectedMember}`
);
Expand Down Expand Up @@ -51,22 +56,28 @@ const MemberRoleUpdate = () => {
}
};

const archiveUnArchiveTheMember = async (id) => {
let archiveRole = null;
const archiveUnArchiveTheMember = async (id, reason) => {
setIsUpdating(true);
let body = {};
if (archived) {
archiveRole = false;
body = {
archived: false,
};
} else if (!reason && !archived) {
body = {
archived: true,
};
} else {
archiveRole = true;
body = {
archived: true,
reason,
};
}
const role = {
archived: archiveRole,
};
try {
const { status } = await memberRoleUpdate(id, role);
const { status } = await memberRoleUpdate(id, body);
setIsUpdating(false);
if (status === 200) {
setUpdateStatus('user archived!');
setUpdateStatus(archived ? 'User unarchived!' : 'User archived!');
}
} catch (error) {
setUpdateStatus('Some error occured, please contact admin');
Expand All @@ -75,30 +86,86 @@ const MemberRoleUpdate = () => {

const memberRoleUpdateButton = (
<button
data-testid="promoteDemoteButton"
className={classNames.moveToMember}
type="button"
onClick={() => promoteDemoteAMember(userId)}
>
{member ? 'Demote Member' : 'Promote to Member'}
</button>
);

const handleValidReason = (id, reason) => {
const isEmptyReason = !reason.length;
const isReasonEmptyOrWhitespace = /^\s*$/.test(reason); // check for empty or multiple whitespaces
const isMoreThan99Words = reason.split(' ').length > 99;
const isMoreThan50Characters = reason.length <= 25;
switch (!archived) {
case isEmptyReason:
setvalidateError('Reason cannot be empty!');
break;
case isReasonEmptyOrWhitespace:
setvalidateError('Reason cannot be empty or multiple whitespaces!');
break;
case isMoreThan99Words:
setvalidateError('Reason cannot be more than 99 words!');
break;
case isMoreThan50Characters:
setvalidateError('Reason should have more than 25 characters!');
break;
default:
setvalidateError('');
}
const isValid =
!isEmptyReason &&
!isReasonEmptyOrWhitespace &&
!isMoreThan99Words &&
!isMoreThan50Characters;
if (isValid || archived) {
archiveUnArchiveTheMember(id, reason);
}
};
const memeberArchiveUnArchiveButton = (
<button
className={classNames.moveToMember}
type="button"
onClick={() => archiveUnArchiveTheMember(userId)}
data-testid="archiveUnArchiveButton"
onClick={() =>
!isDev
? archiveUnArchiveTheMember(userId)
: handleValidReason(userId, reasonText)
}
>
{archived ? 'Unarchive Member' : 'Archive Member'}
</button>
);

const archiveReasonTextBox = (
<div className={classNames.archiveUser}>
<p className={classNames.archiveUser__error}>{validateError}</p>
<label htmlFor="archiveReason" data-testid="reasonInputLabel">
Reason:
<textarea
className={classNames.archiveUser__textArea}
id="archiveReason"
name="archiveReason"
data-testid="reasonTextBox"
rows="10"
cols="20"
onChange={(e) => setReasonText(e.target.value)}
placeholder="Enter the reason for archiving the user"
required
/>
</label>
</div>
);
const renderPromoteButton = () => {
return (
<>
{isDev && !archived ? archiveReasonTextBox : null}

{memberRoleUpdateButton}
{memeberArchiveUnArchiveButton}

{memeberArchiveUnArchiveButton}
<br />

{userData && tagData && levelData && (
Expand All @@ -109,14 +176,15 @@ const MemberRoleUpdate = () => {
/>
)}

<p>{updateStatus}</p>
<p className={classNames.archiveUser__success}>{updateStatus}</p>
</>
);
};

return ReactDOM.createPortal(
<>
<Modal
data-testid="modalUpdateRoles"
show={showMemberRoleUpdateModal}
closeModal={(e) => {
e.preventDefault();
Expand Down
23 changes: 23 additions & 0 deletions src/components/member-role-update/member-role-update.module.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import '../../styles/constants/colors';

.moveToMember {
margin: 10px;
padding: 10px;
Expand All @@ -9,3 +11,24 @@
font-size: inherit;
margin: 10px;
}
.archiveUser {
text-align: left;
&__textArea {
width: 100%;
height: 7rem;
padding: 0.4rem;
text-align: justify;
font-family: inherit;
font-size: inherit;
}
&__error {
color: $color-red;
text-align: center;
font-size: inherit;
}
&__success {
color: $color-green;
text-align: center;
font-size: inherit;
}
}
4 changes: 2 additions & 2 deletions src/helper-functions/action-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
updateMemberRole,
} from './urls';

const memberRoleUpdate = (user, role) =>
fetch(updateMemberRole(user), 'patch', null, role, null, {
const memberRoleUpdate = (user, body) =>
fetch(updateMemberRole(user), 'patch', null, body, null, {
withCredentials: true,
});

Expand Down
2 changes: 2 additions & 0 deletions src/styles/constants/_colors.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
$color-light-grey: #90a4ae;
$color-dark-grey: #455a64;
$color-red: #e13110;
$color-green: #1e7e34;
81 changes: 81 additions & 0 deletions src/test/unit/components/member-profile/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Profile from '@components/member-profile';
import { TaskContextProvider } from '@store/tasks/tasks-context';
import { UserContextProvider } from '@store/user/user-context';
import { KeyboardProvider } from '@store/keyboard/context';
import MemberRoleUpdate from '@components/member-role-update';

const notaMember = {
roles: {
Expand All @@ -19,6 +20,7 @@ const isaMember = {

const initialUserContext = {
isSuperUser: true,
showMemberRoleUpdateModal: true,
};

jest.mock('next/router', () => {
Expand All @@ -32,6 +34,18 @@ jest.mock('next/router', () => {
});

describe('Members Profile', () => {
let portalContainer;

beforeEach(() => {
portalContainer = document.createElement('div');
portalContainer.id = 'memberRoleUpdateModal'; // Make sure it has the same ID as in your component
document.body.appendChild(portalContainer);
});

afterEach(() => {
// Clean up the portal container after each test
document.body.removeChild(portalContainer);
});
it('Should render member status properly', () => {
render(
<KeyboardProvider
Expand Down Expand Up @@ -90,4 +104,71 @@ describe('Members Profile', () => {
expect(icon).toBeDefined();
expect(icon).toHaveAttribute('src', 'icons/info.png');
});
it('Should render memberRoleUpdateModal', () => {
render(
<KeyboardProvider
initialValue={{
isOptionKeyPressed: true,
setIsOptionKeyPressed: jest.fn(),
}}
>
<UserContextProvider value={initialUserContext}>
<TaskContextProvider>
<Profile membersData={notaMember} />
</TaskContextProvider>
</UserContextProvider>
</KeyboardProvider>
);

const memberRoleUpdateModal = screen.getByTestId('memberRoleUpdateModal');
expect(memberRoleUpdateModal).toBeInTheDocument();
});
it('renders the button in the MemberRoleUpdate', () => {
render(
<KeyboardProvider
initialValue={{
isOptionKeyPressed: true,
setIsOptionKeyPressed: jest.fn(),
}}
>
<UserContextProvider value={initialUserContext}>
<MemberRoleUpdate />
</UserContextProvider>
</KeyboardProvider>
);

const promoteButton = screen.getByTestId('promoteDemoteButton');
const archiveUnarchiveButton = screen.getByTestId('archiveUnArchiveButton');
expect(promoteButton).toBeInTheDocument();
expect(promoteButton.textContent).toEqual('Promote to Member');
expect(archiveUnarchiveButton).toBeInTheDocument();
expect(archiveUnarchiveButton.textContent).toEqual('Archive Member');
});
it('Should render the reason text box in the MemberRoleUpdate, when ?dev=true in the query', () => {
render(
<KeyboardProvider
initialValue={{
isOptionKeyPressed: true,
setIsOptionKeyPressed: jest.fn(),
}}
>
<UserContextProvider value={initialUserContext}>
<MemberRoleUpdate />
</UserContextProvider>
</KeyboardProvider>
);

const archiveUnarchiveButton = screen.getByTestId('archiveUnArchiveButton');
const reasonTextBox = screen.getByTestId('reasonTextBox');
const reasonInputLabel = screen.getByTestId('reasonInputLabel');

expect(archiveUnarchiveButton).toBeInTheDocument();
expect(archiveUnarchiveButton.textContent).toEqual('Archive Member');
expect(reasonInputLabel.textContent).toEqual('Reason:');
expect(reasonTextBox).toBeInTheDocument();
expect(reasonTextBox).toHaveAttribute(
'placeholder',
'Enter the reason for archiving the user'
);
});
});

2 comments on commit 2d5f1ff

@vercel
Copy link

@vercel vercel bot commented on 2d5f1ff Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 2d5f1ff Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

members-app – ./

members-app-git-main-rds-team.vercel.app
members.realdevsquad.com
members-app-rds-team.vercel.app

Please sign in to comment.