Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 4fef143

Browse files
committed
fix: issue #575
1 parent fffd587 commit 4fef143

File tree

4 files changed

+292
-19
lines changed

4 files changed

+292
-19
lines changed

src/routes/CancelInterviewPage/index.jsx

Lines changed: 117 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,48 @@
11
/**
22
* Cancel Interview Page
33
*
4-
* Allows users to cancel an interview in Nylas
4+
* Allows users to cancel an interview
55
*/
66
import React, { useEffect, useState } from "react";
7+
import { toastr } from "react-redux-toastr";
8+
import Page from "components/Page";
9+
import moment from "moment";
10+
import PageHeader from "components/PageHeader";
11+
import Button from "components/Button";
12+
import Input from "components/Input";
13+
import LoadingIndicator from "components/LoadingIndicator";
714
import { getAuthUserProfile } from "@topcoder/micro-frontends-navbar-app";
8-
import { getInterview } from "services/interviews";
15+
import { getJobCandidateById } from "services/teams";
16+
import { getJobById } from "services/jobs";
17+
import { getInterview, cancelInterview } from "services/interviews";
918
import { INTERVIEW_STATUS } from "constants";
1019
import withAuthentication from "../../hoc/withAuthentication";
11-
import NylasSchedulerPage from "components/NylasSchedulerPage";
20+
import "./styles.module.scss";
1221

1322
const CancelInterviewPage = ({ interviewId }) => {
14-
const [schedulingPageUrl, setSchedulingPageUrl] = useState(null);
23+
const [interview, setInterview] = useState(null);
24+
const [jobTitle, setJobTitle] = useState("");
1525
const [errorMessage, setErrorMessage] = useState(null);
26+
const [cancelled, setCancelled] = useState(false);
27+
const [cancelling, setCancelling] = useState(false);
28+
const [reason, setReason] = useState("");
29+
30+
const onCancelInterview = () => {
31+
setCancelling(true);
32+
cancelInterview(
33+
interview.nylasPageSlug,
34+
interview.nylasEventEditHash,
35+
reason
36+
)
37+
.then(({ s }) => {
38+
setCancelled(true);
39+
setCancelling(false);
40+
})
41+
.catch((err) => {
42+
setCancelling(false);
43+
toastr.error("Error cancel interview", err.message);
44+
});
45+
};
1646

1747
useEffect(() => {
1848
getAuthUserProfile()
@@ -26,16 +56,23 @@ const CancelInterviewPage = ({ interviewId }) => {
2656
.then((profile) => {
2757
getInterview(interviewId)
2858
.then(({ data }) => {
29-
if (data.status === INTERVIEW_STATUS.SCHEDULED ||data.status === INTERVIEW_STATUS.RESCHEDULED) {
30-
setSchedulingPageUrl(
31-
`https://schedule.nylas.com/${data.nylasPageSlug}/cancel/${data.nylasEventEditHash}?email=${
32-
profile.email
33-
}&name=${encodeURI(
34-
profile.firstName + " " + profile.lastName
35-
)}&prefilled_readonly=true`
59+
if (
60+
data.status === INTERVIEW_STATUS.SCHEDULED ||
61+
data.status === INTERVIEW_STATUS.RESCHEDULED
62+
) {
63+
setInterview(data);
64+
65+
return getJobCandidateById(
66+
data.jobCandidateId
67+
).then(({ data: { jobId } }) =>
68+
getJobById(jobId).then(({ data: { title } }) =>
69+
setJobTitle(title)
70+
)
3671
);
3772
} else {
38-
setErrorMessage(`This interview has status ${data.status} and cannot be cancelled.`);
73+
setErrorMessage(
74+
`This interview has status ${data.status} and cannot be cancelled.`
75+
);
3976
}
4077
})
4178
.catch((err) => {
@@ -45,13 +82,74 @@ const CancelInterviewPage = ({ interviewId }) => {
4582
}, [interviewId]);
4683

4784
return (
48-
<NylasSchedulerPage
49-
pageTitle="Cancel Interview"
50-
src={schedulingPageUrl}
51-
errorMessage={errorMessage}
52-
pageHeader="Cancel Interview"
53-
iframeTitle="Nylas Cancel Schedule Page"
54-
/>
85+
<>
86+
<Page title="Cancel Interview">
87+
{!interview || !jobTitle ? (
88+
<LoadingIndicator error={errorMessage} />
89+
) : (
90+
<>
91+
<PageHeader title="Cancel Interview" />
92+
<div>
93+
<div styleName="top-bar">
94+
<h1>Job Interview for "{jobTitle}"</h1>
95+
</div>
96+
<div styleName="shadowed-content">
97+
<div styleName="slot-cancelview">
98+
<div styleName="left-panel">
99+
<div styleName="booking-summary">
100+
<h2>
101+
{moment(interview.startTimestamp).format("dddd")} <br />{" "}
102+
{moment(interview.startTimestamp).format(
103+
"MMMM DD, yyyy"
104+
)}
105+
</h2>
106+
<h4>
107+
{moment(interview.startTimestamp).format("H:mm A")} -{" "}
108+
{moment(interview.endTimestamp).format("H:mm A")}
109+
</h4>
110+
<p>{interview.guestTimezone}</p>
111+
</div>
112+
</div>
113+
<div styleName="divider"></div>
114+
<div styleName="right-panel">
115+
{cancelled ? (
116+
<form styleName="cancelled-form">
117+
<div styleName="tick-mark"></div>
118+
<h3>Thank you</h3>
119+
<div>your booking has been cancelled</div>
120+
</form>
121+
) : (
122+
<form>
123+
<h3>Are you sure?</h3>
124+
<div>
125+
<div styleName="label">Reason for canceling *</div>
126+
<div>
127+
<Input
128+
placeholder="Please add a brief reason"
129+
onChange={(e) => {
130+
setReason(e.target.value);
131+
}}
132+
/>
133+
</div>
134+
</div>
135+
<Button
136+
onClick={onCancelInterview}
137+
size="medium"
138+
type="primary"
139+
disabled={!reason.length || cancelling}
140+
>
141+
{cancelling ? "Cancelling Event" : "Cancel Event"}
142+
</Button>
143+
</form>
144+
)}
145+
</div>
146+
</div>
147+
</div>
148+
</div>
149+
</>
150+
)}
151+
</Page>
152+
</>
55153
);
56154
};
57155

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
@import "styles/include";
2+
3+
.top-bar {
4+
display: flex;
5+
flex-direction: row;
6+
align-items: center;
7+
justify-content: space-between;
8+
min-width: 600px;
9+
margin-top: 20px;
10+
width: 100%;
11+
h1 {
12+
margin: 0;
13+
font-weight: 500;
14+
height: 90px;
15+
display: flex;
16+
align-items: center;
17+
justify-content: center;
18+
font-size: 2em;
19+
flex-shrink: 0;
20+
flex: 1 1;
21+
}
22+
}
23+
24+
.shadowed-content {
25+
margin: auto;
26+
width: 100%;
27+
max-width: 900px;
28+
box-sizing: border-box;
29+
text-align: left;
30+
border: 1px solid transparent;
31+
border-radius: 6px;
32+
box-shadow: none;
33+
position: relative;
34+
border: 1px solid #d6d6d6;
35+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
36+
}
37+
38+
.slot-cancelview {
39+
display: flex;
40+
flex-direction: row;
41+
position: relative;
42+
box-sizing: border-box;
43+
align-items: center;
44+
z-index: 200;
45+
background: #fff;
46+
border-radius: 6px;
47+
height: 420px;
48+
width: 100%;
49+
50+
h2 {
51+
font-size: 2.1rem;
52+
line-height: 3rem;
53+
white-space: pre-wrap;
54+
}
55+
56+
h4 {
57+
font-size: 1.2rem;
58+
line-height: 1.7rem;
59+
font-weight: 400;
60+
white-space: pre-wrap;
61+
margin: 10px ;
62+
}
63+
64+
}
65+
66+
.left-panel {
67+
display: flex;
68+
align-items: center;
69+
justify-content:center;
70+
padding-bottom: 70px ;
71+
flex: 1 1;
72+
73+
p {
74+
opacity: 0.6;
75+
}
76+
}
77+
78+
.booking-summary {
79+
text-align: center;
80+
}
81+
82+
.divider {
83+
align-self: stretch;
84+
width: 1px ;
85+
flex-grow: 0;
86+
border-left: 1px solid #eee;
87+
margin-top: 20px;
88+
margin-bottom: 20px;
89+
}
90+
91+
.tick-mark {
92+
position: relative;
93+
display: inline-block;
94+
width: 45px;
95+
height: 50px;
96+
margin-bottom: 30px;
97+
&:before {
98+
position: absolute;
99+
left: 0;
100+
top: 50%;
101+
height: 50%;
102+
width: 6px;
103+
background-color: #336699;
104+
content: "";
105+
transform: translateX(10px) rotate(-45deg);
106+
transform-origin: left bottom;
107+
}
108+
&:after {
109+
position: absolute;
110+
left: 0;
111+
bottom: 0;
112+
height: 6px;
113+
width: 100%;
114+
background-color: #336699;
115+
content: "";
116+
transform: translateX(10px) rotate(-45deg);
117+
transform-origin: left bottom;
118+
}
119+
}
120+
121+
.right-panel {
122+
box-sizing: border-box;
123+
flex: 1.4 1;
124+
125+
.cancelled-form {
126+
margin-top: -80px;
127+
text-align: center;
128+
}
129+
130+
form {
131+
padding: 30px 70px 0;
132+
h3 {
133+
margin-bottom: 15px;
134+
font-size: 1.17em;
135+
font-weight: bold;
136+
}
137+
button {
138+
margin: 20px auto;
139+
}
140+
}
141+
input {
142+
margin-top: 10px;
143+
}
144+
.label {
145+
color: rgb(88, 88, 88);
146+
text-align: left;
147+
}
148+
}

src/services/interviews.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,19 @@ export const confirmInterview = (candidateJobId, data) => {
6161
export const getInterview = (interviewId) => {
6262
return axios.get(`${config.API.V5}/getInterview/${interviewId}`);
6363
};
64+
65+
/**
66+
* Cancel interview
67+
* @param {String} nylasPageSlug the nylasPageSlug
68+
* @param {String} nylasEventEditHash The nylasEventEditHash
69+
* @param {String} reason The cancel reason
70+
* @returns Promise
71+
*/
72+
export const cancelInterview = (nylasPageSlug, nylasEventEditHash, reason) => {
73+
return axios.post(
74+
`https://api.schedule.nylas.com/schedule/${nylasPageSlug}/${nylasEventEditHash}/cancel`,
75+
{
76+
reason,
77+
}
78+
);
79+
};

src/services/teams.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,17 @@ export const getPositionDetails = (teamId, positionId) => {
7070
return axios.get(`${config.API.V5}/taas-teams/${teamId}/jobs/${positionId}`);
7171
};
7272

73+
/**
74+
* Get Job Candidate.
75+
*
76+
* @param {string} candidateId position candidate id
77+
*
78+
* @returns {Promise<object{}>} position candidate
79+
*/
80+
export const getJobCandidateById = (candidateId) => {
81+
return axios.get(`${config.API.V5}/jobCandidates/${candidateId}`);
82+
};
83+
7384
/**
7485
* Patch Position Candidate
7586
*

0 commit comments

Comments
 (0)