diff --git a/config/dev.js b/config/dev.js index 17625e9e..54870756 100644 --- a/config/dev.js +++ b/config/dev.js @@ -9,11 +9,6 @@ module.exports = { */ CONNECT_WEBSITE_URL: "https://connect.topcoder-dev.com", - /** - * Email to report issues to - */ - EMAIL_REPORT_ISSUE: "support+team-issue@topcoder-dev.com", - /** * Email to request extension */ @@ -21,6 +16,6 @@ module.exports = { API: { V5: "https://api.topcoder-dev.com/v5", - V3: "https://api.topcoder-dev.com/v3" + V3: "https://api.topcoder-dev.com/v3", }, }; diff --git a/config/prod.js b/config/prod.js index 8f86b506..e2069074 100644 --- a/config/prod.js +++ b/config/prod.js @@ -9,11 +9,6 @@ module.exports = { */ CONNECT_WEBSITE_URL: "https://connect.topcoder.com", - /** - * Email to report issues to - */ - EMAIL_REPORT_ISSUE: "support+team-issue@topcoder.com", - /** * Email to request extension */ @@ -21,6 +16,6 @@ module.exports = { API: { V5: "https://api.topcoder.com/v5", - V3: "https://api.topcoder.com/v3" + V3: "https://api.topcoder.com/v3", }, }; diff --git a/src/components/Babge/styles.module.scss b/src/components/Babge/styles.module.scss index bd2fe66b..940d29e2 100644 --- a/src/components/Babge/styles.module.scss +++ b/src/components/Babge/styles.module.scss @@ -6,7 +6,7 @@ line-height: 20px; border-radius: 5px; height: 20px; - color: #FFFFFF; + color: #ffffff; font-size: 12px; letter-spacing: 0.5px; text-align: left; diff --git a/src/components/CenteredSpinner/index.jsx b/src/components/CenteredSpinner/index.jsx new file mode 100644 index 00000000..3d7f7529 --- /dev/null +++ b/src/components/CenteredSpinner/index.jsx @@ -0,0 +1,32 @@ +/** + * A centered spinner used to indicate loading in modals + */ + +import React from "react"; +import PT from "prop-types"; +import Loader from "react-loader-spinner"; +import "./styles.module.scss"; + +function CenteredSpinner(props) { + const { + type = "TailSpin", + color = "#00BFFF", + height = 80, + width = 80, + } = props; + + return ( +
+ +
+ ); +} + +CenteredSpinner.propTypes = { + type: PT.string, + color: PT.string, + height: PT.number, + width: PT.number, +}; + +export default CenteredSpinner; diff --git a/src/components/CenteredSpinner/styles.module.scss b/src/components/CenteredSpinner/styles.module.scss new file mode 100644 index 00000000..c4914bcc --- /dev/null +++ b/src/components/CenteredSpinner/styles.module.scss @@ -0,0 +1,5 @@ +.loader-container { + display: flex; + align-items: center; + flex-direction: column; +} diff --git a/src/components/ReactSelect/index.jsx b/src/components/ReactSelect/index.jsx index facca399..e2e64a17 100644 --- a/src/components/ReactSelect/index.jsx +++ b/src/components/ReactSelect/index.jsx @@ -63,7 +63,7 @@ const ReactSelect = (props) => { borderRadius: "5px", }), dropdownIndicator: () => ({ - display: "none" + display: "none", }), }; diff --git a/src/components/ReportPopup/actions/index.js b/src/components/ReportPopup/actions/index.js new file mode 100644 index 00000000..c1ce410a --- /dev/null +++ b/src/components/ReportPopup/actions/index.js @@ -0,0 +1,26 @@ +/** + * Report popup actions + */ + +export const ACTION_TYPE = { + OPEN_REPORT: "OPEN_REPORT", + CLOSE_REPORT: "CLOSE_REPORT", +}; + +/** + * Action to populate the report info and open a report popup + * @param {string} teamName Team name + * @param {string|number} teamId Tead ID + * @param {string} memberHandle Member handle + */ +export const openReport = (teamName, teamId, memberHandle) => ({ + type: ACTION_TYPE.OPEN_REPORT, + payload: { teamName, teamId, memberHandle }, +}); + +/** + * Action to close a report popup + */ +export const closeReport = () => ({ + type: ACTION_TYPE.CLOSE_REPORT, +}); diff --git a/src/components/ReportPopup/hooks/useReportPopup.js b/src/components/ReportPopup/hooks/useReportPopup.js new file mode 100644 index 00000000..9a1c376e --- /dev/null +++ b/src/components/ReportPopup/hooks/useReportPopup.js @@ -0,0 +1,20 @@ +/** + * Use report popup hook + */ + +import { useDispatch } from "react-redux"; +import { openReport } from "../actions"; + +/** + * Hook to allow report popup to be opened by any other component + * (as long as it is mounted somewhere in the tree) + * + * @returns func A wrapper around the open report dispatch + */ +export const useReportPopup = () => { + const dispatch = useDispatch(); + + return (teamName, teamId, memberHandle) => { + dispatch(openReport(teamName, teamId, memberHandle)); + }; +}; diff --git a/src/components/ReportPopup/index.jsx b/src/components/ReportPopup/index.jsx new file mode 100644 index 00000000..cbdde73f --- /dev/null +++ b/src/components/ReportPopup/index.jsx @@ -0,0 +1,85 @@ +/** + * A report popup used to report issues with teams or team members + */ + +import React, { useCallback, useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { toastr } from "react-redux-toastr"; +import { closeReport } from "./actions"; +import BaseModal from "components/BaseModal"; +import TextArea from "components/TextArea"; +import Button from "../Button"; +import { postReport } from "services/teams"; +import CenteredSpinner from "components/CenteredSpinner"; + +function ReportPopup() { + const { isOpen, teamName, teamId, memberHandle } = useSelector( + (state) => state.reportPopup + ); + + const dispatch = useDispatch(); + const [textVal, setTextVal] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const submitReport = () => { + setIsLoading(true); + + postReport(teamName, teamId, textVal, memberHandle) + .then(() => { + setIsLoading(false); + closeModal(); + toastr.success("Report submitted successfully"); + }) + .catch((err) => { + setIsLoading(false); + + // Response interceptor passes only error body + // use this to identify server-side errors + if (err instanceof Error) { + toastr.error("Report failed"); + } else { + toastr.error("Report failed", err.message); + } + }); + }; + + const button = ( + + ); + + const closeModal = useCallback(() => { + dispatch(closeReport()); + setTextVal(""); + }, [dispatch]); + + return ( + + {isLoading ? ( + + ) : ( +