diff --git a/config/dev.js b/config/dev.js
index ec65637..5ab57bf 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://platform.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..fc43d81 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -54,7 +54,19 @@ 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 f05eb39..a78999a 100644
--- a/src/components/NavBar/index.jsx
+++ b/src/components/NavBar/index.jsx
@@ -17,12 +17,19 @@ 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 } from "../../utils";
+import {
+ getLoginUrl,
+ getSelfServiceLoginUrl,
+ getSelfServiceSignupUrl,
+} from "../../utils";
+import { BUTTON_TYPE } from "constants/";
import "./styles.css";
import { useMediaQuery } from "react-responsive";
import NotificationsMenu from "../NotificationsMenu";
+import Button from "../Button";
-const NavBar = ({ hideSwitchTools }) => {
+const NavBar = ({ hideSwitchTools, profileUrl }) => {
+ const [isSelfService, setIsSelfService] = useState(false);
// all menu options
const menu = useSelector((state) => state.menu.categories);
// flat list of all apps
@@ -35,13 +42,19 @@ const NavBar = ({ hideSwitchTools }) => {
});
const routerLocation = useLocation();
+
+ const loginUrl = isSelfService ? getSelfServiceLoginUrl() : getLoginUrl();
+ const signupUrl = isSelfService ? getSelfServiceSignupUrl() : "";
+
// Check app title with route activated
useEffect(() => {
const activeApp = apps.find(
(f) => routerLocation.pathname.indexOf(f.path) !== -1
);
setActiveApp(activeApp);
- }, [routerLocation]);
+
+ setIsSelfService(routerLocation.pathname.indexOf("/self-service") !== -1);
+ }, [routerLocation, apps]);
// Change micro-app callback
const changeApp = useCallback(
@@ -51,13 +64,14 @@ const NavBar = ({ hideSwitchTools }) => {
[setActiveApp]
);
- const renderTopcoderLogo = hideSwitchTools ? (
-
- ) : (
-
+ const renderTopcoderLogo =
+ hideSwitchTools && !isSelfService ? (
-
- );
+ ) : (
+
+
+
+ );
return (
@@ -91,38 +105,55 @@ const NavBar = ({ hideSwitchTools }) => {
(auth.tokenV3 ? (
auth.profile && (
- {hideSwitchTools ? null : }
+
)
) : (
-
+
Login
))}
) : (
- {hideSwitchTools ? null : }
-
+ {hideSwitchTools ? null : (
+
+
+
+
+ )}
{auth.isInitialized &&
(auth.tokenV3 ? (
auth.profile && (
- {hideSwitchTools ? null : }
+ {!isSelfService && }
)
) : (
-
- Login
-
+
+
+ Login
+
+ {isSelfService && (
+
+ )}
+
))}
)}
@@ -133,9 +164,11 @@ const NavBar = ({ hideSwitchTools }) => {
NavBar.defaultProps = {
hideSwitchTools: false,
+ profileUrl: '/profile/',
};
NavBar.propTypes = {
+ profileUrl: PropTypes.string,
hideSwitchTools: PropTypes.boolean,
};
diff --git a/src/components/NavBar/styles.css b/src/components/NavBar/styles.css
index 482709f..b1e4acb 100644
--- a/src/components/NavBar/styles.css
+++ b/src/components/NavBar/styles.css
@@ -10,6 +10,10 @@
z-index: 1;
font-family: "Roboto", Arial, Helvetica, sans-serif;
}
+.navbar {
+ padding: 0 24px;
+ background-color: #0C0C0C;
+}
.navbar-left {
display: flex;
@@ -54,3 +58,11 @@
text-decoration: none;
text-transform: uppercase;
}
+
+.navbar-right .navbar-signup {
+ font-size: 14px;
+ font-weight: bold;
+ text-decoration: none;
+ text-transform: uppercase;
+ margin-left: 16px;
+}
diff --git a/src/components/UserMenu/index.jsx b/src/components/UserMenu/index.jsx
index e777b41..c3765fa 100644
--- a/src/components/UserMenu/index.jsx
+++ b/src/components/UserMenu/index.jsx
@@ -13,7 +13,7 @@ import { logout, getLogoutUrl } from "../../utils";
import "./styles.css";
import { useMediaQuery } from "react-responsive";
-const UserMenu = ({ profile, hideSwitchTools }) => {
+const UserMenu = ({ profile, profileUrl }) => {
const [isOpenMenu, setIsOpenMenu] = useState(false);
const closeMenu = useCallback(() => {
@@ -64,16 +64,14 @@ const UserMenu = ({ profile, hideSwitchTools }) => {
- {hideSwitchTools ? null : (
- -
-
- Profile
-
-
- )}
+ -
+
+ Profile
+
+
-
Log Out
@@ -90,11 +88,11 @@ const UserMenu = ({ profile, hideSwitchTools }) => {
};
UserMenu.defaultProps = {
- hideSwitchTools: false,
+ profileUrl: '/profile',
};
UserMenu.propTypes = {
- hideSwitchTools: PropTypes.boolean,
+ profileUrl: PropTypes.string,
};
export default UserMenu;
diff --git a/src/constants/apps.js b/src/constants/apps.js
index 1d377dc..a83cc68 100644
--- a/src/constants/apps.js
+++ b/src/constants/apps.js
@@ -60,6 +60,17 @@ export const APP_CATEGORIES = [
},
],
},
+ {
+ category: "Self Service",
+ apps: [
+ {
+ title: "Work",
+ icon: earnIcon,
+ path: "/self-service",
+ menu: [],
+ },
+ ],
+ },
{
category: "Do",
apps: [
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/reducers/auth.js b/src/reducers/auth.js
index 65c1d84..0ef6930 100644
--- a/src/reducers/auth.js
+++ b/src/reducers/auth.js
@@ -30,10 +30,12 @@ const authReducer = (state = initialState, action) => {
isProfileLoaded: true,
};
case ACTIONS.AUTH.SET_PROFILE_PHOTO:
- return state.profile ? ({
- ...state,
- profile: {...state.profile, photoURL: action.payload},
- }) : state;
+ return state.profile
+ ? {
+ ...state,
+ profile: { ...state.profile, photoURL: action.payload },
+ }
+ : state;
case ACTIONS.AUTH.SET_INITIALIZED:
return {
...state,
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/topcoder-micro-frontends-navbar-app.js b/src/topcoder-micro-frontends-navbar-app.js
index 31fc2ce..a9e02f8 100644
--- a/src/topcoder-micro-frontends-navbar-app.js
+++ b/src/topcoder-micro-frontends-navbar-app.js
@@ -20,6 +20,7 @@ import {
setNotificationPlatform,
disableNavigationForRoute,
enableNavigationForRoute,
+ updateUserProfile,
} from "./utils/exports";
import { login, businessLogin, logout } from "./utils";
@@ -51,5 +52,6 @@ export {
setNotificationPlatform,
disableNavigationForRoute,
enableNavigationForRoute,
+ updateUserProfile,
PLATFORM,
};
diff --git a/src/utils/exports.js b/src/utils/exports.js
index a5fd63d..881efc8 100644
--- a/src/utils/exports.js
+++ b/src/utils/exports.js
@@ -6,6 +6,7 @@
import _ from "lodash";
import { bindActionCreators } from "redux";
import store from "../store";
+import actions from "../actions";
import menuActions from "../actions/menu";
import authActions from "../actions/auth";
import notificationActions from "../actions/notifications";
@@ -77,3 +78,18 @@ export const getAuthUserTokens = () => {
});
}
};
+
+/**
+ * Updates user profile
+ */
+export const updateUserProfile = (firstName, lastName) => {
+ const { auth } = store.getState();
+
+ const newProfile = {
+ ...auth.profile,
+ firstName,
+ lastName,
+ };
+
+ store.dispatch(actions.auth.loadProfile(newProfile || null));
+};
diff --git a/src/utils/index.js b/src/utils/index.js
index 35ab947..1e7ebf1 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -26,6 +26,27 @@ export const getBusinessLoginUrl = () =>
window.location.href.match(/[^?]*/)[0]
)}`;
+/**
+ * Returns login URL using which the user should be redirected to self service
+ * dashboard page after login.
+ *
+ * @returns {string}
+ */
+export const getSelfServiceLoginUrl = () =>
+ `${config.URL.AUTH}?retUrl=${encodeURIComponent(
+ `${window.location.origin}/self-service`
+ )}®Source=selfService&mode=login`;
+
+/**
+ * Returns Sign up URL for self service app.
+ *
+ * @returns {string}
+ */
+export const getSelfServiceSignupUrl = () =>
+ `${config.URL.AUTH}?retUrl=${encodeURIComponent(
+ `${window.location.origin}/self-service`
+ )}®Source=selfService&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"