Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cancel mentorship request #814

Merged
merged 13 commits into from
Jul 6, 2021
Next Next commit
feat(mentorships): cancel request
  • Loading branch information
moshfeu committed Jul 2, 2021
commit a9446a660a579264c4fabb6738447c0238f780c6
31 changes: 30 additions & 1 deletion src/Me/MentorshipReq/MentorshipReq.js
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import { UsersList } from './UsersList';
import { STATUS } from '../../helpers/mentorship';
import { useModal } from '../../context/modalContext/ModalContext';
import { AcceptModal, DeclineModal } from '../Modals/MentorshipReqModals';
import CancelModal from '../Modals/MentorshipReqModals/CancelModal';

const PREV_STATUS = {
[STATUS.viewed]: STATUS.new,
@@ -35,6 +36,21 @@ const MentorshipReq = () => {
setSelectedReq({ id, username });
openAcceptModal();
};

const onCancel = ({ id, username }) => {
setSelectedReq({ id, username });
openCancelModal();
};

const cancelRequest = async message => {
await updateReqStatus(
{ id: selectedReq.id, userId },
STATUS.cancelled,
message
);
closeCancelModal();
};

const onDeclineReq = ({ id, status, username }) => {
if (status !== PREV_STATUS[STATUS.rejected]) return;
setSelectedReq({ id, username });
@@ -100,6 +116,15 @@ const MentorshipReq = () => {
[selectedReq?.id]
);

const [openCancelModal, closeCancelModal] = useModal(
<CancelModal
username={selectedReq?.username}
onSave={cancelRequest}
onClose={() => setSelectedReq(null)}
/>,
[selectedReq?.id]
);

useEffect(() => {
isMount.current = true;
return () => {
@@ -124,7 +149,11 @@ const MentorshipReq = () => {
/>
</Card>
<Card title="My Mentorship Requests">
<UsersList requests={menteeState} isLoading={loadingState} />
<UsersList
requests={menteeState}
isLoading={loadingState}
onCancel={onCancel}
/>
</Card>
</>
);
45 changes: 34 additions & 11 deletions src/Me/MentorshipReq/ReqContent.js
Original file line number Diff line number Diff line change
@@ -52,12 +52,25 @@ const ReqContent = ({
status,
onAccept,
onDecline,
onCancel,
isLoading,
isMine,
menteeEmail,
}) => {
const hideBtns =
isMine || [STATUS.approved, STATUS.rejected].includes(status);
const shouldShowButtons = () => {
const requestReviewed = [
STATUS.approved,
STATUS.rejected,
STATUS.cancelled,
].includes(status);

return {
showAccept: onAccept && !requestReviewed,
showDecline: onDecline && !requestReviewed,
showCancel: onCancel && !requestReviewed,
};
};

const { showAccept, showDecline, showCancel } = shouldShowButtons();
return (
<div data-testid="request-content">
<Block>
@@ -76,16 +89,25 @@ const ReqContent = ({
<p>{expectation}</p>
</Block>
)}
{hideBtns ? null : (
<RequestFooter>
<CallToAction>
<RequestFooter>
<CallToAction>
{showCancel && (
<Button skin="secondary" onClick={onCancel}>
Cancel request
</Button>
)}
{showDecline && (
<Button skin="secondary" onClick={onDecline}>
Decline
</Button>
)}
{showAccept && (
<Button skin="primary" onClick={onAccept} isLoading={isLoading}>
Accept
</Button>
</CallToAction>
)}
</CallToAction>
{showAccept && (
<CallToAction>
<Tooltip
title="Don't forget to approve the request if it works for you"
@@ -101,8 +123,8 @@ const ReqContent = ({
</a>
</Tooltip>
</CallToAction>
</RequestFooter>
)}
)}
</RequestFooter>
</div>
);
};
@@ -111,8 +133,9 @@ ReqContent.propTypes = {
message: PropTypes.string.isRequired,
background: PropTypes.string,
expectation: PropTypes.string,
onAccept: PropTypes.func.isRequired,
onDecline: PropTypes.func.isRequired,
onAccept: PropTypes.func,
onDecline: PropTypes.func,
onCancel: PropTypes.func,
};

export default ReqContent;
18 changes: 14 additions & 4 deletions src/Me/MentorshipReq/UsersList.tsx
Original file line number Diff line number Diff line change
@@ -40,6 +40,7 @@ type UsersListProps = {
onSelect(params: MentorshipRequestOnSelectPayload): void;
onDecline(params: MentorshipRequestOnResponsePayload): Promise<void>;
onAccept(params: MentorshipRequestOnResponsePayload): Promise<void>;
onCancel(params: MentorshipRequestOnResponsePayload): Promise<void>;
};

type RenderListPayload = UsersListProps & {
@@ -48,7 +49,7 @@ type RenderListPayload = UsersListProps & {

const STATUS_THEME: { [key in Status]: RichItemTagTheme } = {
[STATUS.approved]: 'primary',
[STATUS.cancelled]: 'disabled',
[STATUS.cancelled]: 'cancelled',
[STATUS.new]: 'secondary',
[STATUS.rejected]: 'danger',
[STATUS.viewed]: 'checked',
@@ -60,6 +61,7 @@ const renderList = ({
onSelect,
onAccept,
onDecline,
onCancel,
isLoading,
}: RenderListPayload) =>
requests?.map(
@@ -105,14 +107,20 @@ const renderList = ({
>
<ReqContent
status={status}
isMine={isMine}
message={message}
isLoading={isLoading}
background={background}
expectation={expectation}
menteeEmail={mentee.email}
onAccept={() => onAccept({ id, status, username })}
onDecline={() => onDecline({ id, status, username })}
onAccept={
onAccept ? () => onAccept({ id, status, username }) : null
}
onDecline={
onDecline ? () => onDecline({ id, status, username }) : null
}
onCancel={
onCancel ? () => onCancel({ id, status, username }) : null
}
/>
</RichItem>
</li>
@@ -125,6 +133,7 @@ export const UsersList = ({
requests,
onAccept,
onDecline,
onCancel,
onSelect: onItemSelect,
}: UsersListProps) => {
const { expandId, onSelect } = useExpendableRichItems();
@@ -143,6 +152,7 @@ export const UsersList = ({
isLoading,
onAccept,
onDecline,
onCancel,
onSelect: (item: MentorshipRequestOnSelectPayload) => {
onItemSelect?.(item);
onSelect?.(item.id);
90 changes: 90 additions & 0 deletions src/Me/Modals/MentorshipReqModals/CancelModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import BodyStyle from './style';
import { useRef, useState } from 'react';
import { Modal } from '../Modal';
import TextArea from '../../components/Textarea';
import { Loader } from '../../../components/Loader';
import styled from 'styled-components';
import FormField from '../../components/FormField';
import RadioButton, { RadioButtonGroup } from '../../components/RadioButton';

const Spinner = styled(Loader)`
position: absolute;
top: 25%;
`;

const Body = styled(BodyStyle)`
justify-content: flex-start;
p {
text-align: left;
}
`;

type CanceledModalProps = {
username: string;
onSave(reason: string): void;
onClose(): void;
};

const REASONS = {
'1': `I've already found a mentor`,
'2': `I'm no longer looking for a mentor`,
'3': `Other`,
};

type Reason = keyof typeof REASONS;

const CancelModal = ({ username, onSave, onClose }: CanceledModalProps) => {
const [loadingState, setLoadingState] = useState(false);
const [reasonOption, setReasonOption] = useState<Reason>('1');
const reason = useRef<string>('');

return (
<Modal
center
title="Cancel mentorship request"
onSave={() => {
setLoadingState(true);
onSave(reasonOption === '3' ? reason.current : REASONS[reasonOption]);
}}
onClose={onClose}
>
<Body>
{loadingState && <Spinner />}
<div>
<p>
You are about to cancel your mentorship request to <b>{username}</b>{' '}
and that’s ok.
</p>
<p>Please let {username} know why you are canceling the request.</p>
<label>
<FormField label="">
<RadioButtonGroup<Reason>
value={reasonOption}
onChange={setReasonOption}
>
{Object.entries(REASONS).map(([value, label]) => (
<RadioButton
name="reason"
LabelComponent={label}
value={value}
/>
))}
</RadioButtonGroup>
</FormField>
<TextArea
onChange={e => {
reason.current = e.target.value;
}}
disabled={reasonOption !== '3'}
placeholder="I'm canceling the mentorship because..."
cols={35}
rows={7}
/>
</label>
</div>
</Body>
</Modal>
);
};

export default CancelModal;
17 changes: 17 additions & 0 deletions src/Me/Modals/MentorshipReqModals/stories/CancelModal.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import CancelModal from '../CancelModal';
import { action } from '@storybook/addon-actions';
import { StoriesContainer } from '../../../../stories/StoriesContainer';

export default {
title: 'Mentorship/Canceled',
component: CancelModal,
};

const onSave = action('onSave');
const onClose = action('onClose');

export const Default = () => (
<StoriesContainer>
<CancelModal username="John Doe" onSave={onSave} onClose={onClose} />
</StoriesContainer>
);
3 changes: 2 additions & 1 deletion src/Me/components/RichList/ReachItemTypes.d.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@ export type RichItemTagTheme =
| 'secondary'
| 'danger'
| 'checked'
| 'disabled';
| 'disabled'
| 'cancelled';

export type RichItemProps = {
id: string;
13 changes: 7 additions & 6 deletions src/Me/components/RichList/RichItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FC } from 'react';
import styled from 'styled-components';
import { RichItemProps } from './ReachItemTypes';
import { RichItemProps, RichItemTagTheme } from './ReachItemTypes';

const RichItem: FC<RichItemProps> = ({
id,
@@ -33,12 +33,13 @@ const RichItem: FC<RichItemProps> = ({
);
};

const themeColours = {
primary: '#69D5B1',
secondary: '#F3CA3E',
danger: '#FF5F58',
const themeColours: { [key in RichItemTagTheme]: string } = {
primary: '#69d5b1',
secondary: '#f3ca3e',
danger: '#ff5f58',
checked: '#69d579',
disabled: 'e0e0e0',
disabled: '#e0e0e0',
cancelled: '#ff5f58',
};

const ItemRow = styled.div`