From 5a8d2553d0f6c13c047558ce50ba0904462e6b86 Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Wed, 2 Mar 2022 22:32:39 +0800 Subject: [PATCH 01/31] add single control to start date --- package-lock.json | 137 ++++++++++++++++++ package.json | 2 + .../PhaseInput/PhaseInput.module.scss | 7 +- src/components/PhaseInput/index.js | 53 +++++-- 4 files changed, 186 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d9c938e..e44e6c3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1142,6 +1142,17 @@ "glob-to-regexp": "^0.3.0" } }, + "@nateradebaugh/react-datetime": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@nateradebaugh/react-datetime/-/react-datetime-4.4.11.tgz", + "integrity": "sha512-PO/j7v8cb0Aw/MwokOL07jyYAnwkvLp9Y9n+cR4Rqr4bM051uj9VHufuXQduMj1BKAfIMNIIrhb4NhcGCnjwow==", + "requires": { + "@reach/popover": "0.16.2", + "classcat": "^5.0.1", + "date-fns": "^2.28.0", + "use-onclickoutside": "^0.4.0" + } + }, "@nodelib/fs.stat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", @@ -1152,6 +1163,82 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.5.4.tgz", "integrity": "sha512-ZpKr+WTb8zsajqgDkvCEWgp6d5eJT6Q63Ng2neTbzBO76Lbe91vX/iVIW9dikq+Fs3yEo+ls4cxeXABD2LtcbQ==" }, + "@reach/observe-rect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz", + "integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==" + }, + "@reach/popover": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@reach/popover/-/popover-0.16.2.tgz", + "integrity": "sha512-IwkRrHM7Vt33BEkSXneovymJv7oIToOfTDwRKpuYEB/BWYMAuNfbsRL7KVe6MjkgchDeQzAk24cYY1ztQj5HQQ==", + "requires": { + "@reach/portal": "0.16.2", + "@reach/rect": "0.16.0", + "@reach/utils": "0.16.0", + "tabbable": "^4.0.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "@reach/portal": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.16.2.tgz", + "integrity": "sha512-9ur/yxNkuVYTIjAcfi46LdKUvH0uYZPfEp4usWcpt6PIp+WDF57F/5deMe/uGi/B/nfDweQu8VVwuMVrCb97JQ==", + "requires": { + "@reach/utils": "0.16.0", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "@reach/rect": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.16.0.tgz", + "integrity": "sha512-/qO9jQDzpOCdrSxVPR6l674mRHNTqfEjkaxZHluwJ/2qGUtYsA0GSZiF/+wX/yOWeBif1ycxJDa6HusAMJZC5Q==", + "requires": { + "@reach/observe-rect": "1.2.0", + "@reach/utils": "0.16.0", + "prop-types": "^15.7.2", + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "@reach/utils": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.16.0.tgz", + "integrity": "sha512-PCggBet3qaQmwFNcmQ/GqHSefadAFyNCUekq9RrWoaU9hh/S4iaFgf2MBMdM47eQj5i/Bk0Mm07cP/XPFlkN+Q==", + "requires": { + "tiny-warning": "^1.0.3", + "tslib": "^2.3.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, "@sentry/hub": { "version": "5.28.0", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.28.0.tgz", @@ -1822,6 +1909,11 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "are-passive-events-supported": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/are-passive-events-supported/-/are-passive-events-supported-1.1.1.tgz", + "integrity": "sha512-5wnvlvB/dTbfrCvJ027Y4L4gW/6Mwoy1uFSavney0YO++GU+0e/flnjiBBwH+1kh7xNCgCOGvmJC3s32joYbww==" + }, "are-we-there-yet": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", @@ -3455,6 +3547,11 @@ } } }, + "classcat": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.3.tgz", + "integrity": "sha512-6dK2ke4VEJZOFx2ZfdDAl5OhEL8lvkl6EHF92IfRePfHxQTqir5NlcNVUv+2idjDqCX2NDc8m8YSAI5NI975ZQ==" + }, "classnames": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", @@ -4348,6 +4445,11 @@ "whatwg-url": "^8.0.0" } }, + "date-fns": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", + "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==" + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -9829,6 +9931,14 @@ "resolved": "https://registry.npmjs.org/moment-duration-format/-/moment-duration-format-2.3.2.tgz", "integrity": "sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ==" }, + "moment-timezone": { + "version": "0.5.34", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz", + "integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==", + "requires": { + "moment": ">= 2.9.0" + } + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -17425,6 +17535,11 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, + "tabbable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz", + "integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==" + }, "table": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", @@ -18074,6 +18189,28 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "use-isomorphic-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz", + "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==" + }, + "use-latest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.0.tgz", + "integrity": "sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==", + "requires": { + "use-isomorphic-layout-effect": "^1.0.0" + } + }, + "use-onclickoutside": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/use-onclickoutside/-/use-onclickoutside-0.4.0.tgz", + "integrity": "sha512-lg1U+V8SaCfemgBs5dg+cfEOzjuwVS9ATH0VMLSBHI6R11tbfmiKci1lg6pjwXr1sj95XWd2+5EbffJEAPdkJQ==", + "requires": { + "are-passive-events-supported": "^1.1.0", + "use-latest": "^1.0.0" + } + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", diff --git a/package.json b/package.json index f16ebaeb..726e30b9 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@fortawesome/fontawesome-svg-core": "^1.2.14", "@fortawesome/free-solid-svg-icons": "^5.7.1", "@fortawesome/react-fontawesome": "^0.1.4", + "@nateradebaugh/react-datetime": "^4.4.11", "@popperjs/core": "^2.5.4", "@svgr/webpack": "2.4.1", "axios": "^0.19.0", @@ -53,6 +54,7 @@ "mini-css-extract-plugin": "0.4.3", "moment": "^2.24.0", "moment-duration-format": "^2.2.2", + "moment-timezone": "^0.5.34", "node-sass": "^4.14.0", "optimize-css-assets-webpack-plugin": "5.0.1", "pnp-webpack-plugin": "1.1.0", diff --git a/src/components/PhaseInput/PhaseInput.module.scss b/src/components/PhaseInput/PhaseInput.module.scss index 8ec34a00..e16fc96b 100644 --- a/src/components/PhaseInput/PhaseInput.module.scss +++ b/src/components/PhaseInput/PhaseInput.module.scss @@ -1,5 +1,10 @@ @import "../../styles/includes"; -@i + +:global { + div[data-reach-popover] { + z-index: 10; + } +} .container { display: flex; diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index 2a87c6a6..c70afdbb 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -1,28 +1,38 @@ import _ from 'lodash' -import moment from 'moment' +import moment from 'moment-timezone' import React, { Component } from 'react' import PropTypes from 'prop-types' import styles from './PhaseInput.module.scss' import cn from 'classnames' -import DayPickerInput from 'react-day-picker/DayPickerInput' -import TimePicker from 'rc-time-picker' -import { - formatDate, - parseDate -} from 'react-day-picker/moment' import 'react-day-picker/lib/style.css' import 'rc-time-picker/assets/index.css' import Select from '../Select' +import DateTime from '@nateradebaugh/react-datetime' +import isAfter from 'date-fns/isAfter' +import subDays from 'date-fns/subDays' +import '@nateradebaugh/react-datetime/scss/styles.scss' -const timeFormat = 'HH:mm' -const dateFormat = 'MM/DD/YYYY' +const dateFormat = 'MM/DD/YYYY HH:mm' +// const tcTimeZone = 'America/New_York' class PhaseInput extends Component { + constructor (props) { + super(props) + + this.onDateChange = this.onDateChange.bind(this) + } + + onDateChange (e) { + const { onUpdatePhase } = this.props + + onUpdatePhase(moment(e, dateFormat)) + } + render () { const { phase, onUpdateSelect, onUpdatePhase, withDates, withDuration, endDate, readOnly } = this.props if (_.isEmpty(phase)) return null + const date = moment(phase.date).format(dateFormat) - const time = moment(phase.date) return (
@@ -33,13 +43,32 @@ class PhaseInput extends Component { withDuration && endDate && (
Ends: - {moment(endDate).format(`${dateFormat} ${timeFormat}`)} + {moment(endDate).local().format(`${dateFormat}`)}
) }
{ + withDates && ( +
+ { + readOnly ? ( + {date} + ) + : ( + { + const yesterday = subDays(new Date(), 1) + return isAfter(current, yesterday) + }} + />)} +
+ ) + } + {/* { withDates && (
{readOnly ? ( @@ -62,7 +91,7 @@ class PhaseInput extends Component { />)}
) - } + } */} { withDuration && (
From 0afa5a63f9bea836cc809a0a09e61c869822e123 Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Sat, 5 Mar 2022 19:39:43 +0800 Subject: [PATCH 02/31] add phases --- .../ChallengeSchedule-Field/index.js | 6 +- .../ChallengeEditor/ChallengeView/index.js | 15 +- src/components/ChallengeEditor/index.js | 46 ++-- .../PhaseInput/PhaseInput.module.scss | 24 ++- src/components/PhaseInput/index.js | 201 +++++++++--------- .../StartDateInput/StartDateInput.module.scss | 129 +++++++++++ src/components/StartDateInput/index.js | 141 ++++++++++++ src/util/date.js | 4 +- 8 files changed, 438 insertions(+), 128 deletions(-) create mode 100644 src/components/StartDateInput/StartDateInput.module.scss create mode 100644 src/components/StartDateInput/index.js diff --git a/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js b/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js index 328477d2..3abd88b9 100644 --- a/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js +++ b/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js @@ -6,7 +6,7 @@ import $ from 'jquery' import styles from './ChallengeSchedule-Field.module.scss' import cn from 'classnames' import jstz from 'jstimezonedetect' -import PhaseInput from '../../PhaseInput' +import StartDateInput from '../../StartDateInput' import Chart from 'react-google-charts' import Select from '../../Select' import { parseSVG } from '../../../util/svg' @@ -183,7 +183,7 @@ class ChallengeScheduleField extends Component { return ( _.map(challenge.phases, (p, index) => (
-
}
- const showTimeline = false // disables the timeline for time being https://github.com/topcoder-platform/challenge-engine-ui/issues/706 const isTask = _.get(challenge, 'task.isTask', false) + const phases = _.get(challenge, 'phases', []) return (
@@ -188,16 +190,13 @@ const ChallengeView = ({ )} { -
+ phases.map((phase) => ( -
+ )) } {showTimeline && ( )} {!isTask && ( -
- this.onUpdateOthers({ - field: 'startDate', - value: newValue.format() - })} - readOnly={false} - /> -
+ <> + { + phases.map((phase, index) => ( + { + if ((item.startDate && !moment(item.startDate).isSame(phase.scheduledStartDate)) || + (item.endDate && !moment(item.endDate).isSame(phase.scheduledEndDate)) + ) { + this.onUpdatePhaseDate(item, index) + } + }} + /> + )) + } + )} { this.state.isDeleteLaunch && !this.state.isConfirm && ( diff --git a/src/components/PhaseInput/PhaseInput.module.scss b/src/components/PhaseInput/PhaseInput.module.scss index e16fc96b..c1cf43f1 100644 --- a/src/components/PhaseInput/PhaseInput.module.scss +++ b/src/components/PhaseInput/PhaseInput.module.scss @@ -8,16 +8,26 @@ .container { display: flex; + margin-bottom: 10px; } .row { box-sizing: border-box; display: flex; flex-direction: row; - margin: 30px 30px 0 30px; + margin: 20px 30px 0 30px; align-content: space-between; justify-content: flex-start; + .title { + display: flex; + justify-content: center; + flex-direction: column; + margin-right: 10px; + font-size: 14px; + font-weight: 300; + } + .field { @include upto-sm { display: block; @@ -47,6 +57,7 @@ &.phaseName { flex-direction: column; align-items: flex-start; + font-weight: bold; .previewDates { font-size: 13px; @@ -73,7 +84,7 @@ } .dayPicker { - width: 180px; + width: 200px; margin-right: 30px; :global { @@ -90,6 +101,15 @@ } } + .inputField { + margin-right: 30px; + width: 80px; + + input { + padding: 0 0 0 10px; + } + } + .timePicker { width: 90px; diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index c70afdbb..2338d5d3 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -1,141 +1,146 @@ -import _ from 'lodash' -import moment from 'moment-timezone' -import React, { Component } from 'react' +import moment from 'moment' +import React, { useEffect, useState } from 'react' import PropTypes from 'prop-types' import styles from './PhaseInput.module.scss' import cn from 'classnames' import 'react-day-picker/lib/style.css' import 'rc-time-picker/assets/index.css' -import Select from '../Select' import DateTime from '@nateradebaugh/react-datetime' import isAfter from 'date-fns/isAfter' import subDays from 'date-fns/subDays' import '@nateradebaugh/react-datetime/scss/styles.scss' const dateFormat = 'MM/DD/YYYY HH:mm' -// const tcTimeZone = 'America/New_York' +const MAX_LENGTH = 5 -class PhaseInput extends Component { - constructor (props) { - super(props) +const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { + const [startDate, setStartDate] = useState() + const [endDate, setEndDate] = useState() + const [duration, setDuration] = useState() - this.onDateChange = this.onDateChange.bind(this) + useEffect(() => { + if (phase) { + setStartDate(phase.scheduledStartDate) + setEndDate(phase.scheduledEndDate) + setDuration(moment(phase.scheduledEndDate).diff(phase.scheduledStartDate, 'seconds')) + } + }, []) + + useEffect(() => { + if (!readOnly) { + onUpdatePhase({ + startDate, + endDate, + duration + }) + } + }, [startDate, endDate, duration]) + + const onStartDateChange = (e) => { + const start = moment(e).format() + let end = moment(endDate).format() + + if (moment(end).isBefore(moment(start))) { + end = moment(e).add(1, 'day').format(dateFormat) + setEndDate(moment(end).format(dateFormat)) + } + + setStartDate(moment(e).format(dateFormat)) + setDuration(moment(end).diff(start, 'seconds')) } - onDateChange (e) { - const { onUpdatePhase } = this.props + const onEndDateChange = (e) => { + const end = moment(e).format() + const start = moment(startDate).format() + + if (moment(end).isBefore(moment(start))) { + return null + } - onUpdatePhase(moment(e, dateFormat)) + setEndDate(moment(e).format(dateFormat)) + setDuration(moment(end).diff(start, 'seconds')) } - render () { - const { phase, onUpdateSelect, onUpdatePhase, withDates, withDuration, endDate, readOnly } = this.props - if (_.isEmpty(phase)) return null + const onDurationChange = (e) => { + if (e.target.value.length > MAX_LENGTH) return null - const date = moment(phase.date).format(dateFormat) + const dur = parseInt(e.target.value || 0) + setDuration(dur) + const end = moment(startDate).add(duration, 'seconds') + setEndDate(moment(end).format(dateFormat)) + } - return ( -
-
-
- + return ( +
+
+
+ +
+
+ Start Date: +
{ - withDuration && endDate && ( -
- Ends: - {moment(endDate).local().format(`${dateFormat}`)} -
+ readOnly ? ( + {moment(startDate).format(dateFormat)} ) - } + : ( + { + const yesterday = subDays(new Date(), 1) + return isAfter(current, yesterday) + }} + />)}
-
+
+
+ End Date: +
{ - withDates && ( -
- { - readOnly ? ( - {date} - ) - : ( - { - const yesterday = subDays(new Date(), 1) - return isAfter(current, yesterday) - }} - />)} -
+ readOnly ? ( + {moment(endDate).format(dateFormat)} ) - } - {/* { - withDates && ( -
- {readOnly ? ( - {date} - ) : ( onUpdatePhase(moment(`${moment(selectedDay).format(dateFormat)} ${time.format(timeFormat)}`, `${dateFormat} ${timeFormat}`))} format={dateFormat} />)} -
- ) - } - { - withDates && ( -
- {readOnly ? ( - {time.format(timeFormat)} - ) : ( onUpdatePhase(value)} + : ( + { + return isAfter(current, new Date(startDate)) + }} />)} -
- ) - } */} - { - withDuration && ( -
- {readOnly ? ( - {phase.duration} - ) : ( onUpdatePhase(e.target.value)} min={1} placeholder='Duration (hours)' />)} -
- ) - } +
+
+
+ Duration: +
{ - !_.isEmpty(phase.scorecards) && ( -
- )}
- ) - } +
+ ) } PhaseInput.defaultProps = { - withDates: false, - withDuration: false, endDate: null, readOnly: false } PhaseInput.propTypes = { phase: PropTypes.shape().isRequired, - onUpdateSelect: PropTypes.func, onUpdatePhase: PropTypes.func.isRequired, - withDates: PropTypes.bool, - withDuration: PropTypes.bool, - endDate: PropTypes.shape(), - readOnly: PropTypes.bool + readOnly: PropTypes.bool, + phaseIndex: PropTypes.string.isRequired } export default PhaseInput diff --git a/src/components/StartDateInput/StartDateInput.module.scss b/src/components/StartDateInput/StartDateInput.module.scss new file mode 100644 index 00000000..e16fc96b --- /dev/null +++ b/src/components/StartDateInput/StartDateInput.module.scss @@ -0,0 +1,129 @@ +@import "../../styles/includes"; + +:global { + div[data-reach-popover] { + z-index: 10; + } +} + +.container { + display: flex; +} + +.row { + box-sizing: border-box; + display: flex; + flex-direction: row; + margin: 30px 30px 0 30px; + align-content: space-between; + justify-content: flex-start; + + .field { + @include upto-sm { + display: block; + padding-bottom: 10px; + } + + label { + @include roboto-bold(); + + font-size: 16px; + line-height: 19px; + font-weight: 500; + color: $tc-gray-80; + } + + &.col1 { + max-width: 185px; + min-width: 185px; + margin-right: 14px; + margin-bottom: auto; + margin-top: auto; + white-space: nowrap; + display: flex; + align-items: center; + } + + &.phaseName { + flex-direction: column; + align-items: flex-start; + + .previewDates { + font-size: 13px; + display: flex; + color: $red; + font-weight: 300; + + span { + color: $dark-gray; + margin-right: 10px; + } + } + } + + &.col2 { + align-self: flex-end; + margin-bottom: auto; + margin-top: auto; + display: flex; + flex-direction: row; + + input { + border-radius: 2px; + } + + .dayPicker { + width: 180px; + margin-right: 30px; + + :global { + .DayPickerInput { + width: 100%; + } + .DayPickerInput-OverlayWrapper { + z-index: 10; + } + } + + input { + width: 100%; + } + } + + .timePicker { + width: 90px; + + input { + width: 100%; + } + + :global { + .rc-time-picker-clear { + top: 9px; + } + } + } + + .durationPicker { + width: 180px; + } + + .scorecards { + width: 190px; + margin-left: 30px; + } + + :global{ + .rc-time-picker-clear{ + display: none; + } + } + } + } +} + +.readOnlyValue { + color: black; +} + + diff --git a/src/components/StartDateInput/index.js b/src/components/StartDateInput/index.js new file mode 100644 index 00000000..82c3d6fd --- /dev/null +++ b/src/components/StartDateInput/index.js @@ -0,0 +1,141 @@ +import _ from 'lodash' +import moment from 'moment-timezone' +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import styles from './StartDateInput.module.scss' +import cn from 'classnames' +import 'react-day-picker/lib/style.css' +import 'rc-time-picker/assets/index.css' +import Select from '../Select' +import DateTime from '@nateradebaugh/react-datetime' +import isAfter from 'date-fns/isAfter' +import subDays from 'date-fns/subDays' +import '@nateradebaugh/react-datetime/scss/styles.scss' + +const dateFormat = 'MM/DD/YYYY HH:mm' +// const tcTimeZone = 'America/New_York' + +class StartDateInput extends Component { + constructor (props) { + super(props) + + this.onDateChange = this.onDateChange.bind(this) + } + + onDateChange (e) { + const { onUpdatePhase } = this.props + + onUpdatePhase(moment(e, dateFormat)) + } + + render () { + const { phase, onUpdateSelect, onUpdatePhase, withDates, withDuration, endDate, readOnly } = this.props + if (_.isEmpty(phase)) return null + + const date = moment(phase.date).format(dateFormat) + + return ( +
+
+
+ + { + withDuration && endDate && ( +
+ Ends: + {moment(endDate).local().format(`${dateFormat}`)} +
+ ) + } +
+
+ { + withDates && ( +
+ { + readOnly ? ( + {date} + ) + : ( + { + const yesterday = subDays(new Date(), 1) + return isAfter(current, yesterday) + }} + />)} +
+ ) + } + {/* { + withDates && ( +
+ {readOnly ? ( + {date} + ) : ( onUpdatePhase(moment(`${moment(selectedDay).format(dateFormat)} ${time.format(timeFormat)}`, `${dateFormat} ${timeFormat}`))} format={dateFormat} />)} +
+ ) + } + { + withDates && ( +
+ {readOnly ? ( + {time.format(timeFormat)} + ) : ( onUpdatePhase(value)} + />)} +
+ ) + } */} + { + withDuration && ( +
+ {readOnly ? ( + {phase.duration} + ) : ( onUpdatePhase(e.target.value)} min={1} placeholder='Duration (hours)' />)} +
+ ) + } + { + !_.isEmpty(phase.scorecards) && ( +
+ onDurationChange(e.target.value)} + autoFocus={inputRef.current === document.activeElement} + /> +
+ ) +} + +DurationInput.propTypes = { + duration: PropTypes.string, + onDurationChange: PropTypes.func.isRequired, + index: PropTypes.string.isRequired +} + +export default DurationInput diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index 2338d5d3..8e0220d6 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -9,6 +9,7 @@ import DateTime from '@nateradebaugh/react-datetime' import isAfter from 'date-fns/isAfter' import subDays from 'date-fns/subDays' import '@nateradebaugh/react-datetime/scss/styles.scss' +import DurationInput from '../DurationInput' const dateFormat = 'MM/DD/YYYY HH:mm' const MAX_LENGTH = 5 @@ -22,7 +23,7 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { if (phase) { setStartDate(phase.scheduledStartDate) setEndDate(phase.scheduledEndDate) - setDuration(moment(phase.scheduledEndDate).diff(phase.scheduledStartDate, 'seconds')) + setDuration(moment(phase.scheduledEndDate).diff(phase.scheduledStartDate, 'hours')) } }, []) @@ -46,7 +47,7 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { } setStartDate(moment(e).format(dateFormat)) - setDuration(moment(end).diff(start, 'seconds')) + setDuration(moment(end).diff(start, 'hours')) } const onEndDateChange = (e) => { @@ -58,20 +59,20 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { } setEndDate(moment(e).format(dateFormat)) - setDuration(moment(end).diff(start, 'seconds')) + setDuration(moment(end).diff(start, 'hours')) } const onDurationChange = (e) => { - if (e.target.value.length > MAX_LENGTH) return null + if (e.length > MAX_LENGTH) return null - const dur = parseInt(e.target.value || 0) + const dur = parseInt(e || 0) setDuration(dur) - const end = moment(startDate).add(duration, 'seconds') + const end = moment(startDate).add(dur, 'hours') setEndDate(moment(end).format(dateFormat)) } return ( -
+
@@ -118,13 +119,12 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { readOnly ? ( {duration} ) - : ( - )} + : }
From 1d4c52e81d419904e733201252ef567012b7333c Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 7 Mar 2022 22:11:33 +0800 Subject: [PATCH 04/31] merge master --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a52378f9..0d96a6c9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -150,7 +150,7 @@ workflows: context : org-global filters: &filters-dev branches: - only: ['develop', 'issue_1323'] + only: ['develop'] # Production builds are exectuted only on tagged commits to the # master branch. From b4707021aab30ee3a6b04e211d6b92ed9c251a39 Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Tue, 8 Mar 2022 18:34:05 +0800 Subject: [PATCH 05/31] fix issue 1329 --- src/components/ChallengeEditor/index.js | 23 +++++++++++++++++-- .../DurationInput/DurationInput.module.scss | 8 +++++++ src/components/DurationInput/index.js | 8 +++++-- .../PhaseInput/PhaseInput.module.scss | 7 +++++- src/components/PhaseInput/index.js | 13 ++++++++--- 5 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 src/components/DurationInput/DurationInput.module.scss diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 735c7267..0d174e47 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -139,6 +139,7 @@ class ChallengeEditor extends Component { this.onDeleteChallenge = this.onDeleteChallenge.bind(this) this.deleteModalLaunch = this.deleteModalLaunch.bind(this) this.toggleForumOnCreate = this.toggleForumOnCreate.bind(this) + this.isPhaseEditable = this.isPhaseEditable.bind(this) } componentDidMount () { @@ -1216,6 +1217,22 @@ class ChallengeEditor extends Component { return _.filter(timelineTemplates, tt => availableTemplateIds.indexOf(tt.id) !== -1) } + /** + * Check if current phase is active for edit + */ + isPhaseEditable (phaseIndex) { + const { challenge } = this.state + const { phases, currentPhaseNames } = challenge + + let currentIndex = phases.findIndex((item) => { + return item.name !== 'Registration' && currentPhaseNames.includes(item.name) && item.isOpen + }) + + if (currentIndex === -1 || currentIndex > phaseIndex) return false + + return true + } + render () { const { isLaunch, @@ -1577,15 +1594,17 @@ class ChallengeEditor extends Component { phase={phase} phaseIndex={uuidv4()} readOnly={false} + isActive={this.isPhaseEditable(index)} onUpdatePhase={(item) => { if ((item.startDate && !moment(item.startDate).isSame(phase.scheduledStartDate)) || - (item.endDate && !moment(item.endDate).isSame(phase.scheduledEndDate)) + (item.endDate && !moment(item.endDate).isSame(phase.scheduledEndDate)) ) { this.onUpdatePhaseDate(item, index) } }} /> - )) + ) + ) } )} diff --git a/src/components/DurationInput/DurationInput.module.scss b/src/components/DurationInput/DurationInput.module.scss new file mode 100644 index 00000000..ae127eb2 --- /dev/null +++ b/src/components/DurationInput/DurationInput.module.scss @@ -0,0 +1,8 @@ +@import "../../styles/includes"; + +.durationInput { + &:disabled { + cursor: not-allowed !important; + background-color: $inactive !important; + } +} \ No newline at end of file diff --git a/src/components/DurationInput/index.js b/src/components/DurationInput/index.js index 2a637fd2..ca5d9e63 100644 --- a/src/components/DurationInput/index.js +++ b/src/components/DurationInput/index.js @@ -1,12 +1,14 @@ import React, { useRef } from 'react' import PropTypes from 'prop-types' +import styles from './DurationInput.module.scss' -const DurationInput = ({ duration, onDurationChange, index }) => { +const DurationInput = ({ duration, onDurationChange, index, isActive }) => { const inputRef = useRef(null) return (
{ value={Number(duration).toString()} onChange={e => onDurationChange(e.target.value)} autoFocus={inputRef.current === document.activeElement} + disabled={!isActive} />
) @@ -23,7 +26,8 @@ const DurationInput = ({ duration, onDurationChange, index }) => { DurationInput.propTypes = { duration: PropTypes.string, onDurationChange: PropTypes.func.isRequired, - index: PropTypes.string.isRequired + index: PropTypes.string.isRequired, + isActive: PropTypes.bool.isRequired } export default DurationInput diff --git a/src/components/PhaseInput/PhaseInput.module.scss b/src/components/PhaseInput/PhaseInput.module.scss index c1cf43f1..12ea4720 100644 --- a/src/components/PhaseInput/PhaseInput.module.scss +++ b/src/components/PhaseInput/PhaseInput.module.scss @@ -146,4 +146,9 @@ color: black; } - +.dateTimeInput { + &:disabled { + cursor: not-allowed !important; + background-color: $inactive !important; + } +} diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index 8e0220d6..ad985f79 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -14,7 +14,7 @@ import DurationInput from '../DurationInput' const dateFormat = 'MM/DD/YYYY HH:mm' const MAX_LENGTH = 5 -const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { +const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => { const [startDate, setStartDate] = useState() const [endDate, setEndDate] = useState() const [duration, setDuration] = useState() @@ -86,12 +86,14 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { ) : ( { const yesterday = subDays(new Date(), 1) return isAfter(current, yesterday) }} + disabled={!isActive} />)}
@@ -104,11 +106,13 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { ) : ( { return isAfter(current, new Date(startDate)) }} + disabled={!isActive} />)}
@@ -124,6 +128,7 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { name={phase.name} onDurationChange={onDurationChange} index={phaseIndex} + isActive={isActive} />}
@@ -134,13 +139,15 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { PhaseInput.defaultProps = { endDate: null, - readOnly: false + readOnly: false, + isActive: false } PhaseInput.propTypes = { phase: PropTypes.shape().isRequired, onUpdatePhase: PropTypes.func.isRequired, readOnly: PropTypes.bool, - phaseIndex: PropTypes.string.isRequired + phaseIndex: PropTypes.string.isRequired, + isActive: PropTypes.bool } export default PhaseInput From de6f09d76faea39e8ec8ebf2d810866780b2473a Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Wed, 9 Mar 2022 05:58:12 +0600 Subject: [PATCH 06/31] feat: logout user if idle for configured duration --- config/constants/development.js | 6 +- package-lock.json | 5 ++ package.json | 1 + src/routes.js | 147 +++++++++++++++++++++----------- src/styles/modal.module.scss | 114 +++++++++++++++++++++++++ 5 files changed, 223 insertions(+), 50 deletions(-) create mode 100644 src/styles/modal.module.scss diff --git a/config/constants/development.js b/config/constants/development.js index 597dbb57..2efc33f9 100644 --- a/config/constants/development.js +++ b/config/constants/development.js @@ -39,5 +39,9 @@ module.exports = { FILE_PICKER_API_KEY: process.env.FILE_PICKER_API_KEY, FILE_PICKER_CONTAINER_NAME: 'tc-challenge-v5-dev', FILE_PICKER_REGION: 'us-east-1', - FILE_PICKER_CNAME: 'fs.topcoder.com' + FILE_PICKER_CNAME: 'fs.topcoder.com', + // if idle for this many minutes, show user a prompt saying they'll be logged out + IDLE_TIMEOUT_MINUTES: 10, + // duration to show the prompt saying user will be logged out, before actually logging out the user + IDLE_TIMEOUT_GRACE_MINUTES: 5 } diff --git a/package-lock.json b/package-lock.json index e44e6c3c..65c76e8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15002,6 +15002,11 @@ "react-side-effect": "^1.1.0" } }, + "react-idle-timer": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-4.6.4.tgz", + "integrity": "sha512-iq61dPud8fgj7l1KOJEY5pyiD532fW0KcIe/5XUe/0lB/4Vytoy4tZBlLGSiYodPzKxTL6HyKoOmG6tyzjD7OQ==" + }, "react-input-autosize": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz", diff --git a/package.json b/package.json index 726e30b9..02e50216 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "react-dom": "^16.7.0", "react-google-charts": "^3.0.13", "react-helmet": "^5.2.0", + "react-idle-timer": "^4.6.4", "react-js-pagination": "^3.0.3", "react-popper": "^2.2.4", "react-redux": "^6.0.0", diff --git a/src/routes.js b/src/routes.js index 1c3041da..f91e870e 100644 --- a/src/routes.js +++ b/src/routes.js @@ -17,8 +17,15 @@ import { loadChallengeDetails } from './actions/challenges' import { connect } from 'react-redux' import { checkAllowedRoles } from './util/tc' import { setCookie, removeCookie, isBetaMode } from './util/cookie' +import IdleTimer from 'react-idle-timer' +import AlertModal from './components/Modal/AlertModal' +import modalStyles from './styles/modal.module.scss' -const { ACCOUNTS_APP_LOGIN_URL } = process.env +const { ACCOUNTS_APP_LOGIN_URL, IDLE_TIMEOUT_MINUTES, IDLE_TIMEOUT_GRACE_MINUTES, COMMUNITY_APP_URL } = process.env + +const theme = { + container: modalStyles.modalContainer +} class RedirectToChallenge extends React.Component { componentWillMount () { @@ -59,6 +66,19 @@ RedirectToChallenge.propTypes = { const ConnectRedirectToChallenge = connect(mapStateToProps, mapDispatchToProps)(RedirectToChallenge) class Routes extends React.Component { + constructor (props) { + super(props) + this.idleTimer = null + this.handleOnIdle = this.handleOnIdle.bind(this) + + this.logoutIntervalRef = null + this.state = { + showIdleModal: false, + logsoutIn: IDLE_TIMEOUT_GRACE_MINUTES * 60, // convert to seconds + logoutIntervalRef: null + } + } + componentWillMount () { this.checkAuth() } @@ -87,68 +107,97 @@ class Routes extends React.Component { } } + handleOnIdle () { + this.idleTimer.pause() + const intervalId = setInterval(() => { + const remaining = this.state.logsoutIn + if (remaining > 0) { + this.setState(state => ({ ...state, logsoutIn: remaining - 1 })) + } else { + window.location = `${COMMUNITY_APP_URL}/logout` + } + }, 1000) + + this.setState(state => ({ ...state, showIdleModal: true, logoutIntervalRef: intervalId })) + } + render () { if (!this.props.isLoggedIn) { return null } - let isAllowed = checkAllowedRoles(_.get(decodeToken(this.props.token), 'roles')) + const isAllowed = checkAllowedRoles(_.get(decodeToken(this.props.token), 'roles')) + const modal = (= 60 ? Math.ceil(this.state.logsoutIn / 60) + ' minute(s).' : this.state.logsoutIn + ' second(s)'}`} + closeText='Resume Session' + onClose={() => { + clearInterval(this.state.logoutIntervalRef) + if (this.idleTimer.isIdle()) { + this.idleTimer.resume() + this.idleTimer.reset() + this.setState(state => ({ + ...state, showIdleModal: false, logsoutIn: 120 + })) + } + }} + />) - if (!isAllowed) { - let warnMessage = 'You are not authorized to use this application' - return ( + return ( + { this.idleTimer = ref }} timeout={1000 * 60 * IDLE_TIMEOUT_MINUTES} onIdle={this.handleOnIdle} debounce={250}> + {!isAllowed && renderApp( - , + , , )()} - /> + />} + + {isAllowed && <> + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} /> + + renderApp( + , + , + + )()} /> + renderApp( + , + , + + )()} /> + } + + {/* If path is not defined redirect to landing page */} - ) - } - - return ( - - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} /> - - renderApp( - , - , - - )()} /> - renderApp( - , - , - - )()} /> - {/* If path is not defined redirect to landing page */} - - + {this.state.showIdleModal && modal} + ) } } diff --git a/src/styles/modal.module.scss b/src/styles/modal.module.scss new file mode 100644 index 00000000..12a06dbc --- /dev/null +++ b/src/styles/modal.module.scss @@ -0,0 +1,114 @@ +@import "./includes.scss"; + +.modalContainer { + padding: 0; + position: fixed; + overflow: auto; + z-index: 10000; + top: 0; + right: 0; + bottom: 0; + left: 0; + box-sizing: border-box; + width: auto; + max-width: none; + transform: none; + background: transparent; + color: $text-color; + opacity: 1; + display: flex; + justify-content: center; + align-items: center; + + .contentContainer { + box-sizing: border-box; + background: $white; + opacity: 1; + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + border-radius: 6px; + margin: 0 auto; + width: 100% !important; + padding: 30px; + + .content { + padding: 30px; + width: 100%; + height: 100%; + } + + .title { + @include roboto-bold(); + + font-size: 30px; + line-height: 36px; + margin-bottom: 30px; + margin-top: 0; + + } + + span { + @include roboto; + + font-size: 22px; + font-weight: 400; + line-height: 26px; + } + + &.confirm { + width: 999px; + + .buttonGroup { + display: flex; + justify-content: space-between; + margin-top: 30px; + + .buttonSizeA { + width: 193px; + height: 40px; + margin-right: 33px; + + span { + font-size: 18px; + font-weight: 500; + } + } + + .buttonSizeB{ + width: 160px; + height: 40px; + + span { + font-size: 18px; + font-weight: 500; + line-height: 22px; + } + } + } + } + + .buttonGroup { + display: flex; + justify-content: space-between; + margin-top: 30px; + + .button { + width: 135px; + height: 40px; + margin-right: 66px; + + span { + font-size: 18px; + font-weight: 500; + } + } + + .button:last-child { + margin-right: 0; + } + } + } +} From 1949c12a2de544f70267be46498ba84cf9e8fb09 Mon Sep 17 00:00:00 2001 From: sachin-maheshwari Date: Wed, 9 Mar 2022 13:10:12 +0530 Subject: [PATCH 07/31] Update config.yml deploying on dev --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0d96a6c9..ca26190c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -150,7 +150,7 @@ workflows: context : org-global filters: &filters-dev branches: - only: ['develop'] + only: ['develop', 'jira-vuln-2333'] # Production builds are exectuted only on tagged commits to the # master branch. From 4dd155e8ab0ac662e56aeac64e8bd0e1950857fe Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 10 Mar 2022 07:57:53 +0600 Subject: [PATCH 08/31] feat: add ability to logout immediately * add "Logout Now" in idle timeout modal * reset modal display time when user resumes session --- config/constants/development.js | 4 ++-- src/routes.js | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/config/constants/development.js b/config/constants/development.js index 2efc33f9..1057e70b 100644 --- a/config/constants/development.js +++ b/config/constants/development.js @@ -41,7 +41,7 @@ module.exports = { FILE_PICKER_REGION: 'us-east-1', FILE_PICKER_CNAME: 'fs.topcoder.com', // if idle for this many minutes, show user a prompt saying they'll be logged out - IDLE_TIMEOUT_MINUTES: 10, + IDLE_TIMEOUT_MINUTES: 1, // duration to show the prompt saying user will be logged out, before actually logging out the user - IDLE_TIMEOUT_GRACE_MINUTES: 5 + IDLE_TIMEOUT_GRACE_MINUTES: 1 } diff --git a/src/routes.js b/src/routes.js index f91e870e..6fd6e8cf 100644 --- a/src/routes.js +++ b/src/routes.js @@ -18,8 +18,8 @@ import { connect } from 'react-redux' import { checkAllowedRoles } from './util/tc' import { setCookie, removeCookie, isBetaMode } from './util/cookie' import IdleTimer from 'react-idle-timer' -import AlertModal from './components/Modal/AlertModal' import modalStyles from './styles/modal.module.scss' +import ConfirmationModal from './components/Modal/ConfirmationModal' const { ACCOUNTS_APP_LOGIN_URL, IDLE_TIMEOUT_MINUTES, IDLE_TIMEOUT_GRACE_MINUTES, COMMUNITY_APP_URL } = process.env @@ -127,21 +127,25 @@ class Routes extends React.Component { } const isAllowed = checkAllowedRoles(_.get(decodeToken(this.props.token), 'roles')) - const modal = (= 60 ? Math.ceil(this.state.logsoutIn / 60) + ' minute(s).' : this.state.logsoutIn + ' second(s)'}`} - closeText='Resume Session' - onClose={() => { + confirmText='Logout Now' + cancelText='Resume Session' + onCancel={() => { clearInterval(this.state.logoutIntervalRef) if (this.idleTimer.isIdle()) { this.idleTimer.resume() this.idleTimer.reset() this.setState(state => ({ - ...state, showIdleModal: false, logsoutIn: 120 + ...state, showIdleModal: false, logsoutIn: IDLE_TIMEOUT_GRACE_MINUTES * 60 })) } }} + onConfirm={() => { + window.location = `${COMMUNITY_APP_URL}/logout` + }} />) return ( From 23b490939b8e38bbda3de6ea5ba67e4d66509a45 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 10 Mar 2022 08:02:15 +0600 Subject: [PATCH 09/31] update idle timeout duration --- config/constants/development.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/constants/development.js b/config/constants/development.js index 1057e70b..2efc33f9 100644 --- a/config/constants/development.js +++ b/config/constants/development.js @@ -41,7 +41,7 @@ module.exports = { FILE_PICKER_REGION: 'us-east-1', FILE_PICKER_CNAME: 'fs.topcoder.com', // if idle for this many minutes, show user a prompt saying they'll be logged out - IDLE_TIMEOUT_MINUTES: 1, + IDLE_TIMEOUT_MINUTES: 10, // duration to show the prompt saying user will be logged out, before actually logging out the user - IDLE_TIMEOUT_GRACE_MINUTES: 1 + IDLE_TIMEOUT_GRACE_MINUTES: 5 } From 1efc162d07a44613e4bb53037d6f9cfe5c6b4cf0 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Thu, 10 Mar 2022 09:52:14 +0600 Subject: [PATCH 10/31] fix: new challenge not working --- src/routes.js | 82 +++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/src/routes.js b/src/routes.js index 6fd6e8cf..fbe0be96 100644 --- a/src/routes.js +++ b/src/routes.js @@ -150,56 +150,54 @@ class Routes extends React.Component { return ( { this.idleTimer = ref }} timeout={1000 * 60 * IDLE_TIMEOUT_MINUTES} onIdle={this.handleOnIdle} debounce={250}> - - {!isAllowed && + {!isAllowed && renderApp( , , )()} - />} - - {isAllowed && <> - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} /> - - renderApp( - , - , - - )()} /> - renderApp( - , - , - - )()} /> - } - + /> + + } + {isAllowed && + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} /> + + renderApp( + , + , + + )()} /> + renderApp( + , + , + + )()} /> {/* If path is not defined redirect to landing page */} - + } {this.state.showIdleModal && modal} ) From f3efd38b28aa3fb6caa75be25d3a2048c0360826 Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Thu, 10 Mar 2022 17:12:14 +0800 Subject: [PATCH 11/31] fix 1328 --- src/components/ChallengeEditor/index.js | 25 ++++++++++++++++++++++--- src/components/PhaseInput/index.js | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 0d174e47..be561754 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -813,10 +813,28 @@ class ChallengeEditor extends Component { } onUpdatePhaseDate (phase, index) { + const { phases } = this.state.challenge let newChallenge = _.cloneDeep(this.state.challenge) newChallenge.phases[index]['duration'] = phase.duration - newChallenge.phases[index]['scheduledStartDate'] = phase.scheduledStartDate - newChallenge.phases[index]['scheduledEndDate'] = phase.scheduledEndDate + newChallenge.phases[index]['scheduledStartDate'] = phase.startDate + newChallenge.phases[index]['scheduledEndDate'] = phase.endDate + + let lastDate = phase.endDate + for (let phaseIndex = index + 1; phaseIndex < phases.length; phaseIndex++) { + if (moment(phases[phaseIndex]['scheduledStartDate']).isBefore(lastDate)) { + lastDate = moment(lastDate).add('1', 'hour').format('MM/DD/YYYY HH:mm') + newChallenge.phases[phaseIndex]['scheduledStartDate'] = lastDate + + if (moment(phases[phaseIndex]['scheduledEndDate']).isBefore(lastDate)) { + lastDate = moment(lastDate).add('1', 'hour').format('MM/DD/YYYY HH:mm') + newChallenge.phases[phaseIndex]['scheduledEndDate'] = lastDate + } + + newChallenge.phases[phaseIndex]['duration'] = + moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).diff(newChallenge.phases[phaseIndex]['scheduledStartDate'], 'hours') + } + } + this.setState({ challenge: newChallenge }) } @@ -1594,7 +1612,8 @@ class ChallengeEditor extends Component { phase={phase} phaseIndex={uuidv4()} readOnly={false} - isActive={this.isPhaseEditable(index)} + // isActive={this.isPhaseEditable(index)} + isActive onUpdatePhase={(item) => { if ((item.startDate && !moment(item.startDate).isSame(phase.scheduledStartDate)) || (item.endDate && !moment(item.endDate).isSame(phase.scheduledEndDate)) diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index ad985f79..d006747b 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -25,7 +25,7 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => setEndDate(phase.scheduledEndDate) setDuration(moment(phase.scheduledEndDate).diff(phase.scheduledStartDate, 'hours')) } - }, []) + }, [phase]) useEffect(() => { if (!readOnly) { From 13e2ca4de5d9f5aef6a71f39eed8b0c5f9d655fa Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Fri, 11 Mar 2022 15:03:09 +0800 Subject: [PATCH 12/31] fix issue 1334 --- src/components/ChallengeEditor/index.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index be561754..d524b764 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -1239,16 +1239,8 @@ class ChallengeEditor extends Component { * Check if current phase is active for edit */ isPhaseEditable (phaseIndex) { - const { challenge } = this.state - const { phases, currentPhaseNames } = challenge - - let currentIndex = phases.findIndex((item) => { - return item.name !== 'Registration' && currentPhaseNames.includes(item.name) && item.isOpen - }) - - if (currentIndex === -1 || currentIndex > phaseIndex) return false - - return true + const { phases } = this.state.challenge + return moment(phases[phaseIndex].scheduledEndDate).isAfter(moment()) } render () { @@ -1612,8 +1604,7 @@ class ChallengeEditor extends Component { phase={phase} phaseIndex={uuidv4()} readOnly={false} - // isActive={this.isPhaseEditable(index)} - isActive + isActive={this.isPhaseEditable(index)} onUpdatePhase={(item) => { if ((item.startDate && !moment(item.startDate).isSame(phase.scheduledStartDate)) || (item.endDate && !moment(item.endDate).isSame(phase.scheduledEndDate)) From 639bfd99d9815d570437c8cde9f175ce0398e5d9 Mon Sep 17 00:00:00 2001 From: Sachin Maheshwari Date: Tue, 15 Mar 2022 12:30:21 +0530 Subject: [PATCH 13/31] production conf issue --- config/constants/production.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/constants/production.js b/config/constants/production.js index 5d87c493..4040c262 100644 --- a/config/constants/production.js +++ b/config/constants/production.js @@ -39,5 +39,7 @@ module.exports = { FILE_PICKER_API_KEY: process.env.FILE_PICKER_API_KEY, FILE_PICKER_CONTAINER_NAME: 'tc-challenge-v5-prod', FILE_PICKER_REGION: 'us-east-1', - FILE_PICKER_CNAME: 'fs.topcoder.com' + FILE_PICKER_CNAME: 'fs.topcoder.com', + IDLE_TIMEOUT_MINUTES: 10, + IDLE_TIMEOUT_GRACE_MINUTES: 5 } From 188f214a767d1440362712f4fe0bc365a1bdf0d6 Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Fri, 18 Mar 2022 20:40:11 +0800 Subject: [PATCH 14/31] fix phase issue --- src/components/ChallengeEditor/index.js | 4 +++- src/util/date.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index d524b764..7aeab5e1 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -879,7 +879,9 @@ class ChallengeEditor extends Component { } challenge.phases = challenge.phases.map((p) => pick([ 'duration', - 'phaseId' + 'phaseId', + 'scheduledStartDate', + 'scheduledEndDate' ], p)) if (challenge.terms && challenge.terms.length === 0) delete challenge.terms delete challenge.attachments diff --git a/src/util/date.js b/src/util/date.js index 29030d96..cf5d5205 100644 --- a/src/util/date.js +++ b/src/util/date.js @@ -109,6 +109,7 @@ export const updateChallengePhaseBeforeSendRequest = (challengeDetail) => { const hourToSecond = 60 * 60 if (challengeDetail.phases) { const challengeDetailTmp = _.cloneDeep(challengeDetail) + challengeDetailTmp.startDate = challengeDetail.phases[0].scheduledStartDate challengeDetailTmp.phases = challengeDetailTmp.phases.map((p) => ({ duration: p.duration * hourToSecond, phaseId: p.phaseId, From b32911dc0388765c2534a63e441389e659df0cda Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Fri, 25 Mar 2022 10:39:53 +0800 Subject: [PATCH 15/31] fix phase issue --- src/components/ChallengeEditor/index.js | 9 ++++++++- src/components/DurationInput/index.js | 5 ++++- src/util/date.js | 8 +++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 7aeab5e1..c81e9bd4 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -1242,7 +1242,14 @@ class ChallengeEditor extends Component { */ isPhaseEditable (phaseIndex) { const { phases } = this.state.challenge - return moment(phases[phaseIndex].scheduledEndDate).isAfter(moment()) + const phase = phases[phaseIndex] + if (phase.name === 'Registration' && moment(phase.scheduledEndDate).isAfter(moment())) { + return true + } + if (!phase.isOpen) { + return false + } + return moment(phase.scheduledEndDate).isAfter(moment()) } render () { diff --git a/src/components/DurationInput/index.js b/src/components/DurationInput/index.js index ca5d9e63..0ce34f99 100644 --- a/src/components/DurationInput/index.js +++ b/src/components/DurationInput/index.js @@ -15,7 +15,10 @@ const DurationInput = ({ duration, onDurationChange, index, isActive }) => { min={0} type='number' value={Number(duration).toString()} - onChange={e => onDurationChange(e.target.value)} + onChange={e => { + e.preventDefault() + onDurationChange(e.target.value) + }} autoFocus={inputRef.current === document.activeElement} disabled={!isActive} /> diff --git a/src/util/date.js b/src/util/date.js index cf5d5205..b0bad1ad 100644 --- a/src/util/date.js +++ b/src/util/date.js @@ -110,11 +110,13 @@ export const updateChallengePhaseBeforeSendRequest = (challengeDetail) => { if (challengeDetail.phases) { const challengeDetailTmp = _.cloneDeep(challengeDetail) challengeDetailTmp.startDate = challengeDetail.phases[0].scheduledStartDate + // challengeDetailTmp.registrationStartDate = moment(challengeDetail.phases[0].scheduledStartDate) + // challengeDetailTmp.registrationEndDate = moment(challengeDetail.phases[0].scheduledEndDate) + // challengeDetailTmp.submissionStartDate = moment(challengeDetail.phases[1].scheduledStartDate) + // challengeDetailTmp.submissionEndDate = moment(challengeDetail.phases[1].scheduledEndDate) challengeDetailTmp.phases = challengeDetailTmp.phases.map((p) => ({ duration: p.duration * hourToSecond, - phaseId: p.phaseId, - scheduledStartDate: p.scheduledStartDate, - scheduledEndDate: p.scheduledEndDate + phaseId: p.phaseId })) return challengeDetailTmp } From f94e992aed34900ce2b007d2f6db9227afcad65d Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Mon, 28 Mar 2022 11:04:08 +0800 Subject: [PATCH 16/31] additiional fixes --- src/components/ChallengeEditor/index.js | 5 +---- src/components/PhaseInput/index.js | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index c81e9bd4..1c7bc911 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -1243,10 +1243,7 @@ class ChallengeEditor extends Component { isPhaseEditable (phaseIndex) { const { phases } = this.state.challenge const phase = phases[phaseIndex] - if (phase.name === 'Registration' && moment(phase.scheduledEndDate).isAfter(moment())) { - return true - } - if (!phase.isOpen) { + if (phase.name !== 'Registration') { return false } return moment(phase.scheduledEndDate).isAfter(moment()) diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index d006747b..a85686fb 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -128,7 +128,7 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => name={phase.name} onDurationChange={onDurationChange} index={phaseIndex} - isActive={isActive} + isActive />}
From a848a9140674050e2e499f5b8c503054d0629143 Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Tue, 29 Mar 2022 17:44:47 +0800 Subject: [PATCH 17/31] additiional fixes 3 --- src/components/ChallengeEditor/index.js | 7 ++++++- src/components/PhaseInput/index.js | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 1c7bc911..bb239e14 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -823,7 +823,12 @@ class ChallengeEditor extends Component { for (let phaseIndex = index + 1; phaseIndex < phases.length; phaseIndex++) { if (moment(phases[phaseIndex]['scheduledStartDate']).isBefore(lastDate)) { lastDate = moment(lastDate).add('1', 'hour').format('MM/DD/YYYY HH:mm') - newChallenge.phases[phaseIndex]['scheduledStartDate'] = lastDate + + if (newChallenge.phases[phaseIndex]['name'] !== 'Submission') { + newChallenge.phases[phaseIndex]['scheduledStartDate'] = lastDate + } else { + newChallenge.phases[phaseIndex]['scheduledStartDate'] = newChallenge.phases[index]['scheduledStartDate'] + } if (moment(phases[phaseIndex]['scheduledEndDate']).isBefore(lastDate)) { lastDate = moment(lastDate).add('1', 'hour').format('MM/DD/YYYY HH:mm') diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index a85686fb..e12cf34a 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -12,6 +12,8 @@ import '@nateradebaugh/react-datetime/scss/styles.scss' import DurationInput from '../DurationInput' const dateFormat = 'MM/DD/YYYY HH:mm' +const inputDateFormat = 'MM/dd/yyyy' +const inputTimeFormat = 'HH:mm' const MAX_LENGTH = 5 const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => { @@ -25,6 +27,13 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => setEndDate(phase.scheduledEndDate) setDuration(moment(phase.scheduledEndDate).diff(phase.scheduledStartDate, 'hours')) } + }, []) + + useEffect(() => { + if (phase) { + setStartDate(phase.scheduledStartDate) + setEndDate(phase.scheduledEndDate) + } }, [phase]) useEffect(() => { @@ -94,6 +103,8 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => return isAfter(current, yesterday) }} disabled={!isActive} + dateFormat={inputDateFormat} + timeFormat={inputTimeFormat} />)}
@@ -113,6 +124,8 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => return isAfter(current, new Date(startDate)) }} disabled={!isActive} + dateFormat={inputDateFormat} + timeFormat={inputTimeFormat} />)}
From 05308acf4fedc0ae8c18363135c2636e23ffcef4 Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Wed, 30 Mar 2022 10:33:12 +0800 Subject: [PATCH 18/31] additional fixes on phase input --- src/components/ChallengeEditor/index.js | 2 +- src/components/PhaseInput/index.js | 32 ++----------------------- src/util/date.js | 2 +- 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index bb239e14..921a5d86 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -1251,7 +1251,7 @@ class ChallengeEditor extends Component { if (phase.name !== 'Registration') { return false } - return moment(phase.scheduledEndDate).isAfter(moment()) + return !phase.isOpen } render () { diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index e12cf34a..18ad1a9f 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -59,18 +59,6 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => setDuration(moment(end).diff(start, 'hours')) } - const onEndDateChange = (e) => { - const end = moment(e).format() - const start = moment(startDate).format() - - if (moment(end).isBefore(moment(start))) { - return null - } - - setEndDate(moment(e).format(dateFormat)) - setDuration(moment(end).diff(start, 'hours')) - } - const onDurationChange = (e) => { if (e.length > MAX_LENGTH) return null @@ -90,7 +78,7 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => Start Date:
{ - readOnly ? ( + readOnly || !isActive ? ( {moment(startDate).format(dateFormat)} ) : ( @@ -102,7 +90,6 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => const yesterday = subDays(new Date(), 1) return isAfter(current, yesterday) }} - disabled={!isActive} dateFormat={inputDateFormat} timeFormat={inputTimeFormat} />)} @@ -111,22 +98,7 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) =>
End Date:
- { - readOnly ? ( - {moment(endDate).format(dateFormat)} - ) - : ( - { - return isAfter(current, new Date(startDate)) - }} - disabled={!isActive} - dateFormat={inputDateFormat} - timeFormat={inputTimeFormat} - />)} + {moment(endDate).format(dateFormat)}
diff --git a/src/util/date.js b/src/util/date.js index b0bad1ad..32e2733c 100644 --- a/src/util/date.js +++ b/src/util/date.js @@ -109,7 +109,7 @@ export const updateChallengePhaseBeforeSendRequest = (challengeDetail) => { const hourToSecond = 60 * 60 if (challengeDetail.phases) { const challengeDetailTmp = _.cloneDeep(challengeDetail) - challengeDetailTmp.startDate = challengeDetail.phases[0].scheduledStartDate + challengeDetailTmp.startDate = moment(challengeDetail.phases[0].scheduledStartDate) // challengeDetailTmp.registrationStartDate = moment(challengeDetail.phases[0].scheduledStartDate) // challengeDetailTmp.registrationEndDate = moment(challengeDetail.phases[0].scheduledEndDate) // challengeDetailTmp.submissionStartDate = moment(challengeDetail.phases[1].scheduledStartDate) From ad1ed4a1dc87d35dacfa69b2b1f933647fb9f517 Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Tue, 5 Apr 2022 09:36:51 +0800 Subject: [PATCH 19/31] fix issue 1357 --- src/components/ChallengeEditor/index.js | 28 ++++++++++--------- .../DurationInput/DurationInput.module.scss | 10 +++++++ src/components/PhaseInput/index.js | 7 ----- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 921a5d86..4736844f 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -820,23 +820,25 @@ class ChallengeEditor extends Component { newChallenge.phases[index]['scheduledEndDate'] = phase.endDate let lastDate = phase.endDate - for (let phaseIndex = index + 1; phaseIndex < phases.length; phaseIndex++) { - if (moment(phases[phaseIndex]['scheduledStartDate']).isBefore(lastDate)) { - lastDate = moment(lastDate).add('1', 'hour').format('MM/DD/YYYY HH:mm') + for (let phaseIndex = 0; phaseIndex < phases.length; phaseIndex++) { + if (phaseIndex !== index) { + if (moment(phases[phaseIndex]['scheduledStartDate']).isBefore(lastDate)) { + lastDate = moment(lastDate).add('1', 'hour').format('MM/DD/YYYY HH:mm') - if (newChallenge.phases[phaseIndex]['name'] !== 'Submission') { - newChallenge.phases[phaseIndex]['scheduledStartDate'] = lastDate - } else { - newChallenge.phases[phaseIndex]['scheduledStartDate'] = newChallenge.phases[index]['scheduledStartDate'] - } + if (newChallenge.phases[phaseIndex]['name'] !== 'Submission') { + newChallenge.phases[phaseIndex]['scheduledStartDate'] = lastDate + } else { + newChallenge.phases[phaseIndex]['scheduledStartDate'] = newChallenge.phases[index]['scheduledStartDate'] + } - if (moment(phases[phaseIndex]['scheduledEndDate']).isBefore(lastDate)) { - lastDate = moment(lastDate).add('1', 'hour').format('MM/DD/YYYY HH:mm') - newChallenge.phases[phaseIndex]['scheduledEndDate'] = lastDate - } + if (moment(phases[phaseIndex]['scheduledEndDate']).isBefore(lastDate)) { + lastDate = moment(lastDate).add('1', 'hour').format('MM/DD/YYYY HH:mm') + newChallenge.phases[phaseIndex]['scheduledEndDate'] = lastDate + } - newChallenge.phases[phaseIndex]['duration'] = + newChallenge.phases[phaseIndex]['duration'] = moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).diff(newChallenge.phases[phaseIndex]['scheduledStartDate'], 'hours') + } } } diff --git a/src/components/DurationInput/DurationInput.module.scss b/src/components/DurationInput/DurationInput.module.scss index ae127eb2..c6a1298c 100644 --- a/src/components/DurationInput/DurationInput.module.scss +++ b/src/components/DurationInput/DurationInput.module.scss @@ -5,4 +5,14 @@ cursor: not-allowed !important; background-color: $inactive !important; } + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + &[type=number] { + -moz-appearance: textfield; + } } \ No newline at end of file diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index 18ad1a9f..10086d45 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -27,13 +27,6 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => setEndDate(phase.scheduledEndDate) setDuration(moment(phase.scheduledEndDate).diff(phase.scheduledStartDate, 'hours')) } - }, []) - - useEffect(() => { - if (phase) { - setStartDate(phase.scheduledStartDate) - setEndDate(phase.scheduledEndDate) - } }, [phase]) useEffect(() => { From 6deb270e03577b58a1c43c66381ca0432ecc0599 Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Tue, 5 Apr 2022 17:54:47 +0800 Subject: [PATCH 20/31] fix issue 1358 --- src/components/ChallengeEditor/index.js | 4 ++++ src/components/DurationInput/index.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 4736844f..25b6faae 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -836,6 +836,10 @@ class ChallengeEditor extends Component { newChallenge.phases[phaseIndex]['scheduledEndDate'] = lastDate } + if (moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).isAfter(phases[phaseIndex]['scheduledStartDate'])) { + newChallenge.phases[phaseIndex]['scheduledStartDate'] = moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).subtract('1', 'hour').format('MM/DD/YYYY HH:mm') + } + newChallenge.phases[phaseIndex]['duration'] = moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).diff(newChallenge.phases[phaseIndex]['scheduledStartDate'], 'hours') } diff --git a/src/components/DurationInput/index.js b/src/components/DurationInput/index.js index 0ce34f99..ea2c2c78 100644 --- a/src/components/DurationInput/index.js +++ b/src/components/DurationInput/index.js @@ -12,7 +12,7 @@ const DurationInput = ({ duration, onDurationChange, index, isActive }) => { id={`duration-${index}`} key={`duration-${index}`} ref={inputRef} - min={0} + min={1} type='number' value={Number(duration).toString()} onChange={e => { From d750d95f70d3296ba1a6199f760905b838be62be Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Thu, 7 Apr 2022 08:56:35 +0800 Subject: [PATCH 21/31] fix issue 1358 --- src/components/ChallengeEditor/index.js | 6 +++--- src/components/PhaseInput/index.js | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 25b6faae..ac35a431 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -836,9 +836,9 @@ class ChallengeEditor extends Component { newChallenge.phases[phaseIndex]['scheduledEndDate'] = lastDate } - if (moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).isAfter(phases[phaseIndex]['scheduledStartDate'])) { - newChallenge.phases[phaseIndex]['scheduledStartDate'] = moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).subtract('1', 'hour').format('MM/DD/YYYY HH:mm') - } + // if (moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).isAfter(phases[phaseIndex]['scheduledStartDate'])) { + // newChallenge.phases[phaseIndex]['scheduledStartDate'] = moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).subtract('1', 'hour').format('MM/DD/YYYY HH:mm') + // } newChallenge.phases[phaseIndex]['duration'] = moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).diff(newChallenge.phases[phaseIndex]['scheduledStartDate'], 'hours') diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index 10086d45..18ad1a9f 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -27,6 +27,13 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => setEndDate(phase.scheduledEndDate) setDuration(moment(phase.scheduledEndDate).diff(phase.scheduledStartDate, 'hours')) } + }, []) + + useEffect(() => { + if (phase) { + setStartDate(phase.scheduledStartDate) + setEndDate(phase.scheduledEndDate) + } }, [phase]) useEffect(() => { From 063bb862109d4ab5f567b895ccd61b9281f75e21 Mon Sep 17 00:00:00 2001 From: 52code Date: Mon, 11 Apr 2022 17:27:23 +0800 Subject: [PATCH 22/31] fix issue 1370 --- src/components/ChallengeEditor/index.js | 38 +++++++++---------------- src/components/PhaseInput/index.js | 10 +------ 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index ac35a431..9521fa37 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -819,31 +819,21 @@ class ChallengeEditor extends Component { newChallenge.phases[index]['scheduledStartDate'] = phase.startDate newChallenge.phases[index]['scheduledEndDate'] = phase.endDate - let lastDate = phase.endDate - for (let phaseIndex = 0; phaseIndex < phases.length; phaseIndex++) { - if (phaseIndex !== index) { - if (moment(phases[phaseIndex]['scheduledStartDate']).isBefore(lastDate)) { - lastDate = moment(lastDate).add('1', 'hour').format('MM/DD/YYYY HH:mm') - - if (newChallenge.phases[phaseIndex]['name'] !== 'Submission') { - newChallenge.phases[phaseIndex]['scheduledStartDate'] = lastDate - } else { - newChallenge.phases[phaseIndex]['scheduledStartDate'] = newChallenge.phases[index]['scheduledStartDate'] - } - - if (moment(phases[phaseIndex]['scheduledEndDate']).isBefore(lastDate)) { - lastDate = moment(lastDate).add('1', 'hour').format('MM/DD/YYYY HH:mm') - newChallenge.phases[phaseIndex]['scheduledEndDate'] = lastDate - } - - // if (moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).isAfter(phases[phaseIndex]['scheduledStartDate'])) { - // newChallenge.phases[phaseIndex]['scheduledStartDate'] = moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).subtract('1', 'hour').format('MM/DD/YYYY HH:mm') - // } - - newChallenge.phases[phaseIndex]['duration'] = - moment(newChallenge.phases[phaseIndex]['scheduledEndDate']).diff(newChallenge.phases[phaseIndex]['scheduledStartDate'], 'hours') - } + for (let phaseIndex = index + 1; phaseIndex < phases.length; ++phaseIndex) { + if (newChallenge.phases[phaseIndex]['name'] === 'Submission') { + newChallenge.phases[phaseIndex]['scheduledStartDate'] = + newChallenge.phases[phaseIndex - 1]['scheduledStartDate'] + newChallenge.phases[phaseIndex]['duration'] = + _.max(newChallenge.phases[phaseIndex - 1]['duration'], + newChallenge.phases[phaseIndex]['duration']) + } else { + newChallenge.phases[phaseIndex]['scheduledStartDate'] = + newChallenge.phases[phaseIndex - 1]['scheduledEndDate'] } + newChallenge.phases[phaseIndex]['scheduledEndDate'] = + moment(newChallenge.phases[phaseIndex]['scheduledStartDate']) + .add(newChallenge.phases[phaseIndex]['duration'], 'hours') + .format('MM/DD/YYYY HH:mm') } this.setState({ challenge: newChallenge }) diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index 18ad1a9f..6bba77b8 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -47,16 +47,8 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => }, [startDate, endDate, duration]) const onStartDateChange = (e) => { - const start = moment(e).format() - let end = moment(endDate).format() - - if (moment(end).isBefore(moment(start))) { - end = moment(e).add(1, 'day').format(dateFormat) - setEndDate(moment(end).format(dateFormat)) - } - setStartDate(moment(e).format(dateFormat)) - setDuration(moment(end).diff(start, 'hours')) + setEndDate(moment(e).add(duration, 'hours').format(dateFormat)) } const onDurationChange = (e) => { From d5937d8abb4aa8059bc71d29fdb173819f3227b1 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 18 Apr 2022 17:11:53 +1000 Subject: [PATCH 23/31] https://github.com/topcoder-platform/work-manager/issues/1375 --- src/components/ChallengeEditor/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 9521fa37..df9ebc2c 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -823,9 +823,10 @@ class ChallengeEditor extends Component { if (newChallenge.phases[phaseIndex]['name'] === 'Submission') { newChallenge.phases[phaseIndex]['scheduledStartDate'] = newChallenge.phases[phaseIndex - 1]['scheduledStartDate'] - newChallenge.phases[phaseIndex]['duration'] = - _.max(newChallenge.phases[phaseIndex - 1]['duration'], - newChallenge.phases[phaseIndex]['duration']) + newChallenge.phases[phaseIndex]['duration'] = _.max([ + newChallenge.phases[phaseIndex - 1]['duration'], + newChallenge.phases[phaseIndex]['duration'] + ]) } else { newChallenge.phases[phaseIndex]['scheduledStartDate'] = newChallenge.phases[phaseIndex - 1]['scheduledEndDate'] From cba9c35aeec7478b22404dc7282401646ff81cee Mon Sep 17 00:00:00 2001 From: 52code Date: Mon, 18 Apr 2022 21:22:19 +0800 Subject: [PATCH 24/31] https://github.com/topcoder-platform/work-manager/issues/1375 --- .../ChallengeEditor/ChallengeView/index.js | 6 +- src/components/ChallengeEditor/index.js | 27 ++++--- src/components/DurationInput/index.js | 10 ++- src/components/PhaseInput/index.js | 80 ++++++++----------- 4 files changed, 59 insertions(+), 64 deletions(-) diff --git a/src/components/ChallengeEditor/ChallengeView/index.js b/src/components/ChallengeEditor/ChallengeView/index.js index 2e6ae972..f4bed266 100644 --- a/src/components/ChallengeEditor/ChallengeView/index.js +++ b/src/components/ChallengeEditor/ChallengeView/index.js @@ -22,7 +22,6 @@ import { isBetaMode } from '../../../util/cookie' import { loadGroupDetails } from '../../../actions/challenges' import { REVIEW_TYPES, CONNECT_APP_URL, PHASE_PRODUCT_CHALLENGE_ID_FIELD } from '../../../config/constants' import PhaseInput from '../../PhaseInput' -import { v4 as uuidv4 } from 'uuid' const ChallengeView = ({ projectDetail, @@ -190,10 +189,11 @@ const ChallengeView = ({ )} { - phases.map((phase) => ( + phases.map((phase, index) => ( )) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index df9ebc2c..d26ee417 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -9,7 +9,6 @@ import { pick } from 'lodash/fp' import { withRouter } from 'react-router-dom' import { toastr } from 'react-redux-toastr' import xss from 'xss' -import { v4 as uuidv4 } from 'uuid' import { VALIDATION_VALUE_TYPE, @@ -815,9 +814,20 @@ class ChallengeEditor extends Component { onUpdatePhaseDate (phase, index) { const { phases } = this.state.challenge let newChallenge = _.cloneDeep(this.state.challenge) - newChallenge.phases[index]['duration'] = phase.duration - newChallenge.phases[index]['scheduledStartDate'] = phase.startDate - newChallenge.phases[index]['scheduledEndDate'] = phase.endDate + if (phase.isBlur && newChallenge.phases[index]['name'] === 'Submission') { + newChallenge.phases[index]['duration'] = _.max([ + newChallenge.phases[index - 1]['duration'], + phase.duration + ]) + newChallenge.phases[index]['scheduledEndDate'] = + moment(newChallenge.phases[index]['scheduledStartDate']) + .add(newChallenge.phases[index]['duration'], 'hours') + .format('MM/DD/YYYY HH:mm') + } else { + newChallenge.phases[index]['duration'] = phase.duration + newChallenge.phases[index]['scheduledStartDate'] = phase.startDate + newChallenge.phases[index]['scheduledEndDate'] = phase.endDate + } for (let phaseIndex = index + 1; phaseIndex < phases.length; ++phaseIndex) { if (newChallenge.phases[phaseIndex]['name'] === 'Submission') { @@ -1610,15 +1620,12 @@ class ChallengeEditor extends Component { phases.map((phase, index) => ( { - if ((item.startDate && !moment(item.startDate).isSame(phase.scheduledStartDate)) || - (item.endDate && !moment(item.endDate).isSame(phase.scheduledEndDate)) - ) { - this.onUpdatePhaseDate(item, index) - } + this.onUpdatePhaseDate(item, index) }} /> ) diff --git a/src/components/DurationInput/index.js b/src/components/DurationInput/index.js index ea2c2c78..edc462fd 100644 --- a/src/components/DurationInput/index.js +++ b/src/components/DurationInput/index.js @@ -14,11 +14,15 @@ const DurationInput = ({ duration, onDurationChange, index, isActive }) => { ref={inputRef} min={1} type='number' - value={Number(duration).toString()} + value={duration} onChange={e => { e.preventDefault() onDurationChange(e.target.value) }} + onBlur={e => { + e.preventDefault() + onDurationChange(e.target.value, true) + }} autoFocus={inputRef.current === document.activeElement} disabled={!isActive} /> @@ -27,9 +31,9 @@ const DurationInput = ({ duration, onDurationChange, index, isActive }) => { } DurationInput.propTypes = { - duration: PropTypes.string, + duration: PropTypes.number, onDurationChange: PropTypes.func.isRequired, - index: PropTypes.string.isRequired, + index: PropTypes.number.isRequired, isActive: PropTypes.bool.isRequired } diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index 6bba77b8..e3bb64cd 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -1,5 +1,5 @@ import moment from 'moment' -import React, { useEffect, useState } from 'react' +import React from 'react' import PropTypes from 'prop-types' import styles from './PhaseInput.module.scss' import cn from 'classnames' @@ -17,47 +17,31 @@ const inputTimeFormat = 'HH:mm' const MAX_LENGTH = 5 const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => { - const [startDate, setStartDate] = useState() - const [endDate, setEndDate] = useState() - const [duration, setDuration] = useState() + const { scheduledStartDate: startDate, scheduledEndDate: endDate, duration } = phase - useEffect(() => { - if (phase) { - setStartDate(phase.scheduledStartDate) - setEndDate(phase.scheduledEndDate) - setDuration(moment(phase.scheduledEndDate).diff(phase.scheduledStartDate, 'hours')) - } - }, []) - - useEffect(() => { - if (phase) { - setStartDate(phase.scheduledStartDate) - setEndDate(phase.scheduledEndDate) - } - }, [phase]) - - useEffect(() => { - if (!readOnly) { - onUpdatePhase({ - startDate, - endDate, - duration - }) - } - }, [startDate, endDate, duration]) + const getEndDate = (startDate, duration) => moment(startDate).add(duration, 'hours') const onStartDateChange = (e) => { - setStartDate(moment(e).format(dateFormat)) - setEndDate(moment(e).add(duration, 'hours').format(dateFormat)) + let startDate = moment(e).format(dateFormat) + let endDate = getEndDate(startDate, duration) + onUpdatePhase({ + startDate, + endDate, + duration + }) } - const onDurationChange = (e) => { + const onDurationChange = (e, isBlur = false) => { if (e.length > MAX_LENGTH) return null - const dur = parseInt(e || 0) - setDuration(dur) - const end = moment(startDate).add(dur, 'hours') - setEndDate(moment(end).format(dateFormat)) + let duration = parseInt(e || 0) + let endDate = getEndDate(startDate, duration) + onUpdatePhase({ + startDate, + endDate, + duration, + isBlur + }) } return ( @@ -96,17 +80,17 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) =>
Duration:
- { - readOnly ? ( - {duration} - ) - : } + {readOnly ? ( + {duration} + ) : ( + + )}
@@ -122,9 +106,9 @@ PhaseInput.defaultProps = { PhaseInput.propTypes = { phase: PropTypes.shape().isRequired, - onUpdatePhase: PropTypes.func.isRequired, + onUpdatePhase: PropTypes.func, readOnly: PropTypes.bool, - phaseIndex: PropTypes.string.isRequired, + phaseIndex: PropTypes.number.isRequired, isActive: PropTypes.bool } export default PhaseInput From 95548cd055830cd1e82f6b269a2b5d823854a60b Mon Sep 17 00:00:00 2001 From: 52code Date: Tue, 19 Apr 2022 23:20:30 +0800 Subject: [PATCH 25/31] https://github.com/topcoder-platform/work-manager/issues/1378 --- src/components/ChallengeEditor/index.js | 45 +++++++++++++++++-------- src/components/DurationInput/index.js | 7 ++-- src/components/PhaseInput/index.js | 16 ++++----- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index d26ee417..5105607b 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -138,11 +138,21 @@ class ChallengeEditor extends Component { this.onDeleteChallenge = this.onDeleteChallenge.bind(this) this.deleteModalLaunch = this.deleteModalLaunch.bind(this) this.toggleForumOnCreate = this.toggleForumOnCreate.bind(this) - this.isPhaseEditable = this.isPhaseEditable.bind(this) + this.intervalId = null } componentDidMount () { this.resetChallengeData(this.setState.bind(this)) + setTimeout(() => { + this.onTick() + }, 500) + this.intervalId = setInterval(() => { + this.onTick() + }, 60000) + } + + componentDidUnMount () { + clearInterval(this.intervalId) } componentDidUpdate () { @@ -811,6 +821,22 @@ class ChallengeEditor extends Component { this.setState({ challenge: newChallenge }) } + onTick () { + if (this.state && this.state) { + const { phases } = this.state.challenge + let newChallenge = _.cloneDeep(this.state.challenge) + for (let index = 0; index < phases.length; ++index) { + newChallenge.phases[index].isDurationActive = + moment(newChallenge.phases[index]['scheduledEndDate']).isAfter() + newChallenge.phases[index].isStartTimeActive = index > 0 ? false + : moment(newChallenge.phases[0]['scheduledStartDate']).isAfter() + newChallenge.phases[index].isOpen = + newChallenge.phases[index].isDurationActive + } + this.setState({ challenge: newChallenge }) + } + } + onUpdatePhaseDate (phase, index) { const { phases } = this.state.challenge let newChallenge = _.cloneDeep(this.state.challenge) @@ -848,6 +874,10 @@ class ChallengeEditor extends Component { } this.setState({ challenge: newChallenge }) + + setTimeout(() => { + this.onTick() + }, 500) } collectChallengeData (status) { @@ -1249,18 +1279,6 @@ class ChallengeEditor extends Component { return _.filter(timelineTemplates, tt => availableTemplateIds.indexOf(tt.id) !== -1) } - /** - * Check if current phase is active for edit - */ - isPhaseEditable (phaseIndex) { - const { phases } = this.state.challenge - const phase = phases[phaseIndex] - if (phase.name !== 'Registration') { - return false - } - return !phase.isOpen - } - render () { const { isLaunch, @@ -1623,7 +1641,6 @@ class ChallengeEditor extends Component { phaseIndex={index} key={index} readOnly={false} - isActive={this.isPhaseEditable(index)} onUpdatePhase={(item) => { this.onUpdatePhaseDate(item, index) }} diff --git a/src/components/DurationInput/index.js b/src/components/DurationInput/index.js index edc462fd..84a38228 100644 --- a/src/components/DurationInput/index.js +++ b/src/components/DurationInput/index.js @@ -1,10 +1,14 @@ -import React, { useRef } from 'react' +import React, { useEffect, useRef } from 'react' import PropTypes from 'prop-types' import styles from './DurationInput.module.scss' const DurationInput = ({ duration, onDurationChange, index, isActive }) => { const inputRef = useRef(null) + useEffect(() => { + document.getElementById(`duration-${index}`).disabled = !isActive + }, [isActive, index]) + return (
{ onDurationChange(e.target.value, true) }} autoFocus={inputRef.current === document.activeElement} - disabled={!isActive} />
) diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index e3bb64cd..aeaa8619 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -16,10 +16,10 @@ const inputDateFormat = 'MM/dd/yyyy' const inputTimeFormat = 'HH:mm' const MAX_LENGTH = 5 -const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => { - const { scheduledStartDate: startDate, scheduledEndDate: endDate, duration } = phase +const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { + const { scheduledStartDate: startDate, scheduledEndDate: endDate, duration, isStartTimeActive, isDurationActive } = phase - const getEndDate = (startDate, duration) => moment(startDate).add(duration, 'hours') + const getEndDate = (startDate, duration) => moment(startDate).add(duration, 'hours').format(dateFormat) const onStartDateChange = (e) => { let startDate = moment(e).format(dateFormat) @@ -54,7 +54,7 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => Start Date:
{ - readOnly || !isActive ? ( + readOnly || !isStartTimeActive ? ( {moment(startDate).format(dateFormat)} ) : ( @@ -88,7 +88,7 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => name={phase.name} onDurationChange={onDurationChange} index={phaseIndex} - isActive + isActive={isDurationActive || false} /> )}
@@ -100,15 +100,13 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex, isActive }) => PhaseInput.defaultProps = { endDate: null, - readOnly: false, - isActive: false + readOnly: false } PhaseInput.propTypes = { phase: PropTypes.shape().isRequired, onUpdatePhase: PropTypes.func, readOnly: PropTypes.bool, - phaseIndex: PropTypes.number.isRequired, - isActive: PropTypes.bool + phaseIndex: PropTypes.number.isRequired } export default PhaseInput From 182c3ec62b1bd0fa8d5e08806a212394552d78b3 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 21 Apr 2022 09:17:30 +1000 Subject: [PATCH 26/31] Revert "feat: logout user if idle for configured duration" This reverts commit de6f09d76faea39e8ec8ebf2d810866780b2473a. # Conflicts: # src/routes.js --- config/constants/development.js | 6 +- package-lock.json | 5 -- package.json | 1 - src/styles/modal.module.scss | 114 -------------------------------- 4 files changed, 1 insertion(+), 125 deletions(-) delete mode 100644 src/styles/modal.module.scss diff --git a/config/constants/development.js b/config/constants/development.js index 2efc33f9..597dbb57 100644 --- a/config/constants/development.js +++ b/config/constants/development.js @@ -39,9 +39,5 @@ module.exports = { FILE_PICKER_API_KEY: process.env.FILE_PICKER_API_KEY, FILE_PICKER_CONTAINER_NAME: 'tc-challenge-v5-dev', FILE_PICKER_REGION: 'us-east-1', - FILE_PICKER_CNAME: 'fs.topcoder.com', - // if idle for this many minutes, show user a prompt saying they'll be logged out - IDLE_TIMEOUT_MINUTES: 10, - // duration to show the prompt saying user will be logged out, before actually logging out the user - IDLE_TIMEOUT_GRACE_MINUTES: 5 + FILE_PICKER_CNAME: 'fs.topcoder.com' } diff --git a/package-lock.json b/package-lock.json index 65c76e8a..e44e6c3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15002,11 +15002,6 @@ "react-side-effect": "^1.1.0" } }, - "react-idle-timer": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-4.6.4.tgz", - "integrity": "sha512-iq61dPud8fgj7l1KOJEY5pyiD532fW0KcIe/5XUe/0lB/4Vytoy4tZBlLGSiYodPzKxTL6HyKoOmG6tyzjD7OQ==" - }, "react-input-autosize": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz", diff --git a/package.json b/package.json index 02e50216..726e30b9 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "react-dom": "^16.7.0", "react-google-charts": "^3.0.13", "react-helmet": "^5.2.0", - "react-idle-timer": "^4.6.4", "react-js-pagination": "^3.0.3", "react-popper": "^2.2.4", "react-redux": "^6.0.0", diff --git a/src/styles/modal.module.scss b/src/styles/modal.module.scss deleted file mode 100644 index 12a06dbc..00000000 --- a/src/styles/modal.module.scss +++ /dev/null @@ -1,114 +0,0 @@ -@import "./includes.scss"; - -.modalContainer { - padding: 0; - position: fixed; - overflow: auto; - z-index: 10000; - top: 0; - right: 0; - bottom: 0; - left: 0; - box-sizing: border-box; - width: auto; - max-width: none; - transform: none; - background: transparent; - color: $text-color; - opacity: 1; - display: flex; - justify-content: center; - align-items: center; - - .contentContainer { - box-sizing: border-box; - background: $white; - opacity: 1; - position: relative; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - border-radius: 6px; - margin: 0 auto; - width: 100% !important; - padding: 30px; - - .content { - padding: 30px; - width: 100%; - height: 100%; - } - - .title { - @include roboto-bold(); - - font-size: 30px; - line-height: 36px; - margin-bottom: 30px; - margin-top: 0; - - } - - span { - @include roboto; - - font-size: 22px; - font-weight: 400; - line-height: 26px; - } - - &.confirm { - width: 999px; - - .buttonGroup { - display: flex; - justify-content: space-between; - margin-top: 30px; - - .buttonSizeA { - width: 193px; - height: 40px; - margin-right: 33px; - - span { - font-size: 18px; - font-weight: 500; - } - } - - .buttonSizeB{ - width: 160px; - height: 40px; - - span { - font-size: 18px; - font-weight: 500; - line-height: 22px; - } - } - } - } - - .buttonGroup { - display: flex; - justify-content: space-between; - margin-top: 30px; - - .button { - width: 135px; - height: 40px; - margin-right: 66px; - - span { - font-size: 18px; - font-weight: 500; - } - } - - .button:last-child { - margin-right: 0; - } - } - } -} From 3ac85b3115c7dd4578f3077c4e0c1c24d4ad238b Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 21 Apr 2022 09:17:30 +1000 Subject: [PATCH 27/31] Revert "Update config.yml" This reverts commit 1949c12a2de544f70267be46498ba84cf9e8fb09. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ca26190c..0d96a6c9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -150,7 +150,7 @@ workflows: context : org-global filters: &filters-dev branches: - only: ['develop', 'jira-vuln-2333'] + only: ['develop'] # Production builds are exectuted only on tagged commits to the # master branch. From 44fe60378f523e6cceb2b15a0e32b1a2d1d57a8e Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 21 Apr 2022 09:17:44 +1000 Subject: [PATCH 28/31] Revert "feat: add ability to logout immediately" This reverts commit 4dd155e8ab0ac662e56aeac64e8bd0e1950857fe. # Conflicts: # config/constants/development.js --- src/routes.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/routes.js b/src/routes.js index fbe0be96..9fd5729d 100644 --- a/src/routes.js +++ b/src/routes.js @@ -18,8 +18,8 @@ import { connect } from 'react-redux' import { checkAllowedRoles } from './util/tc' import { setCookie, removeCookie, isBetaMode } from './util/cookie' import IdleTimer from 'react-idle-timer' +import AlertModal from './components/Modal/AlertModal' import modalStyles from './styles/modal.module.scss' -import ConfirmationModal from './components/Modal/ConfirmationModal' const { ACCOUNTS_APP_LOGIN_URL, IDLE_TIMEOUT_MINUTES, IDLE_TIMEOUT_GRACE_MINUTES, COMMUNITY_APP_URL } = process.env @@ -127,25 +127,21 @@ class Routes extends React.Component { } const isAllowed = checkAllowedRoles(_.get(decodeToken(this.props.token), 'roles')) - const modal = (= 60 ? Math.ceil(this.state.logsoutIn / 60) + ' minute(s).' : this.state.logsoutIn + ' second(s)'}`} - confirmText='Logout Now' - cancelText='Resume Session' - onCancel={() => { + closeText='Resume Session' + onClose={() => { clearInterval(this.state.logoutIntervalRef) if (this.idleTimer.isIdle()) { this.idleTimer.resume() this.idleTimer.reset() this.setState(state => ({ - ...state, showIdleModal: false, logsoutIn: IDLE_TIMEOUT_GRACE_MINUTES * 60 + ...state, showIdleModal: false, logsoutIn: 120 })) } }} - onConfirm={() => { - window.location = `${COMMUNITY_APP_URL}/logout` - }} />) return ( From 11e561e25f81eb1cbde46ade722676ecc761431e Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 21 Apr 2022 09:18:36 +1000 Subject: [PATCH 29/31] Revert "fix: new challenge not working" This reverts commit 1efc162d07a44613e4bb53037d6f9cfe5c6b4cf0. --- src/routes.js | 82 ++++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/src/routes.js b/src/routes.js index 9fd5729d..f91e870e 100644 --- a/src/routes.js +++ b/src/routes.js @@ -146,54 +146,56 @@ class Routes extends React.Component { return ( { this.idleTimer = ref }} timeout={1000 * 60 * IDLE_TIMEOUT_MINUTES} onIdle={this.handleOnIdle} debounce={250}> - {!isAllowed && + + {!isAllowed && renderApp( , , )()} - /> - - } - {isAllowed && - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} /> - - renderApp( - , - , - - )()} /> - renderApp( - , - , - - )()} /> + />} + + {isAllowed && <> + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} /> + + renderApp( + , + , + + )()} /> + renderApp( + , + , + + )()} /> + } + {/* If path is not defined redirect to landing page */} - } + {this.state.showIdleModal && modal} ) From 665582b350b6ea37601f14d0b09b3de53cd9cb42 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 21 Apr 2022 09:25:52 +1000 Subject: [PATCH 30/31] Revert "Revert "fix: new challenge not working"" This reverts commit 11e561e25f81eb1cbde46ade722676ecc761431e. --- src/routes.js | 82 +++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/src/routes.js b/src/routes.js index f91e870e..9fd5729d 100644 --- a/src/routes.js +++ b/src/routes.js @@ -146,56 +146,54 @@ class Routes extends React.Component { return ( { this.idleTimer = ref }} timeout={1000 * 60 * IDLE_TIMEOUT_MINUTES} onIdle={this.handleOnIdle} debounce={250}> - - {!isAllowed && + {!isAllowed && renderApp( , , )()} - />} - - {isAllowed && <> - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} /> - - renderApp( - , - , - - )()} /> - renderApp( - , - , - - )()} /> - } - + /> + + } + {isAllowed && + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} /> + + renderApp( + , + , + + )()} /> + renderApp( + , + , + + )()} /> {/* If path is not defined redirect to landing page */} - + } {this.state.showIdleModal && modal} ) From dab6206ea60cc0dc72cc58bfa8b75b03c339a53c Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 21 Apr 2022 09:27:10 +1000 Subject: [PATCH 31/31] Revert "feat: logout user if idle for configured duration" This reverts commit de6f09d76faea39e8ec8ebf2d810866780b2473a. # Conflicts: # src/routes.js --- src/routes.js | 147 +++++++++++++++++--------------------------------- 1 file changed, 50 insertions(+), 97 deletions(-) diff --git a/src/routes.js b/src/routes.js index 9fd5729d..1c3041da 100644 --- a/src/routes.js +++ b/src/routes.js @@ -17,15 +17,8 @@ import { loadChallengeDetails } from './actions/challenges' import { connect } from 'react-redux' import { checkAllowedRoles } from './util/tc' import { setCookie, removeCookie, isBetaMode } from './util/cookie' -import IdleTimer from 'react-idle-timer' -import AlertModal from './components/Modal/AlertModal' -import modalStyles from './styles/modal.module.scss' -const { ACCOUNTS_APP_LOGIN_URL, IDLE_TIMEOUT_MINUTES, IDLE_TIMEOUT_GRACE_MINUTES, COMMUNITY_APP_URL } = process.env - -const theme = { - container: modalStyles.modalContainer -} +const { ACCOUNTS_APP_LOGIN_URL } = process.env class RedirectToChallenge extends React.Component { componentWillMount () { @@ -66,19 +59,6 @@ RedirectToChallenge.propTypes = { const ConnectRedirectToChallenge = connect(mapStateToProps, mapDispatchToProps)(RedirectToChallenge) class Routes extends React.Component { - constructor (props) { - super(props) - this.idleTimer = null - this.handleOnIdle = this.handleOnIdle.bind(this) - - this.logoutIntervalRef = null - this.state = { - showIdleModal: false, - logsoutIn: IDLE_TIMEOUT_GRACE_MINUTES * 60, // convert to seconds - logoutIntervalRef: null - } - } - componentWillMount () { this.checkAuth() } @@ -107,95 +87,68 @@ class Routes extends React.Component { } } - handleOnIdle () { - this.idleTimer.pause() - const intervalId = setInterval(() => { - const remaining = this.state.logsoutIn - if (remaining > 0) { - this.setState(state => ({ ...state, logsoutIn: remaining - 1 })) - } else { - window.location = `${COMMUNITY_APP_URL}/logout` - } - }, 1000) - - this.setState(state => ({ ...state, showIdleModal: true, logoutIntervalRef: intervalId })) - } - render () { if (!this.props.isLoggedIn) { return null } - const isAllowed = checkAllowedRoles(_.get(decodeToken(this.props.token), 'roles')) - const modal = (= 60 ? Math.ceil(this.state.logsoutIn / 60) + ' minute(s).' : this.state.logsoutIn + ' second(s)'}`} - closeText='Resume Session' - onClose={() => { - clearInterval(this.state.logoutIntervalRef) - if (this.idleTimer.isIdle()) { - this.idleTimer.resume() - this.idleTimer.reset() - this.setState(state => ({ - ...state, showIdleModal: false, logsoutIn: 120 - })) - } - }} - />) + let isAllowed = checkAllowedRoles(_.get(decodeToken(this.props.token), 'roles')) - return ( - { this.idleTimer = ref }} timeout={1000 * 60 * IDLE_TIMEOUT_MINUTES} onIdle={this.handleOnIdle} debounce={250}> - {!isAllowed && - renderApp( - , - , - - )()} - /> - - } - {isAllowed && + if (!isAllowed) { + let warnMessage = 'You are not authorized to use this application' + return ( + renderApp( - , + , , )()} /> - renderApp( - , - , - - )()} - /> - renderApp( - , - , - - )()} /> - - renderApp( - , - , - - )()} /> - renderApp( - , - , - - )()} /> - {/* If path is not defined redirect to landing page */} - } - {this.state.showIdleModal && modal} - + + ) + } + + return ( + + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} + /> + renderApp( + , + , + + )()} /> + + renderApp( + , + , + + )()} /> + renderApp( + , + , + + )()} /> + {/* If path is not defined redirect to landing page */} + + ) } }