diff --git a/package-lock.json b/package-lock.json index 939b1bc4..422e1d0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4680,8 +4680,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "optional": true + "dev": true }, "to-regex-range": { "version": "5.0.1", diff --git a/src/constants/index.js b/src/constants/index.js index 5237b12e..98a0c5c4 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -12,6 +12,11 @@ export const DAY_FORMAT = "MM/DD/YYYY"; */ export const TEAM_MEMBERS_PER_PAGE = 5; +/** + * How many teams show per page by default + */ +export const TEAMS_PER_PAGE = 20; + /** * How many position candidates show per page by default */ diff --git a/src/routes/MyTeamsDetails/index.jsx b/src/routes/MyTeamsDetails/index.jsx index 51f124d3..2f0d5c75 100644 --- a/src/routes/MyTeamsDetails/index.jsx +++ b/src/routes/MyTeamsDetails/index.jsx @@ -15,14 +15,9 @@ import TeamSummary from "./components/TeamSummary"; import TeamMembers from "./components/TeamMembers"; import TeamPositions from "./components/TeamPositions"; import { useAsync } from "react-use"; -import { - getAuthUserTokens, -} from "@topcoder/micro-frontends-navbar-app"; const MyTeamsDetails = ({ teamId }) => { - const authUserTokens = useAsync(getAuthUserTokens); - const tokenV3 = authUserTokens.value ? authUserTokens.value.tokenV3 : null; - const [team, loadingError] = useData(getTeamById, tokenV3, teamId); + const [team, loadingError] = useData(getTeamById, teamId); return ( diff --git a/src/routes/MyTeamsList/index.jsx b/src/routes/MyTeamsList/index.jsx index 819f97c5..e173c2f1 100644 --- a/src/routes/MyTeamsList/index.jsx +++ b/src/routes/MyTeamsList/index.jsx @@ -3,35 +3,89 @@ * * Page for the list of teams. */ -import React from "react"; +import React, { useCallback, useState, useEffect } from "react"; +import _ from "lodash"; import LayoutContainer from "components/LayoutContainer"; import PageHeader from "components/PageHeader"; -import { useData } from "../../hooks/useData"; +import Input from "components/Input"; +import Pagination from "components/Pagination"; import { getMyTeams } from "../../services/teams"; import TeamCard from "./components/TeamCard"; import TeamCardGrid from "./components/TeamCardGrid"; import LoadingIndicator from "../../components/LoadingIndicator"; -import { useAsync } from "react-use"; -import { - getAuthUserTokens, -} from "@topcoder/micro-frontends-navbar-app"; +import { useDebounce } from "react-use"; +import { TEAMS_PER_PAGE } from "constants"; +import "./styles.module.scss"; const MyTeamsList = () => { - const authUserTokens = useAsync(getAuthUserTokens); - const tokenV3 = authUserTokens.value ? authUserTokens.value.tokenV3 : null; - const [myTeams, loadingError] = useData(getMyTeams, tokenV3); + let [myTeams, setMyTeams] = useState(null); + const [filter, setFilter] = useState(""); + const [tempFilter, setTempFilter] = React.useState(''); + const [loadingError, setLoadingError] = useState(false); + const [page, setPage] = useState(1); + const [total, setTotal] = useState(0); + const onFilterChange = (evt) => { + setTempFilter(evt.target.value) + } + + useDebounce((value) => { + console.log('xxxx', value) + setFilter(tempFilter); + setPage(1); + }, 200, [tempFilter]); + + useEffect(() => { + setMyTeams(null); + getMyTeams(filter, page, TEAMS_PER_PAGE) + .then((response) => { + setMyTeams(response.data); + setTotal(response.headers["x-total"]); + }) + .catch((responseError) => { + setLoadingError(responseError); + }); + }, [filter, page]); + + const onPageClick = useCallback( + (newPage) => { + setPage(newPage); + }, + [setPage] + ); return ( - + + } + /> + {myTeams && myTeams.length === 0 && (
No teams found
)} {!myTeams ? ( ) : ( - - {myTeams.map((team) => ( - - ))} - + <> + + {myTeams.map((team) => ( + + ))} + + {myTeams.length > 0 && ( +
+ +
+ )} + )}
); diff --git a/src/routes/MyTeamsList/styles.module.scss b/src/routes/MyTeamsList/styles.module.scss new file mode 100644 index 00000000..b2158822 --- /dev/null +++ b/src/routes/MyTeamsList/styles.module.scss @@ -0,0 +1,36 @@ +@import "styles/include"; + +.filter-input { + width: 380px; + background-color: #FFFFFF; + border: 1px solid #AAAAAA; + border-radius: 6px; + box-sizing: border-box; + color: #2A2A2A; + font-size: 14px; + height: 40px; + line-height: 38px; + outline: none; + padding: 0 15px; + + &::placeholder { + color: #AAAAAA; + } +} + +.empty { + text-align: center; +} + +.pagination-wrapper { + margin-top: 20px; + margin-right: 20px; + display: flex; + justify-content: flex-end; +} + +@media (max-width: 650px) { + .filter-input { + width: 100%; + } +} diff --git a/src/routes/PositionDetails/actions/index.js b/src/routes/PositionDetails/actions/index.js index c5278a01..e7fdf31a 100644 --- a/src/routes/PositionDetails/actions/index.js +++ b/src/routes/PositionDetails/actions/index.js @@ -2,7 +2,6 @@ * Position Details page actions */ import { getPositionDetails, patchPositionCandidate } from "services/teams"; -import { getAuthUserTokens } from "@topcoder/micro-frontends-navbar-app"; import { ACTION_TYPE } from "constants"; /** @@ -16,12 +15,7 @@ import { ACTION_TYPE } from "constants"; export const loadPosition = (teamId, positionId) => ({ type: ACTION_TYPE.LOAD_POSITION, payload: async () => { - const tokens = await getAuthUserTokens(); - const response = await getPositionDetails( - tokens.tokenV3, - teamId, - positionId - ); + const response = await getPositionDetails(teamId, positionId); return response.data; }, @@ -42,9 +36,7 @@ export const loadPosition = (teamId, positionId) => ({ export const updateCandidate = (candidateId, partialCandidateData) => ({ type: ACTION_TYPE.UPDATE_CANDIDATE, payload: async () => { - const tokens = await getAuthUserTokens(); const response = await patchPositionCandidate( - tokens.tokenV3, candidateId, partialCandidateData ); @@ -61,4 +53,4 @@ export const updateCandidate = (candidateId, partialCandidateData) => ({ */ export const resetPositionState = () => ({ type: ACTION_TYPE.RESET_POSITION_STATE, -}) +}); diff --git a/src/routes/PositionDetails/hooks/useTeamPositionsState.js b/src/routes/PositionDetails/hooks/useTeamPositionsState.js index 6095d3f6..2f196abb 100644 --- a/src/routes/PositionDetails/hooks/useTeamPositionsState.js +++ b/src/routes/PositionDetails/hooks/useTeamPositionsState.js @@ -24,7 +24,7 @@ export const useTeamPositionsState = (teamId, positionId) => { // clear state when we leave the page return () => { dispatch(resetPositionState()); - } + }; }, [dispatch, teamId, positionId]); // bind actions to dispatch method diff --git a/src/routes/PositionDetails/reducers/index.js b/src/routes/PositionDetails/reducers/index.js index f522c503..4b01961c 100644 --- a/src/routes/PositionDetails/reducers/index.js +++ b/src/routes/PositionDetails/reducers/index.js @@ -76,14 +76,10 @@ const reducer = (state = initialState, action) => { }); case ACTION_TYPE.UPDATE_CANDIDATE_SUCCESS: - return patchCandidateInState( - state, - action.meta.candidateId, - { - updating: false, - ...action.payload, - } - ); + return patchCandidateInState(state, action.meta.candidateId, { + updating: false, + ...action.payload, + }); case ACTION_TYPE.UPDATE_CANDIDATE_ERROR: return patchCandidateInState(state, action.meta.candidateId, { @@ -92,7 +88,7 @@ const reducer = (state = initialState, action) => { }); default: - return state + return state; } }; diff --git a/src/services/requestInterceptor.js b/src/services/requestInterceptor.js new file mode 100644 index 00000000..ec369c2b --- /dev/null +++ b/src/services/requestInterceptor.js @@ -0,0 +1,21 @@ +import axios from "axios"; +import store from "../store"; +import { getAuthUserTokens } from "@topcoder/micro-frontends-navbar-app"; + +export const axiosInstance = axios.create({ + headers: { + "Content-Type": "application/json", + }, +}); + +// request interceptor to pass auth token +axiosInstance.interceptors.request.use((config) => { + return getAuthUserTokens() + .then(({ tokenV3: token }) => { + config.headers["Authorization"] = `Bearer ${token}`; + return config; + }) + .catch((err) => { + return config; + }); +}); diff --git a/src/services/teams.js b/src/services/teams.js index 65e158bb..b9121c46 100644 --- a/src/services/teams.js +++ b/src/services/teams.js @@ -1,84 +1,59 @@ /** * Topcoder TaaS Service */ -import axios from "axios"; +import { axiosInstance as axios } from "./requestInterceptor"; import config from "../../config"; /** * Get my teams. - * - * @param {string} tokenV3 login token + * @param {string|number} name team name + * @param {number} page current page + * @param {number} perPage perPage * * @returns {Promise} list of teams */ -export const getMyTeams = (tokenV3) => { - if (!tokenV3) { - return Promise.resolve({ - data: null, - }); +export const getMyTeams = (name, page = 1, perPage) => { + let query = `page=${page}&perPage=${perPage}`; + if (name) { + query += `&name=${name}`; } - return axios.get(`${config.API.V5}/taas-teams`, { - headers: { Authorization: `Bearer ${tokenV3}` }, - }); + + return axios.get(`${config.API.V5}/taas-teams?${query}`); }; /** * Get team by id. * - * @param {string} tokenV3 login token * @param {string|number} teamId team id * * @returns {Promise<{}>} team object */ -export const getTeamById = (tokenV3, teamId) => { - if (!tokenV3) { - return Promise.resolve({ - data: null, - }); - } - return axios.get(`${config.API.V5}/taas-teams/${teamId}`, { - headers: { Authorization: `Bearer ${tokenV3}` }, - }); +export const getTeamById = (teamId) => { + return axios.get(`${config.API.V5}/taas-teams/${teamId}`); }; /** * Get team position details. * - * @param {string} tokenV3 login token * @param {string|number} teamId team id * @param {string|number} positionId position id * * @returns {Promise} job object */ -export const getPositionDetails = (tokenV3, teamId, positionId) => { - if (!tokenV3) { - return Promise.resolve({ - data: null, - }); - } - return axios.get(`${config.API.V5}/taas-teams/${teamId}/jobs/${positionId}`, { - headers: { Authorization: `Bearer ${tokenV3}` }, - }); +export const getPositionDetails = (teamId, positionId) => { + return axios.get(`${config.API.V5}/taas-teams/${teamId}/jobs/${positionId}`); }; /** * Patch Position Candidate * - * @param {string} tokenV3 login token * @param {string} candidateId position candidate id * * @returns {Promise} position candidate */ -export const patchPositionCandidate = ( - tokenV3, - candidateId, - partialCandidateData -) => { +export const patchPositionCandidate = (candidateId, partialCandidateData) => { return axios.patch( `${config.API.V5}/jobCandidates/${candidateId}`, - partialCandidateData, - { - headers: { Authorization: `Bearer ${tokenV3}` }, - } + partialCandidateData ); }; diff --git a/src/utils/format.js b/src/utils/format.js index fef9e488..c6786d00 100644 --- a/src/utils/format.js +++ b/src/utils/format.js @@ -129,5 +129,7 @@ export const formatReportIssueUrl = (subject) => { * @returns {string} request an extension URL */ export const formatRequestExtensionUrl = (subject) => { - return `mailto:${EMAIL_REQUEST_EXTENSION}?subject=${encodeURIComponent(subject)}`; + return `mailto:${EMAIL_REQUEST_EXTENSION}?subject=${encodeURIComponent( + subject + )}`; };