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

Commit 241083d

Browse files
authored
Merge pull request #104 from mbaghel/feature/member-management
fix: issue #19
2 parents be7ec0d + 4b02817 commit 241083d

File tree

25 files changed

+392
-157
lines changed

25 files changed

+392
-157
lines changed

config/dev.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,13 @@ module.exports = {
99
*/
1010
CONNECT_WEBSITE_URL: "https://connect.topcoder-dev.com",
1111

12-
/**
13-
* Email to report issues to
14-
*/
15-
EMAIL_REPORT_ISSUE: "[email protected]",
16-
1712
/**
1813
* Email to request extension
1914
*/
2015
EMAIL_REQUEST_EXTENSION: "[email protected]",
2116

2217
API: {
2318
V5: "https://api.topcoder-dev.com/v5",
24-
V3: "https://api.topcoder-dev.com/v3"
19+
V3: "https://api.topcoder-dev.com/v3",
2520
},
2621
};

config/prod.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,13 @@ module.exports = {
99
*/
1010
CONNECT_WEBSITE_URL: "https://connect.topcoder.com",
1111

12-
/**
13-
* Email to report issues to
14-
*/
15-
EMAIL_REPORT_ISSUE: "[email protected]",
16-
1712
/**
1813
* Email to request extension
1914
*/
2015
EMAIL_REQUEST_EXTENSION: "[email protected]",
2116

2217
API: {
2318
V5: "https://api.topcoder.com/v5",
24-
V3: "https://api.topcoder.com/v3"
19+
V3: "https://api.topcoder.com/v3",
2520
},
2621
};

src/components/Babge/styles.module.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
line-height: 20px;
77
border-radius: 5px;
88
height: 20px;
9-
color: #FFFFFF;
9+
color: #ffffff;
1010
font-size: 12px;
1111
letter-spacing: 0.5px;
1212
text-align: left;
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* A centered spinner used to indicate loading in modals
3+
*/
4+
5+
import React from "react";
6+
import PT from "prop-types";
7+
import Loader from "react-loader-spinner";
8+
import "./styles.module.scss";
9+
10+
function CenteredSpinner(props) {
11+
const {
12+
type = "TailSpin",
13+
color = "#00BFFF",
14+
height = 80,
15+
width = 80,
16+
} = props;
17+
18+
return (
19+
<div styleName="loader-container">
20+
<Loader type={type} color={color} height={height} width={width} />
21+
</div>
22+
);
23+
}
24+
25+
CenteredSpinner.propTypes = {
26+
type: PT.string,
27+
color: PT.string,
28+
height: PT.number,
29+
width: PT.number,
30+
};
31+
32+
export default CenteredSpinner;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.loader-container {
2+
display: flex;
3+
align-items: center;
4+
flex-direction: column;
5+
}

src/components/ReactSelect/index.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const ReactSelect = (props) => {
6363
borderRadius: "5px",
6464
}),
6565
dropdownIndicator: () => ({
66-
display: "none"
66+
display: "none",
6767
}),
6868
};
6969

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Report popup actions
3+
*/
4+
5+
export const ACTION_TYPE = {
6+
OPEN_REPORT: "OPEN_REPORT",
7+
CLOSE_REPORT: "CLOSE_REPORT",
8+
};
9+
10+
/**
11+
* Action to populate the report info and open a report popup
12+
* @param {string} teamName Team name
13+
* @param {string|number} teamId Tead ID
14+
* @param {string} memberHandle Member handle
15+
*/
16+
export const openReport = (teamName, teamId, memberHandle) => ({
17+
type: ACTION_TYPE.OPEN_REPORT,
18+
payload: { teamName, teamId, memberHandle },
19+
});
20+
21+
/**
22+
* Action to close a report popup
23+
*/
24+
export const closeReport = () => ({
25+
type: ACTION_TYPE.CLOSE_REPORT,
26+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Use report popup hook
3+
*/
4+
5+
import { useDispatch } from "react-redux";
6+
import { openReport } from "../actions";
7+
8+
/**
9+
* Hook to allow report popup to be opened by any other component
10+
* (as long as it is mounted somewhere in the tree)
11+
*
12+
* @returns func A wrapper around the open report dispatch
13+
*/
14+
export const useReportPopup = () => {
15+
const dispatch = useDispatch();
16+
17+
return (teamName, teamId, memberHandle) => {
18+
dispatch(openReport(teamName, teamId, memberHandle));
19+
};
20+
};

src/components/ReportPopup/index.jsx

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* A report popup used to report issues with teams or team members
3+
*/
4+
5+
import React, { useCallback, useState } from "react";
6+
import { useSelector, useDispatch } from "react-redux";
7+
import { toastr } from "react-redux-toastr";
8+
import { closeReport } from "./actions";
9+
import BaseModal from "components/BaseModal";
10+
import TextArea from "components/TextArea";
11+
import Button from "../Button";
12+
import { postReport } from "services/teams";
13+
import CenteredSpinner from "components/CenteredSpinner";
14+
15+
function ReportPopup() {
16+
const { isOpen, teamName, teamId, memberHandle } = useSelector(
17+
(state) => state.reportPopup
18+
);
19+
20+
const dispatch = useDispatch();
21+
const [textVal, setTextVal] = useState("");
22+
const [isLoading, setIsLoading] = useState(false);
23+
24+
const submitReport = () => {
25+
setIsLoading(true);
26+
27+
postReport(teamName, teamId, textVal, memberHandle)
28+
.then(() => {
29+
setIsLoading(false);
30+
closeModal();
31+
toastr.success("Report submitted successfully");
32+
})
33+
.catch((err) => {
34+
setIsLoading(false);
35+
36+
// Response interceptor passes only error body
37+
// use this to identify server-side errors
38+
if (err instanceof Error) {
39+
toastr.error("Report failed");
40+
} else {
41+
toastr.error("Report failed", err.message);
42+
}
43+
});
44+
};
45+
46+
const button = (
47+
<Button
48+
onClick={() => submitReport()}
49+
size="medium"
50+
isSubmit
51+
disabled={textVal.trim().length < 1 || isLoading}
52+
>
53+
Submit
54+
</Button>
55+
);
56+
57+
const closeModal = useCallback(() => {
58+
dispatch(closeReport());
59+
setTextVal("");
60+
}, [dispatch]);
61+
62+
return (
63+
<BaseModal
64+
open={isOpen}
65+
onClose={closeModal}
66+
title={`Issue Report - ${teamName}${
67+
memberHandle ? " - " + memberHandle : ""
68+
}`}
69+
button={button}
70+
disabled={isLoading}
71+
>
72+
{isLoading ? (
73+
<CenteredSpinner />
74+
) : (
75+
<TextArea
76+
value={textVal}
77+
onChange={setTextVal}
78+
placeholder="Describe your issue"
79+
/>
80+
)}
81+
</BaseModal>
82+
);
83+
}
84+
85+
export default ReportPopup;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Reducer for Report popup
3+
*/
4+
5+
import { ACTION_TYPE } from "../actions";
6+
7+
const initialState = {
8+
teamName: undefined,
9+
teamId: undefined,
10+
memberHandle: undefined,
11+
isOpen: false,
12+
};
13+
14+
const reducer = (state = initialState, action) => {
15+
switch (action.type) {
16+
case ACTION_TYPE.OPEN_REPORT:
17+
return {
18+
...state,
19+
...action.payload,
20+
isOpen: true,
21+
};
22+
23+
case ACTION_TYPE.CLOSE_REPORT:
24+
return {
25+
...state,
26+
isOpen: false,
27+
};
28+
29+
default:
30+
return state;
31+
}
32+
};
33+
34+
export default reducer;

src/reducers/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import { combineReducers } from "redux";
55
import { reducer as toastrReducer } from "react-redux-toastr";
66
import positionDetailsReducer from "../routes/PositionDetails/reducers";
77
import teamMembersReducer from "../routes/TeamAccess/reducers";
8+
import reportPopupReducer from "../components/ReportPopup/reducers";
89

910
const rootReducer = combineReducers({
1011
toastr: toastrReducer,
1112
positionDetails: positionDetailsReducer,
1213
teamMembers: teamMembersReducer,
14+
reportPopup: reportPopupReducer,
1315
});
1416

1517
export default rootReducer;

src/routes/MyTeamsDetails/components/TeamMembers/index.jsx

+4-6
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,18 @@ import { TEAM_MEMBERS_PER_PAGE } from "constants";
1919
import {
2020
formatDateRange,
2121
formatMoney,
22-
formatReportIssueUrl,
2322
formatRequestExtensionUrl,
2423
} from "utils/format";
2524
import Input from "components/Input";
2625
import { skillShape } from "components/SkillsList";
26+
import { useReportPopup } from "components/ReportPopup/hooks/useReportPopup";
2727

2828
const TeamMembers = ({ team }) => {
2929
const { resources, jobs } = team;
3030
const [filter, setFilter] = useState("");
3131

32+
const showReportPopup = useReportPopup();
33+
3234
const filteredMembers = useMemo(
3335
() =>
3436
resources
@@ -161,11 +163,7 @@ const TeamMembers = ({ team }) => {
161163
{
162164
label: "Report an Issue",
163165
action: () => {
164-
window.open(
165-
formatReportIssueUrl(
166-
`Issue with ${member.handle} on ${team.name}`
167-
)
168-
);
166+
showReportPopup(team.name, team.id, member.handle);
169167
},
170168
},
171169
{

src/routes/MyTeamsDetails/components/TeamSummary/index.jsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ import {
1010
formatConnectProjectUrl,
1111
formatMoney,
1212
formatRemainingTimeForTeam,
13-
formatReportIssueUrl,
1413
} from "utils/format";
1514
import IconClock from "../../../../assets/images/icon-clock.svg";
1615
import IconMoney from "../../../../assets/images/icon-money.svg";
1716
// import IconRating from "../../../../assets/images/icon-rating.svg";
1817
import Button from "components/Button";
18+
import { useReportPopup } from "components/ReportPopup/hooks/useReportPopup";
1919
// import Rating from "components/Rating";
2020
import "./styles.module.scss";
2121

2222
const TeamSummary = ({ team }) => {
23+
const showReportPopup = useReportPopup();
24+
2325
return (
2426
<div styleName="team-summary">
2527
<div styleName="data-items">
@@ -58,7 +60,9 @@ const TeamSummary = ({ team }) => {
5860
<Button
5961
type="warning"
6062
size="medium"
61-
href={formatReportIssueUrl(`TaaS Issue: ${team.name}`)}
63+
onClick={() => {
64+
showReportPopup(team.name, team.id);
65+
}}
6266
target="_blank"
6367
>
6468
REPORT AN ISSUE

src/routes/MyTeamsDetails/index.jsx

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import TeamSummary from "./components/TeamSummary";
1515
import TeamMembers from "./components/TeamMembers";
1616
import TeamPositions from "./components/TeamPositions";
1717
import withAuthentication from "../../hoc/withAuthentication";
18+
import ReportPopup from "components/ReportPopup";
1819

1920
const MyTeamsDetails = ({ teamId }) => {
2021
const [team, loadingError] = useData(getTeamById, teamId);
@@ -35,6 +36,7 @@ const MyTeamsDetails = ({ teamId }) => {
3536
/>
3637
</>
3738
)}
39+
<ReportPopup />
3840
</Page>
3941
);
4042
};

src/routes/MyTeamsList/components/TeamCard/index.jsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ import {
1515
formatDateRange,
1616
formatMoney,
1717
formatRemainingTimeForTeam,
18-
formatReportIssueUrl,
1918
formatConnectProjectUrl,
2019
} from "utils/format";
2120
import AvatarGroup from "components/AvatarGroup";
2221
import ThreeDotsMenu from "components/ThreeDotsMenu";
22+
import { useReportPopup } from "components/ReportPopup/hooks/useReportPopup";
2323

2424
const TeamCard = ({ team }) => {
25+
const showReportPopup = useReportPopup();
26+
2527
return (
2628
<div styleName="team-card">
2729
<div styleName="three-dots-menu">
@@ -30,7 +32,7 @@ const TeamCard = ({ team }) => {
3032
{
3133
label: "Open in Connect",
3234
action: () => {
33-
window.open(formatConnectProjectUrl(team.id));
35+
console.log("Issue reported!");
3436
},
3537
},
3638
{
@@ -48,7 +50,7 @@ const TeamCard = ({ team }) => {
4850
{
4951
label: "Report an Issue",
5052
action: () => {
51-
window.open(formatReportIssueUrl(`TaaS Issue: ${team.name}`));
53+
showReportPopup(team.name, team.id);
5254
},
5355
},
5456
]}

0 commit comments

Comments
 (0)