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

Commit 848668b

Browse files
committed
feat: shortlist/reject candidates
ref issue #25
1 parent 358c2d7 commit 848668b

File tree

17 files changed

+468
-40
lines changed

17 files changed

+468
-40
lines changed

package-lock.json

Lines changed: 88 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"axios": "^0.21.0",
6060
"classnames": "^2.2.6",
6161
"express": "^4.17.1",
62+
"immutability-helper": "^3.1.1",
6263
"lodash": "^4.17.20",
6364
"moment": "^2.29.1",
6465
"prop-types": "^15.7.2",
@@ -67,7 +68,13 @@
6768
"react-dom": "^16.12.0",
6869
"react-outside-click-handler": "^1.3.0",
6970
"react-popper": "^2.2.3",
70-
"react-use": "^15.3.4"
71+
"react-redux": "^7.2.2",
72+
"react-redux-toastr": "^7.6.5",
73+
"react-use": "^15.3.4",
74+
"redux": "^4.0.5",
75+
"redux-logger": "^3.0.6",
76+
"redux-promise-middleware": "^6.1.2",
77+
"redux-thunk": "^2.3.0"
7178
},
7279
"browserslist": [
7380
"last 1 version",

src/constants/index.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,20 @@ export const CANDIDATES_SORT_OPTIONS = [
121121
{ label: "Skill Matched", value: CANDIDATES_SORT_BY.SKILL_MATCHED },
122122
{ label: "Handle", value: CANDIDATES_SORT_BY.HANDLE },
123123
];
124+
125+
/**
126+
* All action types
127+
*/
128+
export const ACTION_TYPE = {
129+
LOAD_POSITION: "LOAD_POSITION",
130+
LOAD_POSITION_PENDING: "LOAD_POSITION_PENDING",
131+
LOAD_POSITION_SUCCESS: "LOAD_POSITION_SUCCESS",
132+
LOAD_POSITION_ERROR: "LOAD_POSITION_ERROR",
133+
134+
RESET_POSITION_STATE: "RESET_POSITION_STATE",
135+
136+
UPDATE_CANDIDATE: "UPDATE_CANDIDATE",
137+
UPDATE_CANDIDATE_PENDING: "UPDATE_CANDIDATE_PENDING",
138+
UPDATE_CANDIDATE_SUCCESS: "UPDATE_CANDIDATE_SUCCESS",
139+
UPDATE_CANDIDATE_ERROR: "UPDATE_CANDIDATE_ERROR",
140+
};

src/reducers/index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Root Redux Reducer
3+
*/
4+
import { combineReducers } from "redux";
5+
import { reducer as toastrReducer } from "react-redux-toastr";
6+
import positionDetailsReducer from "../routes/PositionDetails/reducers";
7+
8+
const rootReducer = combineReducers({
9+
toastr: toastrReducer,
10+
positionDetails: positionDetailsReducer,
11+
});
12+
13+
export default rootReducer;

src/root.component.jsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
import React from "react";
2+
import { Provider } from "react-redux";
23
import { Router } from "@reach/router";
34
import MyTeamsList from "./routes/MyTeamsList";
45
import MyTeamsDetails from "./routes/MyTeamsDetails";
56
import PositionDetails from "./routes/PositionDetails";
6-
import "./styles/main.module.scss";
7+
import ReduxToastr from 'react-redux-toastr'
8+
import store from "./store";
9+
import "./styles/main.vendor.scss";
10+
import styles from "./styles/main.module.scss";
711

812
export default function Root() {
913
return (
10-
<div styleName="topcoder-micro-frontends-teams-app">
11-
<Router>
12-
<MyTeamsList path="/taas/myteams" />
13-
<MyTeamsDetails path="/taas/myteams/:teamId" />
14-
<PositionDetails path="/taas/myteams/:teamId/positions/:positionId" />
15-
</Router>
14+
<div className={styles['topcoder-micro-frontends-teams-app']}>
15+
<Provider store={store}>
16+
<Router>
17+
<MyTeamsList path="/taas/myteams" />
18+
<MyTeamsDetails path="/taas/myteams/:teamId" />
19+
<PositionDetails path="/taas/myteams/:teamId/positions/:positionId" />
20+
</Router>
21+
22+
{/* Global config for Toastr popups */}
23+
<ReduxToastr
24+
timeOut={4000}
25+
position="bottom-left"
26+
transitionIn="fadeIn"
27+
transitionOut="fadeOut"
28+
/>
29+
</Provider>
1630
</div>
1731
);
1832
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* Position Details page actions
3+
*/
4+
import { getPositionDetails, patchPositionCandidate } from "services/teams";
5+
import { getAuthUserTokens } from "@topcoder/micro-frontends-navbar-app";
6+
import { ACTION_TYPE } from "constants";
7+
8+
/**
9+
* Load Team Position details (team job)
10+
*
11+
* @param {string} teamId team id
12+
* @param {string} positionId position id
13+
*
14+
* @returns {Promise<any>} loaded data or error
15+
*/
16+
export const loadPosition = (teamId, positionId) => ({
17+
type: ACTION_TYPE.LOAD_POSITION,
18+
payload: async () => {
19+
const tokens = await getAuthUserTokens();
20+
const response = await getPositionDetails(
21+
tokens.tokenV3,
22+
teamId,
23+
positionId
24+
);
25+
26+
return response.data;
27+
},
28+
meta: {
29+
teamId,
30+
positionId,
31+
},
32+
});
33+
34+
/**
35+
* Update candidate on the server and in Redux store
36+
*
37+
* @param {string} candidateId position candidate id
38+
* @param {object} partialCandidateData partial candidate data
39+
*
40+
* @returns {Promise} updated candidate data or error
41+
*/
42+
export const updateCandidate = (candidateId, partialCandidateData) => ({
43+
type: ACTION_TYPE.UPDATE_CANDIDATE,
44+
payload: async () => {
45+
const tokens = await getAuthUserTokens();
46+
const response = await patchPositionCandidate(
47+
tokens.tokenV3,
48+
candidateId,
49+
partialCandidateData
50+
);
51+
52+
return response.data;
53+
},
54+
meta: {
55+
candidateId,
56+
},
57+
});
58+
59+
/**
60+
* Reset position state
61+
*/
62+
export const resetPositionState = () => ({
63+
type: ACTION_TYPE.RESET_POSITION_STATE,
64+
})

src/routes/PositionDetails/PositionCandidates/index.jsx renamed to src/routes/PositionDetails/components/PositionCandidates/index.jsx

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import User from "components/User";
2020
import SkillsSummary from "components/SkillsSummary";
2121
import Button from "components/Button";
2222
import Pagination from "components/Pagination";
23-
import IconResume from "../../../assets/images/icon-resume.svg";
24-
import { skillShape } from "components/SkillsList";
23+
import IconResume from "../../../../assets/images/icon-resume.svg";
24+
import { toastr } from "react-redux-toastr";
2525

2626
/**
2727
* Generates a function to sort candidates
@@ -45,6 +45,7 @@ const createSortCandidatesMethod = (sortBy) => (candidate1, candidate2) => {
4545
const PositionCandidates = ({
4646
candidates,
4747
candidateStatus,
48+
updateCandidate,
4849
}) => {
4950
const [sortBy, setSortBy] = useState(CANDIDATES_SORT_BY.SKILL_MATCHED);
5051
const filteredCandidates = useMemo(
@@ -86,6 +87,42 @@ const PositionCandidates = ({
8687
[setPage]
8788
);
8889

90+
const markCandidateShortlisted = useCallback(
91+
(candidateId) => {
92+
updateCandidate(candidateId, {
93+
status: CANDIDATE_STATUS.SHORTLIST,
94+
})
95+
.then(() => {
96+
toastr.success("Candidate is marked as interested.");
97+
})
98+
.catch((error) => {
99+
toastr.error(
100+
"Failed to mark candidate as interested",
101+
error.toString()
102+
);
103+
});
104+
},
105+
[updateCandidate]
106+
);
107+
108+
const markCandidateRejected = useCallback(
109+
(candidateId) => {
110+
updateCandidate(candidateId, {
111+
status: CANDIDATE_STATUS.REJECTED,
112+
})
113+
.then(() => {
114+
toastr.success("Candidate is marked as not interested.");
115+
})
116+
.catch((error) => {
117+
toastr.error(
118+
"Failed to mark candidate as not interested",
119+
error.toString()
120+
);
121+
});
122+
},
123+
[updateCandidate]
124+
);
125+
89126
return (
90127
<div styleName="position-candidates">
91128
<CardHeader
@@ -125,24 +162,31 @@ const PositionCandidates = ({
125162
limit={7}
126163
/>
127164
{candidate.resumeLink && (
128-
<a
129-
href={`${candidate.resumeLink}`}
130-
styleName="resume-link"
131-
>
165+
<a href={`${candidate.resumeLink}`} styleName="resume-link">
132166
<IconResume />
133167
Download Resume
134168
</a>
135169
)}
136170
</div>
137171
<div styleName="table-cell cell-action">
138-
{candidateStatus === CANDIDATE_STATUS.SHORTLIST ? (
139-
<Button type="primary">Schedule Interview</Button>
140-
) : (
172+
{candidateStatus === CANDIDATE_STATUS.OPEN && (
141173
<>
142174
Interested in this candidate?
143175
<div styleName="actions">
144-
<Button type="secondary">No</Button>
145-
<Button type="primary">Yes</Button>
176+
<Button
177+
type="secondary"
178+
onClick={() => markCandidateRejected(candidate.id)}
179+
disabled={candidate.updating}
180+
>
181+
No
182+
</Button>
183+
<Button
184+
type="primary"
185+
onClick={() => markCandidateShortlisted(candidate.id)}
186+
disabled={candidate.updating}
187+
>
188+
Yes
189+
</Button>
146190
</div>
147191
</>
148192
)}

0 commit comments

Comments
 (0)