diff --git a/local/login-locally/README b/local/login-locally/README index a72052b8..080616cf 100644 --- a/local/login-locally/README +++ b/local/login-locally/README @@ -5,7 +5,3 @@ 3. Now you can open http://localhost:5000 in browser and click login (wait it a little bit, it may take time to redirect you). After you login, you should be redirected back to http://localhost:3000 PS. You may also download latest version of `setupAuth0WithRedirect.js` file from here - https://github.com/topcoder-platform/tc-auth-lib/blob/dev/web-assets/js/setupAuth0WithRedirect.js - - - - diff --git a/local/login-locally/index.html b/local/login-locally/index.html index 5217f1bc..3129ead1 100644 --- a/local/login-locally/index.html +++ b/local/login-locally/index.html @@ -1,18 +1,20 @@ - - + Auth0 - - + + - + Loaded...redirecting to auth0.(see browser console log) - Login - - + Login + diff --git a/local/login-locally/setupAuth0WithRedirect.js b/local/login-locally/setupAuth0WithRedirect.js index 8f6bdaf9..464a37e3 100644 --- a/local/login-locally/setupAuth0WithRedirect.js +++ b/local/login-locally/setupAuth0WithRedirect.js @@ -1,628 +1,677 @@ -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'; +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); +document.getElementsByTagName("head").item(0).appendChild(script); -/** - * read query string - * +/** + * 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('&')); + 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 = 30000; // in milliseconds - const refreshTokenOffset = 65; // in seconds - const shouldLogout = qs['logout']; - const regSource = qs['regSource']; - const utmSource = qs['utm_source']; - const utmMedium = qs['utm_medium']; - const utmCampaign = qs['utm_campaign']; - const loggerMode = "dev"; - const IframeLogoutRequestType = "LOGOUT_REQUEST"; - const enterpriseCustomers = ['zurich', 'cs']; - const mode = qs['mode'] || 'signIn'; - let returnAppUrl = qs['retUrl']; - let appUrl = qs['appUrl'] || false; - - if (utmSource && - (utmSource != 'undefined') && - (enterpriseCustomers.indexOf(utmSource) > -1)) { - domain = "topcoder-dev.auth0.com"; - returnAppUrl += '&utm_source=' + utmSource; + 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 = 30000; // in milliseconds + const refreshTokenOffset = 65; // in seconds + const shouldLogout = qs["logout"]; + const regSource = qs["regSource"]; + const utmSource = qs["utm_source"]; + const utmMedium = qs["utm_medium"]; + const utmCampaign = qs["utm_campaign"]; + const loggerMode = "dev"; + const IframeLogoutRequestType = "LOGOUT_REQUEST"; + const enterpriseCustomers = ["zurich", "cs"]; + const mode = qs["mode"] || "signIn"; + let returnAppUrl = qs["retUrl"]; + let appUrl = qs["appUrl"] || false; + + 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(); + changeWindowMessage(); + createAuth0Client({ + domain: domain, + client_id: clientId, + cacheLocation: useLocalStorage ? "localstorage" : "memory", + useRefreshTokens: useRefreshTokens, + }) + .then(_init) + .catch(function (e) { + logger("Error occurred in initializing auth0 object: ", e); + window.location.reload(); + }); + 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 if (qs["error"] && qs["state"]) { + logger("Error in executing callback(): ", qs["error_description"]); + showLoginError(qs["error_description"], appUrl); + } else { + logger("User already logged in", true); + postLogin(); } - - - 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(); - changeWindowMessage(); - createAuth0Client({ + 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(v3JWTCookie); + if (!token || isTokenExpired(token)) { + logger( + "refreshing token... at: ", + `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} ` + ); + try { + let issuerHostname = ""; + if (token) { + let tokenJson = decodeToken(token); + let issuer = tokenJson.iss; + issuerHostname = extractHostname(issuer); + } + if (domain !== issuerHostname) { + domain = issuerHostname; + logger("reintialize auth0 for new domain..", domain); + createAuth0Client({ domain: domain, client_id: clientId, - cacheLocation: useLocalStorage - ? 'localstorage' - : 'memory', - useRefreshTokens: useRefreshTokens - }).then(_init).catch(function (e) { - logger("Error occurred in initializing auth0 object: ", e); - window.location.reload(); - }); - 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); + cacheLocation: useLocalStorage ? "localstorage" : "memory", + useRefreshTokens: useRefreshTokens, + }).then(function (newAuth0Obj) { + auth0 = newAuth0Obj; + auth0 + .getTokenSilently() + .then(function (token) { showAuth0Info(); storeToken(); - }).catch(function (e) { - logger('handleRedirectCallback() error: ', e); - }); - } else if (shouldLogout) { - host = returnAppUrl ? returnAppUrl : host; - logout(); - return; - } else if (!isLoggedIn() && returnAppUrl) { - login(); - } else if (qs['error'] && qs['state']) { - logger("Error in executing callback(): ", qs['error_description']); - showLoginError(qs['error_description'], appUrl); - } 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(v3JWTCookie); - if (!token || isTokenExpired(token)) { - logger('refreshing token... at: ', `${d.getHours()}::${d.getMinutes()}::${d.getSeconds()} `); - try { - let issuerHostname = ""; - if (token) { - let tokenJson = decodeToken(token); - let issuer = tokenJson.iss; - issuerHostname = extractHostname(issuer); + logger("refreshing token for new domain..", domain); + }) + .catch(function (e) { + logger("Error in refreshing token: ", e); + if ( + e.error && + (e.error == "login_required" || e.error == "timeout") + ) { + clearInterval(callRefreshTokenFun); + clearAllCookies(); } - if (domain !== issuerHostname) { - domain = issuerHostname; - logger("reintialize auth0 for new domain..", domain); - createAuth0Client({ - domain: domain, - client_id: clientId, - cacheLocation: useLocalStorage - ? 'localstorage' - : 'memory', - useRefreshTokens: useRefreshTokens - }).then(function (newAuth0Obj) { - auth0 = newAuth0Obj; - auth0.getTokenSilently().then(function (token) { - showAuth0Info(); - storeToken(); - logger("refreshing token for new domain..", domain); - }).catch(function (e) { - logger("Error in refreshing token: ", e) - if (e.error && ((e.error == "login_required") || (e.error == "timeout"))) { - clearInterval(callRefreshTokenFun); - clearAllCookies(); - } - } - ); - }); - } else { - 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); - clearAllCookies(); - } - } - ); - } - } catch (e) { - logger("Error in refresh token function ", e.message) - } - - } - }; - - 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, - returnUrl: returnAppUrl, - mode: mode + }); + }); + } else { + auth0 + .getTokenSilently() + .then(function (token) { + showAuth0Info(); + storeToken(); }) - .then(function () { - auth0.isAuthenticated().then(function (isAuthenticated) { - isAuthenticated = isAuthenticated; - if (isAuthenticated) { - showAuth0Info(); - storeToken(); - postLogin(); - } - }); + .catch(function (e) { + logger("Error in refreshing token: ", e); + if ( + e.error && + (e.error == "login_required" || e.error == "timeout") + ) { + clearInterval(callRefreshTokenFun); + clearAllCookies(); + } }); - }; - - const logout = function () { - clearAllCookies(); - auth0.logout({ - returnTo: host + } + } catch (e) { + logger("Error in refresh token function ", e.message); + } + } + }; + + 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, + returnUrl: returnAppUrl, + mode: mode, + }) + .then(function () { + auth0.isAuthenticated().then(function (isAuthenticated) { + isAuthenticated = isAuthenticated; + if (isAuthenticated) { + showAuth0Info(); + storeToken(); + postLogin(); + } }); - }; - - const clearAllCookies = function () { - // TODO - setCookie(tcJWTCookie, "", -1); - setCookie(v3JWTCookie, "", -1); - setCookie(tcSSOCookie, "", -1); - - // to clear any old session - setCookie('auth0Jwt', "", -1); - setCookie('zendeskJwt', "", -1); - setCookie('auth0Refresh', "", -1); - // for scorecard - setCookie('JSESSIONID', "", -1); + }); + }; + + const logout = function () { + clearAllCookies(); + auth0.logout({ + returnTo: host, + }); + }; + + const clearAllCookies = function () { + // TODO + setCookie(tcJWTCookie, "", -1); + setCookie(v3JWTCookie, "", -1); + setCookie(tcSSOCookie, "", -1); + + // to clear any old session + setCookie("auth0Jwt", "", -1); + setCookie("zendeskJwt", "", -1); + setCookie("auth0Refresh", "", -1); + // for scorecard + setCookie("JSESSIONID", "", -1); + }; + + const isLoggedIn = function () { + var token = getCookie(v3JWTCookie); + return token ? !isTokenExpired(token) : false; + }; + + const redirectToApp = function () { + logger("redirect to app", appUrl); + if (appUrl) { + window.location = appUrl; } + }; - const isLoggedIn = function () { - var token = getCookie(v3JWTCookie); - 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); + const postLogin = function () { + if (isLoggedIn() && returnAppUrl) { + auth0.isAuthenticated().then(function (isAuthenticated) { + if (isAuthenticated) { + window.location = returnAppUrl; + } else { + login(); // old session exist case } - 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); - try { - const exT = getCookieExpiry(idToken); - if (exT) { - setDomainCookie(tcJWTCookie, idToken, exT); - setDomainCookie(v3JWTCookie, idToken, exT); - setDomainCookie(tcSSOCookie, tcsso, exT); - } else { - setCookie(tcJWTCookie, idToken, cookieExpireIn); - setCookie(v3JWTCookie, idToken, cookieExpireIn); - setCookie(tcSSOCookie, tcsso, cookieExpireIn); - } - } catch (e) { - logger('Error occured in fecthing token expiry time', e.message); - } - - // session still active, but app calling login - if (!appUrl && returnAppUrl) { - appUrl = returnAppUrl - } - redirectToApp(); + 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); + try { + const exT = getCookieExpiry(idToken); + if (exT) { + setDomainCookie(tcJWTCookie, idToken, exT); + setDomainCookie(v3JWTCookie, idToken, exT); + setDomainCookie(tcSSOCookie, tcsso, exT); } else { - logger("User active ? ", userActive); - host = registerSuccessUrl; - logout(); + setCookie(tcJWTCookie, idToken, cookieExpireIn); + setCookie(v3JWTCookie, idToken, cookieExpireIn); + setCookie(tcSSOCookie, tcsso, cookieExpireIn); } - }).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; + } catch (e) { + logger("Error occured in fecthing token expiry time", e.message); + } + + // session still active, but app calling login + if (!appUrl && returnAppUrl) { + appUrl = returnAppUrl; + } + redirectToApp(); + } else { + logger("User active ? ", userActive); + host = registerSuccessUrl; + logout(); } - const d = new Date(0); // The 0 here is the key, which sets the date to the epoch - d.setUTCSeconds(decoded.exp); - return d; + }) + .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'); - } + function decodeToken(token) { + const parts = token.split("."); - const decoded = urlBase64Decode(parts[1]) + if (parts.length !== 3) { + throw new Error("The token is invalid"); + } - if (!decoded) { - throw new Error('Cannot decode the token'); - } + const decoded = urlBase64Decode(parts[1]); - // covert base64 token in JSON object - let t = JSON.parse(decoded); - return t; + if (!decoded) { + throw new Error("Cannot decode the token"); } - function isTokenExpired(token, offsetSeconds = refreshTokenOffset) { - const d = getTokenExpirationDate(token) + // covert base64 token in JSON object + let t = JSON.parse(decoded); + return t; + } - if (d === null) { - return false; - } + function isTokenExpired(token, offsetSeconds = refreshTokenOffset) { + const d = getTokenExpirationDate(token); - // Token expired? - return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))); + if (d === null) { + return false; } - function urlBase64Decode(str) { - let output = str.replace(/-/g, '+').replace(/_/g, '/') + // Token expired? + return !(d.valueOf() > new Date().valueOf() + offsetSeconds * 1000); + } - switch (output.length % 4) { - case 0: - break; + function urlBase64Decode(str) { + let output = str.replace(/-/g, "+").replace(/_/g, "/"); - 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 - } + switch (output.length % 4) { + case 0: + break; - function setCookie(cname, cvalue, exMins) { - const cdomain = getHostDomain(); + case 2: + output += "=="; + break; - let d = new Date(); - d.setTime(d.getTime() + (exMins * 60 * 1000)); + case 3: + output += "="; + break; - let expires = ";expires=" + d.toUTCString(); - document.cookie = cname + "=" + cvalue + cdomain + expires + ";path=/"; + default: + throw "Illegal base64url string!"; } - - function getCookie(name) { - const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); - return v ? v[2] : undefined; + 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]; } - // 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; + return hostDomain; + } + + function correctOldUrl() { + const pattern = "#!/member"; + const sso_pattern = "/#!/sso-login"; + const logout_pattern = "/#!/logout?"; + + const url = window.location.href; + + const result = url.match(/^(.+)(\#!\/(.+)\?)(.+)/); + + if (result) { + try { + const newUrl = result[1] + "?" + result[4]; + logger("new url: ", newUrl); + window.location.href = newUrl; + } catch (e) { + logger("Creating new url error: ", e.message); + } + } + // Need to cleanup below code, should never execute + if (window.location.href.indexOf(pattern) > -1) { + window.location.href = window.location.href.replace(pattern, ""); } - function correctOldUrl() { - const pattern = '#!/member'; - const sso_pattern = '/#!/sso-login'; - const logout_pattern = '/#!/logout?'; - - const url = window.location.href; - - const result = url.match(/^(.+)(\#!\/(.+)\?)(.+)/); - - if (result) { - try { - const newUrl = result[1] + "?" + result[4]; - logger("new url: ", newUrl); - window.location.href = newUrl; - } catch (e) { - logger("Creating new url error: ", e.message); - } - } - // Need to cleanup below code, should never execute - 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&'); - } + if (window.location.href.indexOf(sso_pattern) > -1) { + window.location.href = window.location.href.replace(sso_pattern, ""); } - function logger(label, message) { - if (loggerMode === "dev") { - console.log(label, message); - } + if (window.location.href.indexOf(logout_pattern) > -1) { + window.location.href = window.location.href.replace( + logout_pattern, + "/?logout=true&" + ); } + } - /** - * 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(); - } - } - if (e.data && e.data.type && e.data.type === "REFRESH_TOKEN") { - const token = getCookie(v3JWTCookie); - const failed = { - type: "FAILURE" - }; - const success = { - type: "SUCCESS" - }; - - const informIt = function (payload) { - e.source.postMessage(payload, e.origin); - } - try { - const storeRefreshedToken = function (aObj) { - aObj.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 refreshed token...', true); - try { - const exT = getCookieExpiry(idToken); - if (exT) { - setDomainCookie(tcJWTCookie, idToken, exT); - setDomainCookie(v3JWTCookie, idToken, exT); - setDomainCookie(tcSSOCookie, tcsso, exT); - } else { - setCookie(tcJWTCookie, idToken, cookieExpireIn); - setCookie(v3JWTCookie, idToken, cookieExpireIn); - setCookie(tcSSOCookie, tcsso, cookieExpireIn); - } - informIt(success); - } catch (e) { - logger('Error occured in fecthing token expiry time', e.message); - informIt(failed); - } - } else { - logger("Refeshed token - user active ? ", userActive); - informIt(failed); - } - }).catch(function (err) { - logger("Refeshed token - error in fetching token from auth0: ", err); - informIt(failed); - }); - }; - - const getToken = function (aObj) { - aObj.getTokenSilently({ timeoutInSeconds: 60 }).then(function (token) { - storeRefreshedToken(aObj); - }).catch(function (err) { - logger("receiveMessage: Error in refreshing token through iframe:", err) - informIt(failed); - }); - - }; - - // main execution start here - if (token && !isTokenExpired(token)) { - informIt(success); - } else if (!token) { - const auth0Session = getCookie('auth0.is.authenticated'); - logger('auth0 session available ?', auth0Session); - if (auth0Session) { - logger('auth session true', 1); - if (!auth0) { - createAuth0Client({ - domain: domain, - client_id: clientId, - cacheLocation: useLocalStorage - ? 'localstorage' - : 'memory', - useRefreshTokens: useRefreshTokens - }).then(function (newAuth0Obj) { - getToken(newAuth0Obj); - }).catch(function (e) { - logger("Error occurred in re-initializing auth0 object: ", e); - informIt(failed); - }); - } else { - getToken(auth0); - } - } else { - informIt(failed); - } - - } else { - if (auth0) { - getToken(auth0); - } else { - informIt(failed); - } + 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(); + } + } + if (e.data && e.data.type && e.data.type === "REFRESH_TOKEN") { + const token = getCookie(v3JWTCookie); + const failed = { + type: "FAILURE", + }; + const success = { + type: "SUCCESS", + }; + + const informIt = function (payload) { + e.source.postMessage(payload, e.origin); + }; + try { + const storeRefreshedToken = function (aObj) { + aObj + .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 refreshed token...", true); + try { + const exT = getCookieExpiry(idToken); + if (exT) { + setDomainCookie(tcJWTCookie, idToken, exT); + setDomainCookie(v3JWTCookie, idToken, exT); + setDomainCookie(tcSSOCookie, tcsso, exT); + } else { + setCookie(tcJWTCookie, idToken, cookieExpireIn); + setCookie(v3JWTCookie, idToken, cookieExpireIn); + setCookie(tcSSOCookie, tcsso, cookieExpireIn); + } + informIt(success); + } catch (e) { + logger( + "Error occured in fecthing token expiry time", + e.message + ); + informIt(failed); } - } catch (e) { - logger("error occured in iframe handler:", e.message); + } else { + logger("Refeshed token - user active ? ", userActive); informIt(failed); + } + }) + .catch(function (err) { + logger( + "Refeshed token - error in fetching token from auth0: ", + err + ); + informIt(failed); + }); + }; + + const getToken = function (aObj) { + aObj + .getTokenSilently({ timeoutInSeconds: 60 }) + .then(function (token) { + storeRefreshedToken(aObj); + }) + .catch(function (err) { + logger( + "receiveMessage: Error in refreshing token through iframe:", + err + ); + informIt(failed); + }); + }; + + // main execution start here + if (token && !isTokenExpired(token)) { + informIt(success); + } else if (!token) { + const auth0Session = getCookie("auth0.is.authenticated"); + logger("auth0 session available ?", auth0Session); + if (auth0Session) { + logger("auth session true", 1); + if (!auth0) { + createAuth0Client({ + domain: domain, + client_id: clientId, + cacheLocation: useLocalStorage ? "localstorage" : "memory", + useRefreshTokens: useRefreshTokens, + }) + .then(function (newAuth0Obj) { + getToken(newAuth0Obj); + }) + .catch(function (e) { + logger("Error occurred in re-initializing auth0 object: ", e); + informIt(failed); + }); + } else { + getToken(auth0); } + } else { + informIt(failed); + } } else { - // do nothing + if (auth0) { + getToken(auth0); + } else { + informIt(failed); + } } + } catch (e) { + logger("error occured in iframe handler:", e.message); + informIt(failed); + } + } else { + // do nothing } - - function changeWindowMessage() { - - if ((!returnAppUrl && !appUrl) || ((returnAppUrl == 'undefined') && (appUrl == 'undefined'))) { - try { - var hdomain = location.hostname.split('.').reverse()[1]; - var linkurl = "http://" + window.location.host + "/?logout=true&retUrl=http://" + window.location.host; - if (hdomain) { - linkurl = "https://" + window.location.host + "/?logout=true&retUrl=https://" + hdomain + ".com"; - } - document.getElementById("page-title-heading").innerHTML = "Alert"; - document.getElementById("loading_message_p").innerHTML = "Login/Logout action is not called. Please check return url (retUrl) value in query parameters or click here"; - } catch (err) { - logger("Error in changing loading message: ", err.message) - } + } + + function changeWindowMessage() { + if ( + (!returnAppUrl && !appUrl) || + (returnAppUrl == "undefined" && appUrl == "undefined") + ) { + try { + var hdomain = location.hostname.split(".").reverse()[1]; + var linkurl = + "http://" + + window.location.host + + "/?logout=true&retUrl=http://" + + window.location.host; + if (hdomain) { + linkurl = + "https://" + + window.location.host + + "/?logout=true&retUrl=https://" + + hdomain + + ".com"; } + document.getElementById("page-title-heading").innerHTML = "Alert"; + document.getElementById("loading_message_p").innerHTML = + "Login/Logout action is not called. Please check return url (retUrl) value in query parameters or click here"; + } catch (err) { + logger("Error in changing loading message: ", err.message); + } } + } - function extractHostname(url) { - var hostname; - //find & remove protocol (http, ftp, etc.) and get hostname + function extractHostname(url) { + var hostname; + //find & remove protocol (http, ftp, etc.) and get hostname - if (url.indexOf("//") > -1) { - hostname = url.split('/')[2]; - } - else { - hostname = url.split('/')[0]; - } - //find & remove port number - hostname = hostname.split(':')[0]; - //find & remove "?" - hostname = hostname.split('?')[0]; - - return hostname; + if (url.indexOf("//") > -1) { + hostname = url.split("/")[2]; + } else { + hostname = url.split("/")[0]; } - - function showLoginError(message, linkUrl) { - try { - document.getElementById("page-title-heading").innerHTML = "Alert"; - document.getElementById("loading_message_p").innerHTML = message + " click here"; - } catch (err) { - logger("Error in changing loading message: ", err.message) - } + //find & remove port number + hostname = hostname.split(":")[0]; + //find & remove "?" + hostname = hostname.split("?")[0]; + + return hostname; + } + + function showLoginError(message, linkUrl) { + try { + document.getElementById("page-title-heading").innerHTML = "Alert"; + document.getElementById("loading_message_p").innerHTML = + message + " click here"; + } catch (err) { + logger("Error in changing loading message: ", err.message); } + } - function getCookieExpiry(token) { - const d = getTokenExpirationDate(token) - if (d === null) { - return false; - } - const diff = d.valueOf() - (new Date().valueOf()); //in millseconds - if (diff > 0) { - return diff; // in milliseconds - } - return false; + function getCookieExpiry(token) { + const d = getTokenExpirationDate(token); + if (d === null) { + return false; } + const diff = d.valueOf() - new Date().valueOf(); //in millseconds + if (diff > 0) { + return diff; // in milliseconds + } + return false; + } - function setDomainCookie(cname, cvalue, exMilliSeconds) { - const cdomain = getHostDomain(); - - let d = new Date(); - d.setTime(d.getTime() + exMilliSeconds); + function setDomainCookie(cname, cvalue, exMilliSeconds) { + const cdomain = getHostDomain(); - let expires = ";expires=" + d.toUTCString(); - document.cookie = cname + "=" + cvalue + cdomain + expires + ";path=/"; - } + let d = new Date(); + d.setTime(d.getTime() + exMilliSeconds); + let expires = ";expires=" + d.toUTCString(); + document.cookie = cname + "=" + cvalue + cdomain + expires + ";path=/"; + } - // execute - init(); + // execute + init(); }; diff --git a/src/components/Accordion/styles.module.scss b/src/components/Accordion/styles.module.scss index ffa9a997..4ff714ef 100644 --- a/src/components/Accordion/styles.module.scss +++ b/src/components/Accordion/styles.module.scss @@ -27,23 +27,23 @@ .down-arrow { display: inline-block; - content: ''; + content: ""; height: 13px; width: 13px; margin-right: 16px; - border-bottom: 3px solid #137D60; - border-right: 3px solid #137D60; + border-bottom: 3px solid #137d60; + border-right: 3px solid #137d60; transform: rotate(45deg); } .right-arrow { display: inline-block; - content: ''; + content: ""; height: 13px; width: 13px; margin-right: 16px; - border-bottom: 3px solid #137D60; - border-right: 3px solid #137D60; + border-bottom: 3px solid #137d60; + border-right: 3px solid #137d60; transform: rotate(-45deg); } @@ -67,4 +67,4 @@ .panel { padding-left: 28px; font-size: 14px; -} \ No newline at end of file +} diff --git a/src/components/ActionsMenu/styles.module.scss b/src/components/ActionsMenu/styles.module.scss index 18d15c78..d1c6fdb0 100644 --- a/src/components/ActionsMenu/styles.module.scss +++ b/src/components/ActionsMenu/styles.module.scss @@ -51,7 +51,7 @@ } .danger { - color: #EF476F; + color: #ef476f; } .disabled { diff --git a/src/components/AsyncSelect/index.jsx b/src/components/AsyncSelect/index.jsx index 269ad803..bb3cd3c2 100644 --- a/src/components/AsyncSelect/index.jsx +++ b/src/components/AsyncSelect/index.jsx @@ -87,8 +87,8 @@ const AsyncSelect = (props) => { defaultOptions={props.defaultOptions} /> - ) -} + ); +}; AsyncSelect.propTypes = { value: PT.string, @@ -106,6 +106,6 @@ AsyncSelect.propTypes = { loadOptions: PT.func, defaultOptions: PT.bool || PT.array, disabled: PT.bool, -} +}; -export default AsyncSelect; \ No newline at end of file +export default AsyncSelect; diff --git a/src/components/Checkbox/styles.module.scss b/src/components/Checkbox/styles.module.scss index 69217b04..dfa800c4 100644 --- a/src/components/Checkbox/styles.module.scss +++ b/src/components/Checkbox/styles.module.scss @@ -36,7 +36,7 @@ height: 20px; width: 20px; background-color: #fff; - border: 1px solid #AAA; + border: 1px solid #aaa; border-radius: 3px; &.float-left { left: 0; @@ -53,7 +53,7 @@ /* When the checkbox is checked, add a green background */ .container input:checked ~ .checkmark { - background-color: #0AB88A; + background-color: #0ab88a; box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.29); } @@ -86,4 +86,4 @@ -ms-transform: rotate(45deg); transform: rotate(45deg); box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.35); -} \ No newline at end of file +} diff --git a/src/components/CustomerScroll/styles.module.scss b/src/components/CustomerScroll/styles.module.scss index 0aa7490e..235f4b01 100644 --- a/src/components/CustomerScroll/styles.module.scss +++ b/src/components/CustomerScroll/styles.module.scss @@ -7,13 +7,13 @@ color: #7f7f7f; text-align: center; text-transform: uppercase; - margin-bottom: 30px; } .customer-logos { background-image: url("../../assets/images/customer-logos.svg"); background-size: contain; background-repeat: no-repeat; + background-position: center; height: 56px; width: 100%; } @@ -21,6 +21,5 @@ @media only screen and (max-height: 859px) { .title { font-size: 16px; - margin-bottom: 15px; } } diff --git a/src/components/InformationTooltip/styles.module.scss b/src/components/InformationTooltip/styles.module.scss index bf1084c1..9a22b5cb 100644 --- a/src/components/InformationTooltip/styles.module.scss +++ b/src/components/InformationTooltip/styles.module.scss @@ -31,4 +31,4 @@ color: #fff; background-color: #2a2a2a; padding: 10px; -} \ No newline at end of file +} diff --git a/src/components/MarkdownEditor/styles.module.scss b/src/components/MarkdownEditor/styles.module.scss index 13c0aa00..79bd0284 100644 --- a/src/components/MarkdownEditor/styles.module.scss +++ b/src/components/MarkdownEditor/styles.module.scss @@ -2,22 +2,28 @@ // we use viewer mode to show editor when it's "disabled" .editor-viewer { - > div:first-child { - border: 1px solid #ddd; - border-radius: 4px; - overflow: hidden; - box-sizing: border-box; - padding: 16px 25px 0px 25px; - height: 280px; - overflow-y: auto; - background: #fafafb; - } + > div:first-child { + border: 1px solid #ddd; + border-radius: 4px; + overflow: hidden; + box-sizing: border-box; + padding: 16px 25px 0px 25px; + height: 280px; + overflow-y: auto; + background: #fafafb; + } } .editor-container { :global { // reset style for heading list in headings selection popup .tui-popup-body { - h1,h2,h3,h4,h4,h5,h6 { + h1, + h2, + h3, + h4, + h4, + h5, + h6 { font-weight: revert; line-height: revert; } @@ -44,15 +50,16 @@ } // hide uplodd file - .tui-editor-popup{ - box-shadow: 0px 0px 15px 5px rgba(0,0,0,0.26); + .tui-editor-popup { + box-shadow: 0px 0px 15px 5px rgba(0, 0, 0, 0.26); } - .te-popup-add-image .te-tab button, .te-popup-add-image .te-file-type{ + .te-popup-add-image .te-tab button, + .te-popup-add-image .te-file-type { display: none !important; } - .te-popup-add-image .te-url-type{ + .te-popup-add-image .te-url-type { display: block !important; } } @@ -72,4 +79,4 @@ border: 1px solid #ffd5d1; cursor: auto; outline: none; -} \ No newline at end of file +} diff --git a/src/components/MonthPicker/styles.module.scss b/src/components/MonthPicker/styles.module.scss index ba93e757..198a4983 100644 --- a/src/components/MonthPicker/styles.module.scss +++ b/src/components/MonthPicker/styles.module.scss @@ -33,7 +33,7 @@ } .react-datepicker__month--selected { border-radius: 100%; - background-color: #0AB88A; + background-color: #0ab88a; } .react-datepicker__navigation { @@ -42,11 +42,12 @@ height: 12px; width: 12px; } - .react-datepicker__navigation--next, .react-datepicker__navigation--previous { + .react-datepicker__navigation--next, + .react-datepicker__navigation--previous { border-right-color: transparent; border-bottom-color: transparent; - border-left-color: #137D60; - border-top-color: #137D60; + border-left-color: #137d60; + border-top-color: #137d60; } .react-datepicker__navigation--next { right: 24px; @@ -57,4 +58,4 @@ transform: rotate(-45deg); } } -} \ No newline at end of file +} diff --git a/src/components/NumberInput/styles.module.scss b/src/components/NumberInput/styles.module.scss index e5941c56..51ad66ec 100644 --- a/src/components/NumberInput/styles.module.scss +++ b/src/components/NumberInput/styles.module.scss @@ -24,7 +24,7 @@ .right-button { @include font-roboto; font-size: 22px; - color: #137D60; + color: #137d60; outline: none; border: none; background: none; @@ -40,4 +40,4 @@ .right-button { right: 0; -} \ No newline at end of file +} diff --git a/src/components/Radio/styles.module.scss b/src/components/Radio/styles.module.scss index 4e4001e0..0fc82b29 100644 --- a/src/components/Radio/styles.module.scss +++ b/src/components/Radio/styles.module.scss @@ -30,11 +30,11 @@ width: 24px; background-color: #fff; border-radius: 50%; - border: 1px solid #AAA; + border: 1px solid #aaa; } .radio-input:checked ~ .custom { - background-color: #0AB88A; + background-color: #0ab88a; box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.29); } @@ -56,4 +56,4 @@ border-radius: 50%; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.35); background: white; -} \ No newline at end of file +} diff --git a/src/components/ReactSelect/index.jsx b/src/components/ReactSelect/index.jsx index 29cf6298..81466160 100644 --- a/src/components/ReactSelect/index.jsx +++ b/src/components/ReactSelect/index.jsx @@ -14,7 +14,7 @@ const ReactSelect = (props) => { control: (provided, state) => ({ ...provided, minHeight: "40px", - border:state.isDisabled ? "1px solid #ddd" : "1px solid #aaaaab", + border: state.isDisabled ? "1px solid #ddd" : "1px solid #aaaaab", borderColor: state.isFocused ? "#55a5ff" : "#aaaaab", boxShadow: state.isFocused ? "0 0 2px 1px #cee6ff" : provided.boxShadow, backgroundColor: state.isDisabled ? "#fafafb" : provided.backgroundColor, diff --git a/src/components/TuiEditor/index.jsx b/src/components/TuiEditor/index.jsx index b8315811..1138a70e 100644 --- a/src/components/TuiEditor/index.jsx +++ b/src/components/TuiEditor/index.jsx @@ -33,10 +33,16 @@ class TuiEditor extends React.Component { }); // always add `https` to the links if link was added without `http` or `https` - this.editorInst.on('convertorAfterHtmlToMarkdownConverted', (inputMarkdown) => { - const outputMarkdown = inputMarkdown.replace(/\[([^\]]*)\]\((?!https?)([^\)]+)\)/g, "[$1](https://$2)") - return outputMarkdown; - }); + this.editorInst.on( + "convertorAfterHtmlToMarkdownConverted", + (inputMarkdown) => { + const outputMarkdown = inputMarkdown.replace( + /\[([^\]]*)\]\((?!https?)([^\)]+)\)/g, + "[$1](https://$2)" + ); + return outputMarkdown; + } + ); } componentDidMount() { @@ -56,7 +62,7 @@ class TuiEditor extends React.Component { this.editorInst.off(eventName); }); - this.editorInst.off('convertorAfterHtmlToMarkdownConverted'); + this.editorInst.off("convertorAfterHtmlToMarkdownConverted"); } shouldComponentUpdate(nextProps) { diff --git a/src/components/TuiEditorViewer/index.jsx b/src/components/TuiEditorViewer/index.jsx index e000d152..6bd14dd1 100644 --- a/src/components/TuiEditorViewer/index.jsx +++ b/src/components/TuiEditorViewer/index.jsx @@ -43,11 +43,11 @@ class TuiViewer extends React.Component { componentWillUnmount() { Object.keys(this.props) - .filter((key) => /^on[A-Z][a-zA-Z]+/.test(key)) - .forEach((key) => { - const eventName = key[2].toLowerCase() + key.slice(3); - this.editorInst.off(eventName); - }); + .filter((key) => /^on[A-Z][a-zA-Z]+/.test(key)) + .forEach((key) => { + const eventName = key[2].toLowerCase() + key.slice(3); + this.editorInst.off(eventName); + }); } shouldComponentUpdate(nextProps) { diff --git a/src/components/User/index.jsx b/src/components/User/index.jsx index 38aff657..a88f8d44 100644 --- a/src/components/User/index.jsx +++ b/src/components/User/index.jsx @@ -12,8 +12,14 @@ import { TOPCODER_COMMUNITY_WEBSITE_URL } from "../../../config"; import IconDirectArrow from "../../assets/images/icon-direct-arrow.svg"; import { Link } from "@reach/router"; -const User = ({ showArrow, user, hideFullName = false, handleLinkTo, noLink }) => { - let link = {user.handle} +const User = ({ + showArrow, + user, + hideFullName = false, + handleLinkTo, + noLink, +}) => { + let link = {user.handle}; // if we set flag for no-link, then don't add link if (!noLink) { @@ -29,7 +35,7 @@ const User = ({ showArrow, user, hideFullName = false, handleLinkTo, noLink }) = > {user.handle} - ) + ); } return ( diff --git a/src/constants/index.js b/src/constants/index.js index 9c993ad3..b3d14fb4 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -402,3 +402,9 @@ export const MIN_DURATION = 4; * Maximum allowed numbers of selecter skills for search. */ export const MAX_SELECTED_SKILLS = 3; + +/** + * Milliseconds for each stage of role search + * There are 3 stages, so total search takes 3x this number + */ +export const SEARCH_STAGE_TIME = 1500; diff --git a/src/routes/CreateNewTeam/components/AddedRolesAccordion/styles.module.scss b/src/routes/CreateNewTeam/components/AddedRolesAccordion/styles.module.scss index 67f7c05e..cd4f68e9 100644 --- a/src/routes/CreateNewTeam/components/AddedRolesAccordion/styles.module.scss +++ b/src/routes/CreateNewTeam/components/AddedRolesAccordion/styles.module.scss @@ -3,7 +3,7 @@ .accordion { border-radius: 8px; box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.02); - background-color: #FFFFFF; + background-color: #ffffff; width: 250px; } @@ -27,8 +27,8 @@ display: inline-block; height: 10px; width: 10px; - border-bottom: 3px solid #2A2A2A; - border-right: 3px solid #2A2A2A; + border-bottom: 3px solid #2a2a2a; + border-right: 3px solid #2a2a2a; margin-bottom: 4px; &.down { transform: rotate(45deg); @@ -57,7 +57,7 @@ .role-name { position: relative; width: 100%; - background-color: #F4F4F4; + background-color: #f4f4f4; border-radius: 6px; padding: 10px; padding-right: 30px; @@ -70,7 +70,7 @@ margin-top: 5px; } - >button { + > button { outline: none; border: none; background: none; @@ -84,4 +84,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/routes/CreateNewTeam/components/ConfirmationModal/styles.module.scss b/src/routes/CreateNewTeam/components/ConfirmationModal/styles.module.scss index 65252c41..26d840bd 100644 --- a/src/routes/CreateNewTeam/components/ConfirmationModal/styles.module.scss +++ b/src/routes/CreateNewTeam/components/ConfirmationModal/styles.module.scss @@ -30,4 +30,4 @@ flex-direction: row; align-items: center; justify-content: center; -} \ No newline at end of file +} diff --git a/src/routes/CreateNewTeam/components/EditRoleForm/styles.module.scss b/src/routes/CreateNewTeam/components/EditRoleForm/styles.module.scss index d6b20d29..dd9078e8 100644 --- a/src/routes/CreateNewTeam/components/EditRoleForm/styles.module.scss +++ b/src/routes/CreateNewTeam/components/EditRoleForm/styles.module.scss @@ -7,7 +7,7 @@ background: none; font-size: 12px; font-weight: 500; - color: #137D60; + color: #137d60; padding: 1px 6px 0 6px; &.toggle-description { diff --git a/src/routes/CreateNewTeam/components/InputContainer/styles.module.scss b/src/routes/CreateNewTeam/components/InputContainer/styles.module.scss index 99cec905..c1331790 100644 --- a/src/routes/CreateNewTeam/components/InputContainer/styles.module.scss +++ b/src/routes/CreateNewTeam/components/InputContainer/styles.module.scss @@ -3,7 +3,7 @@ flex-direction: row; justify-content: center; align-items: flex-start; - margin: 42px 35px; + margin: 42px 35px 30px; .right-side { display: flex; flex-direction: column; @@ -11,4 +11,4 @@ margin-top: 16px; } } -} \ No newline at end of file +} diff --git a/src/routes/CreateNewTeam/components/LandingBox/index.jsx b/src/routes/CreateNewTeam/components/LandingBox/index.jsx index 8cb8cffe..802a1d20 100644 --- a/src/routes/CreateNewTeam/components/LandingBox/index.jsx +++ b/src/routes/CreateNewTeam/components/LandingBox/index.jsx @@ -24,19 +24,19 @@ function LandingBox({ backgroundImage, }} > - {showGap &&
OR
} + {showGap &&
OR
}
-
{icon}
-

{title}

-

{description}

- +
{icon}
+

{title}

+

{description}

+
); diff --git a/src/routes/CreateNewTeam/components/LandingBox/styles.module.scss b/src/routes/CreateNewTeam/components/LandingBox/styles.module.scss index 2ebda7a4..282ac2f9 100644 --- a/src/routes/CreateNewTeam/components/LandingBox/styles.module.scss +++ b/src/routes/CreateNewTeam/components/LandingBox/styles.module.scss @@ -39,7 +39,7 @@ } .description { - flex:1; + flex: 1; white-space: pre-line; font-size: 16px; line-height: 26px; @@ -47,18 +47,17 @@ text-align: center; } - .gap { font-family: Roboto; position: absolute; - background-color: #D4D4D4; - border: 3px solid #F4F5F6; + background-color: #d4d4d4; + border: 3px solid #f4f5f6; border-radius: 100%; width: 42px; height: 42px; line-height: 42px; box-sizing: content-box; - color: #7F7F7F; + color: #7f7f7f; font-size: 16px; text-align: center; right: -32.5px; diff --git a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx index 7606b4b8..96b2ad3c 100644 --- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/index.jsx @@ -42,7 +42,7 @@ function NoMatchingProfilesResultCard({ role }) {
-

Additional Evaluation Needed

+

Dang. No matching talent (yet)

@@ -50,24 +50,24 @@ function NoMatchingProfilesResultCard({ role }) {

{role.jobTitle && role.jobTitle.length ? role.jobTitle - : "Custom Role"} + : "What happens next"}

- We did not get a perfect match to your requirements on the first pass, - but we are confident they are out there. We'd like to dig a little - deeper into our community to find someone who can fit your needs. This - may take up to two weeks. Please continue to submit your request, and - a Topcoder representative will reach out to you soon with next steps. + We did not find a perfect match to your requirements, but we'd like to + dig a little deeper into our community. We’ll start right away, and + this may take up to two weeks. You can modify your criteria, or + continue this search. If you choose to continue, we will reach out + soon with next steps.

{role.rates && role.name ? (
-

{role.name} Rate

+

Estimate for this role

{formatMoney(role.rates[0].global)}

/Week

) : (
-

Custom Rate

+

Estimate for this role

$1,200

/Week

@@ -83,7 +83,7 @@ function NoMatchingProfilesResultCard({ role }) { disabled={!role.roleSearchRequestId || alreadyAdded} type="primary" > - {alreadyAdded ? "Added" : "Add Custom Role"} + {alreadyAdded ? "Added" : "Continue this search"}
diff --git a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss index d60db1e5..8c4765b7 100644 --- a/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss +++ b/src/routes/CreateNewTeam/components/NoMatchingProfilesResultCard/styles.module.scss @@ -15,7 +15,7 @@ padding: 30px 0 60px 0; margin-bottom: 14px; color: #fff; - background-image: linear-gradient(225deg, #555555 0%, #2A2A2A 100%); + background-image: linear-gradient(225deg, #555555 0%, #2a2a2a 100%); position: relative; text-align: center; border-radius: 8px 8px 0 0; @@ -51,7 +51,7 @@ // position text over bottom of header image position: relative; z-index: 100; - } + } p.info-txt { @include font-roboto; font-size: 14px; @@ -61,12 +61,11 @@ } .niche-rate-box { margin-top: 32px; - background-color: #FBFBFB; + background-color: #fbfbfb; padding: 30px; - border: 1px solid #F4F4F4; + border: 1px solid #f4f4f4; border-radius: 6px; width: 196px; - height: 164px; p:first-child { @include font-barlow; margin-top: 4px; diff --git a/src/routes/CreateNewTeam/components/Progress/index.jsx b/src/routes/CreateNewTeam/components/Progress/index.jsx index 228a7307..fb9114ab 100644 --- a/src/routes/CreateNewTeam/components/Progress/index.jsx +++ b/src/routes/CreateNewTeam/components/Progress/index.jsx @@ -8,56 +8,53 @@ import Button from "components/Button"; import React from "react"; import cn from "classnames"; import PT from "prop-types"; +import Spinner from "components/CenteredSpinner"; import ProgressBar from "../ProgressBar"; import "./styles.module.scss"; -import IconMultipleActionsCheck from "../../../../assets/images/icon-multiple-actions-check-2.svg"; -import IconListQuill from "../../../../assets/images/icon-list-quill.svg"; -import IconOfficeFileText from "../../../../assets/images/icon-office-file-text.svg"; - function Progress({ extraStyleName, isDisabled, + isSearching, onClick, buttonLabel, stages, percentage, }) { - let backgroundIcon; - if (extraStyleName === "input-skills") { - backgroundIcon = ; - } else if (extraStyleName === "input-job-description") { - backgroundIcon = ; - } else { - backgroundIcon = ; - } - return (
    - {stages.map((stage) => ( + {stages.map((stage, idx) => (
  • {stage.name}
  • ))}
{buttonLabel !== undefined ? ( - <> - - {backgroundIcon} - + ) : null}
); @@ -66,6 +63,7 @@ function Progress({ Progress.propTypes = { extraStyleName: PT.string, isDisabled: PT.bool, + isSearching: PT.bool, onClick: PT.func, buttonLabel: PT.string, currentStageIdx: PT.number, diff --git a/src/routes/CreateNewTeam/components/Progress/styles.module.scss b/src/routes/CreateNewTeam/components/Progress/styles.module.scss index 65d888bd..0d956dcd 100644 --- a/src/routes/CreateNewTeam/components/Progress/styles.module.scss +++ b/src/routes/CreateNewTeam/components/Progress/styles.module.scss @@ -9,6 +9,13 @@ color: #fff; button { border: none; + &.searching { + pointer-events: none; + } + .spinner { + margin-top: 3px; + margin-right: 5px; + } } } @@ -39,51 +46,45 @@ font-size: 14px; line-height: 16px; font-weight: 400; - color: rgba(255, 255, 255, 0.6); + color: rgba(#fff, 0.6); &:before { - content: ""; - color: #fff; - border: 1px solid #ffffff; + content: attr(data-index); + font-size: 10px; + line-height: 14px; + font-weight: 600; + padding-left: 4px; + color: rgba(#fff, 0.6); + border: 1px solid rgba(#fff, 0.6); border-radius: 100%; width: 16px; height: 16px; margin-right: 10px; display: block; float: left; - visibility: hidden; } &.active { font-weight: 500; color: #fff; &:before { - visibility: visible; + color: #555555; background-color: #fff; } } &.done { - font-weight: 400; + font-weight: 500; &:before { content: "✓"; + padding-left: 3px; font-size: 9px; - line-height: 14px; - padding-left: 4px; - visibility: visible; + font-weight: 900; + background-color: #0ab88a; } } } -.transparent-icon { - position: absolute; - right: -50px; - top: 85px; - opacity: 10%; - width: 144px; - height: 144px; -} - .final-step { height: 230px; } diff --git a/src/routes/CreateNewTeam/components/ProgressBar/index.jsx b/src/routes/CreateNewTeam/components/ProgressBar/index.jsx index 4d84b403..4a96cdb0 100644 --- a/src/routes/CreateNewTeam/components/ProgressBar/index.jsx +++ b/src/routes/CreateNewTeam/components/ProgressBar/index.jsx @@ -17,7 +17,7 @@ function ProgressBar({ percentDone }) { return (
-

Progress

+

Completeness

{percentDone}%
diff --git a/src/routes/CreateNewTeam/components/ResultCard/index.jsx b/src/routes/CreateNewTeam/components/ResultCard/index.jsx index 79f9a91d..50ebef44 100644 --- a/src/routes/CreateNewTeam/components/ResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/ResultCard/index.jsx @@ -59,10 +59,11 @@ function ResultCard({
-

We have matching profiles

+

You've Got Matches

- We have qualified candidates who match {formatPercent(skillsMatch)} - {skillsMatch < 1 ? " or more " : " "} of your job requirements. + You’re one step closer to hiring great talent. Please provide details + about how many people you need, how long you need them, and when you’d + like to start.

@@ -256,7 +257,8 @@ function ResultCard({ {!showRates && (
-
+
+
Matched Skills
{matchedSkills.length ? _.map(matchedSkills, (s) => ( @@ -282,7 +284,7 @@ function ResultCard({ ) : null}
-
+

{numberOfMembersAvailable}+

Members matched

diff --git a/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss b/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss index 150884c0..d89c827a 100644 --- a/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss +++ b/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss @@ -37,6 +37,7 @@ p { font-size: 14px; + width: 80%; } } @@ -71,6 +72,10 @@ display: flex; text-align: center; align-items: center; + .matching-info-left, + .matching-info-right { + flex: 1; + } > div.vertical-line { display: block; height: 170px; @@ -339,3 +344,13 @@ justify-content: space-between; height: 100%; } + +.skills-head { + @include font-barlow; + font-weight: 600; + font-size: 16px; + min-width: 124px; + text-transform: uppercase; + text-align: center; + margin-bottom: 8px; +} diff --git a/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss b/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss index 8aa70eac..96a994ca 100644 --- a/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss +++ b/src/routes/CreateNewTeam/components/RoleDetailsModal/styles.module.scss @@ -15,7 +15,7 @@ .markdown-container { :global { - // resets styles in markdown-viewer + // resets styles in markdown-viewer .tui-editor-contents { @include font-roboto; color: #2a2a2a; @@ -23,13 +23,13 @@ line-height: 26px; ul { list-style: initial; - >li { + > li { margin-bottom: 10px; &::before { background: none; } } - } + } } } } diff --git a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx index 4263abcc..ba9c16dc 100644 --- a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx @@ -2,6 +2,7 @@ import { Router, navigate } from "@reach/router"; import _ from "lodash"; import React, { useCallback, useState, useEffect, useMemo } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { SEARCH_STAGE_TIME } from "constants/"; import { useData } from "hooks/useData"; import { getSkills } from "services/skills"; import { searchRoles } from "services/teams"; @@ -16,7 +17,7 @@ import InputContainer from "../InputContainer"; import SearchContainer from "../SearchContainer"; import SubmitContainer from "../SubmitContainer"; -const SEARCHINGTIME = 1600; +const SEARCHINGTIME = SEARCH_STAGE_TIME * 3 + 100; function SearchAndSubmit(props) { const { stages, setStages, searchObject, onClick, page } = props; @@ -27,38 +28,24 @@ function SearchAndSubmit(props) { const { matchingRole } = useSelector((state) => state.searchedRoles); const matchedSkills = useMemo(() => { - if ( - skills && - matchingRole && - matchingRole.listOfSkills && - searchObject && - searchObject.skills && - searchObject.skills.length - ) { - return _.map(searchObject.skills, (s) => - _.find(skills, (skill) => skill.id === s) + if (skills && matchingRole && matchingRole.matchedSkills) { + return _.map(matchingRole.matchedSkills, (s) => + _.find(skills, (skill) => skill.name === s) ); } else { return []; } - }, [skills, matchingRole, searchObject]); + }, [skills, matchingRole]); const unMatchedSkills = useMemo(() => { - if ( - skills && - matchingRole && - matchingRole.listOfSkills && - matchedSkills.length - ) { - const list = _.filter( - matchingRole.listOfSkills, - (l) => !_.find(matchedSkills, (m) => m.name === l) + if (skills && matchingRole && matchingRole.unMatchedSkills) { + return _.map(matchingRole.unMatchedSkills, (s) => + _.find(skills, (skill) => skill.name === s) ); - return _.map(list, (s) => _.find(skills, (skill) => skill.name === s)); } else { return []; } - }, [skills, matchingRole, matchedSkills]); + }, [skills, matchingRole]); useEffect(() => { const isFromInputPage = searchObject.role || @@ -119,7 +106,9 @@ function SearchAndSubmit(props) { setCurrentStage(2, stages, setStages); setSearchState("done"); }, - Date.now() - searchingBegin > SEARCHINGTIME ? 0 : 1500 + Date.now() - searchingBegin > SEARCHINGTIME + ? 0 + : SEARCH_STAGE_TIME * 3 ); }); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/routes/CreateNewTeam/components/SearchCard/index.jsx b/src/routes/CreateNewTeam/components/SearchCard/index.jsx index dfd365ea..20e7489d 100644 --- a/src/routes/CreateNewTeam/components/SearchCard/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchCard/index.jsx @@ -9,6 +9,7 @@ import IconEarthSearch from "../../../../assets/images/icon-earth-search.svg"; import WorldMapDotted from "../../../../assets/images/world-map-dotted.svg"; import WorldMapSearch1 from "../../../../assets/images/world-map-search1.svg"; import WorldMapSearch2 from "../../../../assets/images/world-map-search2.svg"; +import { SEARCH_STAGE_TIME } from "constants/"; function SearchCard() { const [searchState, setSearchState] = useState(null); @@ -19,8 +20,8 @@ function SearchCard() { setSearchState("state1"); timer2 = setTimeout(() => { setSearchState("state2"); - }, 500); - }, 500); + }, SEARCH_STAGE_TIME); + }, SEARCH_STAGE_TIME); return () => { clearTimeout(timer1); diff --git a/src/routes/CreateNewTeam/components/SearchContainer/styles.module.scss b/src/routes/CreateNewTeam/components/SearchContainer/styles.module.scss index 99cec905..c1331790 100644 --- a/src/routes/CreateNewTeam/components/SearchContainer/styles.module.scss +++ b/src/routes/CreateNewTeam/components/SearchContainer/styles.module.scss @@ -3,7 +3,7 @@ flex-direction: row; justify-content: center; align-items: flex-start; - margin: 42px 35px; + margin: 42px 35px 30px; .right-side { display: flex; flex-direction: column; @@ -11,4 +11,4 @@ margin-top: 16px; } } -} \ No newline at end of file +} diff --git a/src/routes/CreateNewTeam/components/SkillTag/styles.module.scss b/src/routes/CreateNewTeam/components/SkillTag/styles.module.scss index b05fd4c4..26ac585f 100644 --- a/src/routes/CreateNewTeam/components/SkillTag/styles.module.scss +++ b/src/routes/CreateNewTeam/components/SkillTag/styles.module.scss @@ -4,23 +4,21 @@ align-items: center; height: 32px; border-radius: 32px; - border: 1px solid #0AB88A; - color: #2A2A2A; + border: 1px solid #0ab88a; + color: #2a2a2a; font-size: 10px; padding: 8px; line-height: 12px; margin-right: 6px; margin-bottom: 6px; - .image { height: 20px; } &.unmatched { - border-color: #EF476F; - background-color: #E9E9E9; - color: #2A2A2A; - + border-color: #ef476f; + background-color: #e9e9e9; + color: #2a2a2a; } } .item-card { diff --git a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx index 4bae541c..86faa01c 100644 --- a/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SubmitContainer/index.jsx @@ -115,28 +115,32 @@ function SubmitContainer({ setTeamDetailsOpen(false); setTeamObject(teamObject); + requestTeam(teamObject); }; - const requestTeam = useCallback(() => { - setRequestLoading(true); - if (matchingRole.isExternalMember) { - dispatch(addTeamObjects(teamObject)); - navigate("/taas/myteams/createnewteam/create-taas-payment"); - } else { - postTeamRequest(teamObject) - .then(() => { - setTimeout(() => { - dispatch(clearSearchedRoles()); - // Backend api create project has sync issue, so delay 2 seconds - navigate("/taas/myteams"); - }, 2000); - }) - .catch((err) => { - setRequestLoading(false); - toastr.error("Error Requesting Team", err.message); - }); - } - }, [dispatch, teamObject]); + const requestTeam = useCallback( + (teamObject) => { + setRequestLoading(true); + if (matchingRole.isExternalMember) { + dispatch(addTeamObjects(teamObject)); + navigate("/taas/myteams/createnewteam/create-taas-payment"); + } else { + postTeamRequest(teamObject) + .then(() => { + setTimeout(() => { + dispatch(clearSearchedRoles()); + // Backend api create project has sync issue, so delay 2 seconds + navigate("/taas/myteams"); + }, 2000); + }) + .catch((err) => { + setRequestLoading(false); + toastr.error("Error Requesting Team", err.message); + }); + } + }, + [dispatch, teamObject] + ); return (
@@ -177,12 +181,12 @@ function SubmitContainer({ addedRoles={addedRoles} /> )} - setTeamObject(null)} onSubmit={requestTeam} isLoading={requestLoading} - /> + /> */}
); } diff --git a/src/routes/CreateNewTeam/components/SubmitContainer/styles.module.scss b/src/routes/CreateNewTeam/components/SubmitContainer/styles.module.scss index 99cec905..c1331790 100644 --- a/src/routes/CreateNewTeam/components/SubmitContainer/styles.module.scss +++ b/src/routes/CreateNewTeam/components/SubmitContainer/styles.module.scss @@ -3,7 +3,7 @@ flex-direction: row; justify-content: center; align-items: flex-start; - margin: 42px 35px; + margin: 42px 35px 30px; .right-side { display: flex; flex-direction: column; @@ -11,4 +11,4 @@ margin-top: 16px; } } -} \ No newline at end of file +} diff --git a/src/routes/CreateNewTeam/components/SuccessCard/styles.module.scss b/src/routes/CreateNewTeam/components/SuccessCard/styles.module.scss index 686a711d..84838af7 100644 --- a/src/routes/CreateNewTeam/components/SuccessCard/styles.module.scss +++ b/src/routes/CreateNewTeam/components/SuccessCard/styles.module.scss @@ -67,4 +67,4 @@ opacity: 12%; height: 142px; width: 142px; -} \ No newline at end of file +} diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx index 2fd2b479..451b07a5 100644 --- a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx +++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx @@ -80,8 +80,8 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { open={open} onClose={onClose} maxWidth="830px" - title="Team Details" - subtitle="Please provide a name for your Team. This could be the name of the project they will work on, the name of the team they are joining, or whatever else will make this talent request meaningful for you." + title="Customize Your Team" + subtitle="Give your new team a name. This could be the name of the project they will work on or the team they will be joining." buttons={ - {backgroundIcon} - - ) : null} -
- ); -} - -Progress.propTypes = { - extraStyleName: PT.string, - isDisabled: PT.bool, - onClick: PT.func, - buttonLabel: PT.string, - currentStageIdx: PT.number, - stages: PT.arrayOf(PT.string), -}; - -export default Progress; diff --git a/src/routes/CreateNewTeam/components/progress/styles.module.scss b/src/routes/CreateNewTeam/components/progress/styles.module.scss deleted file mode 100644 index 65d888bd..00000000 --- a/src/routes/CreateNewTeam/components/progress/styles.module.scss +++ /dev/null @@ -1,89 +0,0 @@ -@import "styles/include"; - -.progress { - @include rounded-card; - overflow: hidden; - padding: 12px; - position: relative; - width: 250px; - color: #fff; - button { - border: none; - } -} - -.input-job-description { - background-image: linear-gradient(135deg, #2984bd 0%, #0ab88a 100%); -} - -.input-skills { - background-image: linear-gradient(221.5deg, #646cd0 0%, #9d41c9 100%); -} - -.role-selection { - background-image: linear-gradient(45deg, #8b41b0 0%, #ef476f 100%); -} - -.list { - margin-bottom: 55px; - & + button[disabled] { - background-color: #e9e9e9; - color: #fff; - opacity: 1; - filter: none; - } -} - -.list-item { - margin-bottom: 14px; - font-size: 14px; - line-height: 16px; - font-weight: 400; - color: rgba(255, 255, 255, 0.6); - - &:before { - content: ""; - color: #fff; - border: 1px solid #ffffff; - border-radius: 100%; - width: 16px; - height: 16px; - margin-right: 10px; - display: block; - float: left; - visibility: hidden; - } - - &.active { - font-weight: 500; - color: #fff; - &:before { - visibility: visible; - background-color: #fff; - } - } - - &.done { - font-weight: 400; - &:before { - content: "✓"; - font-size: 9px; - line-height: 14px; - padding-left: 4px; - visibility: visible; - } - } -} - -.transparent-icon { - position: absolute; - right: -50px; - top: 85px; - opacity: 10%; - width: 144px; - height: 144px; -} - -.final-step { - height: 230px; -} diff --git a/src/routes/CreateNewTeam/pages/CreateTaasPayment/index.jsx b/src/routes/CreateNewTeam/pages/CreateTaasPayment/index.jsx index 7f76aa66..c42e3633 100644 --- a/src/routes/CreateNewTeam/pages/CreateTaasPayment/index.jsx +++ b/src/routes/CreateNewTeam/pages/CreateTaasPayment/index.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { useSelector } from "react-redux"; import { Elements } from "@stripe/react-stripe-js"; import { loadStripe } from "@stripe/stripe-js"; @@ -10,42 +10,68 @@ import PageHeader from "components/PageHeader"; import { calculateAmount } from "services/teams"; import Progress from "../../components/Progress"; import theme from "./theme"; +import FallbackIcon from "../../../../assets/images/icon-role-fallback.svg"; import "./styles.module.scss"; const stripePromise = loadStripe(process.env.STRIPE_PUBLIC_KEY); + const CreateTassPayment = () => { const [calculatedAmount, setCalculatedAmount] = useState(0); + const [error, setError] = useState(false); + const [value, setValue] = useState([]); const { addedRoles } = useSelector((state) => state.searchedRoles); - // Backend has wrong test data so created dummy data for logic to work - const dummyRates = { - global: 50, - rate20Global: 20, - rate30Global: 20, - }; - useEffect(() => { - const obj = { - numberOfResources: addedRoles.numberOfResources || 1, - rates: dummyRates.global, - durationWeeks: addedRoles.durationWeeks || 1, - }; - calculateAmount(obj) + const temp = []; + const amount = []; + + addedRoles.map((role) => { + const { + imageUrl, + name, + rates: [rates], + numberOfResources, + durationWeeks, + hoursPerWeek, + } = role; + let rate; + + if (hoursPerWeek) { + if (hoursPerWeek === "30") rate = rates.rate30Global; + else if (hoursPerWeek === "20") rate = rates.rate20Global; + else if (hoursPerWeek === "40") rate = rates.global; + } else rate = rates.global; + + temp.push({ + imageUrl, + name, + rate, + numberOfResources, + durationWeeks, + hoursPerWeek, + }); + amount.push({ rate, numberOfResources, durationWeeks }); + }); + setValue(temp); + + calculateAmount(amount) .then((res) => { setCalculatedAmount(res.data.totalAmount); }) .catch((err) => { toastr.error("Error Requesting Team", err.message); }); - }); + }, [addedRoles, setValue]); const stages = [ { name: "Input Job Description", completed: true }, { name: "Search Member", completed: true }, { name: "Overview of the Results", completed: true }, - { name: "Refundable Deposite Payment", isCurrent: true }, + { name: "Refundable Deposit Payment", isCurrent: true }, ]; + const onImgError = useCallback(() => setError(true), []); + return (
@@ -59,31 +85,35 @@ const CreateTassPayment = () => {

summary

- {addedRoles.map((role) => ( + {value.map((data) => (
- role + {data.imageUrl && !error ? ( + {data.name} + ) : ( + + )}
-

{role.name}

+

{data.name}

  • - {role.numberOfResources} x ${dummyRates.global}/ - Week + {data.numberOfResources} x ${data.rate}/ Week
  • -
  • {role.durationWeeks} Week Duration
  • +
  • {data.durationWeeks} Week Duration
  • - {role.hoursPerWeek + {data.hoursPerWeek ? "Part-Time Availability" : "Full-Time Availability"}

- ${role.numberOfResources * dummyRates.global} + ${data.numberOfResources * data.rate}


@@ -130,7 +160,7 @@ const CreateTassPayment = () => { stages={stages} extraStyleName="role-selection final-step" disabled="true" - percentage="97" + percentage="98" />
); diff --git a/src/routes/CreateNewTeam/pages/CreateTaasPayment/styles.module.scss b/src/routes/CreateNewTeam/pages/CreateTaasPayment/styles.module.scss index 02102838..90a0d593 100644 --- a/src/routes/CreateNewTeam/pages/CreateTaasPayment/styles.module.scss +++ b/src/routes/CreateNewTeam/pages/CreateTaasPayment/styles.module.scss @@ -58,14 +58,14 @@ display: flex; justify-content: space-between; margin-right: 15px; - } - .image { - background-color: #ffffff; - border: 1px solid #d4d4d4; - border-radius: 5px; - width: 30px; - height: 30px; + .role-icon { + width: 42px; + height: 42px; + object-fit: cover; + border: 1px solid #d4d4d4; + border-radius: 5px; + } } .title { diff --git a/src/routes/CreateNewTeam/pages/CreateTeamLanding/index.jsx b/src/routes/CreateNewTeam/pages/CreateTeamLanding/index.jsx index cc2f2abb..6c674d90 100644 --- a/src/routes/CreateNewTeam/pages/CreateTeamLanding/index.jsx +++ b/src/routes/CreateNewTeam/pages/CreateTeamLanding/index.jsx @@ -26,21 +26,23 @@ function CreateNewTeam() { return ( - Let’s find you great talent
} /> -

- You can search for your perfect talent matches in 3 unique ways: -

+ Let’s find you great talent
} + /> +

You can search for your perfect talent matches in 3 unique ways:

} backgroundImage="linear-gradient(101.95deg, #8B41B0 0%, #EF476F 100%)" onClick={() => goToRoute("/taas/createnewteam/role")} /> } @@ -49,13 +51,13 @@ function CreateNewTeam() { /> } backgroundImage="linear-gradient(135deg, #2984BD 0%, #0AB88A 100%)" onClick={() => goToRoute("/taas/createnewteam/jd")} /> - -
); diff --git a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx index 307a4a97..ab0bc7d2 100644 --- a/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx +++ b/src/routes/CreateNewTeam/pages/InputJobDescription/index.jsx @@ -18,7 +18,7 @@ function InputJobDescription() { { name: "Input Job Description", isCurrent: true }, { name: "Search Member" }, { name: "Overview of the Results" }, - { name: "Refundable Deposite Payment" }, + { name: "Refundable Deposit Payment" }, ]); const [jdString, setJdString] = useState(""); const [jobTitle, setJobTitle] = useState(""); @@ -73,11 +73,7 @@ function InputJobDescription() { backTo="/taas/createnewteam" />

- Input a Job Description for your opening and the Topcoder Platform - will identify the skills required to perform the job duties and find - the best matching freelancers for your job opening. After inputting - the Job Description click on the "Search" button to see the skills - identified. + Copy and paste in a job description, or enter it manually.

-

- Please select one or more essential skills you require your talent to - have. Topcoder will match to profiles which contain most or all of these - skills. -

+

Select one or more skills.

{filteredSkills.map(({ id, name }) => ( +

Choose the role you want to fill

{filteredRoles.map(({ id, name, imageUrl }) => ( { const createValues = { ...values, projectId: teamId, - } + }; await createJob(createValues).then( () => { toastr.success("Job created successfully."); diff --git a/src/routes/JobForm/utils.js b/src/routes/JobForm/utils.js index 2b4c0fe5..47eb8538 100644 --- a/src/routes/JobForm/utils.js +++ b/src/routes/JobForm/utils.js @@ -26,7 +26,7 @@ const EDIT_JOB_ROWS = [ { type: FORM_ROW_TYPE.SINGLE, fields: ["status"] }, ]; -const validateDuration = (x, y, {duration}) => { +const validateDuration = (x, y, { duration }) => { if (!duration) return undefined; const converted = Number(duration); @@ -35,7 +35,7 @@ const validateDuration = (x, y, {duration}) => { } return undefined; -} +}; /** * return edit job configuration diff --git a/src/routes/PositionDetails/components/CandidatesStatusFilter/index.jsx b/src/routes/PositionDetails/components/CandidatesStatusFilter/index.jsx index 8e1be98e..35f36d8b 100644 --- a/src/routes/PositionDetails/components/CandidatesStatusFilter/index.jsx +++ b/src/routes/PositionDetails/components/CandidatesStatusFilter/index.jsx @@ -17,22 +17,26 @@ const CandidatesStatusFilter = ({ statusFilterKey, onChange, candidates }) => { return (
{CANDIDATE_STATUS_FILTERS.map((statusFilter) => { - const count = _.filter(candidates, (candidate) => statusFilter.statuses.includes(candidate.status)).length; + const count = _.filter(candidates, (candidate) => + statusFilter.statuses.includes(candidate.status) + ).length; return ( - ) - })} + ); + })}
); }; diff --git a/src/routes/PositionDetails/components/InterviewConfirmPopup/styles.module.scss b/src/routes/PositionDetails/components/InterviewConfirmPopup/styles.module.scss index 7d0520e6..f3a95213 100644 --- a/src/routes/PositionDetails/components/InterviewConfirmPopup/styles.module.scss +++ b/src/routes/PositionDetails/components/InterviewConfirmPopup/styles.module.scss @@ -3,7 +3,7 @@ margin-bottom: 20px; } & * a { - color: #0D61BF; + color: #0d61bf; } } @@ -14,4 +14,4 @@ .video { width: 100%; -} \ No newline at end of file +} diff --git a/src/routes/PositionDetails/components/InterviewDetailsPopup/styles.module.scss b/src/routes/PositionDetails/components/InterviewDetailsPopup/styles.module.scss index f813ea88..952ec3fe 100644 --- a/src/routes/PositionDetails/components/InterviewDetailsPopup/styles.module.scss +++ b/src/routes/PositionDetails/components/InterviewDetailsPopup/styles.module.scss @@ -7,7 +7,7 @@ .user { font-size: 14px; - color: #0D61BF; + color: #0d61bf; max-width: 37%; .max-warning-txt { padding-top: 5px; @@ -20,7 +20,7 @@ .top { width: 100%; padding-bottom: 25px; - border-bottom: 1px solid #E9E9E9; + border-bottom: 1px solid #e9e9e9; display: flex; flex-direction: row; align-items: center; @@ -29,7 +29,7 @@ .center { padding: 25px 0; - border-bottom: 1px solid #E9E9E9; + border-bottom: 1px solid #e9e9e9; } .center-header { @@ -56,7 +56,7 @@ background: #fff; margin: 10px 0 0 0; padding: 0; - color: #0D61BF; + color: #0d61bf; border: none; border-radius: 0; @@ -73,7 +73,7 @@ } .array-input { - width: 100% + width: 100%; } .remove-item { @@ -81,9 +81,9 @@ right: 45px; margin-top: 33px; font-size: 33px; - color: #EF476F; + color: #ef476f; cursor: pointer; &:focus { outline: none; } -} \ No newline at end of file +} diff --git a/src/routes/PositionDetails/components/LatestInterview/styles.module.scss b/src/routes/PositionDetails/components/LatestInterview/styles.module.scss index ab6df681..8f0d70f3 100644 --- a/src/routes/PositionDetails/components/LatestInterview/styles.module.scss +++ b/src/routes/PositionDetails/components/LatestInterview/styles.module.scss @@ -5,4 +5,4 @@ .strong { font-weight: bold; margin-bottom: 8px; -} \ No newline at end of file +} diff --git a/src/routes/PositionDetails/components/PrevInterviewItem/styles.module.scss b/src/routes/PositionDetails/components/PrevInterviewItem/styles.module.scss index fc9df5f8..d6ae95b7 100644 --- a/src/routes/PositionDetails/components/PrevInterviewItem/styles.module.scss +++ b/src/routes/PositionDetails/components/PrevInterviewItem/styles.module.scss @@ -1,3 +1,3 @@ .email { margin-bottom: 8px; -} \ No newline at end of file +} diff --git a/src/routes/PositionDetails/components/PreviousInterviewsPopup/styles.module.scss b/src/routes/PositionDetails/components/PreviousInterviewsPopup/styles.module.scss index 915099c3..e4011358 100644 --- a/src/routes/PositionDetails/components/PreviousInterviewsPopup/styles.module.scss +++ b/src/routes/PositionDetails/components/PreviousInterviewsPopup/styles.module.scss @@ -1,6 +1,6 @@ .user { font-size: 14px; - color: #0D61BF; + color: #0d61bf; padding-bottom: 25px; - border-bottom: 1px solid #E9E9E9; -} \ No newline at end of file + border-bottom: 1px solid #e9e9e9; +} diff --git a/src/routes/PositionDetails/components/SelectCandidatePopup/styles.module.scss b/src/routes/PositionDetails/components/SelectCandidatePopup/styles.module.scss index d05d1184..d229fcf7 100644 --- a/src/routes/PositionDetails/components/SelectCandidatePopup/styles.module.scss +++ b/src/routes/PositionDetails/components/SelectCandidatePopup/styles.module.scss @@ -8,8 +8,8 @@ ol { .user { font-size: 14px; - color: #0D61BF; + color: #0d61bf; padding-bottom: 25px; - border-bottom: 1px solid #E9E9E9; + border-bottom: 1px solid #e9e9e9; margin-bottom: 25px; -} \ No newline at end of file +} diff --git a/src/routes/PositionDetails/index.jsx b/src/routes/PositionDetails/index.jsx index 361f1dc4..fd9c8f8e 100644 --- a/src/routes/PositionDetails/index.jsx +++ b/src/routes/PositionDetails/index.jsx @@ -6,6 +6,7 @@ import React, { useCallback, useEffect, useState } from "react"; import PT from "prop-types"; import { navigate } from "@reach/router"; +import _ from "lodash"; import Page from "components/Page"; import LoadingIndicator from "components/LoadingIndicator"; import PageHeader from "components/PageHeader"; @@ -26,7 +27,7 @@ const inReviewStatusFilter = _.find(CANDIDATE_STATUS_FILTERS, { const getKeyFromParam = (urlParam) => { const filter = _.find(CANDIDATE_STATUS_FILTERS, { urlParam }); return filter?.key || undefined; -} +}; const PositionDetails = ({ teamId, positionId, candidateStatus }) => { // by default show "interested" tab @@ -40,25 +41,31 @@ const PositionDetails = ({ teamId, positionId, candidateStatus }) => { const onCandidateStatusChange = useCallback( (statusFilter) => { - navigate(`/taas/myteams/${teamId}/positions/${positionId}/candidates/${statusFilter.urlParam}`); + navigate( + `/taas/myteams/${teamId}/positions/${positionId}/candidates/${statusFilter.urlParam}`, + { replace: true } + ); }, [teamId, positionId] ); // if there are some candidates to review, then show "To Review" tab by default useEffect(() => { + const key = getKeyFromParam(candidateStatus); if (position) { - const key = getKeyFromParam(candidateStatus); if (key) { setCandidateStatusFilterKey(key); - } else if (_.filter(position.candidates, (candidate) => - inReviewStatusFilter.statuses.includes(candidate.status) + } else if ( + _.filter(position.candidates, (candidate) => + inReviewStatusFilter.statuses.includes(candidate.status) ).length > 0 ) { - setCandidateStatusFilterKey(CANDIDATE_STATUS_FILTER_KEY.TO_REVIEW); + onCandidateStatusChange({ urlParam: "to-review" }); + } else { + onCandidateStatusChange({ urlParam: "interviews" }); } } - }, [position, candidateStatus]); + }, [position, candidateStatus, onCandidateStatusChange]); return ( diff --git a/src/routes/ResourceBookingForm/index.jsx b/src/routes/ResourceBookingForm/index.jsx index 10d2c994..58c8ed7b 100644 --- a/src/routes/ResourceBookingForm/index.jsx +++ b/src/routes/ResourceBookingForm/index.jsx @@ -67,18 +67,18 @@ const ResourceBookingDetails = ({ teamId, resourceBookingId }) => { const getRequestData = (values) => { // omit read-only fields - const data = _.omit(values, ['handle', 'jobTitle']) + const data = _.omit(values, ["handle", "jobTitle"]); // convert dates to the API format before sending if (data.startDate) { - data.startDate = moment(data.startDate).format('YYYY-MM-DD') + data.startDate = moment(data.startDate).format("YYYY-MM-DD"); } if (data.endDate) { - data.endDate = moment(data.endDate).format('YYYY-MM-DD') + data.endDate = moment(data.endDate).format("YYYY-MM-DD"); } - return data - } + return data; + }; return ( diff --git a/src/routes/TeamAccess/components/AddModal/index.jsx b/src/routes/TeamAccess/components/AddModal/index.jsx index 7049f7b0..4f924e2d 100644 --- a/src/routes/TeamAccess/components/AddModal/index.jsx +++ b/src/routes/TeamAccess/components/AddModal/index.jsx @@ -17,30 +17,30 @@ import { getMemberSuggestions } from "services/teams"; * * @returns {Promise} A promise that resolves to list of suggested users */ -const loadSuggestions = inputVal => { +const loadSuggestions = (inputVal) => { return getMemberSuggestions(inputVal) - .then(res => { + .then((res) => { const users = _.get(res, "data.result.content", []); - return users.map(user => ({ + return users.map((user) => ({ label: user.handle, - value: user.handle - })) + value: user.handle, + })); }) .catch(() => { console.warn("could not get suggestions"); return []; - }) -} + }); +}; /** * Function to call if user does not have permission to see suggestions * @returns {Promise} Promise resolving to empty array */ const emptySuggestions = () => { - return new Promise(resolve => { + return new Promise((resolve) => { resolve([]); - }) -} + }); +}; /** * Filters selected members, keeping those who could not be added to team @@ -120,7 +120,7 @@ const AddModal = ({ open, onClose, teamId, validateAdds, showSuggestions }) => { const numAdds = success.length; toastr.success( "Members Added", - `Successfully added ${formatPlural(numAdds, 'member')}` + `Successfully added ${formatPlural(numAdds, "member")}` ); } @@ -210,7 +210,7 @@ const AddModal = ({ open, onClose, teamId, validateAdds, showSuggestions }) => { placeholder="Enter email address(es) or user handles" noOptionsText="Type to search" loadingText="Loading..." - loadOptions={showSuggestions ? loadSuggestions: emptySuggestions} + loadOptions={showSuggestions ? loadSuggestions : emptySuggestions} defaultOptions={[]} /> {validationError && ( diff --git a/src/routes/TeamAccess/components/AddModalContainer/index.jsx b/src/routes/TeamAccess/components/AddModalContainer/index.jsx index 2b9a2788..140dd1e1 100644 --- a/src/routes/TeamAccess/components/AddModalContainer/index.jsx +++ b/src/routes/TeamAccess/components/AddModalContainer/index.jsx @@ -25,7 +25,9 @@ const checkForMatches = (newMember, memberList) => { return member.email && member.email.toLowerCase() === lowered; }); } - return memberList.find((member) => member.handle && member.handle.toLowerCase() === lowered); + return memberList.find( + (member) => member.handle && member.handle.toLowerCase() === lowered + ); }; const AddModalContainer = ({ diff --git a/src/routes/TeamAccess/components/DeleteModal/index.jsx b/src/routes/TeamAccess/components/DeleteModal/index.jsx index 14efd7f2..c0eb3721 100644 --- a/src/routes/TeamAccess/components/DeleteModal/index.jsx +++ b/src/routes/TeamAccess/components/DeleteModal/index.jsx @@ -23,11 +23,12 @@ const getText = (isSelf, userHandleOrEmail) => access to the team and couldn't see or interact with it anymore. Do you still want to remove the member?`; -const getSuccessTitle = (isSelf) => - isSelf ? "Team Left" : "Member Removed"; +const getSuccessTitle = (isSelf) => (isSelf ? "Team Left" : "Member Removed"); const getSuccessText = (isSelf, userHandleOrEmail) => - isSelf ? "You have successfully left the team" : `You have successfully removed ${userHandleOrEmail} from the team`; + isSelf + ? "You have successfully left the team" + : `You have successfully removed ${userHandleOrEmail} from the team`; const getFailedTitle = (isSelf) => isSelf ? "Failed to Leave the Team" : "Failed to Remove Member"; @@ -39,7 +40,10 @@ function DeleteModal({ selected, open, onClose, teamId }) { const [loading, setLoading] = useState(false); const { userId } = useSelector((state) => state.authUser); - const isSelf = useMemo(() => selected && selected.userId === userId, [selected, userId]); + const isSelf = useMemo(() => selected && selected.userId === userId, [ + selected, + userId, + ]); const userHandleOrEmail = useMemo(() => { if (selected) { return selected.handle ? selected.handle : selected.email;