diff --git a/config/dev.js b/config/dev.js index ec65637..d434242 100644 --- a/config/dev.js +++ b/config/dev.js @@ -5,6 +5,7 @@ module.exports = { TC_NOTIFICATION_URL: "https://api.topcoder-dev.com/v5/notifications", CONNECT_DOMAIN: "https://connect.topcoder-dev.com", COMMUNITY_DOMAIN: "https://www.topcoder-dev.com", + PLATFORM_DOMAIN: "https://local.topcoder-dev.com", TAAS_APP: "https://platform.topcoder-dev.com/taas/myteams", }, API: { diff --git a/config/prod.js b/config/prod.js index 685ca66..39ed894 100644 --- a/config/prod.js +++ b/config/prod.js @@ -5,6 +5,7 @@ module.exports = { TC_NOTIFICATION_URL: "https://api.topcoder.com/v5/notifications", CONNECT_DOMAIN: "https://connect.topcoder.com", COMMUNITY_DOMAIN: "https://www.topcoder.com", + PLATFORM_DOMAIN: "https://platform.topcoder.com", TAAS_APP: "https://platform.topcoder.com/taas/myteams", }, API: { diff --git a/src/App.jsx b/src/App.jsx index 1ea6bd9..9cee259 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,6 +5,7 @@ import React, { useState, useCallback, useMemo, useEffect } from "react"; import _ from "lodash"; import MainMenu from "./components/MainMenu"; import NavBar from "./components/NavBar"; +import SelfServiceNavbar from "./components/SelfService/NavBar"; import { navigate, Router, useLocation } from "@reach/router"; import { useSelector } from "react-redux"; import useMatchSomeRoute from "./hooks/useMatchSomeRoute"; @@ -54,7 +55,15 @@ const App = () => { return ( <> - + + + + {!isSideBarDisabled && (
diff --git a/src/assets/icons/icon-cross.svg b/src/assets/icons/icon-cross.svg new file mode 100644 index 0000000..82a7a8b --- /dev/null +++ b/src/assets/icons/icon-cross.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/user-menu-header-bg.svg b/src/assets/images/user-menu-header-bg.svg new file mode 100644 index 0000000..88b9753 --- /dev/null +++ b/src/assets/images/user-menu-header-bg.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/src/components/Button/index.jsx b/src/components/Button/index.jsx new file mode 100644 index 0000000..f6008cc --- /dev/null +++ b/src/components/Button/index.jsx @@ -0,0 +1,84 @@ +/** + * Button + * + * Supports: + * - size - see BUTTON_SIZE values + * - type - see BUTTON_TYPE values + * + * If `routeTo` is set, then button works as React Router Link + * + * if `href` is set, then button is rendered as a link `` + */ +import { Link } from "@reach/router"; +import cn from "classnames"; +import { BUTTON_SIZE, BUTTON_TYPE } from "constants"; +import PT from "prop-types"; +import React from "react"; +import styles from "./styles.module.scss"; + +const Button = ({ + children, + size = BUTTON_SIZE.SMALL, + type = BUTTON_TYPE.PRIMARY, + onClick, + className, + innerRef, + disabled, + routeTo, + href, + target, + isSubmit, +}) => { + if (href) { + return ( + + {children} + + ); + } else { + const button = ( + + ); + + return routeTo ? {button} : button; + } +}; + +Button.propTypes = { + children: PT.node, + size: PT.oneOf(Object.values(BUTTON_SIZE)), + type: PT.oneOf(Object.values(BUTTON_TYPE)), + onClick: PT.func, + className: PT.string, + innerRef: PT.func, + disabled: PT.bool, + routeTo: PT.string, + href: PT.string, + isSubmit: PT.bool, +}; + +export default Button; diff --git a/src/components/Button/styles.module.scss b/src/components/Button/styles.module.scss new file mode 100644 index 0000000..b7a4f96 --- /dev/null +++ b/src/components/Button/styles.module.scss @@ -0,0 +1,115 @@ +@import "styles/variables"; +@import "styles/mixins"; + +.button { + @include font-roboto; + background: transparent; + border: 0; + box-sizing: border-box; + cursor: pointer; + align-items: center; + display: flex; + margin: 0; + padding: 0; + text-decoration: none; + outline: none; + white-space: nowrap; + + &[disabled] { + cursor: default; + } +} + +.size-medium { + border-radius: 20px; + font-size: 14px; + font-weight: 700; + letter-spacing: 0.8px; + line-height: 38px; + height: 40px; + padding: 0 19px; + text-transform: uppercase; +} + +.size-small { + border-radius: 15px; + font-size: 12px; + font-weight: 700; + line-height: 28px; + letter-spacing: 0.8px; + height: 30px; + padding: 0 14px; + text-transform: uppercase; +} + +.size-tiny { + border-radius: 12px; + padding: 6px 15px; + height: 24px; + font-size: 10px; + font-weight: 700; + line-height: 22px; + letter-spacing: 0.8px; + text-transform: uppercase; +} + +.type-primary { + border: 1px solid $green1; + background-color: $green1; + color: #fff; +} + +.type-warning { + border: 1px solid #ef476f; + background-color: #ef476f; + color: #fff; +} + +.type-secondary { + background-color: #fff; + border: 1px solid $green1; + color: #229174; +} + +.type-secondary[disabled] { + border-color: #b5b5b5; + color: #b5b5b5; +} + +.type-rounded { + position: relative; + width: 22px; + border-radius: 50%; + background-color: #fff; + border: 1px solid $green1; + color: #229174; + + svg { + position: absolute; + left: 37%; + } +} + +.type-text { + background-color: #fff; + border: 1px solid #fff; + color: $green1; +} + +.type-text-inverted { + background-color: transparent; + border: 1px solid transparent; + color: #fff; +} + +.type-rounded[disabled] { + border-radius: 50%; + border-color: #b5b5b5; + color: #b5b5b5; +} + +.type-primary[disabled], +.type-warning[disabled] { + background-color: #b5b5b5; + border-color: #b5b5b5; +} diff --git a/src/components/NavBar/index.jsx b/src/components/NavBar/index.jsx index cdffdde..f06b5dc 100644 --- a/src/components/NavBar/index.jsx +++ b/src/components/NavBar/index.jsx @@ -10,7 +10,6 @@ import React, { useEffect, useMemo, } from "react"; -import cn from "classnames"; import _ from "lodash"; import PropTypes from "prop-types"; import UserMenu from "../UserMenu"; @@ -18,12 +17,10 @@ import AllAppsMenu from "../AllAppsMenu"; import { useSelector } from "react-redux"; import { Link, useLocation } from "@reach/router"; import TCLogo from "../../assets/images/tc-logo.svg"; -import { getLoginUrl, getSelfServiceLoginUrl } from "../../utils"; +import { getLoginUrl } from "../../utils"; import "./styles.css"; import { useMediaQuery } from "react-responsive"; import NotificationsMenu from "../NotificationsMenu"; -import SelfServiceNotifications from "../SelfServiceNotificationsMenu"; -import SelfServiceUserMenu from "../SelfServiceUserMenu"; const NavBar = ({ hideSwitchTools }) => { // all menu options @@ -39,9 +36,7 @@ const NavBar = ({ hideSwitchTools }) => { const routerLocation = useLocation(); - const loginUrl = routerLocation.pathname.startsWith("/self-service/wizard") - ? getSelfServiceLoginUrl() - : getLoginUrl(); + const loginUrl = getLoginUrl(); // Check app title with route activated useEffect(() => { @@ -68,7 +63,7 @@ const NavBar = ({ hideSwitchTools }) => { ); return ( -
+
{isMobile ? ( hideSwitchTools ? null : ( @@ -97,13 +92,7 @@ const NavBar = ({ hideSwitchTools }) => { {auth.isInitialized && (auth.tokenV3 ? ( - auth.profile && - (hideSwitchTools ? ( - - - - - ) : ( + auth.profile && ( { hideSwitchTools={hideSwitchTools} /> - )) + ) ) : ( Login @@ -128,13 +117,7 @@ const NavBar = ({ hideSwitchTools }) => { )} {auth.isInitialized && (auth.tokenV3 ? ( - auth.profile && - (hideSwitchTools ? ( - - - - - ) : ( + auth.profile && ( { hideSwitchTools={hideSwitchTools} /> - )) + ) ) : ( Login diff --git a/src/components/NavBar/styles.css b/src/components/NavBar/styles.css index a43f09a..1773023 100644 --- a/src/components/NavBar/styles.css +++ b/src/components/NavBar/styles.css @@ -10,7 +10,7 @@ z-index: 1; font-family: "Roboto", Arial, Helvetica, sans-serif; } -.navbar.self-service-navbar { +.navbar { padding: 0 24px; background-color: #0C0C0C; } @@ -30,25 +30,6 @@ text-align: left; color: #fff; } -.self-service-navbar .navbar-app-title { - position: relative; - font-family: "Barlow Condensed", Arial, Helvetica, sans-serif; - font-weight: 500; - font-size: 20px; - line-height: 18px; -} -.self-service-navbar .navbar-app-title::after { - content: ""; - display: block; - position: absolute; - left: 8.5px; - top: 100%; - right: 8.5px; - margin: 10px 0 0; - border-radius: 2px; - height: 2px; - background-color: #0AB88A; -} .navbar-divider { width: 1px; height: 30px; @@ -57,9 +38,6 @@ .navbar-left .navbar-divider { margin: 0 30px; } -.self-service-navbar .navbar-left .navbar-divider { - margin: 0 23px; -} .navbar-center { left: 50%; position: absolute; diff --git a/src/components/SelfService/NavBar/index.jsx b/src/components/SelfService/NavBar/index.jsx new file mode 100644 index 0000000..21f33c8 --- /dev/null +++ b/src/components/SelfService/NavBar/index.jsx @@ -0,0 +1,112 @@ +/** + * NavBar component. + * + * Shows global top navigation bar with all apps menu, logo and user menu. + */ +import React, { useState, useEffect, useMemo, useCallback } from "react"; +import { useSelector } from "react-redux"; +import { Link, useLocation } from "@reach/router"; +import cn from "classnames"; +import _ from "lodash"; +import { useMediaQuery } from "react-responsive"; +import Button from "../../Button"; +import NotificationsMenu from "../NotificationsMenu"; +import UserMenu from "../UserMenu"; +import TCLogo from "../../../assets/images/tc-logo.svg"; +import { getSelfServiceLoginUrl, getSelfServiceSignupUrl } from "utils"; +import { BUTTON_TYPE } from "../../../constants"; +import "./styles.css"; + +const NavBar = ({ hideSwitchTools }) => { + // all menu options + const menu = useSelector((state) => state.menu.categories); + // flat list of all apps + const apps = useMemo(() => _.flatMap(menu, "apps"), [menu]); + // Active app + const [activeApp, setActiveApp] = useState(null); + const auth = useSelector((state) => state.auth); + const isMobile = useMediaQuery({ + query: "(max-width: 1023px)", + }); + + const routerLocation = useLocation(); + + const isHomePage = routerLocation.pathname.startsWith("/self-service/home"); + + const activeAppTitle = (isHomePage ? "Topcoder" : activeApp?.title) || null; + + const onClickBtnLogIn = useCallback(() => { + window.location.href = getSelfServiceLoginUrl(); + }, []); + + const onClickBtnSignUp = useCallback(() => { + window.location.href = getSelfServiceSignupUrl(); + }, []); + + // Check app title with route activated + useEffect(() => { + const activeApp = apps.find( + (f) => routerLocation.pathname.indexOf(f.path) !== -1 + ); + setActiveApp(activeApp); + }, [routerLocation, apps]); + + const renderTopcoderLogo = hideSwitchTools ? ( + Topcoder Logo + ) : ( + + Topcoder Logo + + ); + + return ( +
+
+ {isMobile ? null : ( + <> + {renderTopcoderLogo} +
+
+ {activeAppTitle} +
+ + )} +
+
+ {isMobile ? renderTopcoderLogo : null} + {process.env.NODE_ENV === "test" && ( +

Navbar App Test

+ )} +
+
+ {auth.isInitialized && + (auth.tokenV3 ? ( + auth.profile && ( + <> + + + + ) + ) : ( + <> + + + + ))} +
+
+ ); +}; + +export default NavBar; diff --git a/src/components/SelfService/NavBar/styles.css b/src/components/SelfService/NavBar/styles.css new file mode 100644 index 0000000..8a77d95 --- /dev/null +++ b/src/components/SelfService/NavBar/styles.css @@ -0,0 +1,92 @@ +.navbar { + align-items: center; + display: flex; + background-color: #2a2a2a; + height: var(--navbarHeight); + justify-content: space-between; + padding: 0 24px; + padding-right: 16px; + position: relative; + z-index: 1; + font-family: "Roboto", Arial, Helvetica, sans-serif; +} +.navbar.self-service-navbar { + padding: 0 24px; + background-color: #0c0c0c; +} + +.navbar-left { + display: flex; + flex-direction: row; + align-items: center; +} + +.navbar-app-title { + font-family: "Roboto", Arial, Helvetica, sans-serif; + font-weight: 500; + font-weight: normal; + text-transform: uppercase; + font-size: 22px; + text-align: left; + color: #fff; +} + +.self-service-navbar .navbar-app-title { + position: relative; + font-family: "Barlow Condensed", Arial, Helvetica, sans-serif; + font-weight: 500; + font-size: 22px; + line-height: 32px; +} +.self-service-navbar .navbar-app-title.navbar-app-title-underlined { + font-size: 20px; + line-height: 18px; +} +.self-service-navbar .navbar-app-title.navbar-app-title-underlined::after { + content: ""; + display: block; + position: absolute; + left: 8.5px; + top: 100%; + right: 8.5px; + margin: 10px 0 0; + border-radius: 2px; + height: 2px; + background-color: #0ab88a; +} +.self-service-navbar .navbar-app-title.navbar-app-title-underlined:empty:after { + display: none; +} + +.navbar-divider { + width: 1px; + height: 30px; + background-color: #555; +} + +.navbar-left .navbar-divider { + margin: 0 30px; +} +.self-service-navbar .navbar-left .navbar-divider { + margin: 0 23px; +} +.navbar-center { + left: 50%; + position: absolute; + transform: translateX(-50%); +} +.navbar-right { + display: flex; + align-items: center; + flex-direction: row; +} +.navbar-right .navbar-divider { + margin: 0 15px; +} +.navbar-right .navbar-login { + color: #fff; + font-size: 14px; + font-weight: bold; + text-decoration: none; + text-transform: uppercase; +} diff --git a/src/components/SelfServiceNotificationsMenu/index.jsx b/src/components/SelfService/NotificationsMenu/index.jsx similarity index 89% rename from src/components/SelfServiceNotificationsMenu/index.jsx rename to src/components/SelfService/NotificationsMenu/index.jsx index ec3dc23..3bd4579 100644 --- a/src/components/SelfServiceNotificationsMenu/index.jsx +++ b/src/components/SelfService/NotificationsMenu/index.jsx @@ -1,5 +1,5 @@ import React from "react"; -import Bell from "../../assets/icons/ui-bell.svg"; +import Bell from "../../../assets/icons/ui-bell.svg"; import styles from "./styles.module.scss"; /** diff --git a/src/components/SelfServiceNotificationsMenu/styles.module.scss b/src/components/SelfService/NotificationsMenu/styles.module.scss similarity index 100% rename from src/components/SelfServiceNotificationsMenu/styles.module.scss rename to src/components/SelfService/NotificationsMenu/styles.module.scss diff --git a/src/components/SelfServiceUserMenu/index.jsx b/src/components/SelfService/UserMenu/index.jsx similarity index 57% rename from src/components/SelfServiceUserMenu/index.jsx rename to src/components/SelfService/UserMenu/index.jsx index ef7bf7e..2470fb6 100644 --- a/src/components/SelfServiceUserMenu/index.jsx +++ b/src/components/SelfService/UserMenu/index.jsx @@ -1,6 +1,9 @@ import React, { useCallback, useState } from "react"; +import { Link } from "@reach/router"; import PT from "prop-types"; +import cn from "classnames"; import OutsideClickHandler from "react-outside-click-handler"; +import IconCross from "../../../assets/icons/icon-cross.svg"; import { logout, getLogoutUrl } from "utils"; import styles from "./styles.module.scss"; @@ -28,8 +31,12 @@ const SelfServiceUserMenu = ({ profile }) => { logout(); }, []); + const onClickMyProfile = useCallback(() => { + setIsOpenMenu(false); + }, []); + return ( -
+
{ role="button" tabIndex={0} > - {firstName.charAt(0)} - {lastName.charAt(0)} + + {firstName.charAt(0)} + {lastName.charAt(0)} + +
{isOpenMenu && (
- - Log Out - +
+ {firstName} {lastName.charAt(0)}. +
+
)} diff --git a/src/components/SelfService/UserMenu/styles.module.scss b/src/components/SelfService/UserMenu/styles.module.scss new file mode 100644 index 0000000..31b8e19 --- /dev/null +++ b/src/components/SelfService/UserMenu/styles.module.scss @@ -0,0 +1,108 @@ +@import "styles/mixins"; + +.container { + position: relative; + display: flex; +} + +.button { + display: flex; + justify-content: center; + align-items: center; + border: 2px solid #fff; + border-radius: 16px; + padding: 0; + width: 32px; + height: 32px; + @include font-barlow-condensed; + font-weight: 700; + font-size: 14px; + line-height: 14px; + background: linear-gradient(90deg, #16679a 0%, #2c95d7 100%); + color: #fff; + user-select: none; + cursor: pointer; + + .icon { + display: none; + } +} + +.menuIsOpen { + .button { + background: #fff; + + .initials { + display: none; + } + + .icon { + display: block; + width: 14px; + height: auto; + } + } +} + +.menu { + z-index: 3; + position: absolute; + top: 100%; + right: -16px; + margin-top: 20px; + border-radius: 8px; + padding: 12px 16px 16px; + width: 168px; + white-space: nowrap; + background-color: #fff; + box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.2); + user-select: none; + + &::before { + content: ""; + display: block; + z-index: 1; + position: absolute; + left: 0; + top: -10px; + right: 0; + width: 168px; + height: 58px; + background-repeat: no-repeat; + background-position: center top; + background-image: url("../../../assets/images/user-menu-header-bg.svg"); + } +} + +.userInfo { + z-index: 2; + position: relative; + padding: 0 0 29px; + @include font-roboto; + font-size: 14px; + line-height: 16px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + color: #fff; +} + +.items { + margin-top: 0px; + @include font-barlow-condensed; + font-weight: 500; + font-size: 14px; + line-height: 16px; + text-transform: uppercase; + + li { + margin-top: 8px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + + &:first-child { + margin-top: 0; + } + } +} diff --git a/src/components/SelfServiceUserMenu/styles.module.scss b/src/components/SelfServiceUserMenu/styles.module.scss deleted file mode 100644 index 836168d..0000000 --- a/src/components/SelfServiceUserMenu/styles.module.scss +++ /dev/null @@ -1,34 +0,0 @@ -.container { - position: relative; - display: flex; -} - -.button { - border: 2px solid #fff; - border-radius: 16px; - width: 32px; - height: 32px; - font-family: "Barlow Condensed", Arial, Helvetica, sans-serif; - font-weight: 700; - font-size: 14px; - line-height: 28px; - text-align: center; - background: linear-gradient(90deg, #16679A 0%, #2C95D7 100%); - color: #fff; - user-select: none; - cursor: pointer; -} - -.menu { - z-index: 3; - position: absolute; - top: 100%; - right: 0; - margin-top: 10px; - border: 1px solid #555; - border-radius: 8px; - padding: 5px 12px; - white-space: nowrap; - background-color: #fff; - user-select: none; -} diff --git a/src/constants/index.js b/src/constants/index.js index be97d87..fc846e0 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,8 +1,10 @@ /** * Application Constants */ +/* global process */ import { APP_CATEGORIES } from "./apps"; export { APP_CATEGORIES }; +import config from "../../config"; export const ACTIONS = { AUTH: { @@ -20,3 +22,29 @@ export const ACTIONS = { ENABLE_NAVIGATION_FOR_ROUTE: "ENABLE_NAVIGATION_FOR_ROUTE", }, }; + +/** + * Supported Button Sizes + */ +export const BUTTON_SIZE = { + TINY: "tiny", + SMALL: "small", + MEDIUM: "medium", +}; + +/** + * Supported Button Types + */ +export const BUTTON_TYPE = { + PRIMARY: "primary", + SECONDARY: "secondary", + WARNING: "warning", + ROUNDED: "rounded", + TEXT: "text", + TEXT_INVERTED: "text-inverted", +}; + +export const PLATFORM_DOMAIN = + process.env.APPENV === "local" + ? window.location.origin + : config.URL.PLATFORM_DOMAIN; diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss new file mode 100644 index 0000000..bbfe9c4 --- /dev/null +++ b/src/styles/_mixins.scss @@ -0,0 +1,25 @@ +// reusable mixins + +@mixin font-roboto { + font-family: "Roboto", Arial, Helvetica, sans-serif; +} + +@mixin font-barlow-condensed { + font-family: "Barlow Condensed", Arial, Helvetica, sans-serif; +} + +@mixin font-barlow { + font-family: "Barlow", Arial, Helvetica, sans-serif; +} + +@mixin mobile { + @media (max-width: 1199px) { + @content; + } +} + +@mixin desktop { + @media (min-width: 1200px) { + @content; + } +} diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss new file mode 100644 index 0000000..ceac08d --- /dev/null +++ b/src/styles/_variables.scss @@ -0,0 +1,23 @@ +$green1: #137d60; +$blue1: #2f757e; +$blue2: #eaf6fd; +$grey-bg: #f4f4f4; +$teal: #227681; +$grey-text: #2a2a2a; +$black: #000000; +$divider-color: #e9e9e9; +$orange-25: #fff0ce; +$red-120: #be405e; +$purple-100: #9d41c9; +$gray-10: #e9e9e9; +$gray-80: #555555; +$gui-kit-level-2: #0ab88a; +$gui-kit-level-5: #ef476f; +$gui-kit-gray-90: #2a2a2a; +$tc-white: #fff; +$gui-kit-gray-30: #aaa; +$link-blue: #2e55b9; +$green2: #06d6a0; + +$error-color: $gui-kit-level-5; +$success-color: $gui-kit-level-2; diff --git a/src/utils/index.js b/src/utils/index.js index a12fc82..1f7ee5c 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,6 +1,7 @@ import _ from "lodash"; import moment from "moment"; import config from "../../config"; +import { PLATFORM_DOMAIN } from "../constants"; /** * Generate Logout URL @@ -34,8 +35,18 @@ export const getBusinessLoginUrl = () => */ export const getSelfServiceLoginUrl = () => `${config.URL.AUTH}?retUrl=${encodeURIComponent( - `${window.location.origin}/self-service` - )}`; + `${PLATFORM_DOMAIN}/self-service` + )}®Source=tcBusiness&mode=login`; + +/** + * Returns Sign up URL for self service app. + * + * @returns {string} + */ +export const getSelfServiceSignupUrl = () => + `${config.URL.AUTH}?retUrl=${encodeURIComponent( + `${PLATFORM_DOMAIN}/self-service` + )}®Source=tcBusiness&mode=signUp`; /** * Logout user from Topcoder diff --git a/webpack.config.js b/webpack.config.js index 7786c51..d1ec5fa 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -104,6 +104,7 @@ module.exports = (webpackConfigEnv, options) => { components: path.resolve(__dirname, "src/components"), fonts: path.resolve(__dirname, "src/assets/fonts"), styles: path.resolve(__dirname, "src/styles"), + utils: path.resolve(__dirname, "src/utils"), handlebars: path.resolve( __dirname, "node_modules/handlebars/dist/handlebars.min.js"