From e85a639e634ef1c3eba5d323f4a08ffec1a558ba Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Mon, 5 Apr 2021 14:11:33 +0530 Subject: [PATCH 01/35] feat: git#4336-Project Plan Template: Hide/Disable Timeline & Milestones --- .../detail/components/PhaseCard/PhaseCard.jsx | 8 +- .../components/PhaseCard/PhaseCard.scss | 3 + .../detail/components/ProjectStage.jsx | 90 ++++++++++--------- 3 files changed, 54 insertions(+), 47 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index e04d8ac5d..8049f73a6 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -139,6 +139,8 @@ class PhaseCard extends React.Component { hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN) && ( status !== PHASE_STATUS_COMPLETED || hasPermission(PERMISSIONS.MANAGE_COMPLETED_PHASE) ) ) + const searchParams = new URLSearchParams(window.location.search) + const isBetaMode = searchParams.get('beta') === 'true' return (
@@ -155,7 +157,7 @@ class PhaseCard extends React.Component { {(matches) => (matches || !isExpanded ? (
-
+
@@ -176,7 +178,7 @@ class PhaseCard extends React.Component {
{attr.duration} {attr.startEndDates} - {attr.posts && {attr.posts}} + {!isBetaMode && attr.posts && {attr.posts}}
@@ -239,7 +241,7 @@ class PhaseCard extends React.Component { }
- {!this.state.isEditting && ( + {!isBetaMode && !this.state.isEditting && ( )} {status && status === PHASE_STATUS_ACTIVE && diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.scss b/src/projects/detail/components/PhaseCard/PhaseCard.scss index 7570a0f5a..2651e30cf 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.scss +++ b/src/projects/detail/components/PhaseCard/PhaseCard.scss @@ -67,6 +67,9 @@ width: 8px; } } + .beta-mode { + cursor: default; + } .col { &:nth-child(1) { flex: 1 500px; diff --git a/src/projects/detail/components/ProjectStage.jsx b/src/projects/detail/components/ProjectStage.jsx index 751841f67..a53f65374 100644 --- a/src/projects/detail/components/ProjectStage.jsx +++ b/src/projects/detail/components/ProjectStage.jsx @@ -225,50 +225,52 @@ class ProjectStage extends React.Component{ expandProjectPhase={expandProjectPhase} project={project} > -
- - - {currentActiveTab === 'timeline' && - - } - - {currentActiveTab === 'posts' && ( - - )} - - {currentActiveTab === 'specification' && -
- - updateProduct(project.id, phase.id, product.id, model)} - saving={isProcessing} - fireProjectDirty={(values) => fireProductDirty(phase.id, product.id, values)} - fireProjectDirtyUndo= {fireProductDirtyUndo} - addAttachment={this.addProductAttachment} - updateAttachment={this.updateProductAttachment} - removeAttachment={this.removeProductAttachment} - attachmentsStorePath={attachmentsStorePath} - canManageAttachments={hasPermission(PERMISSIONS.EDIT_PROJECT_SPECIFICATION)} - disableAutoScrolling - /> -
- } -
+ { project.version === 'v3' && +
+ + + {currentActiveTab === 'timeline' && + + } + + {currentActiveTab === 'posts' && ( + + )} + + {currentActiveTab === 'specification' && +
+ + updateProduct(project.id, phase.id, product.id, model)} + saving={isProcessing} + fireProjectDirty={(values) => fireProductDirty(phase.id, product.id, values)} + fireProjectDirtyUndo= {fireProductDirtyUndo} + addAttachment={this.addProductAttachment} + updateAttachment={this.updateProductAttachment} + removeAttachment={this.removeProductAttachment} + attachmentsStorePath={attachmentsStorePath} + canManageAttachments={hasPermission(PERMISSIONS.EDIT_PROJECT_SPECIFICATION)} + disableAutoScrolling + /> +
+ } +
+ } ) } From f1b3f492994609beb9005512502582198e4518e3 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Mon, 5 Apr 2021 17:39:08 +0530 Subject: [PATCH 02/35] Making feature branch deployable --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5b9dace6e..9711ba7ed 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -128,7 +128,7 @@ workflows: - build-dev filters: branches: - only: ['dev', 'feature/add_msg_for_taas_projects'] + only: ['dev', 'feature/project-plan-simplification-0'] - deployTest01: context : org-global From eaa7e5fed9afe0f8ffbbf25e6b5a33719af7a87b Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Thu, 8 Apr 2021 17:15:25 +0530 Subject: [PATCH 03/35] feat: git#4337-Project Plan Template: Update Set-Up Requirements feat: git#4339- Project Plan Template: Phase Status Logic --- src/config/constants.js | 2 +- src/projects/actions/project.js | 2 ++ src/projects/actions/projectDashboard.js | 2 +- .../create/containers/CreateContainer.jsx | 4 ++- .../CreatePhaseForm/CreatePhaseForm.jsx | 33 +++++++++++++++---- .../CreatePhaseForm/CreatePhaseForm.scss | 8 +++-- .../components/PhaseCard/EditStageForm.jsx | 10 ++++++ .../components/PhaseCard/EditStageForm.scss | 3 +- .../detail/components/PhaseCard/PhaseCard.jsx | 17 +++++----- .../components/PhaseCard/PhaseCard.scss | 2 +- .../detail/components/ProjectStage.jsx | 1 + .../detail/containers/DashboardContainer.jsx | 2 ++ 12 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/config/constants.js b/src/config/constants.js index c44a42c72..3bd431d45 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -613,7 +613,7 @@ export const PHASE_STATUS = [ {color: 'gray', name: 'Draft', fullName: 'Phase is in draft', value: PHASE_STATUS_DRAFT, order: 2, dropDownOrder: 1 }, // {color: 'gray', name: 'In review', fullName: 'Phase is in review', value: PHASE_STATUS_IN_REVIEW, order: 3, dropDownOrder: 2 }, {color: 'gray', name: 'Planned', fullName: 'Phase is reviewed', value: PHASE_STATUS_REVIEWED, order: 4, dropDownOrder: 3 }, - {color: 'green', name: 'Active', fullName: 'Phase is active', value: PHASE_STATUS_ACTIVE, order: 1, dropDownOrder: 4 }, + {color: 'green', name: 'Published', fullName: 'Phase is active', value: PHASE_STATUS_ACTIVE, order: 1, dropDownOrder: 4 }, {color: 'black', name: 'Completed', fullName: 'Phase is completed', value: PHASE_STATUS_COMPLETED, order: 5, dropDownOrder: 5 }, // {color: 'black', name: 'Cancelled', fullName: 'Phase is canceled', value: PHASE_STATUS_CANCELLED, order: 6, dropDownOrder: 6 }, // {color: 'red', name: 'Paused', fullName: 'Phase is paused', value: PHASE_STATUS_PAUSED, order: 7, dropDownOrder: 7 } diff --git a/src/projects/actions/project.js b/src/projects/actions/project.js index 1b2c7d821..333aa7b35 100644 --- a/src/projects/actions/project.js +++ b/src/projects/actions/project.js @@ -178,6 +178,7 @@ function getProjectPhasesWithProducts(projectId) { 'endDate', 'id', 'name', + 'description', 'progress', 'projectId', 'spentBudget', @@ -283,6 +284,7 @@ export function createProjectPhaseAndProduct(project, productTemplate, status = const param = { status, name: productTemplate.name, + description: productTemplate.description, productTemplateId: productTemplate.id } if (startDate) { diff --git a/src/projects/actions/projectDashboard.js b/src/projects/actions/projectDashboard.js index b7d9db0de..efb9127e2 100644 --- a/src/projects/actions/projectDashboard.js +++ b/src/projects/actions/projectDashboard.js @@ -33,7 +33,7 @@ const getDashboardData = (dispatch, getState, projectId, isOnlyLoadProjectInfo) } // for new projects load phases, products, project template and product templates - if (project.version === 'v3') { + if (['v3', 'v4'].indexOf(project.version) !== -1) { promises.push( dispatch(loadProjectPlan(projectId, userIds)) ) diff --git a/src/projects/create/containers/CreateContainer.jsx b/src/projects/create/containers/CreateContainer.jsx index 5e687c758..acb9b3e3b 100644 --- a/src/projects/create/containers/CreateContainer.jsx +++ b/src/projects/create/containers/CreateContainer.jsx @@ -245,8 +245,10 @@ class CreateContainer extends React.Component { } _.set(project, 'details.utm.google', googleAnalytics) } + const searchParams = new URLSearchParams(window.location.search) + const isBetaMode = searchParams.get('beta') === 'true' if (projectTemplate) { - project.version = 'v3' + project.version = isBetaMode ? 'v3' : 'v4' project.templateId = projectTemplate.id project.type = projectTemplate.category } diff --git a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx index 352e1d994..3aee4e6a0 100644 --- a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx +++ b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx @@ -27,7 +27,7 @@ const getMilestoneModelByIndex = (model, index) => { let milestoneModel // omit phase fields - _.forEach(_.keys(_.omit(model, ['title', 'startDate', 'endDate'])), (key) => { + _.forEach(_.keys(_.omit(model, ['title', 'description', 'startDate', 'endDate'])), (key) => { const keyMatches = key.match(/(\w+)_(\d+)/) if (keyMatches.length !== 3) { @@ -120,6 +120,7 @@ class CreatePhaseForm extends React.Component { const phaseData = { title: model.title, + description: model.description, startDate: moment(model.startDate), endDate: moment(model.endDate), } @@ -127,6 +128,7 @@ class CreatePhaseForm extends React.Component { const apiMilestones = milestones.map((milestone, index) => ({ // default values ...MILESTONE_DEFAULT_VALUES[milestone.type], + ..._.omit(milestone, 'pseudoId'), // values from the form ...getMilestoneModelByIndex(model, index), @@ -152,12 +154,14 @@ class CreatePhaseForm extends React.Component { * @param change changed form model in flattened form */ handleChange(change) { - const { - milestones - } = this.state + const { projectVersion } = this.props + // DO NOT update milestones state if project version is 4 + if (projectVersion === 4) return + const { milestones } = this.state // update all milestones in state from the Formzy model const newMilestones = milestones.map((milestone, index) => { + const milestoneModel = getMilestoneModelByIndex(change, index) const updatedMilestone = { @@ -216,7 +220,7 @@ class CreatePhaseForm extends React.Component { const { milestones } = this.state - + const ms = _.map(milestones, (m, index) => { return (
@@ -322,6 +326,10 @@ class CreatePhaseForm extends React.Component { render() { const { isAddButtonClicked } = this.state + const { projectVersion } = this.props + + const searchParams = new URLSearchParams(window.location.search) + const isBetaMode = searchParams.get('beta') === 'true' if (!isAddButtonClicked) { return ( @@ -357,6 +365,16 @@ class CreatePhaseForm extends React.Component { maxLength={48} />
+
+ +
- {this.renderTab()} - {this.renderMilestones()} + { !isBetaMode && projectVersion !== 4 && this.renderTab()} + { !isBetaMode && projectVersion !== 4 && this.renderMilestones()}
+
+ +
@@ -157,7 +158,7 @@ class PhaseCard extends React.Component { {(matches) => (matches || !isExpanded ? (
-
+
@@ -178,7 +179,7 @@ class PhaseCard extends React.Component {
{attr.duration} {attr.startEndDates} - {!isBetaMode && attr.posts && {attr.posts}} + {!isSimplePlan && attr.posts && {attr.posts}}
@@ -203,7 +204,7 @@ class PhaseCard extends React.Component {
) } - {status && status === PHASE_STATUS_ACTIVE && + { status && status === PHASE_STATUS_ACTIVE && (
{attr.price}
@@ -216,7 +217,7 @@ class PhaseCard extends React.Component { }
- {status && status !== PHASE_STATUS_ACTIVE && + {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && (
{statusDetails.name} @@ -224,7 +225,7 @@ class PhaseCard extends React.Component {
) } - {status && status === PHASE_STATUS_ACTIVE && + { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && (
- {!isBetaMode && !this.state.isEditting && ( + { !isSimplePlan && !this.state.isEditting && ( )} {status && status === PHASE_STATUS_ACTIVE && diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.scss b/src/projects/detail/components/PhaseCard/PhaseCard.scss index 2651e30cf..120650563 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.scss +++ b/src/projects/detail/components/PhaseCard/PhaseCard.scss @@ -67,7 +67,7 @@ width: 8px; } } - .beta-mode { + .simple-plan { cursor: default; } .col { diff --git a/src/projects/detail/components/ProjectStage.jsx b/src/projects/detail/components/ProjectStage.jsx index a53f65374..f8c9a7d3d 100644 --- a/src/projects/detail/components/ProjectStage.jsx +++ b/src/projects/detail/components/ProjectStage.jsx @@ -216,6 +216,7 @@ class ProjectStage extends React.Component{ deleteProjectPhase(project.id, phase.id)} timeline={timeline} hasUnseen={hasAnyNotifications} diff --git a/src/projects/detail/containers/DashboardContainer.jsx b/src/projects/detail/containers/DashboardContainer.jsx index 43420918e..c29beaa78 100644 --- a/src/projects/detail/containers/DashboardContainer.jsx +++ b/src/projects/detail/containers/DashboardContainer.jsx @@ -122,6 +122,7 @@ class DashboardContainer extends React.Component { const productTemplate = { name: phase.title, + description: phase.description, id: PHASE_PRODUCT_TEMPLATE_ID, } @@ -284,6 +285,7 @@ class DashboardContainer extends React.Component { {isCreatingPhase? : null} {isProjectLive && !isCreatingPhase && hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN) && !isLoadingPhases && ( )} From d0df627dd9afeeee55f32912b3812139022d227a Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Mon, 12 Apr 2021 10:29:17 +0530 Subject: [PATCH 04/35] feat: git#4337-Project Plan Template: Update Set-Up Requirements Fixed the dates of the planned and active phases. --- src/helpers/projectHelper.js | 11 +++++++++-- src/projects/actions/project.js | 3 ++- .../components/CreatePhaseForm/CreatePhaseForm.jsx | 10 ++++++---- .../detail/components/PhaseCard/PhaseCard.jsx | 10 ++++++++-- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/helpers/projectHelper.js b/src/helpers/projectHelper.js index f27e72f77..2b306fb48 100644 --- a/src/helpers/projectHelper.js +++ b/src/helpers/projectHelper.js @@ -158,14 +158,21 @@ export function getPhaseActualData(phase, timeline) { duration = phase.duration ? phase.duration : 0 progress = phase.progress ? phase.progress : 0 - if (startDate) { + // if start date and duration are set, use them to calculate endDate + if (startDate && duration > 0) { endDate = startDate.clone().add(duration, 'days') - } else { + } + // default to today if start date not set + if (!startDate) { startDate = moment().hours(0).minutes(0).seconds(0).milliseconds(0) } + // default to today if end date not set if (!endDate) { endDate = moment().hours(0).minutes(0).seconds(0).milliseconds(0) } + console.log(startDate) + // re-caclulate the duration of the phase + duration = endDate.diff(startDate, 'days') + 1 // if phase's product has timeline get data from timeline } else { diff --git a/src/projects/actions/project.js b/src/projects/actions/project.js index 333aa7b35..279f05bb6 100644 --- a/src/projects/actions/project.js +++ b/src/projects/actions/project.js @@ -466,6 +466,7 @@ export function updatePhase(projectId, phaseId, updatedProps, phaseIndex) { const phaseStartDate = timeline ? timeline.startDate : phase.startDate const startDateChanged = updatedProps.startDate ? updatedProps.startDate.diff(phaseStartDate) : null const phaseActivated = phaseStatusChanged && updatedProps.status === PHASE_STATUS_ACTIVE + const projectVersion = state.projectState.project.version if (updatedProps.startDate) { updatedProps.startDate = moment(updatedProps.startDate).format('YYYY-MM-DD') @@ -496,7 +497,7 @@ export function updatePhase(projectId, phaseId, updatedProps, phaseIndex) { // - phase's status is changed to active // - there is not active milestone alreay (this can happen when phase is made active more than once // e.g. Active => Paused => Active) - if (timeline && !activeMilestone && phaseActivated ) { + if (projectVersion !== 'v4' && timeline && !activeMilestone && phaseActivated ) { dispatch( updateProductMilestone( productId, diff --git a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx index 3aee4e6a0..8a12e8793 100644 --- a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx +++ b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx @@ -129,6 +129,8 @@ class CreatePhaseForm extends React.Component { // default values ...MILESTONE_DEFAULT_VALUES[milestone.type], ..._.omit(milestone, 'pseudoId'), + startDate: model.startDate, + endDate: model.endDate, // values from the form ...getMilestoneModelByIndex(model, index), @@ -328,8 +330,8 @@ class CreatePhaseForm extends React.Component { const { isAddButtonClicked } = this.state const { projectVersion } = this.props - const searchParams = new URLSearchParams(window.location.search) - const isBetaMode = searchParams.get('beta') === 'true' + // const searchParams = new URLSearchParams(window.location.search) + // const isBetaMode = searchParams.get('beta') === 'true' if (!isAddButtonClicked) { return ( @@ -409,8 +411,8 @@ class CreatePhaseForm extends React.Component { value={moment.utc().add(3, 'days').format('YYYY-MM-DD')} />
- { !isBetaMode && projectVersion !== 4 && this.renderTab()} - { !isBetaMode && projectVersion !== 4 && this.renderMilestones()} + { projectVersion !== 4 && this.renderTab()} + { projectVersion !== 4 && this.renderMilestones()}
+
{attr.phase.description}
{attr.duration} {attr.startEndDates} diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.scss b/src/projects/detail/components/PhaseCard/PhaseCard.scss index 120650563..caa006b04 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.scss +++ b/src/projects/detail/components/PhaseCard/PhaseCard.scss @@ -215,6 +215,12 @@ justify-content: space-between; } } + .project-description{ + margin-bottom: 5px; + @include roboto; + color: $tc-black; + font-size: 13px; + } .edit-btn { width: 40px; height: 40px; From 763dcce9c958f08b969b86c42250ed41989226c8 Mon Sep 17 00:00:00 2001 From: System Administrator Date: Fri, 16 Apr 2021 00:47:07 +0700 Subject: [PATCH 09/35] fix: git#4347-Showing phase description --- index.html | 18 -- setupAuth0WithRedirect.js | 359 -------------------------------------- 2 files changed, 377 deletions(-) delete mode 100644 index.html delete mode 100644 setupAuth0WithRedirect.js diff --git a/index.html b/index.html deleted file mode 100644 index 5217f1bce..000000000 --- a/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Auth0 - - - - - - Loaded...redirecting to auth0.(see browser console log) - - Login - - - diff --git a/setupAuth0WithRedirect.js b/setupAuth0WithRedirect.js deleted file mode 100644 index 335999df1..000000000 --- a/setupAuth0WithRedirect.js +++ /dev/null @@ -1,359 +0,0 @@ -var script = document.createElement('script'); -script.src = "https://cdn.auth0.com/js/auth0-spa-js/1.10/auth0-spa-js.production.js"; -script.type = 'text/javascript'; -script.defer = true; -document.getElementsByTagName('head').item(0).appendChild(script); - -/** - * read query string - * - */ -const qs = (function (a) { - if (a == "") return {}; - let b = {}; - for (let i = 0; i < a.length; ++i) { - let p = a[i].split('=', 2); - if (p.length == 1) - b[p[0]] = ""; - else - b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); - } - return b; -})(window.location.search.substr(1).split('&')); - -const authSetup = function () { - - let domain = 'auth.topcoder-dev.com'; - const clientId = 'BXWXUWnilVUPdN01t2Se29Tw2ZYNGZvH'; - const useLocalStorage = false; - const useRefreshTokens = false; - const v3JWTCookie = 'v3jwt'; - const tcJWTCookie = 'tcjwt'; - const tcSSOCookie = 'tcsso'; - const cookieExpireIn = 12 * 60; // 12 hrs - const refreshTokenInterval = 60000; // in milliseconds - const refreshTokenOffset = 65; // in seconds - let returnAppUrl = qs['retUrl']; - const shouldLogout = qs['logout']; - const regSource = qs['regSource']; - const utmSource = qs['utm_source']; - const utmMedium = qs['utm_medium']; - const utmCampaign = qs['utm_campaign']; - const appUrl = qs['appUrl'] || false; - const loggerMode = "dev"; - const IframeLogoutRequestType = "LOGOUT_REQUEST"; - const enterpriseCustomers = ['wipro', 'zurich', 'cs']; - - if (utmSource && - (utmSource != 'undefined') && - (enterpriseCustomers.indexOf(utmSource) > -1)) { - domain = "topcoder-dev.auth0.com"; - returnAppUrl += '&utm_source=' + utmSource; - } - - - var auth0 = null; - var isAuthenticated = false; - var idToken = null; - var callRefreshTokenFun = null; - var host = window.location.protocol + "//" + window.location.host - const registerSuccessUrl = host + '/register_success.html'; - - const init = function () { - correctOldUrl(); - createAuth0Client({ - domain: domain, - client_id: clientId, - cacheLocation: useLocalStorage - ? 'localstorage' - : 'memory', - useRefreshTokens: useRefreshTokens - }).then(_init); - window.addEventListener("message", receiveMessage, false); - }; - - const _init = function (authObj) { - auth0 = authObj - if (qs['code'] && qs['state']) { - auth0.handleRedirectCallback().then(function (data) { - logger('handleRedirectCallback() success: ', data); - showAuth0Info(); - storeToken(); - }).catch(function (e) { - logger('handleRedirectCallback() error: ', e); - }); - } else if (shouldLogout) { - host = returnAppUrl ? returnAppUrl : host; - logout(); - return; - } else if (!isLoggedIn() && returnAppUrl) { - login(); - } else { - logger("User already logged in", true); - postLogin(); - } - showAuthenticated(); - }; - - const showAuthenticated = function () { - auth0.isAuthenticated().then(function (isAuthenticated) { - isAuthenticated = isAuthenticated; - logger("_init:isAuthenticated", isAuthenticated); - }); - }; - - const refreshToken = function () { - let d = new Date(); - logger('checking token status at: ', `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} `); - var token = getCookie(tcJWTCookie); - if (!token || isTokenExpired(token)) { - logger('refreshing token... at: ', `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} `); - auth0.getTokenSilently().then(function (token) { - showAuth0Info(); - storeToken(); - }).catch(function (e) { - logger("Error in refreshing token: ", e) - if (e.error && ((e.error == "login_required") || (e.error == "timeout"))) { - clearInterval(callRefreshTokenFun); - } - } - ); - } - }; - - const showAuth0Info = function () { - auth0.getUser().then(function (user) { - logger("User Profile: ", user); - }); - auth0.getIdTokenClaims().then(function (claims) { - idToken = claims.__raw; - logger("JWT Token: ", idToken); - }); - }; - - const login = function () { - auth0 - .loginWithRedirect({ - redirect_uri: host + '?appUrl=' + returnAppUrl, - regSource: regSource, - utmSource: utmSource, - utmCampaign: utmCampaign, - utmMedium: utmMedium - }) - .then(function () { - auth0.isAuthenticated().then(function (isAuthenticated) { - isAuthenticated = isAuthenticated; - if (isAuthenticated) { - showAuth0Info(); - storeToken(); - postLogin(); - } - }); - }); - }; - - const logout = function () { - auth0.logout({ - returnTo: host - }); - // TODO - setCookie(tcJWTCookie, "", -1); - setCookie(v3JWTCookie, "", -1); - setCookie(tcSSOCookie, "", -1); - }; - - const isLoggedIn = function () { - var token = getCookie(tcJWTCookie); - return token ? !isTokenExpired(token) : false; - }; - - const redirectToApp = function () { - logger("redirect to app", appUrl); - if (appUrl) { - window.location = appUrl; - } - }; - - const postLogin = function () { - if (isLoggedIn() && returnAppUrl) { - auth0.isAuthenticated().then(function (isAuthenticated) { - if (isAuthenticated) { - window.location = returnAppUrl; - } else { - login(); // old session exist case - } - }); - } - logger('calling postLogin: ', true); - logger('callRefreshTokenFun: ', callRefreshTokenFun); - if (callRefreshTokenFun != null) { - clearInterval(callRefreshTokenFun); - } - refreshToken(); - callRefreshTokenFun = setInterval(refreshToken, refreshTokenInterval); - } - - const storeToken = function () { - auth0.getIdTokenClaims().then(function (claims) { - idToken = claims.__raw; - let userActive = false; - Object.keys(claims).findIndex(function (key) { - if (key.includes('active')) { - userActive = claims[key]; - return true; - } - return false; - }); - if (userActive) { - let tcsso = ''; - Object.keys(claims).findIndex(function (key) { - if (key.includes(tcSSOCookie)) { - tcsso = claims[key]; - return true; - } - return false; - }); - logger('Storing token...', true); - setCookie(tcJWTCookie, idToken, cookieExpireIn); - setCookie(v3JWTCookie, idToken, cookieExpireIn); - setCookie(tcSSOCookie, tcsso, cookieExpireIn); - redirectToApp(); - } else { - logger("User active ? ", userActive); - host = registerSuccessUrl; - logout(); - } - }).catch(function (e) { - logger("Error in fetching token from auth0: ", e); - }); - }; - - /////// Token.js - - function getTokenExpirationDate(token) { - const decoded = decodeToken(token); - if (typeof decoded.exp === 'undefined') { - return null; - } - const d = new Date(0); // The 0 here is the key, which sets the date to the epoch - d.setUTCSeconds(decoded.exp); - return d; - } - - function decodeToken(token) { - const parts = token.split('.'); - - if (parts.length !== 3) { - throw new Error('The token is invalid'); - } - - const decoded = urlBase64Decode(parts[1]) - - if (!decoded) { - throw new Error('Cannot decode the token'); - } - - // covert base64 token in JSON object - let t = JSON.parse(decoded); - return t; - } - - function isTokenExpired(token, offsetSeconds = refreshTokenOffset) { - const d = getTokenExpirationDate(token) - - if (d === null) { - return false; - } - - // Token expired? - return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))); - } - - function urlBase64Decode(str) { - let output = str.replace(/-/g, '+').replace(/_/g, '/') - - switch (output.length % 4) { - case 0: - break; - - case 2: - output += '==' - break; - - case 3: - output += '=' - break; - - default: - throw 'Illegal base64url string!'; - } - return decodeURIComponent(escape(atob(output))); //polyfill https://github.com/davidchambers/Base64.js - } - - function setCookie(cname, cvalue, exMins) { - const cdomain = getHostDomain(); - - let d = new Date(); - d.setTime(d.getTime() + (exMins * 60 * 1000)); - - let expires = ";expires=" + d.toUTCString(); - document.cookie = cname + "=" + cvalue + cdomain + expires + ";path=/"; - } - - function getCookie(name) { - const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); - return v ? v[2] : undefined; - } - // end token.js - - function getHostDomain() { - let hostDomain = ""; - if (location.hostname !== 'localhost') { - hostDomain = ";domain=." + - location.hostname.split('.').reverse()[1] + - "." + location.hostname.split('.').reverse()[0]; - } - return hostDomain; - } - - function correctOldUrl() { - const pattern = '#!/member'; - const sso_pattern = '/#!/sso-login'; - const logout_pattern = '/#!/logout?'; - - if (window.location.href.indexOf(pattern) > -1) { - window.location.href = window.location.href.replace(pattern, ''); - } - - if (window.location.href.indexOf(sso_pattern) > -1) { - window.location.href = window.location.href.replace(sso_pattern, ''); - } - - if (window.location.href.indexOf(logout_pattern) > -1) { - window.location.href = window.location.href.replace(logout_pattern, '/?logout=true&'); - } - } - - function logger(label, message) { - if (loggerMode === "dev") { - console.log(label, message); - } - } - - /** - * will receive message from iframe - */ - function receiveMessage(e) { - logger("received Event:", e); - if (e.data && e.data.type && e.origin) { - if (e.data.type === IframeLogoutRequestType) { - host = e.origin; - logout(); - } - } - - } - - // execute - init(); -}; From 3f8af15fcef931c4867c038ac355898c868a1cce Mon Sep 17 00:00:00 2001 From: System Administrator Date: Fri, 16 Apr 2021 01:10:29 +0700 Subject: [PATCH 10/35] fix: git#4349-Increase the size (height) of the Description field --- src/projects/detail/components/PhaseCard/EditStageForm.jsx | 4 ++-- src/projects/detail/components/PhaseCard/EditStageForm.scss | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.jsx b/src/projects/detail/components/PhaseCard/EditStageForm.jsx index e07714779..2745dba6b 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.jsx +++ b/src/projects/detail/components/PhaseCard/EditStageForm.jsx @@ -277,10 +277,10 @@ class EditStageForm extends React.Component { />
- Date: Fri, 16 Apr 2021 03:39:56 +0700 Subject: [PATCH 11/35] fix: git#4348,#4350-Remove status column for the customers, UI layout updated in editing view --- .../CreatePhaseForm/CreatePhaseForm.jsx | 4 +- .../CreatePhaseForm/CreatePhaseForm.scss | 6 ++- .../components/PhaseCard/EditStageForm.jsx | 32 +++++++++---- .../components/PhaseCard/EditStageForm.scss | 46 ++++++++++++++++--- .../detail/components/PhaseCard/PhaseCard.jsx | 6 ++- .../PhaseCardListHeader.scss | 1 + 6 files changed, 74 insertions(+), 21 deletions(-) diff --git a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx index 8a12e8793..8849bab8c 100644 --- a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx +++ b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx @@ -368,10 +368,10 @@ class CreatePhaseForm extends React.Component { />
- @@ -276,16 +278,18 @@ class EditStageForm extends React.Component { maxLength={48} />
-
- -
+ {phase.description && ( +
+ +
+ )}
+
{!showActivatingWarning ? (
diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.scss b/src/projects/detail/components/PhaseCard/EditStageForm.scss index c1374e54b..7a8ebcb50 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.scss +++ b/src/projects/detail/components/PhaseCard/EditStageForm.scss @@ -33,7 +33,7 @@ .input-row { display: inline-block; - width: 50%; + width: 45%; :global { @@ -51,7 +51,7 @@ } input, textarea { display: inline-block; - width:calc(100% - 170px)!important; + width:calc(100% - 170px); } textarea{ @@ -158,6 +158,40 @@ } } +.label-layer { + .input-row:nth-child(1) { + input { + width: calc(100% - 170px); + } + } + + .input-row:nth-child(2) { + width: 35%; + + label { + width: 75px; + } + + input{ + width: calc(100% - 100px); + } + } + + .input-row:nth-child(3) { + width: 20%; + + label { + width: 70px; + padding-right: 0; + } + + input{ + width: calc(100% - 90px); + font-size: 13px; + } + } +} + // screen < 1024px @media screen and (max-width: 1023px) { .label-layer:nth-child(4) > :global(.Tooltip) { @@ -171,7 +205,7 @@ .label-layer, .input-row { display: block; - width: 100%; + width: 100% !important; input:not([type="checkbox"]):disabled { width: 100%; } @@ -198,12 +232,12 @@ div { padding: 0!important; } - input { + input, textarea { width: 100%!important; display: block; } label { - width: 100%; + width: 100% !important; display: block; text-align: left; margin: 0; @@ -228,4 +262,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index f1b24375a..6dabbe5a3 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -21,6 +21,7 @@ import { SCREEN_BREAKPOINT_MD, EVENT_TYPE, PHASE_STATUS_REVIEWED, + PROJECT_ROLE_CUSTOMER } from '../../../../config/constants' import ProjectProgress from '../../../../components/ProjectProgress/ProjectProgress' @@ -139,6 +140,7 @@ class PhaseCard extends React.Component { const status = getVisualPhaseStatus(attr, projectVersion) const statusDetails = _.find(PHASE_STATUS, s => s.value === status) + const currentRole = this.props.currentUserRoles[2] const phaseEditable = ( projectStatus !== PROJECT_STATUS_CANCELLED && projectStatus !== PROJECT_STATUS_COMPLETED ) @@ -224,7 +226,7 @@ class PhaseCard extends React.Component { }
- {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && + {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && currentRole !== PROJECT_ROLE_CUSTOMER && (
{statusDetails.name} @@ -232,7 +234,7 @@ class PhaseCard extends React.Component {
) } - { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && + { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && currentRole !== PROJECT_ROLE_CUSTOMER && (
Date: Fri, 16 Apr 2021 14:21:36 +0700 Subject: [PATCH 12/35] #4348 - Hide status column for customer --- index.html | 18 + setupAuth0WithRedirect.js | 359 ++++++++++++++++++ .../detail/components/PhaseCard/PhaseCard.jsx | 5 +- 3 files changed, 379 insertions(+), 3 deletions(-) create mode 100644 index.html create mode 100644 setupAuth0WithRedirect.js diff --git a/index.html b/index.html new file mode 100644 index 000000000..5217f1bce --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + Auth0 + + + + + + Loaded...redirecting to auth0.(see browser console log) + + Login + + + diff --git a/setupAuth0WithRedirect.js b/setupAuth0WithRedirect.js new file mode 100644 index 000000000..335999df1 --- /dev/null +++ b/setupAuth0WithRedirect.js @@ -0,0 +1,359 @@ +var script = document.createElement('script'); +script.src = "https://cdn.auth0.com/js/auth0-spa-js/1.10/auth0-spa-js.production.js"; +script.type = 'text/javascript'; +script.defer = true; +document.getElementsByTagName('head').item(0).appendChild(script); + +/** + * read query string + * + */ +const qs = (function (a) { + if (a == "") return {}; + let b = {}; + for (let i = 0; i < a.length; ++i) { + let p = a[i].split('=', 2); + if (p.length == 1) + b[p[0]] = ""; + else + b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); + } + return b; +})(window.location.search.substr(1).split('&')); + +const authSetup = function () { + + let domain = 'auth.topcoder-dev.com'; + const clientId = 'BXWXUWnilVUPdN01t2Se29Tw2ZYNGZvH'; + const useLocalStorage = false; + const useRefreshTokens = false; + const v3JWTCookie = 'v3jwt'; + const tcJWTCookie = 'tcjwt'; + const tcSSOCookie = 'tcsso'; + const cookieExpireIn = 12 * 60; // 12 hrs + const refreshTokenInterval = 60000; // in milliseconds + const refreshTokenOffset = 65; // in seconds + let returnAppUrl = qs['retUrl']; + const shouldLogout = qs['logout']; + const regSource = qs['regSource']; + const utmSource = qs['utm_source']; + const utmMedium = qs['utm_medium']; + const utmCampaign = qs['utm_campaign']; + const appUrl = qs['appUrl'] || false; + const loggerMode = "dev"; + const IframeLogoutRequestType = "LOGOUT_REQUEST"; + const enterpriseCustomers = ['wipro', 'zurich', 'cs']; + + if (utmSource && + (utmSource != 'undefined') && + (enterpriseCustomers.indexOf(utmSource) > -1)) { + domain = "topcoder-dev.auth0.com"; + returnAppUrl += '&utm_source=' + utmSource; + } + + + var auth0 = null; + var isAuthenticated = false; + var idToken = null; + var callRefreshTokenFun = null; + var host = window.location.protocol + "//" + window.location.host + const registerSuccessUrl = host + '/register_success.html'; + + const init = function () { + correctOldUrl(); + createAuth0Client({ + domain: domain, + client_id: clientId, + cacheLocation: useLocalStorage + ? 'localstorage' + : 'memory', + useRefreshTokens: useRefreshTokens + }).then(_init); + window.addEventListener("message", receiveMessage, false); + }; + + const _init = function (authObj) { + auth0 = authObj + if (qs['code'] && qs['state']) { + auth0.handleRedirectCallback().then(function (data) { + logger('handleRedirectCallback() success: ', data); + showAuth0Info(); + storeToken(); + }).catch(function (e) { + logger('handleRedirectCallback() error: ', e); + }); + } else if (shouldLogout) { + host = returnAppUrl ? returnAppUrl : host; + logout(); + return; + } else if (!isLoggedIn() && returnAppUrl) { + login(); + } else { + logger("User already logged in", true); + postLogin(); + } + showAuthenticated(); + }; + + const showAuthenticated = function () { + auth0.isAuthenticated().then(function (isAuthenticated) { + isAuthenticated = isAuthenticated; + logger("_init:isAuthenticated", isAuthenticated); + }); + }; + + const refreshToken = function () { + let d = new Date(); + logger('checking token status at: ', `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} `); + var token = getCookie(tcJWTCookie); + if (!token || isTokenExpired(token)) { + logger('refreshing token... at: ', `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} `); + auth0.getTokenSilently().then(function (token) { + showAuth0Info(); + storeToken(); + }).catch(function (e) { + logger("Error in refreshing token: ", e) + if (e.error && ((e.error == "login_required") || (e.error == "timeout"))) { + clearInterval(callRefreshTokenFun); + } + } + ); + } + }; + + const showAuth0Info = function () { + auth0.getUser().then(function (user) { + logger("User Profile: ", user); + }); + auth0.getIdTokenClaims().then(function (claims) { + idToken = claims.__raw; + logger("JWT Token: ", idToken); + }); + }; + + const login = function () { + auth0 + .loginWithRedirect({ + redirect_uri: host + '?appUrl=' + returnAppUrl, + regSource: regSource, + utmSource: utmSource, + utmCampaign: utmCampaign, + utmMedium: utmMedium + }) + .then(function () { + auth0.isAuthenticated().then(function (isAuthenticated) { + isAuthenticated = isAuthenticated; + if (isAuthenticated) { + showAuth0Info(); + storeToken(); + postLogin(); + } + }); + }); + }; + + const logout = function () { + auth0.logout({ + returnTo: host + }); + // TODO + setCookie(tcJWTCookie, "", -1); + setCookie(v3JWTCookie, "", -1); + setCookie(tcSSOCookie, "", -1); + }; + + const isLoggedIn = function () { + var token = getCookie(tcJWTCookie); + return token ? !isTokenExpired(token) : false; + }; + + const redirectToApp = function () { + logger("redirect to app", appUrl); + if (appUrl) { + window.location = appUrl; + } + }; + + const postLogin = function () { + if (isLoggedIn() && returnAppUrl) { + auth0.isAuthenticated().then(function (isAuthenticated) { + if (isAuthenticated) { + window.location = returnAppUrl; + } else { + login(); // old session exist case + } + }); + } + logger('calling postLogin: ', true); + logger('callRefreshTokenFun: ', callRefreshTokenFun); + if (callRefreshTokenFun != null) { + clearInterval(callRefreshTokenFun); + } + refreshToken(); + callRefreshTokenFun = setInterval(refreshToken, refreshTokenInterval); + } + + const storeToken = function () { + auth0.getIdTokenClaims().then(function (claims) { + idToken = claims.__raw; + let userActive = false; + Object.keys(claims).findIndex(function (key) { + if (key.includes('active')) { + userActive = claims[key]; + return true; + } + return false; + }); + if (userActive) { + let tcsso = ''; + Object.keys(claims).findIndex(function (key) { + if (key.includes(tcSSOCookie)) { + tcsso = claims[key]; + return true; + } + return false; + }); + logger('Storing token...', true); + setCookie(tcJWTCookie, idToken, cookieExpireIn); + setCookie(v3JWTCookie, idToken, cookieExpireIn); + setCookie(tcSSOCookie, tcsso, cookieExpireIn); + redirectToApp(); + } else { + logger("User active ? ", userActive); + host = registerSuccessUrl; + logout(); + } + }).catch(function (e) { + logger("Error in fetching token from auth0: ", e); + }); + }; + + /////// Token.js + + function getTokenExpirationDate(token) { + const decoded = decodeToken(token); + if (typeof decoded.exp === 'undefined') { + return null; + } + const d = new Date(0); // The 0 here is the key, which sets the date to the epoch + d.setUTCSeconds(decoded.exp); + return d; + } + + function decodeToken(token) { + const parts = token.split('.'); + + if (parts.length !== 3) { + throw new Error('The token is invalid'); + } + + const decoded = urlBase64Decode(parts[1]) + + if (!decoded) { + throw new Error('Cannot decode the token'); + } + + // covert base64 token in JSON object + let t = JSON.parse(decoded); + return t; + } + + function isTokenExpired(token, offsetSeconds = refreshTokenOffset) { + const d = getTokenExpirationDate(token) + + if (d === null) { + return false; + } + + // Token expired? + return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))); + } + + function urlBase64Decode(str) { + let output = str.replace(/-/g, '+').replace(/_/g, '/') + + switch (output.length % 4) { + case 0: + break; + + case 2: + output += '==' + break; + + case 3: + output += '=' + break; + + default: + throw 'Illegal base64url string!'; + } + return decodeURIComponent(escape(atob(output))); //polyfill https://github.com/davidchambers/Base64.js + } + + function setCookie(cname, cvalue, exMins) { + const cdomain = getHostDomain(); + + let d = new Date(); + d.setTime(d.getTime() + (exMins * 60 * 1000)); + + let expires = ";expires=" + d.toUTCString(); + document.cookie = cname + "=" + cvalue + cdomain + expires + ";path=/"; + } + + function getCookie(name) { + const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); + return v ? v[2] : undefined; + } + // end token.js + + function getHostDomain() { + let hostDomain = ""; + if (location.hostname !== 'localhost') { + hostDomain = ";domain=." + + location.hostname.split('.').reverse()[1] + + "." + location.hostname.split('.').reverse()[0]; + } + return hostDomain; + } + + function correctOldUrl() { + const pattern = '#!/member'; + const sso_pattern = '/#!/sso-login'; + const logout_pattern = '/#!/logout?'; + + if (window.location.href.indexOf(pattern) > -1) { + window.location.href = window.location.href.replace(pattern, ''); + } + + if (window.location.href.indexOf(sso_pattern) > -1) { + window.location.href = window.location.href.replace(sso_pattern, ''); + } + + if (window.location.href.indexOf(logout_pattern) > -1) { + window.location.href = window.location.href.replace(logout_pattern, '/?logout=true&'); + } + } + + function logger(label, message) { + if (loggerMode === "dev") { + console.log(label, message); + } + } + + /** + * will receive message from iframe + */ + function receiveMessage(e) { + logger("received Event:", e); + if (e.data && e.data.type && e.origin) { + if (e.data.type === IframeLogoutRequestType) { + host = e.origin; + logout(); + } + } + + } + + // execute + init(); +}; diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index 6dabbe5a3..6551b8413 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -140,7 +140,6 @@ class PhaseCard extends React.Component { const status = getVisualPhaseStatus(attr, projectVersion) const statusDetails = _.find(PHASE_STATUS, s => s.value === status) - const currentRole = this.props.currentUserRoles[2] const phaseEditable = ( projectStatus !== PROJECT_STATUS_CANCELLED && projectStatus !== PROJECT_STATUS_COMPLETED ) @@ -226,7 +225,7 @@ class PhaseCard extends React.Component { }
- {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && currentRole !== PROJECT_ROLE_CUSTOMER && + {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && !this.props.currentUserRoles.includes(PROJECT_ROLE_CUSTOMER) && (
{statusDetails.name} @@ -234,7 +233,7 @@ class PhaseCard extends React.Component {
) } - { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && currentRole !== PROJECT_ROLE_CUSTOMER && + { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && !this.props.currentUserRoles.includes(PROJECT_ROLE_CUSTOMER) && (
Date: Fri, 16 Apr 2021 14:21:57 +0700 Subject: [PATCH 13/35] #4348 - Hide status column for customer --- index.html | 18 -- setupAuth0WithRedirect.js | 359 -------------------------------------- 2 files changed, 377 deletions(-) delete mode 100644 index.html delete mode 100644 setupAuth0WithRedirect.js diff --git a/index.html b/index.html deleted file mode 100644 index 5217f1bce..000000000 --- a/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Auth0 - - - - - - Loaded...redirecting to auth0.(see browser console log) - - Login - - - diff --git a/setupAuth0WithRedirect.js b/setupAuth0WithRedirect.js deleted file mode 100644 index 335999df1..000000000 --- a/setupAuth0WithRedirect.js +++ /dev/null @@ -1,359 +0,0 @@ -var script = document.createElement('script'); -script.src = "https://cdn.auth0.com/js/auth0-spa-js/1.10/auth0-spa-js.production.js"; -script.type = 'text/javascript'; -script.defer = true; -document.getElementsByTagName('head').item(0).appendChild(script); - -/** - * read query string - * - */ -const qs = (function (a) { - if (a == "") return {}; - let b = {}; - for (let i = 0; i < a.length; ++i) { - let p = a[i].split('=', 2); - if (p.length == 1) - b[p[0]] = ""; - else - b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); - } - return b; -})(window.location.search.substr(1).split('&')); - -const authSetup = function () { - - let domain = 'auth.topcoder-dev.com'; - const clientId = 'BXWXUWnilVUPdN01t2Se29Tw2ZYNGZvH'; - const useLocalStorage = false; - const useRefreshTokens = false; - const v3JWTCookie = 'v3jwt'; - const tcJWTCookie = 'tcjwt'; - const tcSSOCookie = 'tcsso'; - const cookieExpireIn = 12 * 60; // 12 hrs - const refreshTokenInterval = 60000; // in milliseconds - const refreshTokenOffset = 65; // in seconds - let returnAppUrl = qs['retUrl']; - const shouldLogout = qs['logout']; - const regSource = qs['regSource']; - const utmSource = qs['utm_source']; - const utmMedium = qs['utm_medium']; - const utmCampaign = qs['utm_campaign']; - const appUrl = qs['appUrl'] || false; - const loggerMode = "dev"; - const IframeLogoutRequestType = "LOGOUT_REQUEST"; - const enterpriseCustomers = ['wipro', 'zurich', 'cs']; - - if (utmSource && - (utmSource != 'undefined') && - (enterpriseCustomers.indexOf(utmSource) > -1)) { - domain = "topcoder-dev.auth0.com"; - returnAppUrl += '&utm_source=' + utmSource; - } - - - var auth0 = null; - var isAuthenticated = false; - var idToken = null; - var callRefreshTokenFun = null; - var host = window.location.protocol + "//" + window.location.host - const registerSuccessUrl = host + '/register_success.html'; - - const init = function () { - correctOldUrl(); - createAuth0Client({ - domain: domain, - client_id: clientId, - cacheLocation: useLocalStorage - ? 'localstorage' - : 'memory', - useRefreshTokens: useRefreshTokens - }).then(_init); - window.addEventListener("message", receiveMessage, false); - }; - - const _init = function (authObj) { - auth0 = authObj - if (qs['code'] && qs['state']) { - auth0.handleRedirectCallback().then(function (data) { - logger('handleRedirectCallback() success: ', data); - showAuth0Info(); - storeToken(); - }).catch(function (e) { - logger('handleRedirectCallback() error: ', e); - }); - } else if (shouldLogout) { - host = returnAppUrl ? returnAppUrl : host; - logout(); - return; - } else if (!isLoggedIn() && returnAppUrl) { - login(); - } else { - logger("User already logged in", true); - postLogin(); - } - showAuthenticated(); - }; - - const showAuthenticated = function () { - auth0.isAuthenticated().then(function (isAuthenticated) { - isAuthenticated = isAuthenticated; - logger("_init:isAuthenticated", isAuthenticated); - }); - }; - - const refreshToken = function () { - let d = new Date(); - logger('checking token status at: ', `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} `); - var token = getCookie(tcJWTCookie); - if (!token || isTokenExpired(token)) { - logger('refreshing token... at: ', `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} `); - auth0.getTokenSilently().then(function (token) { - showAuth0Info(); - storeToken(); - }).catch(function (e) { - logger("Error in refreshing token: ", e) - if (e.error && ((e.error == "login_required") || (e.error == "timeout"))) { - clearInterval(callRefreshTokenFun); - } - } - ); - } - }; - - const showAuth0Info = function () { - auth0.getUser().then(function (user) { - logger("User Profile: ", user); - }); - auth0.getIdTokenClaims().then(function (claims) { - idToken = claims.__raw; - logger("JWT Token: ", idToken); - }); - }; - - const login = function () { - auth0 - .loginWithRedirect({ - redirect_uri: host + '?appUrl=' + returnAppUrl, - regSource: regSource, - utmSource: utmSource, - utmCampaign: utmCampaign, - utmMedium: utmMedium - }) - .then(function () { - auth0.isAuthenticated().then(function (isAuthenticated) { - isAuthenticated = isAuthenticated; - if (isAuthenticated) { - showAuth0Info(); - storeToken(); - postLogin(); - } - }); - }); - }; - - const logout = function () { - auth0.logout({ - returnTo: host - }); - // TODO - setCookie(tcJWTCookie, "", -1); - setCookie(v3JWTCookie, "", -1); - setCookie(tcSSOCookie, "", -1); - }; - - const isLoggedIn = function () { - var token = getCookie(tcJWTCookie); - return token ? !isTokenExpired(token) : false; - }; - - const redirectToApp = function () { - logger("redirect to app", appUrl); - if (appUrl) { - window.location = appUrl; - } - }; - - const postLogin = function () { - if (isLoggedIn() && returnAppUrl) { - auth0.isAuthenticated().then(function (isAuthenticated) { - if (isAuthenticated) { - window.location = returnAppUrl; - } else { - login(); // old session exist case - } - }); - } - logger('calling postLogin: ', true); - logger('callRefreshTokenFun: ', callRefreshTokenFun); - if (callRefreshTokenFun != null) { - clearInterval(callRefreshTokenFun); - } - refreshToken(); - callRefreshTokenFun = setInterval(refreshToken, refreshTokenInterval); - } - - const storeToken = function () { - auth0.getIdTokenClaims().then(function (claims) { - idToken = claims.__raw; - let userActive = false; - Object.keys(claims).findIndex(function (key) { - if (key.includes('active')) { - userActive = claims[key]; - return true; - } - return false; - }); - if (userActive) { - let tcsso = ''; - Object.keys(claims).findIndex(function (key) { - if (key.includes(tcSSOCookie)) { - tcsso = claims[key]; - return true; - } - return false; - }); - logger('Storing token...', true); - setCookie(tcJWTCookie, idToken, cookieExpireIn); - setCookie(v3JWTCookie, idToken, cookieExpireIn); - setCookie(tcSSOCookie, tcsso, cookieExpireIn); - redirectToApp(); - } else { - logger("User active ? ", userActive); - host = registerSuccessUrl; - logout(); - } - }).catch(function (e) { - logger("Error in fetching token from auth0: ", e); - }); - }; - - /////// Token.js - - function getTokenExpirationDate(token) { - const decoded = decodeToken(token); - if (typeof decoded.exp === 'undefined') { - return null; - } - const d = new Date(0); // The 0 here is the key, which sets the date to the epoch - d.setUTCSeconds(decoded.exp); - return d; - } - - function decodeToken(token) { - const parts = token.split('.'); - - if (parts.length !== 3) { - throw new Error('The token is invalid'); - } - - const decoded = urlBase64Decode(parts[1]) - - if (!decoded) { - throw new Error('Cannot decode the token'); - } - - // covert base64 token in JSON object - let t = JSON.parse(decoded); - return t; - } - - function isTokenExpired(token, offsetSeconds = refreshTokenOffset) { - const d = getTokenExpirationDate(token) - - if (d === null) { - return false; - } - - // Token expired? - return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))); - } - - function urlBase64Decode(str) { - let output = str.replace(/-/g, '+').replace(/_/g, '/') - - switch (output.length % 4) { - case 0: - break; - - case 2: - output += '==' - break; - - case 3: - output += '=' - break; - - default: - throw 'Illegal base64url string!'; - } - return decodeURIComponent(escape(atob(output))); //polyfill https://github.com/davidchambers/Base64.js - } - - function setCookie(cname, cvalue, exMins) { - const cdomain = getHostDomain(); - - let d = new Date(); - d.setTime(d.getTime() + (exMins * 60 * 1000)); - - let expires = ";expires=" + d.toUTCString(); - document.cookie = cname + "=" + cvalue + cdomain + expires + ";path=/"; - } - - function getCookie(name) { - const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); - return v ? v[2] : undefined; - } - // end token.js - - function getHostDomain() { - let hostDomain = ""; - if (location.hostname !== 'localhost') { - hostDomain = ";domain=." + - location.hostname.split('.').reverse()[1] + - "." + location.hostname.split('.').reverse()[0]; - } - return hostDomain; - } - - function correctOldUrl() { - const pattern = '#!/member'; - const sso_pattern = '/#!/sso-login'; - const logout_pattern = '/#!/logout?'; - - if (window.location.href.indexOf(pattern) > -1) { - window.location.href = window.location.href.replace(pattern, ''); - } - - if (window.location.href.indexOf(sso_pattern) > -1) { - window.location.href = window.location.href.replace(sso_pattern, ''); - } - - if (window.location.href.indexOf(logout_pattern) > -1) { - window.location.href = window.location.href.replace(logout_pattern, '/?logout=true&'); - } - } - - function logger(label, message) { - if (loggerMode === "dev") { - console.log(label, message); - } - } - - /** - * will receive message from iframe - */ - function receiveMessage(e) { - logger("received Event:", e); - if (e.data && e.data.type && e.origin) { - if (e.data.type === IframeLogoutRequestType) { - host = e.origin; - logout(); - } - } - - } - - // execute - init(); -}; From b8da23c9cc5356a7af2f9645034820c37261b89c Mon Sep 17 00:00:00 2001 From: System Administrator Date: Sat, 17 Apr 2021 12:34:34 +0700 Subject: [PATCH 14/35] #4355 - Fix error validation on phase Start Date & End Date --- index.html | 18 + setupAuth0WithRedirect.js | 359 ++++++++++++++++++ .../components/PhaseCard/EditStageForm.jsx | 3 +- 3 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 index.html create mode 100644 setupAuth0WithRedirect.js diff --git a/index.html b/index.html new file mode 100644 index 000000000..5217f1bce --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + Auth0 + + + + + + Loaded...redirecting to auth0.(see browser console log) + + Login + + + diff --git a/setupAuth0WithRedirect.js b/setupAuth0WithRedirect.js new file mode 100644 index 000000000..335999df1 --- /dev/null +++ b/setupAuth0WithRedirect.js @@ -0,0 +1,359 @@ +var script = document.createElement('script'); +script.src = "https://cdn.auth0.com/js/auth0-spa-js/1.10/auth0-spa-js.production.js"; +script.type = 'text/javascript'; +script.defer = true; +document.getElementsByTagName('head').item(0).appendChild(script); + +/** + * read query string + * + */ +const qs = (function (a) { + if (a == "") return {}; + let b = {}; + for (let i = 0; i < a.length; ++i) { + let p = a[i].split('=', 2); + if (p.length == 1) + b[p[0]] = ""; + else + b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); + } + return b; +})(window.location.search.substr(1).split('&')); + +const authSetup = function () { + + let domain = 'auth.topcoder-dev.com'; + const clientId = 'BXWXUWnilVUPdN01t2Se29Tw2ZYNGZvH'; + const useLocalStorage = false; + const useRefreshTokens = false; + const v3JWTCookie = 'v3jwt'; + const tcJWTCookie = 'tcjwt'; + const tcSSOCookie = 'tcsso'; + const cookieExpireIn = 12 * 60; // 12 hrs + const refreshTokenInterval = 60000; // in milliseconds + const refreshTokenOffset = 65; // in seconds + let returnAppUrl = qs['retUrl']; + const shouldLogout = qs['logout']; + const regSource = qs['regSource']; + const utmSource = qs['utm_source']; + const utmMedium = qs['utm_medium']; + const utmCampaign = qs['utm_campaign']; + const appUrl = qs['appUrl'] || false; + const loggerMode = "dev"; + const IframeLogoutRequestType = "LOGOUT_REQUEST"; + const enterpriseCustomers = ['wipro', 'zurich', 'cs']; + + if (utmSource && + (utmSource != 'undefined') && + (enterpriseCustomers.indexOf(utmSource) > -1)) { + domain = "topcoder-dev.auth0.com"; + returnAppUrl += '&utm_source=' + utmSource; + } + + + var auth0 = null; + var isAuthenticated = false; + var idToken = null; + var callRefreshTokenFun = null; + var host = window.location.protocol + "//" + window.location.host + const registerSuccessUrl = host + '/register_success.html'; + + const init = function () { + correctOldUrl(); + createAuth0Client({ + domain: domain, + client_id: clientId, + cacheLocation: useLocalStorage + ? 'localstorage' + : 'memory', + useRefreshTokens: useRefreshTokens + }).then(_init); + window.addEventListener("message", receiveMessage, false); + }; + + const _init = function (authObj) { + auth0 = authObj + if (qs['code'] && qs['state']) { + auth0.handleRedirectCallback().then(function (data) { + logger('handleRedirectCallback() success: ', data); + showAuth0Info(); + storeToken(); + }).catch(function (e) { + logger('handleRedirectCallback() error: ', e); + }); + } else if (shouldLogout) { + host = returnAppUrl ? returnAppUrl : host; + logout(); + return; + } else if (!isLoggedIn() && returnAppUrl) { + login(); + } else { + logger("User already logged in", true); + postLogin(); + } + showAuthenticated(); + }; + + const showAuthenticated = function () { + auth0.isAuthenticated().then(function (isAuthenticated) { + isAuthenticated = isAuthenticated; + logger("_init:isAuthenticated", isAuthenticated); + }); + }; + + const refreshToken = function () { + let d = new Date(); + logger('checking token status at: ', `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} `); + var token = getCookie(tcJWTCookie); + if (!token || isTokenExpired(token)) { + logger('refreshing token... at: ', `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} `); + auth0.getTokenSilently().then(function (token) { + showAuth0Info(); + storeToken(); + }).catch(function (e) { + logger("Error in refreshing token: ", e) + if (e.error && ((e.error == "login_required") || (e.error == "timeout"))) { + clearInterval(callRefreshTokenFun); + } + } + ); + } + }; + + const showAuth0Info = function () { + auth0.getUser().then(function (user) { + logger("User Profile: ", user); + }); + auth0.getIdTokenClaims().then(function (claims) { + idToken = claims.__raw; + logger("JWT Token: ", idToken); + }); + }; + + const login = function () { + auth0 + .loginWithRedirect({ + redirect_uri: host + '?appUrl=' + returnAppUrl, + regSource: regSource, + utmSource: utmSource, + utmCampaign: utmCampaign, + utmMedium: utmMedium + }) + .then(function () { + auth0.isAuthenticated().then(function (isAuthenticated) { + isAuthenticated = isAuthenticated; + if (isAuthenticated) { + showAuth0Info(); + storeToken(); + postLogin(); + } + }); + }); + }; + + const logout = function () { + auth0.logout({ + returnTo: host + }); + // TODO + setCookie(tcJWTCookie, "", -1); + setCookie(v3JWTCookie, "", -1); + setCookie(tcSSOCookie, "", -1); + }; + + const isLoggedIn = function () { + var token = getCookie(tcJWTCookie); + return token ? !isTokenExpired(token) : false; + }; + + const redirectToApp = function () { + logger("redirect to app", appUrl); + if (appUrl) { + window.location = appUrl; + } + }; + + const postLogin = function () { + if (isLoggedIn() && returnAppUrl) { + auth0.isAuthenticated().then(function (isAuthenticated) { + if (isAuthenticated) { + window.location = returnAppUrl; + } else { + login(); // old session exist case + } + }); + } + logger('calling postLogin: ', true); + logger('callRefreshTokenFun: ', callRefreshTokenFun); + if (callRefreshTokenFun != null) { + clearInterval(callRefreshTokenFun); + } + refreshToken(); + callRefreshTokenFun = setInterval(refreshToken, refreshTokenInterval); + } + + const storeToken = function () { + auth0.getIdTokenClaims().then(function (claims) { + idToken = claims.__raw; + let userActive = false; + Object.keys(claims).findIndex(function (key) { + if (key.includes('active')) { + userActive = claims[key]; + return true; + } + return false; + }); + if (userActive) { + let tcsso = ''; + Object.keys(claims).findIndex(function (key) { + if (key.includes(tcSSOCookie)) { + tcsso = claims[key]; + return true; + } + return false; + }); + logger('Storing token...', true); + setCookie(tcJWTCookie, idToken, cookieExpireIn); + setCookie(v3JWTCookie, idToken, cookieExpireIn); + setCookie(tcSSOCookie, tcsso, cookieExpireIn); + redirectToApp(); + } else { + logger("User active ? ", userActive); + host = registerSuccessUrl; + logout(); + } + }).catch(function (e) { + logger("Error in fetching token from auth0: ", e); + }); + }; + + /////// Token.js + + function getTokenExpirationDate(token) { + const decoded = decodeToken(token); + if (typeof decoded.exp === 'undefined') { + return null; + } + const d = new Date(0); // The 0 here is the key, which sets the date to the epoch + d.setUTCSeconds(decoded.exp); + return d; + } + + function decodeToken(token) { + const parts = token.split('.'); + + if (parts.length !== 3) { + throw new Error('The token is invalid'); + } + + const decoded = urlBase64Decode(parts[1]) + + if (!decoded) { + throw new Error('Cannot decode the token'); + } + + // covert base64 token in JSON object + let t = JSON.parse(decoded); + return t; + } + + function isTokenExpired(token, offsetSeconds = refreshTokenOffset) { + const d = getTokenExpirationDate(token) + + if (d === null) { + return false; + } + + // Token expired? + return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))); + } + + function urlBase64Decode(str) { + let output = str.replace(/-/g, '+').replace(/_/g, '/') + + switch (output.length % 4) { + case 0: + break; + + case 2: + output += '==' + break; + + case 3: + output += '=' + break; + + default: + throw 'Illegal base64url string!'; + } + return decodeURIComponent(escape(atob(output))); //polyfill https://github.com/davidchambers/Base64.js + } + + function setCookie(cname, cvalue, exMins) { + const cdomain = getHostDomain(); + + let d = new Date(); + d.setTime(d.getTime() + (exMins * 60 * 1000)); + + let expires = ";expires=" + d.toUTCString(); + document.cookie = cname + "=" + cvalue + cdomain + expires + ";path=/"; + } + + function getCookie(name) { + const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); + return v ? v[2] : undefined; + } + // end token.js + + function getHostDomain() { + let hostDomain = ""; + if (location.hostname !== 'localhost') { + hostDomain = ";domain=." + + location.hostname.split('.').reverse()[1] + + "." + location.hostname.split('.').reverse()[0]; + } + return hostDomain; + } + + function correctOldUrl() { + const pattern = '#!/member'; + const sso_pattern = '/#!/sso-login'; + const logout_pattern = '/#!/logout?'; + + if (window.location.href.indexOf(pattern) > -1) { + window.location.href = window.location.href.replace(pattern, ''); + } + + if (window.location.href.indexOf(sso_pattern) > -1) { + window.location.href = window.location.href.replace(sso_pattern, ''); + } + + if (window.location.href.indexOf(logout_pattern) > -1) { + window.location.href = window.location.href.replace(logout_pattern, '/?logout=true&'); + } + } + + function logger(label, message) { + if (loggerMode === "dev") { + console.log(label, message); + } + } + + /** + * will receive message from iframe + */ + function receiveMessage(e) { + logger("received Event:", e); + if (e.data && e.data.type && e.origin) { + if (e.data.type === IframeLogoutRequestType) { + host = e.origin; + logout(); + } + } + + } + + // execute + init(); +}; diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.jsx b/src/projects/detail/components/PhaseCard/EditStageForm.jsx index 1714220d7..02cc9c37e 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.jsx +++ b/src/projects/detail/components/PhaseCard/EditStageForm.jsx @@ -89,7 +89,8 @@ class EditStageForm extends React.Component { const updateParam = _.assign({}, model, { startDate: updatedStartDate, endDate: updatedEndDate || '', - status: newStatus + status: newStatus, + duration: moment.utc(updatedEndDate).diff(updatedStartDate, 'days') }) this.setState({ isUpdating: true, From 4af78ac62d494df5f16eaabb6cdd6415d9dbf1e9 Mon Sep 17 00:00:00 2001 From: System Administrator Date: Sat, 17 Apr 2021 12:34:50 +0700 Subject: [PATCH 15/35] #4355 - Fix error validation on phase Start Date & End Date --- index.html | 18 -- setupAuth0WithRedirect.js | 359 -------------------------------------- 2 files changed, 377 deletions(-) delete mode 100644 index.html delete mode 100644 setupAuth0WithRedirect.js diff --git a/index.html b/index.html deleted file mode 100644 index 5217f1bce..000000000 --- a/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Auth0 - - - - - - Loaded...redirecting to auth0.(see browser console log) - - Login - - - diff --git a/setupAuth0WithRedirect.js b/setupAuth0WithRedirect.js deleted file mode 100644 index 335999df1..000000000 --- a/setupAuth0WithRedirect.js +++ /dev/null @@ -1,359 +0,0 @@ -var script = document.createElement('script'); -script.src = "https://cdn.auth0.com/js/auth0-spa-js/1.10/auth0-spa-js.production.js"; -script.type = 'text/javascript'; -script.defer = true; -document.getElementsByTagName('head').item(0).appendChild(script); - -/** - * read query string - * - */ -const qs = (function (a) { - if (a == "") return {}; - let b = {}; - for (let i = 0; i < a.length; ++i) { - let p = a[i].split('=', 2); - if (p.length == 1) - b[p[0]] = ""; - else - b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); - } - return b; -})(window.location.search.substr(1).split('&')); - -const authSetup = function () { - - let domain = 'auth.topcoder-dev.com'; - const clientId = 'BXWXUWnilVUPdN01t2Se29Tw2ZYNGZvH'; - const useLocalStorage = false; - const useRefreshTokens = false; - const v3JWTCookie = 'v3jwt'; - const tcJWTCookie = 'tcjwt'; - const tcSSOCookie = 'tcsso'; - const cookieExpireIn = 12 * 60; // 12 hrs - const refreshTokenInterval = 60000; // in milliseconds - const refreshTokenOffset = 65; // in seconds - let returnAppUrl = qs['retUrl']; - const shouldLogout = qs['logout']; - const regSource = qs['regSource']; - const utmSource = qs['utm_source']; - const utmMedium = qs['utm_medium']; - const utmCampaign = qs['utm_campaign']; - const appUrl = qs['appUrl'] || false; - const loggerMode = "dev"; - const IframeLogoutRequestType = "LOGOUT_REQUEST"; - const enterpriseCustomers = ['wipro', 'zurich', 'cs']; - - if (utmSource && - (utmSource != 'undefined') && - (enterpriseCustomers.indexOf(utmSource) > -1)) { - domain = "topcoder-dev.auth0.com"; - returnAppUrl += '&utm_source=' + utmSource; - } - - - var auth0 = null; - var isAuthenticated = false; - var idToken = null; - var callRefreshTokenFun = null; - var host = window.location.protocol + "//" + window.location.host - const registerSuccessUrl = host + '/register_success.html'; - - const init = function () { - correctOldUrl(); - createAuth0Client({ - domain: domain, - client_id: clientId, - cacheLocation: useLocalStorage - ? 'localstorage' - : 'memory', - useRefreshTokens: useRefreshTokens - }).then(_init); - window.addEventListener("message", receiveMessage, false); - }; - - const _init = function (authObj) { - auth0 = authObj - if (qs['code'] && qs['state']) { - auth0.handleRedirectCallback().then(function (data) { - logger('handleRedirectCallback() success: ', data); - showAuth0Info(); - storeToken(); - }).catch(function (e) { - logger('handleRedirectCallback() error: ', e); - }); - } else if (shouldLogout) { - host = returnAppUrl ? returnAppUrl : host; - logout(); - return; - } else if (!isLoggedIn() && returnAppUrl) { - login(); - } else { - logger("User already logged in", true); - postLogin(); - } - showAuthenticated(); - }; - - const showAuthenticated = function () { - auth0.isAuthenticated().then(function (isAuthenticated) { - isAuthenticated = isAuthenticated; - logger("_init:isAuthenticated", isAuthenticated); - }); - }; - - const refreshToken = function () { - let d = new Date(); - logger('checking token status at: ', `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} `); - var token = getCookie(tcJWTCookie); - if (!token || isTokenExpired(token)) { - logger('refreshing token... at: ', `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} `); - auth0.getTokenSilently().then(function (token) { - showAuth0Info(); - storeToken(); - }).catch(function (e) { - logger("Error in refreshing token: ", e) - if (e.error && ((e.error == "login_required") || (e.error == "timeout"))) { - clearInterval(callRefreshTokenFun); - } - } - ); - } - }; - - const showAuth0Info = function () { - auth0.getUser().then(function (user) { - logger("User Profile: ", user); - }); - auth0.getIdTokenClaims().then(function (claims) { - idToken = claims.__raw; - logger("JWT Token: ", idToken); - }); - }; - - const login = function () { - auth0 - .loginWithRedirect({ - redirect_uri: host + '?appUrl=' + returnAppUrl, - regSource: regSource, - utmSource: utmSource, - utmCampaign: utmCampaign, - utmMedium: utmMedium - }) - .then(function () { - auth0.isAuthenticated().then(function (isAuthenticated) { - isAuthenticated = isAuthenticated; - if (isAuthenticated) { - showAuth0Info(); - storeToken(); - postLogin(); - } - }); - }); - }; - - const logout = function () { - auth0.logout({ - returnTo: host - }); - // TODO - setCookie(tcJWTCookie, "", -1); - setCookie(v3JWTCookie, "", -1); - setCookie(tcSSOCookie, "", -1); - }; - - const isLoggedIn = function () { - var token = getCookie(tcJWTCookie); - return token ? !isTokenExpired(token) : false; - }; - - const redirectToApp = function () { - logger("redirect to app", appUrl); - if (appUrl) { - window.location = appUrl; - } - }; - - const postLogin = function () { - if (isLoggedIn() && returnAppUrl) { - auth0.isAuthenticated().then(function (isAuthenticated) { - if (isAuthenticated) { - window.location = returnAppUrl; - } else { - login(); // old session exist case - } - }); - } - logger('calling postLogin: ', true); - logger('callRefreshTokenFun: ', callRefreshTokenFun); - if (callRefreshTokenFun != null) { - clearInterval(callRefreshTokenFun); - } - refreshToken(); - callRefreshTokenFun = setInterval(refreshToken, refreshTokenInterval); - } - - const storeToken = function () { - auth0.getIdTokenClaims().then(function (claims) { - idToken = claims.__raw; - let userActive = false; - Object.keys(claims).findIndex(function (key) { - if (key.includes('active')) { - userActive = claims[key]; - return true; - } - return false; - }); - if (userActive) { - let tcsso = ''; - Object.keys(claims).findIndex(function (key) { - if (key.includes(tcSSOCookie)) { - tcsso = claims[key]; - return true; - } - return false; - }); - logger('Storing token...', true); - setCookie(tcJWTCookie, idToken, cookieExpireIn); - setCookie(v3JWTCookie, idToken, cookieExpireIn); - setCookie(tcSSOCookie, tcsso, cookieExpireIn); - redirectToApp(); - } else { - logger("User active ? ", userActive); - host = registerSuccessUrl; - logout(); - } - }).catch(function (e) { - logger("Error in fetching token from auth0: ", e); - }); - }; - - /////// Token.js - - function getTokenExpirationDate(token) { - const decoded = decodeToken(token); - if (typeof decoded.exp === 'undefined') { - return null; - } - const d = new Date(0); // The 0 here is the key, which sets the date to the epoch - d.setUTCSeconds(decoded.exp); - return d; - } - - function decodeToken(token) { - const parts = token.split('.'); - - if (parts.length !== 3) { - throw new Error('The token is invalid'); - } - - const decoded = urlBase64Decode(parts[1]) - - if (!decoded) { - throw new Error('Cannot decode the token'); - } - - // covert base64 token in JSON object - let t = JSON.parse(decoded); - return t; - } - - function isTokenExpired(token, offsetSeconds = refreshTokenOffset) { - const d = getTokenExpirationDate(token) - - if (d === null) { - return false; - } - - // Token expired? - return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))); - } - - function urlBase64Decode(str) { - let output = str.replace(/-/g, '+').replace(/_/g, '/') - - switch (output.length % 4) { - case 0: - break; - - case 2: - output += '==' - break; - - case 3: - output += '=' - break; - - default: - throw 'Illegal base64url string!'; - } - return decodeURIComponent(escape(atob(output))); //polyfill https://github.com/davidchambers/Base64.js - } - - function setCookie(cname, cvalue, exMins) { - const cdomain = getHostDomain(); - - let d = new Date(); - d.setTime(d.getTime() + (exMins * 60 * 1000)); - - let expires = ";expires=" + d.toUTCString(); - document.cookie = cname + "=" + cvalue + cdomain + expires + ";path=/"; - } - - function getCookie(name) { - const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); - return v ? v[2] : undefined; - } - // end token.js - - function getHostDomain() { - let hostDomain = ""; - if (location.hostname !== 'localhost') { - hostDomain = ";domain=." + - location.hostname.split('.').reverse()[1] + - "." + location.hostname.split('.').reverse()[0]; - } - return hostDomain; - } - - function correctOldUrl() { - const pattern = '#!/member'; - const sso_pattern = '/#!/sso-login'; - const logout_pattern = '/#!/logout?'; - - if (window.location.href.indexOf(pattern) > -1) { - window.location.href = window.location.href.replace(pattern, ''); - } - - if (window.location.href.indexOf(sso_pattern) > -1) { - window.location.href = window.location.href.replace(sso_pattern, ''); - } - - if (window.location.href.indexOf(logout_pattern) > -1) { - window.location.href = window.location.href.replace(logout_pattern, '/?logout=true&'); - } - } - - function logger(label, message) { - if (loggerMode === "dev") { - console.log(label, message); - } - } - - /** - * will receive message from iframe - */ - function receiveMessage(e) { - logger("received Event:", e); - if (e.data && e.data.type && e.origin) { - if (e.data.type === IframeLogoutRequestType) { - host = e.origin; - logout(); - } - } - - } - - // execute - init(); -}; From 19febdc604c11c639dce8e7f5c59d4306ad16f6c Mon Sep 17 00:00:00 2001 From: System Administrator Date: Sat, 17 Apr 2021 13:07:34 +0700 Subject: [PATCH 16/35] #4337 - Fix empty string description validation during phase creation/update --- .../detail/components/CreatePhaseForm/CreatePhaseForm.jsx | 2 +- src/projects/detail/components/PhaseCard/EditStageForm.jsx | 4 +++- src/projects/detail/components/PhaseCard/PhaseCard.jsx | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx index 8849bab8c..ba3a56a6f 100644 --- a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx +++ b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx @@ -120,7 +120,7 @@ class CreatePhaseForm extends React.Component { const phaseData = { title: model.title, - description: model.description, + description: model.description || ' ', startDate: moment(model.startDate), endDate: moment(model.endDate), } diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.jsx b/src/projects/detail/components/PhaseCard/EditStageForm.jsx index 02cc9c37e..6758fdbef 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.jsx +++ b/src/projects/detail/components/PhaseCard/EditStageForm.jsx @@ -76,6 +76,7 @@ class EditStageForm extends React.Component { } submitValue(model) { + console.log(model) const { phase, phaseIndex, updatePhaseAction } = this.props const { publishClicked @@ -87,6 +88,7 @@ class EditStageForm extends React.Component { newStatus = PHASE_STATUS_ACTIVE } const updateParam = _.assign({}, model, { + description: model.description || ' ', startDate: updatedStartDate, endDate: updatedEndDate || '', status: newStatus, @@ -286,7 +288,7 @@ class EditStageForm extends React.Component { wrapperClass={`${styles['input-row']}`} label="Description" name="description" - value={phase.description} + value={phase.description.trim()} maxLength={255} />
diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index 6551b8413..8b0f9ab76 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -183,7 +183,7 @@ class PhaseCard extends React.Component { {phaseEditable && !this.state.isEditting && ( )}
-
{attr.phase.description}
+ {attr.phase.description.trim().length > 0 &&
{attr.phase.description}
}
{attr.duration} {attr.startEndDates} From 416fa1c0267a33e663e89b6d50072e3a4711f5bc Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Mon, 19 Apr 2021 19:45:11 +0530 Subject: [PATCH 17/35] fix: git#4355-Screen loading continuously if Phase start date set as past date NPE fixing --- src/projects/detail/components/PhaseCard/EditStageForm.jsx | 2 +- src/projects/detail/components/PhaseCard/PhaseCard.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.jsx b/src/projects/detail/components/PhaseCard/EditStageForm.jsx index 6758fdbef..6cb752b6a 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.jsx +++ b/src/projects/detail/components/PhaseCard/EditStageForm.jsx @@ -288,7 +288,7 @@ class EditStageForm extends React.Component { wrapperClass={`${styles['input-row']}`} label="Description" name="description" - value={phase.description.trim()} + value={phase.description ? phase.description.trim() : ''} maxLength={255} />
diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index 8b0f9ab76..7a26b20c1 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -183,7 +183,7 @@ class PhaseCard extends React.Component { {phaseEditable && !this.state.isEditting && (
)}
- {attr.phase.description.trim().length > 0 &&
{attr.phase.description}
} + {attr.phase.description && attr.phase.description.trim().length > 0 &&
{attr.phase.description}
}
{attr.duration} {attr.startEndDates} From 6cb96da57860ea766106210d0830cf22d247e557 Mon Sep 17 00:00:00 2001 From: Noviandra Syahputra Date: Tue, 20 Apr 2021 13:03:52 +0700 Subject: [PATCH 18/35] fix: #4355 - remove duration from payload, #4350 - revert back duration field and css --- .../components/PhaseCard/EditStageForm.jsx | 15 +----- .../components/PhaseCard/EditStageForm.scss | 46 +++---------------- 2 files changed, 8 insertions(+), 53 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.jsx b/src/projects/detail/components/PhaseCard/EditStageForm.jsx index 6cb752b6a..7e898529f 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.jsx +++ b/src/projects/detail/components/PhaseCard/EditStageForm.jsx @@ -76,7 +76,6 @@ class EditStageForm extends React.Component { } submitValue(model) { - console.log(model) const { phase, phaseIndex, updatePhaseAction } = this.props const { publishClicked @@ -91,8 +90,7 @@ class EditStageForm extends React.Component { description: model.description || ' ', startDate: updatedStartDate, endDate: updatedEndDate || '', - status: newStatus, - duration: moment.utc(updatedEndDate).diff(updatedStartDate, 'days') + status: newStatus }) this.setState({ isUpdating: true, @@ -102,6 +100,7 @@ class EditStageForm extends React.Component { } onFormSubmit(model) { + console.log(model) const { phase } = this.props const { showActivatingWarning, publishClicked } = this.state @@ -244,8 +243,6 @@ class EditStageForm extends React.Component { endDate = moment.utc(endDate).format('YYYY-MM-DD') const canDelete = phase.status !== PHASE_STATUS_ACTIVE && phase.status !== PHASE_STATUS_COMPLETED const isDraft = phase.status === PHASE_STATUS_DRAFT - const duration = moment.utc(endDate).diff(startDate, 'days') - console.log(duration) return (
@@ -326,14 +323,6 @@ class EditStageForm extends React.Component { name="endDate" value={endDate} /> -
{!showActivatingWarning ? (
diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.scss b/src/projects/detail/components/PhaseCard/EditStageForm.scss index 7a8ebcb50..cf1788619 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.scss +++ b/src/projects/detail/components/PhaseCard/EditStageForm.scss @@ -33,7 +33,7 @@ .input-row { display: inline-block; - width: 45%; + width: 50%; :global { @@ -51,7 +51,7 @@ } input, textarea { display: inline-block; - width:calc(100% - 170px); + width:calc(100% - 170px)!important; } textarea{ @@ -67,7 +67,7 @@ white-space: nowrap; display: block; width: 150px; - + } input:not([type="checkbox"]):disabled { @@ -108,7 +108,7 @@ .title-label-layer, .description-label-layer { - + .input-row { width: 100%; input { @@ -158,40 +158,6 @@ } } -.label-layer { - .input-row:nth-child(1) { - input { - width: calc(100% - 170px); - } - } - - .input-row:nth-child(2) { - width: 35%; - - label { - width: 75px; - } - - input{ - width: calc(100% - 100px); - } - } - - .input-row:nth-child(3) { - width: 20%; - - label { - width: 70px; - padding-right: 0; - } - - input{ - width: calc(100% - 90px); - font-size: 13px; - } - } -} - // screen < 1024px @media screen and (max-width: 1023px) { .label-layer:nth-child(4) > :global(.Tooltip) { @@ -205,7 +171,7 @@ .label-layer, .input-row { display: block; - width: 100% !important; + width: 100%; input:not([type="checkbox"]):disabled { width: 100%; } @@ -237,7 +203,7 @@ display: block; } label { - width: 100% !important; + width: 100%; display: block; text-align: left; margin: 0; From 7d640885c00323ebef298f022a079384decdb8af Mon Sep 17 00:00:00 2001 From: Diky Diwo Suwanto Date: Tue, 20 Apr 2021 14:32:31 +0700 Subject: [PATCH 19/35] Fix: hide actual phase status from customers --- src/projects/detail/components/PhaseCard/PhaseCard.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index 7a26b20c1..1e9c4cc5a 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -225,7 +225,7 @@ class PhaseCard extends React.Component { }
- {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && !this.props.currentUserRoles.includes(PROJECT_ROLE_CUSTOMER) && + {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && !this.props.currentUserRoles.includes(PROJECT_ROLE_CUSTOMER) && this.props.currentUserRoles.length > 2 && (
{statusDetails.name} @@ -233,7 +233,7 @@ class PhaseCard extends React.Component {
) } - { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && !this.props.currentUserRoles.includes(PROJECT_ROLE_CUSTOMER) && + { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && !this.props.currentUserRoles.includes(PROJECT_ROLE_CUSTOMER) && this.props.currentUserRoles.length > 2 && (
Date: Tue, 20 Apr 2021 16:46:37 +0530 Subject: [PATCH 20/35] fix: git#4361-Few Project Tabs missing for new projects --- src/helpers/projectHelper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/projectHelper.js b/src/helpers/projectHelper.js index f7e0f5f8f..bab78e28d 100644 --- a/src/helpers/projectHelper.js +++ b/src/helpers/projectHelper.js @@ -270,7 +270,7 @@ export function getProjectNavLinks(project, projectId, renderFAQs) { messagesTab = { label: 'Messages', to: `/projects/${projectId}/messages`, Icon: MessagesIcon, iconClassName: 'stroke', exact: false } } // choose set of menu links based on the project version - const navLinks = project.version === 'v3' ? [ + const navLinks = ['v3', 'v4'].includes(project.version) ? [ { label: 'Dashboard', to: `/projects/${projectId}`, Icon: DashboardIcon, iconClassName: 'stroke' }, messagesTab, { label: 'Scope', to: `/projects/${projectId}/scope`, Icon: ScopeIcon, iconClassName: 'fill' }, From 612752d624e09e7f323abe98bf14bf46880184e1 Mon Sep 17 00:00:00 2001 From: Diky Diwo Suwanto Date: Tue, 20 Apr 2021 19:26:25 +0700 Subject: [PATCH 21/35] Fix: hide actual phase status from customers --- src/projects/detail/components/PhaseCard/PhaseCard.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index 1e9c4cc5a..92316465c 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -150,6 +150,8 @@ class PhaseCard extends React.Component { // const searchParams = new URLSearchParams(window.location.search) const isSimplePlan = projectVersion === 'v4' + console.log(hasPermission(PERMISSIONS.MANAGE_COMPLETED_PHASE)); + return (
- {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && !this.props.currentUserRoles.includes(PROJECT_ROLE_CUSTOMER) && this.props.currentUserRoles.length > 2 && + {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && !hasPermission(PERMISSIONS.EXPAND_ACTIVE_PHASES_BY_DEFAULT) && (
{statusDetails.name} @@ -233,7 +235,7 @@ class PhaseCard extends React.Component {
) } - { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && !this.props.currentUserRoles.includes(PROJECT_ROLE_CUSTOMER) && this.props.currentUserRoles.length > 2 && + { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && !hasPermission(PERMISSIONS.EXPAND_ACTIVE_PHASES_BY_DEFAULT) && (
Date: Tue, 20 Apr 2021 19:28:15 +0700 Subject: [PATCH 22/35] Fix: hide actual phase status from customers --- src/projects/detail/components/PhaseCard/PhaseCard.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index 92316465c..13a6edaf2 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -150,8 +150,6 @@ class PhaseCard extends React.Component { // const searchParams = new URLSearchParams(window.location.search) const isSimplePlan = projectVersion === 'v4' - console.log(hasPermission(PERMISSIONS.MANAGE_COMPLETED_PHASE)); - return (
Date: Wed, 21 Apr 2021 15:03:32 +0700 Subject: [PATCH 23/35] Fix lint error --- src/projects/detail/components/PhaseCard/PhaseCard.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index 13a6edaf2..eb0ae9312 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -20,8 +20,7 @@ import { PROJECT_STATUS_CANCELLED, SCREEN_BREAKPOINT_MD, EVENT_TYPE, - PHASE_STATUS_REVIEWED, - PROJECT_ROLE_CUSTOMER + PHASE_STATUS_REVIEWED } from '../../../../config/constants' import ProjectProgress from '../../../../components/ProjectProgress/ProjectProgress' From 5b32199d769ea853929ca9a40cbe49da09737670 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 21 Apr 2021 15:40:05 +0530 Subject: [PATCH 24/35] fix: git#4361-Few Project Tabs missing for new projects Fixed scope => specification redirection for new projects --- src/projects/components/projectsCard/ProjectCard.jsx | 2 +- src/projects/components/projectsCard/ProjectCardBody.jsx | 2 +- src/projects/detail/ProjectDetail.jsx | 2 +- src/projects/detail/containers/SecondaryToolBarContainer.jsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/projects/components/projectsCard/ProjectCard.jsx b/src/projects/components/projectsCard/ProjectCard.jsx index 1265508ec..eba8078c8 100644 --- a/src/projects/components/projectsCard/ProjectCard.jsx +++ b/src/projects/components/projectsCard/ProjectCard.jsx @@ -15,7 +15,7 @@ function ProjectCard({ project, disabled, currentUser, history, onChangeStatus, const isMember = _.some(project.members, m => (m.userId === currentUser.userId && m.deletedAt === null)) // check whether has pending invition const isInvited = _.some(project.invites, m => ((m.userId === currentUser.userId || m.email === currentUser.email) && !m.deletedAt && m.status === 'pending')) - const projectDetailsURL = project.version === 'v3' + const projectDetailsURL = ['v3', 'v4'].includes(project.version) ? `/projects/${project.id}/scope` : `/projects/${project.id}/specification` diff --git a/src/projects/components/projectsCard/ProjectCardBody.jsx b/src/projects/components/projectsCard/ProjectCardBody.jsx index 12b124625..3e5bcbb5b 100644 --- a/src/projects/components/projectsCard/ProjectCardBody.jsx +++ b/src/projects/components/projectsCard/ProjectCardBody.jsx @@ -32,7 +32,7 @@ function ProjectCardBody({ project, descLinesCount = 8, const progress = _.get(process, 'percent', 0) - const projectDetailsURL = project.version === 'v3' + const projectDetailsURL = ['v3', 'v4'].includes(project.version) ? `/projects/${project.id}/scope` : `/projects/${project.id}/specification` diff --git a/src/projects/detail/ProjectDetail.jsx b/src/projects/detail/ProjectDetail.jsx index f44ae71f7..f479e37da 100644 --- a/src/projects/detail/ProjectDetail.jsx +++ b/src/projects/detail/ProjectDetail.jsx @@ -169,7 +169,7 @@ class ProjectDetail extends Component { // if project version not v3 , URL /scope redirect to /specification if(project && project.version - && project.version !== 'v3' + && !['v3', 'v4'].includes(project.version) && project.id === parseInt(match.params.projectId) && this.props.history.location.pathname.indexOf('/scope') !== -1 ){ this.props.history.push(this.props.history.location.pathname.replace('/scope', '/specification')) diff --git a/src/projects/detail/containers/SecondaryToolBarContainer.jsx b/src/projects/detail/containers/SecondaryToolBarContainer.jsx index 0ad5e252e..74c9a4009 100644 --- a/src/projects/detail/containers/SecondaryToolBarContainer.jsx +++ b/src/projects/detail/containers/SecondaryToolBarContainer.jsx @@ -21,7 +21,7 @@ const SecondaryToolBarContainer = ({ } // choose set of menu links based on the project version - const navLinks = project.version === 'v3' ? [ + const navLinks = ['v3', 'v4'].includes(project.version) ? [ { label: 'Dashboard', to: `/projects/${match.params.projectId}` }, { label: 'Messages', to: `/projects/${match.params.projectId}/messages` }, { label: 'Scope', to: `/projects/${match.params.projectId}/scope` }, From dc9b2b6183a0c8a73674271f99b62a90eb833f2f Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 21 Apr 2021 16:21:18 +0530 Subject: [PATCH 25/35] fix: git#4348-https://github.com/appirio-tech/connect-app/issues/4348# Added new permission for showing status of the phases --- src/config/permissions.js | 14 ++++++++++++++ .../detail/components/PhaseCard/PhaseCard.jsx | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/config/permissions.js b/src/config/permissions.js index 4e60f67bd..82e33250d 100644 --- a/src/config/permissions.js +++ b/src/config/permissions.js @@ -199,6 +199,20 @@ export const PERMISSIONS = { ], }, + SHOW_PHASE_STATUS: { + meta: { + group: 'Project Plan', + title: 'Show project phase status', + }, + projectRoles: [ + ..._.difference(PROJECT_ALL, [PROJECT_ROLE_CUSTOMER]) + ], + topcoderRoles: [ + ROLE_CONNECT_MANAGER, + ...TOPCODER_ADMINS, + ], + }, + /* Project Members */ diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index eb0ae9312..1a4e1d293 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -224,7 +224,7 @@ class PhaseCard extends React.Component { }
- {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && !hasPermission(PERMISSIONS.EXPAND_ACTIVE_PHASES_BY_DEFAULT) && + {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && hasPermission(PERMISSIONS.SHOW_PHASE_STATUS) && (
{statusDetails.name} @@ -232,7 +232,7 @@ class PhaseCard extends React.Component {
) } - { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && !hasPermission(PERMISSIONS.EXPAND_ACTIVE_PHASES_BY_DEFAULT) && + { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && hasPermission(PERMISSIONS.SHOW_PHASE_STATUS) && (
Date: Wed, 21 Apr 2021 17:16:13 +0530 Subject: [PATCH 26/35] fix: #4350-Phase UI Layout (Non-Set-Up/Editing View) - Updated layout for read only view of phase cards --- .../detail/components/PhaseCard/PhaseCard.jsx | 5 +++-- .../detail/components/PhaseCard/PhaseCard.scss | 18 +++++++++++------- .../detail/components/ProjectStage.jsx | 16 ++++++++-------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index 1a4e1d293..eb6b92295 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -184,8 +184,9 @@ class PhaseCard extends React.Component {
{attr.phase.description && attr.phase.description.trim().length > 0 &&
{attr.phase.description}
}
- {attr.duration} - {attr.startEndDates} + {attr.duration} + {attr.actualStartDate.format('YYYY-MM-DD')} + {attr.actualEndDate.format('YYYY-MM-DD')} {!isSimplePlan && attr.posts && {attr.posts}}
diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.scss b/src/projects/detail/components/PhaseCard/PhaseCard.scss index caa006b04..5c02b17e8 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.scss +++ b/src/projects/detail/components/PhaseCard/PhaseCard.scss @@ -160,15 +160,19 @@ .meta { line-height: 20px; position: relative; - &:after { - content: " • "; + margin-left: 10px; + + label { display: inline-block; - margin: 0 4px; + color: $tc-gray-50; + font-size: 11px; + font-weight: 600; + line-height: 20px; + margin-right: 5px; } - &:last-child { - &:after { - display: none; - } + + &:first-child { + margin-left: 0px; } } } diff --git a/src/projects/detail/components/ProjectStage.jsx b/src/projects/detail/components/ProjectStage.jsx index f8c9a7d3d..24be9434a 100644 --- a/src/projects/detail/components/ProjectStage.jsx +++ b/src/projects/detail/components/ProjectStage.jsx @@ -57,13 +57,13 @@ function formatPhaseCardAttr(phase, phaseIndex, productTemplates, feed, timeline } = getPhaseActualData(phase, timeline) const duration = `${plannedDuration} day${plannedDuration !== 1 ? 's' : ''}` - let startEndDates = startDate ? `${startDate.format('MMM D')}` : '' - // appends end date to the start date only if end date is greater than start date - startEndDates += startDate && endDate && endDate.diff(startDate, 'days') > 0 ? `-${endDate.format('MMM D')}` : '' - // extracts the start date's month string plus white space - const monthStr = startEndDates.substr(0, 4) - // replaces the second occurrence of the month part i.e. removes the end date's month part - startEndDates = startEndDates.lastIndexOf(monthStr) !== 0 ? startEndDates.replace(`-${monthStr}`, '-') : startEndDates + // let startEndDates = startDate ? `${startDate.format('MMM D')}` : '' + // // appends end date to the start date only if end date is greater than start date + // startEndDates += startDate && endDate && endDate.diff(startDate, 'days') > 0 ? `-${endDate.format('MMM D')}` : '' + // // extracts the start date's month string plus white space + // const monthStr = startEndDates.substr(0, 4) + // // replaces the second occurrence of the month part i.e. removes the end date's month part + // startEndDates = startEndDates.lastIndexOf(monthStr) !== 0 ? startEndDates.replace(`-${monthStr}`, '-') : startEndDates const actualPrice = phase.spentBudget let paidStatus = 'Quoted' @@ -81,7 +81,7 @@ function formatPhaseCardAttr(phase, phaseIndex, productTemplates, feed, timeline icon, title, duration, - startEndDates, + // startEndDates, price, paidStatus, status, From ebf79046f3453b90cd0e4ab3f3e4bb70d609b9ed Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 21 Apr 2021 17:16:35 +0530 Subject: [PATCH 27/35] fix: #4350-Phase UI Layout (Non-Set-Up/Editing View) - Updated layout for Total duration and dates of phases --- .../PhaseCardListFooter/PhaseCardListFooter.jsx | 14 +++++++++----- .../PhaseCardListFooter/PhaseCardListFooter.scss | 13 ++++++++----- src/projects/detail/components/ProjectStages.jsx | 8 +++++--- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.jsx b/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.jsx index 1b525983f..731bc863c 100644 --- a/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.jsx +++ b/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.jsx @@ -9,15 +9,17 @@ import './PhaseCardListFooter.scss' const PhaseCardListFooter = ({ duration, price, - startEndDates + minStartDate, + maxEndDate, }) => (
Total:
- {duration} - {startEndDates} + {duration} + {minStartDate.format('YYYY-MM-DD')} + {maxEndDate.format('YYYY-MM-DD')}
{parseInt(price, 10) > 0 &&
price
} @@ -29,14 +31,16 @@ const PhaseCardListFooter = ({ PhaseCardListFooter.defaultProps = { duration: null, price: null, - startEndDates: null, + minStartDate: null, + maxEndDate: null, projectId: 0 } PhaseCardListFooter.propTypes = { duration: PT.string, price: PT.string, - startEndDates: PT.string + minStartDate: PT.Date, + maxEndDate: PT.Date, } export default PhaseCardListFooter diff --git a/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.scss b/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.scss index e75d0e0a2..215ecb783 100644 --- a/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.scss +++ b/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.scss @@ -51,14 +51,17 @@ .meta-list { .meta { - &::after { - content: " • "; + color: $tc-gray-30; + margin-left: 10px; + + label { display: inline-block; - margin: 0 $base-unit; + color: $tc-white; + margin-right: 5px; } - &:last-child::after { - display: none; + &:first-child { + margin-left: 0px; } &.meta-dark { diff --git a/src/projects/detail/components/ProjectStages.jsx b/src/projects/detail/components/ProjectStages.jsx index 7953ea54f..36f624a58 100644 --- a/src/projects/detail/components/ProjectStages.jsx +++ b/src/projects/detail/components/ProjectStages.jsx @@ -43,8 +43,8 @@ function formatPhaseCardListFooterProps(phases, productsTimelines) { const minStartDate = startDates.length > 0 ? moment.min(startDates) : null const maxEndDate = endDates.length > 0 ? moment.max(endDates) : null - let startEndDates = minStartDate ? `${minStartDate.format('MMM D')}` : '' - startEndDates += minStartDate && maxEndDate ? `–${maxEndDate.format('MMM D')}` : '' + // let startEndDates = minStartDate ? `${minStartDate.format('MMM D')}` : '' + // startEndDates += minStartDate && maxEndDate ? `–${maxEndDate.format('MMM D')}` : '' const totalPrice = _.sumBy(filteredPhases, 'budget') @@ -54,7 +54,9 @@ function formatPhaseCardListFooterProps(phases, productsTimelines) { return { duration, - startEndDates, + // startEndDates, + minStartDate, + maxEndDate, price } } From d10e42b16a6def1a266e6021d5e81ba1ab6b37ee Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 21 Apr 2021 18:02:16 +0530 Subject: [PATCH 28/35] fix: #4348-Customer must not see the status of the phase Trying to fix the logic for showing status --- src/projects/detail/components/PhaseCard/PhaseCard.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index eb6b92295..308347e30 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -225,7 +225,7 @@ class PhaseCard extends React.Component { }
- {status && (isSimplePlan || status !== PHASE_STATUS_ACTIVE) && hasPermission(PERMISSIONS.SHOW_PHASE_STATUS) && + {status && status !== PHASE_STATUS_ACTIVE && (!isSimplePlan || hasPermission(PERMISSIONS.SHOW_PHASE_STATUS)) && (
{statusDetails.name} @@ -233,7 +233,7 @@ class PhaseCard extends React.Component {
) } - { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && hasPermission(PERMISSIONS.SHOW_PHASE_STATUS) && + { status && status === PHASE_STATUS_ACTIVE && (!isSimplePlan || hasPermission(PERMISSIONS.SHOW_PHASE_STATUS)) && (
Date: Thu, 22 Apr 2021 14:11:41 +0700 Subject: [PATCH 29/35] Add subtext 255 character maximum under the Description header --- .../detail/components/PhaseCard/EditStageForm.jsx | 1 + .../detail/components/PhaseCard/EditStageForm.scss | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.jsx b/src/projects/detail/components/PhaseCard/EditStageForm.jsx index 7e898529f..1d2c98afe 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.jsx +++ b/src/projects/detail/components/PhaseCard/EditStageForm.jsx @@ -288,6 +288,7 @@ class EditStageForm extends React.Component { value={phase.description ? phase.description.trim() : ''} maxLength={255} /> +
)}
diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.scss b/src/projects/detail/components/PhaseCard/EditStageForm.scss index cf1788619..92738336a 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.scss +++ b/src/projects/detail/components/PhaseCard/EditStageForm.scss @@ -108,6 +108,7 @@ .title-label-layer, .description-label-layer { + position: relative; .input-row { width: 100%; @@ -120,6 +121,14 @@ width: 150px; } } + + .description-sub-label { + font-style: italic; + text-align: right; + margin-bottom: 10px; + color: $tc-gray-50; + font-size: 11px; + } } .group-bottom { From f0fb9e394c6f6419136677b16d4793734f942285 Mon Sep 17 00:00:00 2001 From: Diky Diwo Suwanto Date: Thu, 22 Apr 2021 14:22:44 +0700 Subject: [PATCH 30/35] Add subtext 255 character maximum under the Description header --- src/projects/detail/components/PhaseCard/EditStageForm.jsx | 2 +- src/projects/detail/components/PhaseCard/EditStageForm.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.jsx b/src/projects/detail/components/PhaseCard/EditStageForm.jsx index 1d2c98afe..d31c22b11 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.jsx +++ b/src/projects/detail/components/PhaseCard/EditStageForm.jsx @@ -288,7 +288,7 @@ class EditStageForm extends React.Component { value={phase.description ? phase.description.trim() : ''} maxLength={255} /> - +
255 character maximum
)}
diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.scss b/src/projects/detail/components/PhaseCard/EditStageForm.scss index 92738336a..53cc4985a 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.scss +++ b/src/projects/detail/components/PhaseCard/EditStageForm.scss @@ -126,6 +126,7 @@ font-style: italic; text-align: right; margin-bottom: 10px; + margin-top: -10px; color: $tc-gray-50; font-size: 11px; } From 56fc11c60e6c67139689edd04ed231b7bc3a2196 Mon Sep 17 00:00:00 2001 From: Diky Diwo Suwanto Date: Thu, 22 Apr 2021 14:24:36 +0700 Subject: [PATCH 31/35] Add subtext 255 character maximum under the Description header --- src/projects/detail/components/PhaseCard/EditStageForm.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.jsx b/src/projects/detail/components/PhaseCard/EditStageForm.jsx index d31c22b11..1d2c98afe 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.jsx +++ b/src/projects/detail/components/PhaseCard/EditStageForm.jsx @@ -288,7 +288,7 @@ class EditStageForm extends React.Component { value={phase.description ? phase.description.trim() : ''} maxLength={255} /> -
255 character maximum
+
)}
From 71aa8394305f9f7e6742249cfc15f5cbfc771734 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Thu, 22 Apr 2021 13:01:40 +0530 Subject: [PATCH 32/35] fix: #4348-Customer must not see the status of the phase Removed phase progress for manager users for simplified phases --- src/projects/detail/components/PhaseCard/PhaseCard.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index 308347e30..74f1c4874 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -233,7 +233,7 @@ class PhaseCard extends React.Component {
) } - { status && status === PHASE_STATUS_ACTIVE && (!isSimplePlan || hasPermission(PERMISSIONS.SHOW_PHASE_STATUS)) && + { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && (
Date: Thu, 22 Apr 2021 14:46:23 +0700 Subject: [PATCH 33/35] Add subtext 255 character maximum under the Description header --- .../components/CreatePhaseForm/CreatePhaseForm.jsx | 1 + .../components/CreatePhaseForm/CreatePhaseForm.scss | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx index ba3a56a6f..a9547b40e 100644 --- a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx +++ b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx @@ -376,6 +376,7 @@ class CreatePhaseForm extends React.Component { value={''} maxLength={255} /> +
Date: Thu, 22 Apr 2021 14:29:13 +0530 Subject: [PATCH 34/35] fix: #4348-Customer must not see the status of the phase Adding back the text status for active and above status phases --- src/projects/detail/components/PhaseCard/PhaseCard.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index 74f1c4874..840ed0c23 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -233,17 +233,19 @@ class PhaseCard extends React.Component {
) } - { !isSimplePlan && status && status === PHASE_STATUS_ACTIVE && + { status && status === PHASE_STATUS_ACTIVE && (!isSimplePlan || hasPermission(PERMISSIONS.SHOW_PHASE_STATUS)) && (
- {progressInPercent}% completed - + ) + } {statusDetails.name}
) From 25aa7afd8e1233161d057707c1c6310bfd8105c2 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Thu, 22 Apr 2021 14:37:53 +0530 Subject: [PATCH 35/35] Lint fix --- .../detail/components/PhaseCard/PhaseCard.jsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index 840ed0c23..8b9c7c675 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -236,16 +236,16 @@ class PhaseCard extends React.Component { { status && status === PHASE_STATUS_ACTIVE && (!isSimplePlan || hasPermission(PERMISSIONS.SHOW_PHASE_STATUS)) && (
- { !isSimplePlan && - ( - {progressInPercent}% completed - ) - } + { !isSimplePlan && ( + + {progressInPercent}% completed + + )} {statusDetails.name}
)