From 869364a777affbba43a4f84147a0b54ccfc77acb Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Sat, 20 Nov 2021 00:34:44 +0800 Subject: [PATCH 01/16] [Bug Bash] Fix 1 (#173) * issue #145 * fix: issue #110 * Fixed #96 * Reset Pagination to 1 When Choosing Filter * linter * fix: issue #126 * fix: issue #106 * issue #143 * Prevent Trigger Change On Date Picker Input * fix: issue #85 * Fixes #95 * fix: issue #76 * issue #97 * ci:deploying * Add Close Button in Calendar * issue #132 * issue 132: focus selection range when having value * issue 132: fixed duration of a week having 8 days * Clear Challenge Filter On Menu Click * Reset Pagination to 1 * linter * Implement Not Found Error * Use debounce function for sort by * issue #75 * Minor update completed * Updated totalPrizesTo * issue 75: refixed * fix: issue #76 * fix: issue #85 * ci:deploying * Fixes #95 * issue #75 * issue 75: refixed * Use debounce function for sort by * issue #97 * Reset Pagination to 1 When Choosing Filter * linter * issue #145 * Implement Not Found Error * Prevent Trigger Change On Date Picker Input * issue #132 * issue 132: focus selection range when having value * issue 132: fixed duration of a week having 8 days * =resolve merge conflict * Replace Track QA * Reset Pagination * Minor updates done for fixing #96 * fix: lint * fix: refine * restore ci Co-authored-by: Nguyen Viet Co-authored-by: yoution Co-authored-by: Shivam Kumar Singh Co-authored-by: M Fikri A Co-authored-by: Nguyen Viet <36178659+nqviet@users.noreply.github.com> --- .circleci/config.yml | 1 - config/dev.js | 4 + config/prod.js | 4 + .../DateRangePicker/DateInput/index.jsx | 10 +- src/components/DateRangePicker/helpers.js | 24 +++-- src/components/DateRangePicker/index.jsx | 55 +++++++--- src/components/DateRangePicker/style.scss | 13 ++- src/components/DropdownTerms/index.jsx | 4 +- src/components/NotFoundError/index.jsx | 16 +++ src/components/NotFoundError/styles.scss | 21 ++++ src/components/Pagination/index.jsx | 90 +++++++++------- src/components/TextInput/index.jsx | 2 +- .../challenge-detail/Submissions/index.jsx | 4 +- .../ChallengeItem/TrackIcon/styles.scss | 1 + src/containers/Challenges/Listing/index.jsx | 100 +++++++++++------- src/containers/Challenges/index.jsx | 21 +++- .../Filter/ChallengeFilter/index.jsx | 23 +++- .../Submission/Submit/Header/index.jsx | 6 +- .../Submission/Submit/Uploading/index.jsx | 20 +++- src/containers/challenge-detail/index.jsx | 17 +++ src/reducers/challenges.js | 2 + src/routers/challenge-list/index.jsx | 22 ++-- src/services/challenges.js | 2 +- ...topcoder-micro-frontends-challenges-app.js | 3 +- src/utils/index.js | 8 +- src/utils/lifeCycle.js | 24 +++-- src/utils/url.js | 20 +++- 27 files changed, 367 insertions(+), 150 deletions(-) create mode 100644 src/components/NotFoundError/index.jsx create mode 100644 src/components/NotFoundError/styles.scss diff --git a/.circleci/config.yml b/.circleci/config.yml index 4be26f9..21fcb93 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -77,7 +77,6 @@ workflows: branches: only: - dev - - submission-page # Production builds are exectuted only on tagged commits to the # master branch. diff --git a/config/dev.js b/config/dev.js index 5ca5059..632f11b 100644 --- a/config/dev.js +++ b/config/dev.js @@ -139,4 +139,8 @@ module.exports = { process.env.FILESTACK_SUBMISSION_CONTAINER || "topcoder-dev-submissions-dmz", }, + /* Time in MS to wait before refreshing challenge details after register + * and unregister. Used to allow API sufficent time to update. + */ + CHALLENGE_DETAILS_REFRESH_DELAY: 3000, }; diff --git a/config/prod.js b/config/prod.js index ab93169..bb2f09b 100644 --- a/config/prod.js +++ b/config/prod.js @@ -134,4 +134,8 @@ module.exports = { process.env.FILESTACK_SUBMISSION_CONTAINER || "topcoder-dev-submissions-dmz", }, + /* Time in MS to wait before refreshing challenge details after register + * and unregister. Used to allow API sufficent time to update. + */ + CHALLENGE_DETAILS_REFRESH_DELAY: 3000, }; diff --git a/src/components/DateRangePicker/DateInput/index.jsx b/src/components/DateRangePicker/DateInput/index.jsx index 1d62dfb..f543b52 100644 --- a/src/components/DateRangePicker/DateInput/index.jsx +++ b/src/components/DateRangePicker/DateInput/index.jsx @@ -20,6 +20,7 @@ const DateInput = ({ onClickCalendarIcon, onStartEndDateChange, placeholder, + enterToSubmit, }) => { const ref = useRef(null); const [focused, setFocused] = useState(false); @@ -125,7 +126,14 @@ const DateInput = ({ size="xs" value={rangeText} onChange={(value) => { - onChangeRangeTextDebounced.current(() => onChangeRangeText(value)); + if (!enterToSubmit) { + onChangeRangeTextDebounced.current(() => + onChangeRangeText(value) + ); + } + }} + onEnterKey={(value) => { + onChangeRangeText(value); }} placeholder={placeholder} /> diff --git a/src/components/DateRangePicker/helpers.js b/src/components/DateRangePicker/helpers.js index cef8582..df745c9 100644 --- a/src/components/DateRangePicker/helpers.js +++ b/src/components/DateRangePicker/helpers.js @@ -50,39 +50,45 @@ const staticRangeHandler = { * @return {object[]} list of defined ranges */ export function createStaticRanges() { - const now = moment().utcOffset(0); - const pastWeek = now.clone().subtract(1, "week"); - const pastMonth = now.clone().subtract(1, "month"); - const past6Months = now.clone().subtract(6, "month"); - const pastYear = now.clone().subtract(1, "year"); + const today = moment(); + const endOfToday = today.set({ + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + }); + const pastWeek = endOfToday.clone().subtract(1, "week"); + const pastMonth = endOfToday.clone().subtract(1, "month"); + const past6Months = endOfToday.clone().subtract(6, "month"); + const pastYear = endOfToday.clone().subtract(1, "year"); const ranges = [ { label: "Past Week", range: () => ({ startDate: pastWeek.startOf("day").toDate(), - endDate: now.endOf("day").toDate(), + endDate: endOfToday.toDate(), }), }, { label: "Past Month", range: () => ({ startDate: pastMonth.startOf("day").toDate(), - endDate: now.endOf("day").toDate(), + endDate: endOfToday.toDate(), }), }, { label: "Past 6 Months", range: () => ({ startDate: past6Months.startOf("day").toDate(), - endDate: now.endOf("day").toDate(), + endDate: endOfToday.toDate(), }), }, { label: "Past Year", range: () => ({ startDate: pastYear.startOf("day").toDate(), - endDate: now.endOf("day").toDate(), + endDate: endOfToday.toDate(), }), }, ]; diff --git a/src/components/DateRangePicker/index.jsx b/src/components/DateRangePicker/index.jsx index 64b5f52..67a6b31 100644 --- a/src/components/DateRangePicker/index.jsx +++ b/src/components/DateRangePicker/index.jsx @@ -16,7 +16,7 @@ import { } from "./helpers"; function DateRangePicker(props) { - const { id, range, onChange, placeholder } = props; + const { id, range, onChange, placeholder, enterToSubmit = false } = props; const [rangeString, setRangeString] = useState({ startDateString: "", @@ -344,8 +344,19 @@ function DateRangePicker(props) { const onPreviewChange = (date) => { if (!(date instanceof Date)) { setPreview(null); - setActiveDate(null); - setFocusedRange([0, focusedRange[1]]); + + // --- + // workaround for fixing issue 132: + // - set the active range's background to transparent color + // to prevent the calendar auto focusing on the day of today by default when no + // start date nor end date are set. + // - does not set focus on the empty selection range when mouse leaves. + // --- + + // setActiveDate(null); + if (range.startDate || range.endDate) { + setFocusedRange([0, focusedRange[1]]); + } return; } @@ -485,7 +496,7 @@ function DateRangePicker(props) { startDate: activeDate, endDate: activeDate, key: "active", - color: "#D8FDD8", + color: preview ? "#D8FDD8" : "#D8FDD800", }, ]; } @@ -538,6 +549,7 @@ function DateRangePicker(props) { }} onStartEndDateChange={onStartEndDateChange} placeholder={placeholder} + enterToSubmit={enterToSubmit} />
@@ -562,18 +574,29 @@ function DateRangePicker(props) { preview={preview} onPreviewChange={onPreviewChange} /> - +
+ + +
)} diff --git a/src/components/DateRangePicker/style.scss b/src/components/DateRangePicker/style.scss index 8a9caa9..ff8c7c8 100644 --- a/src/components/DateRangePicker/style.scss +++ b/src/components/DateRangePicker/style.scss @@ -391,6 +391,7 @@ $darkGreen: #0AB88A;; z-index: 10; @include phone { + width: 100vw; position: fixed; top: 0; left: 0; @@ -402,7 +403,15 @@ $darkGreen: #0AB88A;; border-radius: 0; } - .reset-button { + .calendar-footer { + width: 100%; + + @include phone { + padding: 0 20px; + } + } + + .calendar-button { @include roboto-bold; width: 71px; @@ -421,7 +430,7 @@ $darkGreen: #0AB88A;; height: 26px; line-height: 27px; font-size: 12px; - margin: 20px 12px 0; + margin: 0 12px 0; } } } diff --git a/src/components/DropdownTerms/index.jsx b/src/components/DropdownTerms/index.jsx index 5b3ced8..b605974 100644 --- a/src/components/DropdownTerms/index.jsx +++ b/src/components/DropdownTerms/index.jsx @@ -51,8 +51,8 @@ function DropdownTerms({ } }, [focused, selectedOption]); useEffect(() => { - setInternalTerms(terms); - }, [terms]); + setInternalTerms(terms); // eslint-disable-next-line react-hooks/exhaustive-deps + }, [terms && terms.length]); const CustomReactSelectRow = React.forwardRef( ({ className, option, children, onSelect }, ref) => diff --git a/src/components/NotFoundError/index.jsx b/src/components/NotFoundError/index.jsx new file mode 100644 index 0000000..72c7f02 --- /dev/null +++ b/src/components/NotFoundError/index.jsx @@ -0,0 +1,16 @@ +import React from "react"; +import IconNotFound from "assets/icons/not-found.png"; + +import "./styles.scss"; + +const NotFoundError = ({ message }) => ( +
+
+ not found +
+

404 Not found

+

Sorry, we couldn’t find that page

+
+); + +export default NotFoundError; diff --git a/src/components/NotFoundError/styles.scss b/src/components/NotFoundError/styles.scss new file mode 100644 index 0000000..fb8a1d4 --- /dev/null +++ b/src/components/NotFoundError/styles.scss @@ -0,0 +1,21 @@ +@import "styles/variables"; + +.not-found-error { + padding: 16px 24px; + min-height: 136px; + margin-bottom: 35px; + font-size: $font-size-sm; + line-height: 22px; + text-align: center; + background: $white; + border-radius: $border-radius-lg; + + h1 { + padding: 15px 0 10px; + margin-bottom: 20px; + } + + p { + margin-bottom: 8px; + } +} diff --git a/src/components/Pagination/index.jsx b/src/components/Pagination/index.jsx index 1d7835b..ee625ef 100644 --- a/src/components/Pagination/index.jsx +++ b/src/components/Pagination/index.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useRef, useState, useEffect } from "react"; import PT from "prop-types"; import Dropdown from "../Dropdown"; import { @@ -12,6 +12,20 @@ import "./styles.scss"; const N = PAGINATION_MAX_PAGE_DISPLAY; +const createDisplayPages = (p, n) => { + const pages = []; + for ( + let start = utils.clamp(p - N, 0, n), + end = utils.clamp(p + N, 0, n), + i = start; + i < end; + i += 1 + ) { + pages.push(i); + } + return pages.slice(-N); +}; + /** * Pagination with the first page index being as 0 and the last page index being as `total - 1` */ @@ -20,24 +34,6 @@ const Pagination = ({ length, pageIndex, pageSize, onChange }) => { const perPageOptions = utils.createDropdownOptions(PAGINATION_PER_PAGES); utils.setSelectedDropdownOption(perPageOptions, `${pageSize}`); - const createDisplayPages = (p, n) => { - const pages = []; - for ( - let start = utils.clamp(p - N, 0, n), - end = utils.clamp(p + N, 0, n), - i = start; - i < end; - i += 1 - ) { - pages.push(i); - } - return pages.slice(-N); - }; - - const [displayPages, setDisplayPages] = useState( - createDisplayPages(pageIndex, total) - ); - const onChangePageSize = (options) => { const selectedOption = utils.getSelectedDropdownOption(options); const newPageSize = +selectedOption.label; @@ -59,33 +55,45 @@ const Pagination = ({ length, pageIndex, pageSize, onChange }) => { } }; - const latestPropsRef = useRef(null); - latestPropsRef.current = { displayPages, pageIndex }; + const previousPropsRef = useRef(); + const [displayPages, setDisplayPages] = useState([]); useEffect(() => { - const newTotal = Math.ceil(length / pageSize); - const _pageIndex = latestPropsRef.current.pageIndex; - setDisplayPages(createDisplayPages(_pageIndex, newTotal)); - }, [length, pageSize]); + let _displayPages = displayPages; - useEffect(() => { - const _displayPages = latestPropsRef.current.displayPages; - const start = _displayPages[0]; - const end = _displayPages[_displayPages.length - 1]; - - const updateDisplayPages = []; - if (pageIndex < start) { - for (let i = pageIndex; i < pageIndex + N; i += 1) { - updateDisplayPages.push(i); - } - setDisplayPages(updateDisplayPages); - } else if (pageIndex > end) { - for (let i = pageIndex; i > pageIndex - N; i -= 1) { - updateDisplayPages.unshift(i); + if ( + !previousPropsRef.current || + previousPropsRef.current.length !== length || + previousPropsRef.current.pageSize !== pageSize + ) { + const newTotal = Math.ceil(length / pageSize); + _displayPages = createDisplayPages(pageIndex, newTotal); + setDisplayPages(_displayPages); + } + + if ( + !previousPropsRef.current || + previousPropsRef.current.pageIndex !== pageIndex + ) { + const start = _displayPages[0]; + const end = _displayPages[_displayPages.length - 1]; + + const updateDisplayPages = []; + if (pageIndex < start) { + for (let i = pageIndex; i < pageIndex + N; i += 1) { + updateDisplayPages.push(i); + } + setDisplayPages(updateDisplayPages); + } else if (pageIndex > end) { + for (let i = pageIndex; i > pageIndex - N; i -= 1) { + updateDisplayPages.unshift(i); + } + setDisplayPages(updateDisplayPages); } - setDisplayPages(updateDisplayPages); } - }, [pageIndex]); + + previousPropsRef.current = { length, pageSize, pageIndex }; + }, [length, pageSize, pageIndex, displayPages, setDisplayPages]); const formatPage = (p) => `${p + 1}`; diff --git a/src/components/TextInput/index.jsx b/src/components/TextInput/index.jsx index e543b0a..4ccca63 100644 --- a/src/components/TextInput/index.jsx +++ b/src/components/TextInput/index.jsx @@ -55,7 +55,7 @@ function TextInput({ }} onKeyPress={(e) => { if (e.key === "Enter") { - onEnterKey(); + onEnterKey(e.target.value); } }} /> diff --git a/src/components/challenge-detail/Submissions/index.jsx b/src/components/challenge-detail/Submissions/index.jsx index 7317378..6c17173 100644 --- a/src/components/challenge-detail/Submissions/index.jsx +++ b/src/components/challenge-detail/Submissions/index.jsx @@ -206,8 +206,8 @@ class SubmissionsComponent extends React.Component { valueA = getFinalScore(a); valueB = getFinalScore(b); } else { - valueA = !_.isEmpty(a.review) && a.review[0].score; - valueB = !_.isEmpty(b.review) && b.review[0].score; + valueA = !_.isEmpty(a.review) ? a.review[0].score : 0; + valueB = !_.isEmpty(b.review) ? b.review[0].score : 0; } break; } diff --git a/src/containers/Challenges/Listing/ChallengeItem/TrackIcon/styles.scss b/src/containers/Challenges/Listing/ChallengeItem/TrackIcon/styles.scss index 1528470..489052a 100644 --- a/src/containers/Challenges/Listing/ChallengeItem/TrackIcon/styles.scss +++ b/src/containers/Challenges/Listing/ChallengeItem/TrackIcon/styles.scss @@ -6,6 +6,7 @@ height: 36px; vertical-align: middle; line-height: 1; + cursor: pointer; > svg { position: absolute; diff --git a/src/containers/Challenges/Listing/index.jsx b/src/containers/Challenges/Listing/index.jsx index 17a0ec5..970ba11 100644 --- a/src/containers/Challenges/Listing/index.jsx +++ b/src/containers/Challenges/Listing/index.jsx @@ -3,6 +3,7 @@ import PT from "prop-types"; import _ from "lodash"; import moment from "moment"; import Panel from "../../../components/Panel"; +import ChallengeError from "../Listing/errors/ChallengeError"; import Pagination from "../../../components/Pagination"; import ChallengeItem from "./ChallengeItem"; import TextInput from "../../../components/TextInput"; @@ -35,6 +36,14 @@ const Listing = ({ ); const onSearch = useRef(_.debounce((f) => f(), 1000)); + const onChangeSortBy = (newSortByOptions) => { + const selectedOption = utils.getSelectedDropdownOption(newSortByOptions); + const filterChange = { + sortBy: constants.CHALLENGE_SORT_BY[selectedOption.label], + page: 1, + }; + updateFilter(filterChange); + }; return ( @@ -50,7 +59,10 @@ const Listing = ({ size="xs" onChange={(value) => { onSearch.current(() => { - const filterChange = { search: value }; + const filterChange = { + search: value, + page: 1, + }; updateFilter(filterChange); }); }} @@ -66,15 +78,7 @@ const Listing = ({ label="Sort by" options={sortByOptions} size="xs" - onChange={(newSortByOptions) => { - const selectedOption = utils.getSelectedDropdownOption( - newSortByOptions - ); - const filterChange = { - sortBy: constants.CHALLENGE_SORT_BY[selectedOption.label], - }; - updateFilter(filterChange); - }} + onChange={_.debounce(onChangeSortBy, 1000)} />
{ const d = range.endDate ? moment(range.endDate).toISOString() @@ -90,7 +95,11 @@ const Listing = ({ const s = range.startDate ? moment(range.startDate).toISOString() : null; - const filterChange = { endDateStart: s, startDateEnd: d }; + const filterChange = { + endDateStart: s, + startDateEnd: d, + page: 1, + }; updateFilter(filterChange); }} range={{ @@ -101,38 +110,51 @@ const Listing = ({
- - {challenges.map((challenge, index) => ( -
- { - const filterChange = { tags: [tag] }; - updateFilter(filterChange); - }} - onClickTrack={(track) => { - const filterChange = { tracks: [track] }; + {challenges.length ? ( + + {challenges.map((challenge, index) => ( +
+ { + const filterChange = { + tags: [tag], + page: 1, + }; + updateFilter(filterChange); + }} + onClickTrack={(track) => { + const filterChange = { + tracks: [track.replace("Quality Assurance", "QA")], + page: 1, + }; + updateFilter(filterChange); + }} + isLoggedIn={isLoggedIn} + /> +
+ ))} +
+ { + const filterChange = { + page: utils.pagination.pageIndexToPage(event.pageIndex), + perPage: event.pageSize, + }; updateFilter(filterChange); }} - isLoggedIn={isLoggedIn} />
- ))} -
- { - const filterChange = { - page: utils.pagination.pageIndexToPage(event.pageIndex), - perPage: event.pageSize, - }; - updateFilter(filterChange); - }} - /> -
-
+ + ) : ( + + )} ); }; diff --git a/src/containers/Challenges/index.jsx b/src/containers/Challenges/index.jsx index cb1a484..9ed26b9 100644 --- a/src/containers/Challenges/index.jsx +++ b/src/containers/Challenges/index.jsx @@ -3,7 +3,6 @@ import PT from "prop-types"; import { connect } from "react-redux"; import Listing from "./Listing"; import actions from "../../actions"; -import ChallengeError from "./Listing/errors/ChallengeError"; // import ChallengeRecommendedError from "./Listing/errors/ChallengeRecommendedError"; import * as constants from "../../constants"; import IconListView from "../../assets/icons/list-view.svg"; @@ -15,6 +14,7 @@ import "./styles.scss"; const Challenges = ({ challenges, + challengesMeta, search, page, perPage, @@ -39,6 +39,21 @@ const Challenges = ({ checkIsLoggedIn(); }, []); + // reset pagination + if ( + page > 1 && + challengesMeta.total && + challengesMeta.total > 0 && + challenges.length === 0 + ) { + updateFilter({ + page: 1, + }); + updateQuery({ + page: 1, + }); + } + const BUCKET_OPEN_FOR_REGISTRATION = constants.FILTER_BUCKETS[1]; const isRecommended = recommended && bucket === BUCKET_OPEN_FOR_REGISTRATION; const sortByValue = isRecommended @@ -70,8 +85,7 @@ const Challenges = ({ - {challenges.length === 0 && initialized && } - {challenges.length > 0 && ( + {initialized && ( <> {/*noRecommendedChallenges && */} ({ endDateStart: state.filter.challenge.endDateStart, startDateEnd: state.filter.challenge.startDateEnd, challenges: state.challenges.challenges, + challengesMeta: state.challenges.challengesMeta, bucket: state.filter.challenge.bucket, recommended: state.filter.challenge.recommended, recommendedChallenges: state.challenges.recommendedChallenges, diff --git a/src/containers/Filter/ChallengeFilter/index.jsx b/src/containers/Filter/ChallengeFilter/index.jsx index d28ff5b..bdf2f16 100644 --- a/src/containers/Filter/ChallengeFilter/index.jsx +++ b/src/containers/Filter/ChallengeFilter/index.jsx @@ -90,7 +90,10 @@ const ChallengeFilter = ({ const newTypes = checked ? types.concat(type) : types.filter((i) => i !== type); - const filterChange = { types: newTypes }; + const filterChange = { + types: newTypes, + page: 1, + }; updateFilter(filterChange); }} /> @@ -111,7 +114,10 @@ const ChallengeFilter = ({ const newTracks = checked ? tracks.concat(track) : tracks.filter((i) => i !== track); - const filterChange = { tracks: newTracks }; + const filterChange = { + tracks: newTracks, + page: 1, + }; updateFilter(filterChange); }} /> @@ -132,6 +138,7 @@ const ChallengeFilter = ({ ); const filterChange = { tags: selectedTagOptions.map((tagOption) => tagOption.label), + page: 1, }; updateFilter(filterChange); }} @@ -164,6 +171,7 @@ const ChallengeFilter = ({ } const filterChange = { totalPrizesFrom: value, + page: 1, }; updateFilter(filterChange); }) @@ -196,6 +204,7 @@ const ChallengeFilter = ({ } const filterChange = { totalPrizesTo: value, + page: 1, }; updateFilter(filterChange); }) @@ -237,7 +246,10 @@ const ChallengeFilter = ({ event !== utils.challenge.getCommunityEvent(subCommunity) ); - filterChange = { events: newEvents }; + filterChange = { + events: newEvents, + page: 1, + }; } else { const newGroups = checked ? groups.concat( @@ -248,7 +260,10 @@ const ChallengeFilter = ({ group !== utils.challenge.getCommunityGroup(subCommunity) ); - filterChange = { groups: newGroups }; + filterChange = { + groups: newGroups, + page: 1, + }; } updateFilter(filterChange); diff --git a/src/containers/Submission/Submit/Header/index.jsx b/src/containers/Submission/Submit/Header/index.jsx index 946eef2..447268a 100644 --- a/src/containers/Submission/Submit/Header/index.jsx +++ b/src/containers/Submission/Submit/Header/index.jsx @@ -1,14 +1,16 @@ import React from "react"; import PT from "prop-types"; import { Link } from "@reach/router"; -import config from '../../../../../config' +import config from "../../../../../config"; import "./styles.scss"; const Header = ({ title, challengeId }) => { return (
- +

Back to challenge

diff --git a/src/containers/Submission/Submit/Uploading/index.jsx b/src/containers/Submission/Submit/Uploading/index.jsx index 8c712dd..9f83d8d 100644 --- a/src/containers/Submission/Submit/Uploading/index.jsx +++ b/src/containers/Submission/Submit/Uploading/index.jsx @@ -1,6 +1,6 @@ -import React from "react"; +import React, { useEffect, useRef } from "react"; import PT from "prop-types"; -import { Link } from "@reach/router"; +import { Link, navigate } from "@reach/router"; import { PrimaryButton, DefaultButton as Button } from "components/Buttons"; import { COMPETITION_TRACKS, CHALLENGES_URL } from "../../../../constants"; import RobotHappy from "assets/icons/robot-happy.svg"; @@ -20,6 +20,22 @@ const Uploading = ({ uploadProgress, back, }) => { + const propsRef = useRef(); + propsRef.current = { submitDone, challengeId }; + + useEffect(() => { + return () => { + if (propsRef.current.submitDone) { + const backUrl = window.location.pathname; + if (backUrl === `${CHALLENGES_URL}/${challengeId}`) { + navigate( + `${CHALLENGES_URL}/${propsRef.current.challengeId}?reload=true` + ); + } + } + }; // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return (
diff --git a/src/containers/challenge-detail/index.jsx b/src/containers/challenge-detail/index.jsx index b2a17b1..5720f19 100644 --- a/src/containers/challenge-detail/index.jsx +++ b/src/containers/challenge-detail/index.jsx @@ -221,6 +221,10 @@ class ChallengeDetailPageContainer extends React.Component { challenge, // loadingRecommendedChallengesUUID, history, + loadChallengeDetails, + loadFullChallengeDetails, + isLoadingChallenge, + isLoadingTerms, } = this.props; if ( @@ -247,6 +251,19 @@ class ChallengeDetailPageContainer extends React.Component { // getAllRecommendedChallenges(auth.tokenV3, recommendedTechnology); // } + const query = new URLSearchParams(history.location.search); + const isReloading = isLoadingChallenge || isLoadingTerms; + if (query.get("reload") && !isReloading) { + history.replace(history.location.pathname, history.state); + loadChallengeDetails( + nextProps.auth, + challengeId, + loadFullChallengeDetails + ); + + return; + } + const { thriveArticles } = this.state; const userId = _.get(this, "props.auth.user.userId"); const nextUserId = _.get(nextProps, "auth.user.userId"); diff --git a/src/reducers/challenges.js b/src/reducers/challenges.js index dc8d54c..37fc947 100644 --- a/src/reducers/challenges.js +++ b/src/reducers/challenges.js @@ -4,6 +4,7 @@ const defaultState = { loadingChallenges: false, loadingChallengesError: null, challenges: [], + challengesMeta: {}, total: 0, loadingRecommendedChallenges: false, loadingRecommendedChallengesError: null, @@ -31,6 +32,7 @@ function onGetChallengesDone(state, { payload }) { loadingChallenges: false, loadingChallengesError: null, challenges: payload.challenges, + challengesMeta: payload.challenges?.meta, total: payload.total, openForRegistrationCount: payload.openForRegistrationCount, initialized: true, diff --git a/src/routers/challenge-list/index.jsx b/src/routers/challenge-list/index.jsx index 49bcaa2..6adcfb0 100644 --- a/src/routers/challenge-list/index.jsx +++ b/src/routers/challenge-list/index.jsx @@ -9,7 +9,7 @@ import { FeedbackButton, showMenu } from "@topcoder/micro-frontends-earn-app"; import actions from "../../actions"; import * as utils from "../../utils"; import store from "../../store"; -import { initialChallengeFilter } from "../..//reducers/filter"; +import { initialChallengeFilter } from "../../reducers/filter"; import _ from "lodash"; import "react-date-range/dist/theme/default.css"; @@ -29,14 +29,24 @@ const App = () => { useEffect(() => { if (!location.search) { - store.dispatch(actions.challenges.getChallengesInit()); - store.dispatch( - actions.challenges.getChallengesDone(initialChallengeFilter) - ); + const currentFilter = store.getState().filter.challenge; + const diff = !_.isEqual(initialChallengeFilter, currentFilter); + + if (diff) { + const params = utils.challenge.createChallengeParams(currentFilter); + utils.url.updateQuery(params, true); + } else { + store.dispatch(actions.challenges.getChallengesInit()); + store.dispatch(actions.challenges.getChallengesDone(currentFilter)); + } + return; } - const params = utils.url.parseUrlQuery(location.search); + let search = location.href.split("?").length + ? "?" + location.href.split("?")[1] + : ""; + const params = utils.url.parseUrlQuery(search); const toUpdate = utils.challenge.createChallengeFilter(params); if (!toUpdate.types) toUpdate.types = []; diff --git a/src/services/challenges.js b/src/services/challenges.js index 7e6728a..2ecbe5d 100644 --- a/src/services/challenges.js +++ b/src/services/challenges.js @@ -17,7 +17,7 @@ import { getService as getSubmissionsService } from "./submissions"; * @return {Array} challenges */ async function getChallenges(filter, cancellationSignal) { - const challengeQuery = util.buildQueryString(filter); + const challengeQuery = util.buildQueryString(filter, true); return api.get( `/challenges/${challengeQuery}`, undefined, diff --git a/src/topcoder-micro-frontends-challenges-app.js b/src/topcoder-micro-frontends-challenges-app.js index bee6e7e..1bb7678 100644 --- a/src/topcoder-micro-frontends-challenges-app.js +++ b/src/topcoder-micro-frontends-challenges-app.js @@ -4,6 +4,7 @@ import ReactDOM from "react-dom"; import singleSpaReact from "single-spa-react"; import Root from "./root.component"; import appInit from "./utils/lifeCycle"; +import NotFoundError from "./components/NotFoundError"; const appLifecycles = appInit(); @@ -13,7 +14,7 @@ const lifecycles = singleSpaReact({ rootComponent: Root, errorBoundary(err, info, props) { // Customize the root error boundary for your microfrontend here. - return null; + return ; }, }); diff --git a/src/utils/index.js b/src/utils/index.js index b3fc4e1..ada0b97 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -167,14 +167,14 @@ export function parseTotalPrizes(s) { valid = valid && n.toLocaleString("en-US") === val; } } - if (valid) return n; + return n; } -export function triggerDownload(fileName,blob) { +export function triggerDownload(fileName, blob) { const url = window.URL.createObjectURL(new Blob([blob])); - const link = document.createElement('a'); + const link = document.createElement("a"); link.href = url; - link.setAttribute('download', fileName); + link.setAttribute("download", fileName); document.body.appendChild(link); link.click(); link.parentNode.removeChild(link); diff --git a/src/utils/lifeCycle.js b/src/utils/lifeCycle.js index 735a9c3..3ccf7ff 100644 --- a/src/utils/lifeCycle.js +++ b/src/utils/lifeCycle.js @@ -5,22 +5,30 @@ import * as utils from "../utils"; export default function appInit() { let initialQuery; let urlPath; + let firstMounted = true; function bootstrap() { return Promise.resolve().then(() => { initialQuery = window.location.search; - urlPath = window.location.pathname; + urlPath = utils.url.removeTrailingSlash(window.location.pathname); }); } - function mount() { - if (initialQuery) { - const params = utils.url.parseUrlQuery(initialQuery); - const filter = utils.challenge.createChallengeFilter(params); - store.dispatch(action.initApp(filter)); + async function mount() { + try { + if (firstMounted) { + if (initialQuery && urlPath === "/earn/find/challenges") { + const params = utils.url.parseUrlQuery(initialQuery); + const filter = utils.challenge.createChallengeFilter(params); + store.dispatch(action.initApp(filter)); + } + firstMounted = false; + } + } catch (error) { + console.error(error); + } finally { + return Promise.resolve(); } - - return Promise.resolve(); } function unmount() { diff --git a/src/utils/url.js b/src/utils/url.js index 501c761..a2598c6 100644 --- a/src/utils/url.js +++ b/src/utils/url.js @@ -15,9 +15,11 @@ import qs from "qs"; * @params {Object<{[key: string]: any}>} params Query string parameters * @return {String} */ -export function buildQueryString(params) { +export function buildQueryString(params, disableEncode) { params = _.omitBy(params, (p) => p == null || p === "" || p.length === 0); - + if (!disableEncode) { + params.tags = _.map(params.tags, (t) => encodeURIComponent(t)); + } let queryString = qs.stringify(params, { encode: false, arrayFormat: "brackets", @@ -28,15 +30,23 @@ export function buildQueryString(params) { } export function parseUrlQuery(queryString) { - return qs.parse(queryString, { ignoreQueryPrefix: true }); + let params = qs.parse(queryString, { ignoreQueryPrefix: true }); + if (params.tags) { + params.tags = _.map(params.tags, (t) => decodeURIComponent(t)); + } + return params; } -export function updateQuery(params) { +export function updateQuery(params, replace = false) { const oldQuery = decodeURIComponent(window.location.search); let query = buildQueryString(params); query = `?${query.substring(1).split("&").sort().join("&")}`; if (query !== oldQuery) { - window.history.pushState(window.history.state, "", query); + if (replace) { + window.history.replaceState(window.history.state, "", query); + } else { + window.history.pushState(window.history.state, "", query); + } } } From 3d14542ef039461afb6a9bb9ee40044427b5ed5f Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Sat, 20 Nov 2021 14:28:06 +0800 Subject: [PATCH 02/16] Challenges bug bash - three addition tickets (#178) Three addition bug tickets --- src/components/TextInput/index.jsx | 3 +++ src/containers/Challenges/Listing/index.jsx | 1 + src/containers/Filter/ChallengeFilter/index.jsx | 14 +++++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/TextInput/index.jsx b/src/components/TextInput/index.jsx index 4ccca63..149cde7 100644 --- a/src/components/TextInput/index.jsx +++ b/src/components/TextInput/index.jsx @@ -18,6 +18,7 @@ function TextInput({ type, onEnterKey, readonly, + maxLength, }) { const [val, setVal] = useState(value); const delayedOnChange = useRef( @@ -41,6 +42,7 @@ function TextInput({ readOnly={readonly} defaultValue={value} type={type} + maxLength={maxLength} placeholder={`${placeholder}${placeholder && required ? " *" : ""}`} styleName={`${value || val ? "haveValue" : ""} ${ errorMsg ? "haveError" : "" @@ -85,6 +87,7 @@ TextInput.defaultProps = { type: "text", onEnterKey: () => {}, readonly: false, + maxLength: undefined, }; TextInput.propTypes = { diff --git a/src/containers/Challenges/Listing/index.jsx b/src/containers/Challenges/Listing/index.jsx index 970ba11..d2cd3e4 100644 --- a/src/containers/Challenges/Listing/index.jsx +++ b/src/containers/Challenges/Listing/index.jsx @@ -66,6 +66,7 @@ const Listing = ({ updateFilter(filterChange); }); }} + maxLength="100" />
diff --git a/src/containers/Filter/ChallengeFilter/index.jsx b/src/containers/Filter/ChallengeFilter/index.jsx index bdf2f16..2573dde 100644 --- a/src/containers/Filter/ChallengeFilter/index.jsx +++ b/src/containers/Filter/ChallengeFilter/index.jsx @@ -35,7 +35,7 @@ const ChallengeFilter = ({ // const BUCKET_OPEN_FOR_REGISTRATION = constants.FILTER_BUCKETS[1]; const tagOptions = utils.createDropdownTermOptions(challengeTags, tags); const bucketOptions = utils.createRadioOptions(challengeBuckets, bucket); - + const maxPrize = 100000; const caseSensitive = false; utils.setSelectedDropdownTermOptions(tagOptions, tags, caseSensitive); @@ -163,6 +163,12 @@ const ChallengeFilter = ({ if (value == null) { setTotalPrizesFromError("Invalid format"); return; + } else if (value > maxPrize) { + setTotalPrizesFromError("Too big"); + return; + } else if (value >= totalPrizesTo) { + setTotalPrizesFromError("Too big"); + return; } else { setTotalPrizesFromError(null); } @@ -196,6 +202,12 @@ const ChallengeFilter = ({ if (value == null) { setTotalPrizesToError("Invalid format"); return; + } else if (value > maxPrize) { + setTotalPrizesToError("Too big"); + return; + } else if (value <= totalPrizesFrom) { + setTotalPrizesToError("Too small"); + return; } else { setTotalPrizesToError(null); } From 56ddf82897c44c2f42999211f11a4c6c5c71d858 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Sun, 21 Nov 2021 21:27:54 +0800 Subject: [PATCH 03/16] [Bug Bash] Challenges bug bash - One more ticket (#185) --- .../challenge-detail/Header/ChallengeTags.jsx | 19 ++++++- src/constants/index.js | 4 +- .../Listing/ChallengeItem/index.jsx | 20 ++++---- src/containers/Challenges/Listing/index.jsx | 2 +- .../Filter/ChallengeFilter/index.jsx | 2 +- src/containers/challenge-detail/index.jsx | 3 +- src/reducers/challenges.js | 50 ++++++++----------- src/utils/challenge.js | 34 +++++++++---- src/utils/lifeCycle.js | 3 +- 9 files changed, 82 insertions(+), 55 deletions(-) diff --git a/src/components/challenge-detail/Header/ChallengeTags.jsx b/src/components/challenge-detail/Header/ChallengeTags.jsx index 7ca45e9..f4f8ef2 100644 --- a/src/components/challenge-detail/Header/ChallengeTags.jsx +++ b/src/components/challenge-detail/Header/ChallengeTags.jsx @@ -26,6 +26,8 @@ import { COMPETITION_TRACKS } from "utils/tc"; import VerifiedTag from "components/challenge-listing/VerifiedTag"; import MatchScore from "components/challenge-listing/ChallengeCard/MatchScore"; import { calculateScore } from "../../../utils/challenge-listing/helper"; +import * as urlUtil from "utils/url"; +import * as constants from "constants"; import "./style.module.scss"; export default function ChallengeTags(props) { @@ -75,6 +77,19 @@ export default function ChallengeTags(props) { const tags = technPlatforms.filter((tag) => !matchSkills.includes(tag)); + const filterByChallengeType = urlUtil.buildQueryString({ + bucket: constants.FILTER_BUCKETS[1], + tracks: _.values(constants.FILTER_CHALLENGE_TRACK_ABBREVIATIONS), + page: 1, + }); + + const filterByTag = urlUtil.buildQueryString({ + bucket: constants.FILTER_BUCKETS[1], + tracks: _.values(constants.FILTER_CHALLENGE_TRACK_ABBREVIATIONS), + page: 1, + types: _.values(constants.FILTER_CHALLENGE_TYPE_ABBREVIATIONS), + }); + return (
{challengeType && ( @@ -84,7 +99,7 @@ export default function ChallengeTags(props) { setChallengeListingFilter({ types: [challengeType.name] }) ) } - to={`${challengesUrl}?types[]=${encodeURIComponent( + to={`${challengesUrl}${filterByChallengeType}&types[]=${encodeURIComponent( challengeType.abbreviation )}`} > @@ -112,7 +127,7 @@ export default function ChallengeTags(props) { onClick={() => setImmediate(() => setChallengeListingFilter({ tags: [tag] })) } - to={`${challengesUrl}?tags[]=${encodeURIComponent(tag)}`} + to={`${challengesUrl}${filterByTag}&tags[]=${encodeURIComponent(tag)}`} > {tag} diff --git a/src/constants/index.js b/src/constants/index.js index f679d1d..77faca2 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -29,14 +29,14 @@ export const FILTER_CHALLENGE_TRACKS = [ "Design", "Development", "Data Science", - "QA", + "Quality Assurance", ]; export const FILTER_CHALLENGE_TRACK_ABBREVIATIONS = { Design: "DES", Development: "DEV", "Data Science": "DS", - QA: "QA", + "Quality Assurance": "QA", }; export const CHALLENGE_SORT_BY = { diff --git a/src/containers/Challenges/Listing/ChallengeItem/index.jsx b/src/containers/Challenges/Listing/ChallengeItem/index.jsx index 82653ef..e9605b0 100644 --- a/src/containers/Challenges/Listing/ChallengeItem/index.jsx +++ b/src/containers/Challenges/Listing/ChallengeItem/index.jsx @@ -12,6 +12,8 @@ import * as utils from "../../../../utils"; import ProgressTooltip from "../tooltips/ProgressTooltip"; import PlacementsTooltip from "../tooltips/PlacementsTooltip"; import TagsMoreTooltip from "../tooltips/TagsMoreTooltip"; +import { CHALLENGES_URL } from 'constants'; +import { Link } from '@reach/router'; import "./styles.scss"; @@ -25,7 +27,7 @@ const ChallengeItem = ({ challenge, onClickTag, onClickTrack, isLoggedIn }) => { challenge.prizeSets ); - let submissionLink = `/earn/find/challenges/${challenge.id}`; + let submissionLink = `${CHALLENGES_URL}/${challenge.id}`; if (isLoggedIn && challenge.numOfSubmissions > 0) { submissionLink += "?tab=submissions"; } @@ -43,11 +45,11 @@ const ChallengeItem = ({ challenge, onClickTag, onClickTrack, isLoggedIn }) => {
diff --git a/src/containers/Challenges/Listing/index.jsx b/src/containers/Challenges/Listing/index.jsx index d2cd3e4..e376d19 100644 --- a/src/containers/Challenges/Listing/index.jsx +++ b/src/containers/Challenges/Listing/index.jsx @@ -129,7 +129,7 @@ const Listing = ({ }} onClickTrack={(track) => { const filterChange = { - tracks: [track.replace("Quality Assurance", "QA")], + tracks: [track], page: 1, }; updateFilter(filterChange); diff --git a/src/containers/Filter/ChallengeFilter/index.jsx b/src/containers/Filter/ChallengeFilter/index.jsx index 2573dde..cc3e9ae 100644 --- a/src/containers/Filter/ChallengeFilter/index.jsx +++ b/src/containers/Filter/ChallengeFilter/index.jsx @@ -121,7 +121,7 @@ const ChallengeFilter = ({ updateFilter(filterChange); }} /> - {track} + {track.replace('Quality Assurance', 'QA')} ))}
diff --git a/src/containers/challenge-detail/index.jsx b/src/containers/challenge-detail/index.jsx index 5720f19..f3f3cfc 100644 --- a/src/containers/challenge-detail/index.jsx +++ b/src/containers/challenge-detail/index.jsx @@ -978,8 +978,7 @@ const mapDispatchToProps = (dispatch) => { } dispatch(updateFilter(change)); dispatch( - updateQuery({ ...stateProps.filter.challenge, ...change }), - change + updateQuery({ ...stateProps.filter.challenge, ...change }) ); }, setSpecsTabState: (state) => diff --git a/src/reducers/challenges.js b/src/reducers/challenges.js index 37fc947..31ae15a 100644 --- a/src/reducers/challenges.js +++ b/src/reducers/challenges.js @@ -17,14 +17,9 @@ function onGetChallengesInit(state) { return { ...state, loadingChallenges: true, loadingChallengesError: null }; } -function onGetChallengesDone(state, { payload }) { - const error = payload; - if (error.name === "AbortError") { - return { - ...state, - loadingChallenges: false, - loadingChallengesError: null, - }; +function onGetChallengesDone(state, { error, payload }) { + if (error) { + return onGetChallengesFailure(state, { payload }); } return { @@ -39,32 +34,31 @@ function onGetChallengesDone(state, { payload }) { }; } -// function onGetChallengesFailure(state, { payload }) { -// const error = payload; -// if (error.name === "AbortError") { -// return { -// ...state, -// loadingChallenges: false, -// loadingChallengesError: null, -// }; -// } +function onGetChallengesFailure(state, { payload }) { + const error = payload; + if (error.name === "AbortError") { + return { + ...state, + loadingChallenges: false, + loadingChallengesError: null, + }; + } -// return { -// ...state, -// loadingChallenges: false, -// loadingChallengesError: payload, -// challenges: [], -// total: 0, -// openForRegistrationCount: 0, -// initialized: true, -// }; -// } + return { + ...state, + loadingChallenges: false, + loadingChallengesError: payload, + challenges: [], + total: 0, + openForRegistrationCount: 0, + initialized: true, + }; +} export default handleActions( { GET_CHALLENGE_INIT: onGetChallengesInit, GET_CHALLENGES_DONE: onGetChallengesDone, - // GET_CHALLENGES_FAILURE: onGetChallengesFailure, }, defaultState ); diff --git a/src/utils/challenge.js b/src/utils/challenge.js index 5aca8a4..1775a04 100644 --- a/src/utils/challenge.js +++ b/src/utils/challenge.js @@ -6,34 +6,49 @@ import Joi from "joi"; import { initialChallengeFilter } from "../reducers/filter"; Joi.optionalId = () => Joi.string().uuid(); -Joi.page = () => Joi.number().integer().min(1); + +Joi.page = () => + Joi.alternatives() + .try( + Joi.number() + .min(1), + Joi.any().custom(() => 1) + ); + Joi.perPage = () => - Joi.number() - .integer() - .min(1) - .max(100) - .valid(...constants.PAGINATION_PER_PAGES); + Joi.alternatives() + .try( + Joi.number() + .integer() + .min(1) + .max(100) + .valid(...constants.PAGINATION_PER_PAGES), + Joi.any().custom(() => constants.PAGINATION_PER_PAGES[0]) + ); + Joi.bucket = () => Joi.string().custom((param) => constants.FILTER_BUCKETS.find( (bucket) => param && param.toLowerCase() === bucket.toLowerCase() - ) + ) || null ); + Joi.track = () => Joi.string().custom((param) => _.findKey( constants.FILTER_CHALLENGE_TRACK_ABBREVIATIONS, (trackAbbreviation) => param && param.toLowerCase() === trackAbbreviation.toLowerCase() - ) + ) || null ); + Joi.type = () => Joi.string().custom((param) => _.findKey( constants.FILTER_CHALLENGE_TYPE_ABBREVIATIONS, (typeAbbreviation) => param && param.toLowerCase() === typeAbbreviation.toLowerCase() - ) + ) || null ); export function getCurrencySymbol(prizeSets) { @@ -65,6 +80,7 @@ export function getCheckpointPrizes(prizeSets) { */ export function createChallengeFilter(params) { const schema = createChallengeFilter.schema; + const normalized = Joi.attempt( params, schema, diff --git a/src/utils/lifeCycle.js b/src/utils/lifeCycle.js index 3ccf7ff..0f64daf 100644 --- a/src/utils/lifeCycle.js +++ b/src/utils/lifeCycle.js @@ -1,6 +1,7 @@ import store from "../store"; import action from "../actions/initApp"; import * as utils from "../utils"; +import { CHALLENGES_URL } from '../constants'; export default function appInit() { let initialQuery; @@ -17,7 +18,7 @@ export default function appInit() { async function mount() { try { if (firstMounted) { - if (initialQuery && urlPath === "/earn/find/challenges") { + if (initialQuery && urlPath === CHALLENGES_URL) { const params = utils.url.parseUrlQuery(initialQuery); const filter = utils.challenge.createChallengeFilter(params); store.dispatch(action.initApp(filter)); From c514d88f923b51d2ffa9a5311debaaf58aa6f96e Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Tue, 23 Nov 2021 00:49:32 +0800 Subject: [PATCH 04/16] [Additional Fix] Challenges bug bash (#192) Additional fix for issue_132 --- src/components/DateRangePicker/index.jsx | 56 +++++++++++++++++------ src/components/DateRangePicker/style.scss | 4 ++ 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/components/DateRangePicker/index.jsx b/src/components/DateRangePicker/index.jsx index 67a6b31..5c82772 100644 --- a/src/components/DateRangePicker/index.jsx +++ b/src/components/DateRangePicker/index.jsx @@ -269,6 +269,37 @@ function DateRangePicker(props) { setPreview(null); }; + const onReset = (presetRange) => { + let newStartDate; + let newEndDate; + + if (presetRange) { + newStartDate = presetRange.startDate; + newEndDate = presetRange.endDate; + } + + setFocusedRange([0, 0]); + + setErrors({ + startDate: "", + endDate: "", + }); + + setRangeString({ + startDateString: newStartDate + ? moment(newStartDate).format("MMM D, YYYY") + : "", + endDateString: newEndDate ? moment(newEndDate).format("MMM D, YYYY") : "", + }); + + onChange({ + startDate: newStartDate ? moment(newStartDate) : null, + endDate: newEndDate ? moment(newEndDate) : null, + }); + + setIsComponentVisible(false); + } + /** * Event handler on date selection changes * @param {Object} newRange nnew range that has endDate and startDate data @@ -350,13 +381,13 @@ function DateRangePicker(props) { // - set the active range's background to transparent color // to prevent the calendar auto focusing on the day of today by default when no // start date nor end date are set. - // - does not set focus on the empty selection range when mouse leaves. + // - does not set focus on the selection range when mouse leaves. // --- // setActiveDate(null); - if (range.startDate || range.endDate) { - setFocusedRange([0, focusedRange[1]]); - } + // if (range.startDate || range.endDate) { + // setFocusedRange([0, focusedRange[1]]); + // } return; } @@ -558,9 +589,13 @@ function DateRangePicker(props) { - onDateRangePickerChange(item.selection || item.active) - } + onChange={(item) => { + if (!preview) { + onReset(item.selection || item.active); + } else { + onDateRangePickerChange(item.selection || item.active); + } + }} dateDisplayFormat="MM/dd/yyyy" showDateDisplay={false} staticRanges={createStaticRanges()} @@ -578,12 +613,7 @@ function DateRangePicker(props) { diff --git a/src/components/DateRangePicker/style.scss b/src/components/DateRangePicker/style.scss index ff8c7c8..c0bd6f1 100644 --- a/src/components/DateRangePicker/style.scss +++ b/src/components/DateRangePicker/style.scss @@ -348,6 +348,10 @@ $darkGreen: #0AB88A;; } } + .rdrStartEdge.rdrEndEdge ~ .rdrDayNumber span { + color: $tc-black; + } + .rdrDayNumber { top: 0; bottom: 0; From c0efb7eecb01d1b4191041fb5ce9b52a43ffaad8 Mon Sep 17 00:00:00 2001 From: pauline Date: Tue, 23 Nov 2021 20:56:45 +0300 Subject: [PATCH 05/16] Commented out Grid View Button (#195) Co-authored-by: Pauline --- src/containers/Challenges/index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/containers/Challenges/index.jsx b/src/containers/Challenges/index.jsx index 9ed26b9..92a5ab5 100644 --- a/src/containers/Challenges/index.jsx +++ b/src/containers/Challenges/index.jsx @@ -6,7 +6,7 @@ import actions from "../../actions"; // import ChallengeRecommendedError from "./Listing/errors/ChallengeRecommendedError"; import * as constants from "../../constants"; import IconListView from "../../assets/icons/list-view.svg"; -import IconCardView from "../../assets/icons/card-view.svg"; +//import IconCardView from "../../assets/icons/card-view.svg"; import { Banner } from "@topcoder/micro-frontends-earn-app"; import * as utils from "../../utils"; @@ -80,9 +80,9 @@ const Challenges = ({ - + */} {initialized && ( From 0c8607f6995231b737c7d6a24561d2b06df76edb Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Wed, 24 Nov 2021 01:57:02 +0800 Subject: [PATCH 06/16] Revert "Commented out Grid View Button (#195)" (#196) This reverts commit c0efb7eecb01d1b4191041fb5ce9b52a43ffaad8. --- src/containers/Challenges/index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/containers/Challenges/index.jsx b/src/containers/Challenges/index.jsx index 92a5ab5..9ed26b9 100644 --- a/src/containers/Challenges/index.jsx +++ b/src/containers/Challenges/index.jsx @@ -6,7 +6,7 @@ import actions from "../../actions"; // import ChallengeRecommendedError from "./Listing/errors/ChallengeRecommendedError"; import * as constants from "../../constants"; import IconListView from "../../assets/icons/list-view.svg"; -//import IconCardView from "../../assets/icons/card-view.svg"; +import IconCardView from "../../assets/icons/card-view.svg"; import { Banner } from "@topcoder/micro-frontends-earn-app"; import * as utils from "../../utils"; @@ -80,9 +80,9 @@ const Challenges = ({ - {/* */} + {initialized && ( From a53108a8cae55410d12beb0e088197de23bc3e3f Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 29 Nov 2021 13:00:59 +0800 Subject: [PATCH 07/16] [Bug Bash] Challenges bug bash - 2 (#267) --- src/actions/auth.js | 19 ++++- src/actions/challenge.js | 15 ++++ src/components/DateRangePicker/index.jsx | 2 +- .../challenge-detail/Header/ChallengeTags.jsx | 4 +- .../ChallengeLoading/index.jsx | 19 +++++ .../ChallengeLoading/styles.module.scss | 84 +++++++++++++++++++ src/constants/index.js | 6 ++ .../Listing/ChallengeItem/index.jsx | 12 +-- src/containers/Challenges/Listing/index.jsx | 9 +- src/containers/Challenges/index.jsx | 24 ++++-- .../Filter/ChallengeFilter/index.jsx | 2 +- src/containers/Submission/index.jsx | 28 ++++++- src/containers/challenge-detail/index.jsx | 4 +- src/reducers/challenge.js | 25 ++++++ src/reducers/challenges.js | 4 +- src/services/challenge.js | 1 + src/utils/challenge.js | 62 +++++++------- src/utils/lifeCycle.js | 2 +- src/utils/token.js | 2 +- 19 files changed, 265 insertions(+), 59 deletions(-) create mode 100644 src/components/challenge-listing/ChallengeLoading/index.jsx create mode 100644 src/components/challenge-listing/ChallengeLoading/styles.module.scss diff --git a/src/actions/auth.js b/src/actions/auth.js index 4f19902..1f7241b 100644 --- a/src/actions/auth.js +++ b/src/actions/auth.js @@ -4,10 +4,11 @@ */ import { createActions } from "redux-actions"; -import { decodeToken } from "../utils/token"; +import { decodeToken, readCookie } from "../utils/token"; import { getApiV3, getApiV5 } from "../services/challenge-api"; import { setErrorIcon, ERROR_ICON_TYPES } from "../utils/errors"; import { getAuthUserTokens } from "@topcoder/micro-frontends-navbar-app"; +import { TOKEN_COOKIE_KEYS } from "../constants/index"; /** * Helper method that checks for HTTP error response v5 and throws Error in this case. @@ -83,11 +84,27 @@ async function setAuthDone() { return user; } +/** + * @static + * @desc Check token cookies to find if a user is logged out: + * This is because all the token cookies are cleared if a user is logged out. + * @return {Action} + */ +function checkIsLoggedOut() { + const tokenKeys = Object.keys(TOKEN_COOKIE_KEYS); + const isLoggedOut = _.every( + tokenKeys, + (k) => readCookie(TOKEN_COOKIE_KEYS[k]) === undefined + ); + return { isLoggedOut }; +} + export default createActions({ AUTH: { LOAD_PROFILE: loadProfileDone, SET_TC_TOKEN_V2: setTcTokenV2, SET_TC_TOKEN_V3: setTcTokenV3, SET_AUTH_DONE: setAuthDone, + CHECK_IS_LOGGED_OUT: checkIsLoggedOut, }, }); diff --git a/src/actions/challenge.js b/src/actions/challenge.js index 5256ad9..84272bd 100644 --- a/src/actions/challenge.js +++ b/src/actions/challenge.js @@ -454,6 +454,20 @@ function getChallengeDone(challengeId) { return challengeService.getChallenge(challengeId); } +/** + * @static + * @desc Check if a user has registered a challenge + * @param {String} challengeId Challenge ID. + * @param {String} userId User Id. + * @return {Action} + */ +async function getIsRegistered(challengeId, userId) { + const registrants = await challengeService.getChallengeRegistrants(challengeId); + const isRegistered = _.some(registrants, (r) => `${r.memberId}` === `${userId}`); + return { isRegistered }; +} + + export default createActions({ CHALLENGE: { DROP_CHECKPOINTS: dropCheckpoints, @@ -483,5 +497,6 @@ export default createActions({ GET_SUBMISSION_INFORMATION_DONE: getSubmissionInformationDone, GET_CHALLENGE_INIT: _.noop, GET_CHALLENGE_DONE: getChallengeDone, + GET_IS_REGISTERED: getIsRegistered, }, }); diff --git a/src/components/DateRangePicker/index.jsx b/src/components/DateRangePicker/index.jsx index 5c82772..96b0d27 100644 --- a/src/components/DateRangePicker/index.jsx +++ b/src/components/DateRangePicker/index.jsx @@ -298,7 +298,7 @@ function DateRangePicker(props) { }); setIsComponentVisible(false); - } + }; /** * Event handler on date selection changes diff --git a/src/components/challenge-detail/Header/ChallengeTags.jsx b/src/components/challenge-detail/Header/ChallengeTags.jsx index f4f8ef2..a96a7fb 100644 --- a/src/components/challenge-detail/Header/ChallengeTags.jsx +++ b/src/components/challenge-detail/Header/ChallengeTags.jsx @@ -127,7 +127,9 @@ export default function ChallengeTags(props) { onClick={() => setImmediate(() => setChallengeListingFilter({ tags: [tag] })) } - to={`${challengesUrl}${filterByTag}&tags[]=${encodeURIComponent(tag)}`} + to={`${challengesUrl}${filterByTag}&tags[]=${encodeURIComponent( + tag + )}`} > {tag} diff --git a/src/components/challenge-listing/ChallengeLoading/index.jsx b/src/components/challenge-listing/ChallengeLoading/index.jsx new file mode 100644 index 0000000..42ac1e2 --- /dev/null +++ b/src/components/challenge-listing/ChallengeLoading/index.jsx @@ -0,0 +1,19 @@ +import React from "react"; +import "./styles.module.scss"; + +export default function ChallengeLoading() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+ ) +} diff --git a/src/components/challenge-listing/ChallengeLoading/styles.module.scss b/src/components/challenge-listing/ChallengeLoading/styles.module.scss new file mode 100644 index 0000000..f0b4ed9 --- /dev/null +++ b/src/components/challenge-listing/ChallengeLoading/styles.module.scss @@ -0,0 +1,84 @@ +@import "~styles/mixins"; + +@keyframes placeholderAnim { + 0% { + background-position: -$base-unit * 94 0; + } + + 100% { + background-position: $base-unit * 94 0; + } +} + +.animated-placeholder-template { + animation-duration: 1.25s; + animation-fill-mode: forwards; + animation-iteration-count: infinite; + animation-name: placeholderAnim; + animation-timing-function: linear; + background: $tc-gray-neutral-dark; + background: linear-gradient(to right, $tc-gray-neutral-dark 8%, $tc-gray-10 18%, $tc-gray-neutral-dark 33%); + background-size: $base-unit * 256 $base-unit * 20; + position: relative; +} + +.placeholder-template { + border-radius: $corner-radius; + + @extend .animated-placeholder-template; +} + +.challenge-loading { + height: 126px; + padding: 16px; + margin: 0 24px; + display: flex; + border-bottom: 1px solid #E9E9E9; + border-top: 1px solid #E9E9E9; + + > div { + margin-right: 16px; + } + + &:nth-child(even) { + background: #FBFBFB; + } + .track { + flex: 1 0 46px; + width: 46px; + height: 44px; + } + + .main { + flex: 1 1 70%; + } + + .title { + height: 22px; + width: 100px; + margin-bottom: 8px; + } + + .info { + height: 18px; + width: 200px; + margin-bottom: 16px; + } + + .footer { + height: 18px; + width: 70%; + } + + .prize { + height: 18px; + width: 60px; + margin-bottom: 8px; + margin-right: 40px; + } + + .prize-nominal { + height: 42px; + width: 40px; + } +} diff --git a/src/constants/index.js b/src/constants/index.js index 77faca2..c6c8cf8 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -96,3 +96,9 @@ export const COMPETITION_TRACKS = { DEV: "Development", QA: "Quality Assurance", }; + +export const TOKEN_COOKIE_KEYS = { + V3JWT: "v3jwt", + TCJWT: "tcjwt", + TCSSO: "tcsso", +}; diff --git a/src/containers/Challenges/Listing/ChallengeItem/index.jsx b/src/containers/Challenges/Listing/ChallengeItem/index.jsx index e9605b0..1fef4cd 100644 --- a/src/containers/Challenges/Listing/ChallengeItem/index.jsx +++ b/src/containers/Challenges/Listing/ChallengeItem/index.jsx @@ -12,8 +12,8 @@ import * as utils from "../../../../utils"; import ProgressTooltip from "../tooltips/ProgressTooltip"; import PlacementsTooltip from "../tooltips/PlacementsTooltip"; import TagsMoreTooltip from "../tooltips/TagsMoreTooltip"; -import { CHALLENGES_URL } from 'constants'; -import { Link } from '@reach/router'; +import { CHALLENGES_URL } from "constants"; +import { Link } from "@reach/router"; import "./styles.scss"; @@ -45,9 +45,7 @@ const ChallengeItem = ({ challenge, onClickTag, onClickTrack, isLoggedIn }) => {
- + {challenge.name}
@@ -72,9 +70,7 @@ const ChallengeItem = ({ challenge, onClickTag, onClickTrack, isLoggedIn }) => { />
- + diff --git a/src/containers/Challenges/Listing/index.jsx b/src/containers/Challenges/Listing/index.jsx index e376d19..4934b8c 100644 --- a/src/containers/Challenges/Listing/index.jsx +++ b/src/containers/Challenges/Listing/index.jsx @@ -9,14 +9,16 @@ import ChallengeItem from "./ChallengeItem"; import TextInput from "../../../components/TextInput"; import Dropdown from "../../../components/Dropdown"; import DateRangePicker from "../../../components/DateRangePicker"; +import ChallengeLoading from "../../../components/challenge-listing/ChallengeLoading"; import * as utils from "../../../utils"; + import * as constants from "../../../constants"; import IconSearch from "../../../assets/icons/search.svg"; - import "./styles.scss"; const Listing = ({ challenges, + loadingChallenges, search, page, perPage, @@ -111,7 +113,9 @@ const Listing = ({
- {challenges.length ? ( + {loadingChallenges ? + _.times(3, () => ) : + challenges.length ? ( {challenges.map((challenge, index) => (
{ const [isLoggedIn, setIsLoggedIn] = useState(null); @@ -76,20 +79,21 @@ const Challenges = ({

CHALLENGES - + {/* - + */}

- {initialized && ( + {initialized ? ( <> {/*noRecommendedChallenges && */} + ) : ( + + + + + )}
); @@ -128,6 +138,7 @@ Challenges.propTypes = { initialized: PT.bool, updateQuery: PT.func, tags: PT.arrayOf(PT.string), + loadingChallenges: PT.bool, }; const mapStateToProps = (state) => ({ @@ -146,6 +157,7 @@ const mapStateToProps = (state) => ({ recommendedChallenges: state.challenges.recommendedChallenges, initialized: state.challenges.initialized, tags: state.filter.challenge.tags, + loadingChallenges: state.challenges.loadingChallenges }); const mapDispatchToProps = { diff --git a/src/containers/Filter/ChallengeFilter/index.jsx b/src/containers/Filter/ChallengeFilter/index.jsx index cc3e9ae..e6d407f 100644 --- a/src/containers/Filter/ChallengeFilter/index.jsx +++ b/src/containers/Filter/ChallengeFilter/index.jsx @@ -121,7 +121,7 @@ const ChallengeFilter = ({ updateFilter(filterChange); }} /> - {track.replace('Quality Assurance', 'QA')} + {track.replace("Quality Assurance", "QA")} ))}
diff --git a/src/containers/Submission/index.jsx b/src/containers/Submission/index.jsx index 9c1e1ea..81375be 100644 --- a/src/containers/Submission/index.jsx +++ b/src/containers/Submission/index.jsx @@ -5,6 +5,7 @@ import { navigate } from "@reach/router"; import { PrimaryButton } from "components/Buttons"; import AccessDenied from "components/AccessDenied"; import LoadingIndicator from "components/LoadingIndicator"; +import { login } from "@topcoder/micro-frontends-navbar-app"; import { ACCESS_DENIED_REASON, CHALLENGES_URL } from "../../constants"; import Submit from "./Submit"; import actions from "../../actions"; @@ -38,6 +39,7 @@ const Submission = ({ submitDone, uploadProgress, + getIsRegistered, getChallenge, submit, resetForm, @@ -47,6 +49,7 @@ const Submission = ({ setFilePickerUploadProgress, setFilePickerDragged, setSubmissionFilestackData, + checkIsLoggedOut, setAuth, }) => { const propsRef = useRef(); @@ -92,6 +95,17 @@ const Submission = ({ ); } + const handleSubmit = async (data) => { + const isLoggedOut = checkIsLoggedOut(); + if (isLoggedOut) { + window.sessionStorage && window.sessionStorage.clear(); + login(); + } else { + const registered = await getIsRegistered(challengeId, userId); + if (registered) submit(data); + } + }; + return ( ); }; @@ -155,6 +169,7 @@ Submission.propTypes = { uploadProgress: PT.number, getChallenge: PT.func, + getIsRegistered: PT.func, submit: PT.func, resetForm: PT.func, setAgreed: PT.func, @@ -164,6 +179,7 @@ Submission.propTypes = { setFilePickerDragged: PT.func, setSubmissionFilestackData: PT.func, setAuth: PT.func, + checkIsLoggedOut: PT.func, }; const mapStateToProps = (state, ownProps) => { @@ -209,6 +225,16 @@ const mapDispatchToProps = (dispatch) => { setAuth: () => { dispatch(actions.auth.setAuthDone()); }, + checkIsLoggedOut: () => { + const action = dispatch(actions.auth.checkIsLoggedOut()); + return action?.payload?.isLoggedOut; + }, + getIsRegistered: async (challengeId, userId) => { + const action = await dispatch( + actions.challenge.getIsRegistered(challengeId, userId) + ); + return action?.payload?.isRegistered; + }, getChallenge: (challengeId) => { dispatch(actions.challenge.getChallengeInit(challengeId)); dispatch(actions.challenge.getChallengeDone(challengeId)); diff --git a/src/containers/challenge-detail/index.jsx b/src/containers/challenge-detail/index.jsx index f3f3cfc..d84f39e 100644 --- a/src/containers/challenge-detail/index.jsx +++ b/src/containers/challenge-detail/index.jsx @@ -977,9 +977,7 @@ const mapDispatchToProps = (dispatch) => { change.types = constants.FILTER_CHALLENGE_TYPES; } dispatch(updateFilter(change)); - dispatch( - updateQuery({ ...stateProps.filter.challenge, ...change }) - ); + dispatch(updateQuery({ ...stateProps.filter.challenge, ...change })); }, setSpecsTabState: (state) => dispatch(pageActions.page.challengeDetails.setSpecsTabState(state)), diff --git a/src/reducers/challenge.js b/src/reducers/challenge.js index 346db3e..5e213f8 100644 --- a/src/reducers/challenge.js +++ b/src/reducers/challenge.js @@ -472,6 +472,30 @@ function onGetChallengeDone(state, { error, payload }) { }; } +/** + * Update isRegistered to before challenge submit + * @param {Object} state Old state. + * @param {Object} actions Action error/payload. + * @param {Object} action Action. + */ +function onGetIsRegistered(state, { error, payload }) { + if (error) { + logger.error("Failed to get the user's registration status!", payload); + fireErrorMessage( + "ERROR: Failed to submit", + "Please, try again a bit later" + ); + return state; + } + return { + ...state, + challenge: { + ...state.challenge, + isRegistered: payload.isRegistered + } + }; +} + /** * Creates a new Challenge reducer with the specified initial state. * @param {Object} initialState Optional. Initial state. @@ -520,6 +544,7 @@ function create(initialState) { [a.getSubmissionInformationDone]: onGetSubmissionInformationDone, [a.getChallengeInit]: onGetChallengeInit, [a.getChallengeDone]: onGetChallengeDone, + [a.getIsRegistered]: onGetIsRegistered, }, _.defaults(initialState, { details: null, diff --git a/src/reducers/challenges.js b/src/reducers/challenges.js index 31ae15a..2266300 100644 --- a/src/reducers/challenges.js +++ b/src/reducers/challenges.js @@ -1,7 +1,7 @@ import { handleActions } from "redux-actions"; const defaultState = { - loadingChallenges: false, + loadingChallenges: true, loadingChallengesError: null, challenges: [], challengesMeta: {}, @@ -57,7 +57,7 @@ function onGetChallengesFailure(state, { payload }) { export default handleActions( { - GET_CHALLENGE_INIT: onGetChallengesInit, + GET_CHALLENGES_INIT: onGetChallengesInit, GET_CHALLENGES_DONE: onGetChallengesDone, }, defaultState diff --git a/src/services/challenge.js b/src/services/challenge.js index 38e4c6b..adde73c 100644 --- a/src/services/challenge.js +++ b/src/services/challenge.js @@ -113,4 +113,5 @@ async function getChallenge(challengeId) { export default { getChallenge, + getChallengeRegistrants }; diff --git a/src/utils/challenge.js b/src/utils/challenge.js index 1775a04..e9b1f04 100644 --- a/src/utils/challenge.js +++ b/src/utils/challenge.js @@ -8,47 +8,47 @@ import { initialChallengeFilter } from "../reducers/filter"; Joi.optionalId = () => Joi.string().uuid(); Joi.page = () => - Joi.alternatives() - .try( - Joi.number() - .min(1), - Joi.any().custom(() => 1) - ); + Joi.alternatives().try( + Joi.number().min(1), + Joi.any().custom(() => 1) + ); Joi.perPage = () => - Joi.alternatives() - .try( - Joi.number() - .integer() - .min(1) - .max(100) - .valid(...constants.PAGINATION_PER_PAGES), - Joi.any().custom(() => constants.PAGINATION_PER_PAGES[0]) - ); + Joi.alternatives().try( + Joi.number() + .integer() + .min(1) + .max(100) + .valid(...constants.PAGINATION_PER_PAGES), + Joi.any().custom(() => constants.PAGINATION_PER_PAGES[0]) + ); Joi.bucket = () => - Joi.string().custom((param) => - constants.FILTER_BUCKETS.find( - (bucket) => param && param.toLowerCase() === bucket.toLowerCase() - ) || null + Joi.string().custom( + (param) => + constants.FILTER_BUCKETS.find( + (bucket) => param && param.toLowerCase() === bucket.toLowerCase() + ) || null ); Joi.track = () => - Joi.string().custom((param) => - _.findKey( - constants.FILTER_CHALLENGE_TRACK_ABBREVIATIONS, - (trackAbbreviation) => - param && param.toLowerCase() === trackAbbreviation.toLowerCase() - ) || null + Joi.string().custom( + (param) => + _.findKey( + constants.FILTER_CHALLENGE_TRACK_ABBREVIATIONS, + (trackAbbreviation) => + param && param.toLowerCase() === trackAbbreviation.toLowerCase() + ) || null ); Joi.type = () => - Joi.string().custom((param) => - _.findKey( - constants.FILTER_CHALLENGE_TYPE_ABBREVIATIONS, - (typeAbbreviation) => - param && param.toLowerCase() === typeAbbreviation.toLowerCase() - ) || null + Joi.string().custom( + (param) => + _.findKey( + constants.FILTER_CHALLENGE_TYPE_ABBREVIATIONS, + (typeAbbreviation) => + param && param.toLowerCase() === typeAbbreviation.toLowerCase() + ) || null ); export function getCurrencySymbol(prizeSets) { diff --git a/src/utils/lifeCycle.js b/src/utils/lifeCycle.js index 0f64daf..28dce2b 100644 --- a/src/utils/lifeCycle.js +++ b/src/utils/lifeCycle.js @@ -1,7 +1,7 @@ import store from "../store"; import action from "../actions/initApp"; import * as utils from "../utils"; -import { CHALLENGES_URL } from '../constants'; +import { CHALLENGES_URL } from "../constants"; export default function appInit() { let initialQuery; diff --git a/src/utils/token.js b/src/utils/token.js index 0c914cf..f6f83a1 100644 --- a/src/utils/token.js +++ b/src/utils/token.js @@ -87,6 +87,6 @@ function parseCookie(cookie) { ); } -function readCookie(name) { +export function readCookie(name) { return parseCookie(document.cookie)[name]; } From 3bd90cee0cf9372d7f6a6b5629bb2632d7274f2b Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 29 Nov 2021 13:07:10 +0800 Subject: [PATCH 08/16] [Bug Bash] Fixign lint --- src/actions/auth.js | 1 + src/actions/challenge.js | 10 +++++++--- .../challenge-listing/ChallengeLoading/index.jsx | 2 +- src/containers/Challenges/Listing/index.jsx | 6 +++--- src/containers/Challenges/index.jsx | 2 +- src/reducers/challenge.js | 4 ++-- src/services/challenge.js | 2 +- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/actions/auth.js b/src/actions/auth.js index 1f7241b..7b2c05d 100644 --- a/src/actions/auth.js +++ b/src/actions/auth.js @@ -4,6 +4,7 @@ */ import { createActions } from "redux-actions"; +import _ from "lodash"; import { decodeToken, readCookie } from "../utils/token"; import { getApiV3, getApiV5 } from "../services/challenge-api"; import { setErrorIcon, ERROR_ICON_TYPES } from "../utils/errors"; diff --git a/src/actions/challenge.js b/src/actions/challenge.js index 84272bd..747177a 100644 --- a/src/actions/challenge.js +++ b/src/actions/challenge.js @@ -462,12 +462,16 @@ function getChallengeDone(challengeId) { * @return {Action} */ async function getIsRegistered(challengeId, userId) { - const registrants = await challengeService.getChallengeRegistrants(challengeId); - const isRegistered = _.some(registrants, (r) => `${r.memberId}` === `${userId}`); + const registrants = await challengeService.getChallengeRegistrants( + challengeId + ); + const isRegistered = _.some( + registrants, + (r) => `${r.memberId}` === `${userId}` + ); return { isRegistered }; } - export default createActions({ CHALLENGE: { DROP_CHECKPOINTS: dropCheckpoints, diff --git a/src/components/challenge-listing/ChallengeLoading/index.jsx b/src/components/challenge-listing/ChallengeLoading/index.jsx index 42ac1e2..3a7f572 100644 --- a/src/components/challenge-listing/ChallengeLoading/index.jsx +++ b/src/components/challenge-listing/ChallengeLoading/index.jsx @@ -15,5 +15,5 @@ export default function ChallengeLoading() {
- ) + ); } diff --git a/src/containers/Challenges/Listing/index.jsx b/src/containers/Challenges/Listing/index.jsx index 4934b8c..bfc1318 100644 --- a/src/containers/Challenges/Listing/index.jsx +++ b/src/containers/Challenges/Listing/index.jsx @@ -113,9 +113,9 @@ const Listing = ({ - {loadingChallenges ? - _.times(3, () => ) : - challenges.length ? ( + {loadingChallenges ? ( + _.times(3, () => ) + ) : challenges.length ? ( {challenges.map((challenge, index) => (
({ recommendedChallenges: state.challenges.recommendedChallenges, initialized: state.challenges.initialized, tags: state.filter.challenge.tags, - loadingChallenges: state.challenges.loadingChallenges + loadingChallenges: state.challenges.loadingChallenges, }); const mapDispatchToProps = { diff --git a/src/reducers/challenge.js b/src/reducers/challenge.js index 5e213f8..95f3dee 100644 --- a/src/reducers/challenge.js +++ b/src/reducers/challenge.js @@ -491,8 +491,8 @@ function onGetIsRegistered(state, { error, payload }) { ...state, challenge: { ...state.challenge, - isRegistered: payload.isRegistered - } + isRegistered: payload.isRegistered, + }, }; } diff --git a/src/services/challenge.js b/src/services/challenge.js index adde73c..944cef9 100644 --- a/src/services/challenge.js +++ b/src/services/challenge.js @@ -113,5 +113,5 @@ async function getChallenge(challengeId) { export default { getChallenge, - getChallengeRegistrants + getChallengeRegistrants, }; From 76bb2f41d6315300bb0fa6629e80d1aa4b4784fa Mon Sep 17 00:00:00 2001 From: Shivam Kumar Singh Date: Wed, 1 Dec 2021 19:34:20 +0530 Subject: [PATCH 09/16] fixes #254 (#275) --- src/containers/Submission/Submit/SubmitForm/index.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/containers/Submission/Submit/SubmitForm/index.jsx b/src/containers/Submission/Submit/SubmitForm/index.jsx index 8d2798b..4a2c838 100644 --- a/src/containers/Submission/Submit/SubmitForm/index.jsx +++ b/src/containers/Submission/Submit/SubmitForm/index.jsx @@ -254,10 +254,7 @@ const SubmitForm = ({ > Topcoder terms of use - ‌ and to the extent your uploaded file wins a topcoder - Competition, you hereby assign, grant and transfer and agree to - assign, grant and transfer to topcoder all right and title in and to - the Winning Submission (as further described in the terms of use). + ‌ and to the extent your uploaded file wins a topcoder Competition, you hereby agree to assign, grant and transfer to Topcoder all right and title in and to the Winning Submission (as further described in the terms of use).

Date: Wed, 1 Dec 2021 22:04:37 +0800 Subject: [PATCH 10/16] Revert "fixes #254 (#275)" (#282) This reverts commit 76bb2f41d6315300bb0fa6629e80d1aa4b4784fa. --- src/containers/Submission/Submit/SubmitForm/index.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/containers/Submission/Submit/SubmitForm/index.jsx b/src/containers/Submission/Submit/SubmitForm/index.jsx index 4a2c838..8d2798b 100644 --- a/src/containers/Submission/Submit/SubmitForm/index.jsx +++ b/src/containers/Submission/Submit/SubmitForm/index.jsx @@ -254,7 +254,10 @@ const SubmitForm = ({ > Topcoder terms of use - ‌ and to the extent your uploaded file wins a topcoder Competition, you hereby agree to assign, grant and transfer to Topcoder all right and title in and to the Winning Submission (as further described in the terms of use). + ‌ and to the extent your uploaded file wins a topcoder + Competition, you hereby assign, grant and transfer and agree to + assign, grant and transfer to topcoder all right and title in and to + the Winning Submission (as further described in the terms of use).

Date: Sat, 4 Dec 2021 20:46:11 +0800 Subject: [PATCH 11/16] [Bug Bash] Challenges bug bash - Round 3 (#287) * issue #145 * fix: issue #110 * Fixed #96 * Reset Pagination to 1 When Choosing Filter * linter * fix: issue #126 * fix: issue #106 * issue #143 * Prevent Trigger Change On Date Picker Input * fix: issue #85 * Fixes #95 * fix: issue #76 * issue #97 * ci:deploying * Add Close Button in Calendar * issue #132 * issue 132: focus selection range when having value * issue 132: fixed duration of a week having 8 days * Clear Challenge Filter On Menu Click * Reset Pagination to 1 * linter * Implement Not Found Error * Use debounce function for sort by * issue #75 * Minor update completed * Updated totalPrizesTo * issue 75: refixed * fix: issue #76 * fix: issue #85 * ci:deploying * Fixes #95 * issue #75 * issue 75: refixed * Use debounce function for sort by * issue #97 * Reset Pagination to 1 When Choosing Filter * linter * issue #145 * Implement Not Found Error * Prevent Trigger Change On Date Picker Input * issue #132 * issue 132: focus selection range when having value * issue 132: fixed duration of a week having 8 days * =resolve merge conflict * Replace Track QA * Reset Pagination * Minor updates done for fixing #96 * fix: lint * fix: refine * restore ci * deploy bug bash * issue 91 fix (#169) * issue 91 fix * feedback fix * adjustment * restore ci * issue #115 (#183) * ci: deploying * fixed some issues (#184) * fixed issues: - detailed page navigation should not refresh the page - Joi validation of url query should return default values if not matched - missing QA track icon * fixed track -> [track] * ci: revert * issue #132: fixed focused date jump (#191) * fix lint * ci:deploying * Fix Issue 182 (#199) * Add loading indicator * update margin Co-authored-by: mfikria * DO not reset challenges (#200) * issue 102: submit without registration (#197) * fix: issue 133 (#203) * issue_133 * issue_133 - code updated * revert ci * fix lint * fix:254 * issue #258 (#279) * ci: deploying challengeapp * fixed responsive (#280) * fixed responsive * fixed responsive for ipad (768px) * fixed title font size * fixed clicking mobile menu * wrap code (#286) * Issue#228 (#270) * fixes #228 * Update index.jsx for #228 * Update index.js for #228 * fix lint * clear btn * handle * fixes #226 (#290) * fix price * handle width * reset ci Co-authored-by: Nguyen Viet Co-authored-by: yoution Co-authored-by: Shivam Kumar Singh Co-authored-by: M Fikri A Co-authored-by: Nguyen Viet <36178659+nqviet@users.noreply.github.com> Co-authored-by: Gaurav Seta Co-authored-by: M Fikri A <81801960+fikzzzy@users.noreply.github.com> Co-authored-by: mfikria Co-authored-by: linmiao <51183663+flaming-cl@users.noreply.github.com> --- package.json | 1 + src/assets/icons/close-gray.svg | 12 ++ src/assets/images/social/icon_email.svg | 2 +- src/assets/images/social/icon_facebook.svg | 2 +- src/assets/images/social/icon_plus.svg | 2 +- src/assets/images/social/icon_print.svg | 2 +- src/assets/images/social/icon_twitter.svg | 2 +- .../DateRangePicker/DateInput/styles.scss | 1 - src/components/DateRangePicker/index.jsx | 14 +- src/components/DateRangePicker/style.scss | 64 +++++---- src/components/DropdownTerms/index.jsx | 4 +- .../Editor/MarkdownEditor/style.scss | 1 + src/components/Pagination/index.jsx | 5 +- src/components/Pagination/styles.scss | 67 +++++++-- src/components/Panel/styles.scss | 15 ++ .../challenge-detail/Header/ChallengeTags.jsx | 10 -- .../challenge-detail/Header/index.jsx | 3 - .../Specification/styles.module.scss | 3 +- .../ChallengeLoading/styles.module.scss | 16 +++ .../Listing/ChallengeItem/Prize/index.jsx | 2 +- .../Listing/ChallengeItem/Prize/styles.scss | 14 ++ .../Listing/ChallengeItem/Tags/index.jsx | 35 ++++- .../Listing/ChallengeItem/Tags/styles.scss | 10 +- .../Listing/ChallengeItem/index.jsx | 6 +- .../Listing/ChallengeItem/styles.scss | 82 ++++++++++- src/containers/Challenges/Listing/index.jsx | 130 +++++++++++------- src/containers/Challenges/Listing/styles.scss | 74 ++++++++-- src/containers/Challenges/index.jsx | 62 +++++++-- src/containers/Challenges/styles.scss | 49 +++++++ .../Filter/ChallengeFilter/styles.scss | 8 +- .../Submission/Submit/SubmitForm/index.jsx | 18 ++- src/containers/challenge-detail/index.jsx | 26 +--- src/routers/challenge-list/index.jsx | 37 +++-- src/styles/_utils.scss | 52 ++++++- src/styles/main.scss | 6 +- src/styles/mixins/_media.scss | 19 +++ src/utils/challenge.js | 16 ++- src/utils/hooks/useClickOutside.js | 36 +++++ src/utils/hooks/useCssVariable.js | 13 ++ src/utils/hooks/useTargetSize.js | 33 +++++ src/utils/index.js | 14 ++ src/utils/tag.js | 23 ++-- 42 files changed, 779 insertions(+), 212 deletions(-) create mode 100644 src/assets/icons/close-gray.svg create mode 100644 src/utils/hooks/useClickOutside.js create mode 100644 src/utils/hooks/useCssVariable.js create mode 100644 src/utils/hooks/useTargetSize.js diff --git a/package.json b/package.json index 58c092c..e34ff3e 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "react-dom": "^16.12.0", "react-redux": "^7.2.3", "react-select": "^1.3.0", + "react-responsive": "^9.0.0-beta.5", "redux": "^4.0.5", "redux-actions": "^2.6.5", "redux-logger": "^3.0.6", diff --git a/src/assets/icons/close-gray.svg b/src/assets/icons/close-gray.svg new file mode 100644 index 0000000..d773daa --- /dev/null +++ b/src/assets/icons/close-gray.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/social/icon_email.svg b/src/assets/images/social/icon_email.svg index 7a8b2e7..1281306 100644 --- a/src/assets/images/social/icon_email.svg +++ b/src/assets/images/social/icon_email.svg @@ -1,5 +1,5 @@ - icon email + email diff --git a/src/assets/images/social/icon_facebook.svg b/src/assets/images/social/icon_facebook.svg index 4fb1b6e..fd89ed4 100644 --- a/src/assets/images/social/icon_facebook.svg +++ b/src/assets/images/social/icon_facebook.svg @@ -2,7 +2,7 @@ - icon facebook + facebook diff --git a/src/assets/images/social/icon_plus.svg b/src/assets/images/social/icon_plus.svg index deca5c7..7567503 100644 --- a/src/assets/images/social/icon_plus.svg +++ b/src/assets/images/social/icon_plus.svg @@ -1,7 +1,7 @@ - icon + + + Created with Sketch. diff --git a/src/assets/images/social/icon_print.svg b/src/assets/images/social/icon_print.svg index aa0b1e8..34411c4 100644 --- a/src/assets/images/social/icon_print.svg +++ b/src/assets/images/social/icon_print.svg @@ -1,7 +1,7 @@ - icon print + print Created with Sketch. diff --git a/src/assets/images/social/icon_twitter.svg b/src/assets/images/social/icon_twitter.svg index 55b1e7b..0342b99 100644 --- a/src/assets/images/social/icon_twitter.svg +++ b/src/assets/images/social/icon_twitter.svg @@ -1,7 +1,7 @@ - icon twitter + twitter Created with Sketch. diff --git a/src/components/DateRangePicker/DateInput/styles.scss b/src/components/DateRangePicker/DateInput/styles.scss index 0f01a28..4ac3434 100644 --- a/src/components/DateRangePicker/DateInput/styles.scss +++ b/src/components/DateRangePicker/DateInput/styles.scss @@ -18,7 +18,6 @@ } .date-range-input { - width: 230px; margin-top: -12px; font-size: $font-size-sm; diff --git a/src/components/DateRangePicker/index.jsx b/src/components/DateRangePicker/index.jsx index 96b0d27..a970a13 100644 --- a/src/components/DateRangePicker/index.jsx +++ b/src/components/DateRangePicker/index.jsx @@ -551,15 +551,11 @@ function DateRangePicker(props) { return isBeforeDay(preview.endDate, range.startDate); }; - const className = ` - ${focusedRange[1] === 1 && styles.endDate} - ${" "} - ${range.startDate && range.endDate && styles.isRange} - ${" "} - ${isInvalidPreview() && styles.isInvalidPreview} - ${" "} - ${(errors.startDate || errors.endDate) && styles.isErrorInput} - `; + const className = `${(focusedRange[1] === 1 && styles.endDate) || ""} ${ + (range.startDate && range.endDate && styles.isRange) || "" + } ${(isInvalidPreview() && styles.isInvalidPreview) || ""} ${ + ((errors.startDate || errors.endDate) && styles.isErrorInput) || "" + }`; return (
diff --git a/src/components/DateRangePicker/style.scss b/src/components/DateRangePicker/style.scss index c0bd6f1..2e8467a 100644 --- a/src/components/DateRangePicker/style.scss +++ b/src/components/DateRangePicker/style.scss @@ -57,24 +57,25 @@ $darkGreen: #0AB88A;; @include phone { .rdrDateRangePickerWrapper { position: relative !important; - width: 100vw !important; + width: 100% !important; flex-direction: column-reverse; align-items: center; justify-content: flex-end; padding: 0 20px; + border-radius: 0 !important; .rdrDefinedRangesWrapper { - width: 100vw; - padding-bottom: 10px; - .rdrStaticRanges { display: inline-flex; flex-direction: row; justify-content: space-around; + flex-wrap: wrap; margin-top: 10px !important; - border-bottom: 1px solid $tc-gray-20; - width: 100vw; - padding-bottom: 10px; + width: calc(100% - 40px); + + .rdrStaticRange { + width: 50%; + } .rdrStaticRangeLabel { font-size: 14px; @@ -83,10 +84,6 @@ $darkGreen: #0AB88A;; > button:hover .rdrStaticRangeLabel { background-color: $green; } - - & > *:last-child { - display: none; - } } } @@ -137,14 +134,14 @@ $darkGreen: #0AB88A;; .rdrDateRangePickerWrapper { z-index: 15; position: relative; - background: $tc-white; + // background: $tc-white; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; overflow: hidden; width: 455px; .rdrDefinedRangesWrapper { - width: unset; + width: 100%; border: none; .rdrStaticRanges { @@ -164,6 +161,10 @@ $darkGreen: #0AB88A;; background-color: $green; } } + + .rdrInputRanges { + display: none; + } } } @@ -373,7 +374,6 @@ $darkGreen: #0AB88A;; } .dateInputWrapper { - display: inline-flex; position: relative; text-align: left; } @@ -395,26 +395,32 @@ $darkGreen: #0AB88A;; z-index: 10; @include phone { - width: 100vw; + width: 100%; position: fixed; top: 0; left: 0; - right: 0; + right: 20px; bottom: 0; z-index: 15; - padding: 65px 0 0; + padding: 187px 0 0; border: 0; border-radius: 0; - } + background: rgba(#2A2A2A, 0.6); - .calendar-footer { - width: 100%; + @include down(320px) { + padding: 40px 0 0; + } - @include phone { - padding: 0 20px; + .calendar-footer { + margin: 0 20px; + padding: 20px 0; } } + .calendar-footer { + background: $tc-white; + } + .calendar-button { @include roboto-bold; @@ -439,20 +445,18 @@ $darkGreen: #0AB88A;; } } -.endDate { +@include tablet { .calendar-container, .calendar-inner-container { - left: 0; + right: 0; } :global { .rdrDateRangePickerWrapper { - @include tablet { - .calendar-container, - .calendar-inner-container { - right: auto; - left: 62px; - } + .calendar-container, + .calendar-inner-container { + right: 62px; + left: auto; } } } diff --git a/src/components/DropdownTerms/index.jsx b/src/components/DropdownTerms/index.jsx index b605974..a93798b 100644 --- a/src/components/DropdownTerms/index.jsx +++ b/src/components/DropdownTerms/index.jsx @@ -4,7 +4,7 @@ import React, { useState, useRef, useEffect } from "react"; import PT from "prop-types"; import _ from "lodash"; -import { Creatable } from "react-select"; +import Select from "react-select"; import iconDown from "assets/icons/dropdown-arrow.png"; import config from "../../../config"; import "./styles.scss"; @@ -100,7 +100,7 @@ function DropdownTerms({ }`} >
- setFocused(true)} onClose={() => setFocused(false)} autosize={false} diff --git a/src/components/Editor/MarkdownEditor/style.scss b/src/components/Editor/MarkdownEditor/style.scss index 63b91b0..6f3e02c 100644 --- a/src/components/Editor/MarkdownEditor/style.scss +++ b/src/components/Editor/MarkdownEditor/style.scss @@ -18,6 +18,7 @@ display: block; font-family: "Roboto Mono", monospace; padding: 15px 20px; + white-space: pre-wrap; &:global.inline { background: $tc-gray-10; diff --git a/src/components/Pagination/index.jsx b/src/components/Pagination/index.jsx index ee625ef..503899f 100644 --- a/src/components/Pagination/index.jsx +++ b/src/components/Pagination/index.jsx @@ -105,14 +105,14 @@ const Pagination = ({ length, pageIndex, pageSize, onChange }) => { onChange={onChangePageSize} size="xs" /> + per page
  • {displayPages.map((p) => ( @@ -132,7 +132,6 @@ const Pagination = ({ length, pageIndex, pageSize, onChange }) => { }`} > +
- {loadingChallenges ? ( - _.times(3, () => ) - ) : challenges.length ? ( - - {challenges.map((challenge, index) => ( -
- { - const filterChange = { - tags: [tag], - page: 1, - }; - updateFilter(filterChange); - }} - onClickTrack={(track) => { - const filterChange = { - tracks: [track], - page: 1, - }; - updateFilter(filterChange); - }} - isLoggedIn={isLoggedIn} - /> -
- ))} -
- { - const filterChange = { - page: utils.pagination.pageIndexToPage(event.pageIndex), - perPage: event.pageSize, - }; - updateFilter(filterChange); - }} - /> -
-
- ) : ( - - )} + {loadingChallenges && _.times(3, () => )} + {!loadingChallenges && + (challenges.length ? ( + + {challenges.map((challenge, index) => ( +
+ { + const filterChange = { + tags: [tag], + page: 1, + }; + updateFilter(filterChange); + }} + onClickTrack={(track) => { + const filterChange = { + tracks: [track], + page: 1, + }; + updateFilter(filterChange); + }} + isLoggedIn={isLoggedIn} + /> +
+ ))} +
+ ) : ( + + ))} + +
+ { + const filterChange = { + page: utils.pagination.pageIndexToPage(event.pageIndex), + perPage: event.pageSize, + }; + updateFilter(filterChange); + }} + /> +
+
); }; diff --git a/src/containers/Challenges/Listing/styles.scss b/src/containers/Challenges/Listing/styles.scss index 331471b..57f6fa8 100644 --- a/src/containers/Challenges/Listing/styles.scss +++ b/src/containers/Challenges/Listing/styles.scss @@ -11,7 +11,46 @@ .header-container { display: flex; - align-items: top; + align-items: flex-start; + + @include down($mfe-screen-xs) { + flex-wrap: wrap; + + .separator { + display: none; + } + + .input-group { + width: 100%; + max-width: none; + flex: auto; + margin: 0 0 20px 0; + } + + .sort-by { + max-width: 241px; + } + + .sort-by, + .from-to { + flex: 1 1 auto; + margin: 0 22px 0 0; + + @include down(375px - 1px) { + max-width: calc(100% - 92px); + } + } + + .filter-button { + display: block; + margin: 0 0 0 auto; + + button { + height: 40px; + border-radius: 20px; + } + } + } } .input-group { @@ -19,13 +58,8 @@ display: inline-block; margin-right: 16px; margin-bottom: 17px; - width: 38%; - min-width: 380px; - - @include xs-to-md { - width: 62%; - min-width: 230px; - } + flex: auto; + max-width: 380px; .search-icon { position: absolute; @@ -39,6 +73,19 @@ height: 40px; } + .clear-icon { + position: absolute; + bottom: 0; + right: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 46px; + height: 40px; + cursor: pointer; + } + input { margin-top: 0 !important; padding-left: 46px !important; @@ -60,7 +107,8 @@ } .from-to { - display: inline-block; + width: 50%; + max-width: 241px; } .sort-by, @@ -72,6 +120,14 @@ } } +.filter-button { + display: none; +} + .pagination { padding: 17px 0 15px; + + @include down($mfe-screen-xs) { + padding: 20px; + } } diff --git a/src/containers/Challenges/index.jsx b/src/containers/Challenges/index.jsx index 73833b7..3e9e9fe 100644 --- a/src/containers/Challenges/index.jsx +++ b/src/containers/Challenges/index.jsx @@ -12,7 +12,12 @@ import * as constants from "../../constants"; import { Banner } from "@topcoder/micro-frontends-earn-app"; import * as utils from "../../utils"; +import { useMediaQuery } from "react-responsive"; +import { useCssVariable } from "../../utils/hooks/useCssVariable"; +import IconArrow from "../../assets/icons/arrow.svg"; + import "./styles.scss"; +import { useClickOutside } from "../../utils/hooks/useClickOutside"; const Challenges = ({ challenges, @@ -74,20 +79,55 @@ const Challenges = ({ recommended && recommendedChallenges.length === 0; + const screenXs = useCssVariable("--mfe-screen-xs", (value) => + parseInt(value) + ); + const isScreenXs = useMediaQuery({ maxWidth: screenXs }); + + const [menuExpanded, setMenuExpanded] = useState(false); + const menuRef = useClickOutside(menuExpanded, (event) => { + setMenuExpanded(false); + }); + + const mobileMenu = ( +