diff --git a/.circleci/config.yml b/.circleci/config.yml index 114ab17160..ea2e339f75 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -349,7 +349,7 @@ workflows: filters: branches: only: - - develop + - reskin-profile # This is alternate dev env for parallel testing - "build-test": context : org-global @@ -370,7 +370,8 @@ workflows: filters: branches: only: - - reskin + - footer-update + - reskin-profile # This is stage env for production QA releases - "build-prod-staging": context : org-global diff --git a/__tests__/shared/components/ProfilePage/Skill/__snapshots__/index.jsx.snap b/__tests__/shared/components/ProfilePage/Skill/__snapshots__/index.jsx.snap deleted file mode 100644 index 1df8853ee2..0000000000 --- a/__tests__/shared/components/ProfilePage/Skill/__snapshots__/index.jsx.snap +++ /dev/null @@ -1,38 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders a skill correctly 1`] = ` -
-
- -
-
-
- Test Skill -
-
- -
-
-
-`; diff --git a/__tests__/shared/components/ProfilePage/Skill/index.jsx b/__tests__/shared/components/ProfilePage/Skill/index.jsx deleted file mode 100644 index 4ea54d77f0..0000000000 --- a/__tests__/shared/components/ProfilePage/Skill/index.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; - -import Skill from 'components/ProfilePage/Skill'; - -const rnd = new Renderer(); - -it('renders a skill correctly', () => { - rnd.render(()); - expect(rnd.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/ProfilePage/__snapshots__/index.jsx.snap b/__tests__/shared/components/ProfilePage/__snapshots__/index.jsx.snap index 212ffd27cc..440ab14076 100644 --- a/__tests__/shared/components/ProfilePage/__snapshots__/index.jsx.snap +++ b/__tests__/shared/components/ProfilePage/__snapshots__/index.jsx.snap @@ -4,615 +4,711 @@ exports[`renders a full Profile correctly 1`] = `
-
+
+
- + +

-

- -
-
+ Test Description +

-
-
-

- Skills -

-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- - VIEW ALL - -
-
-
- -
-
-

- On The Web -

-
- - - -
-
+ } + wins={0} + />
+
`; @@ -620,170 +716,181 @@ exports[`renders an empty Profile correctly 1`] = `
-
+
+
- +

-

- -
-
+ Test Description +

-
-

- BEEP. BEEP. HELLO! -

- -

- Seems like this member doesn’t have much information to share yet. -

-
-
- -
+ } + wins={0} + />
+
`; diff --git a/src/assets/images/chevron-down-green.svg b/src/assets/images/chevron-down-green.svg new file mode 100644 index 0000000000..970cd41137 --- /dev/null +++ b/src/assets/images/chevron-down-green.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/profile/header-overlay-mobile.svg b/src/assets/images/profile/header-overlay-mobile.svg new file mode 100644 index 0000000000..816be6bed6 --- /dev/null +++ b/src/assets/images/profile/header-overlay-mobile.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/profile/header-overlay.svg b/src/assets/images/profile/header-overlay.svg new file mode 100644 index 0000000000..b4d2bc7d82 --- /dev/null +++ b/src/assets/images/profile/header-overlay.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/profile/ico-info.svg b/src/assets/images/profile/ico-info.svg new file mode 100644 index 0000000000..159d215f4e --- /dev/null +++ b/src/assets/images/profile/ico-info.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/profile/link-button.svg b/src/assets/images/profile/link-button.svg new file mode 100644 index 0000000000..a3449c315e --- /dev/null +++ b/src/assets/images/profile/link-button.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/profile/verified-badge.svg b/src/assets/images/profile/verified-badge.svg new file mode 100644 index 0000000000..e95bc1c601 --- /dev/null +++ b/src/assets/images/profile/verified-badge.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/profile/verified-icon-white.svg b/src/assets/images/profile/verified-icon-white.svg new file mode 100644 index 0000000000..78b59c15cb --- /dev/null +++ b/src/assets/images/profile/verified-icon-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/profile/verified-icon.svg b/src/assets/images/profile/verified-icon.svg new file mode 100644 index 0000000000..83fb0dca95 --- /dev/null +++ b/src/assets/images/profile/verified-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/shared/components/ProfilePage/Activity/ActivityCard/index.jsx b/src/shared/components/ProfilePage/Activity/ActivityCard/index.jsx new file mode 100644 index 0000000000..6bc5058817 --- /dev/null +++ b/src/shared/components/ProfilePage/Activity/ActivityCard/index.jsx @@ -0,0 +1,181 @@ +/* eslint-disable react/no-array-index-key */ +import React, { useState } from 'react'; +import PT from 'prop-types'; +import { Modal } from 'topcoder-react-ui-kit'; +import { + getSummary, +} from 'utils/memberStats'; + +import IconClose from 'assets/images/icon-close-green.svg'; +import ChevronDown from 'assets/images/chevron-down-green.svg'; +import LinkButton from 'assets/images/profile/link-button.svg'; +import { getRatingColor } from 'utils/tc'; + +import { Link } from 'react-router-dom'; +import _ from 'lodash'; + +import style from './styles.scss'; + +const trackNameMap = { + DESIGN: 'UX / UI Design', + COPILOT: 'SPECIALIZED ROLES', + DATA_SCIENCE: 'DATA SCIENCE', + DEVELOP: 'DEVELOPMENT', + QA: 'QUALITY ASSURANCE', +}; + +const ActivityCard = ({ + trackName, subTracks, handle, hasMM, stats, +}) => { + const [collapsed, setCollapsed] = useState(false); + const [showModal, toggleModal] = useState(false); + const [curSubtrack, setCurSubtrack] = useState(''); + + const onShowModal = (substrack) => { + setCurSubtrack(substrack); + toggleModal(true); + }; + + const subTrackSummary = getSummary(stats && stats[0], trackName, curSubtrack) || []; + return ( +
+
setCollapsed(!collapsed)}> + {trackNameMap[trackName]} + +
+ +
+
+ + { + !collapsed + && ( +
+ {showModal && curSubtrack === 'COPILOT' && ( + toggleModal(false)} theme={style}> +
+
+

[COPILOT CATEGORY]

+
toggleModal(false)}> + +
+
+
+ { + subTrackSummary.map(({ label, value }) => ( +
+
+ {value || '-'} +
+

+ {label} +

+
+ )) + } +
+
+
+ )} + { + subTracks.map((subtrack, index) => ( +
+ + {_.upperFirst(subtrack.name.replace('FIRST_2_FINISH', 'FIRST2FINISH').replace(/_/g, ' ').toLowerCase())} + + +
+ { + subtrack.rank && !_.isUndefined(subtrack.rank.rating) + && ( +
+
+ {subtrack.name === 'MARATHON MATCH' && !subtrack.challenges && hasMM ? '' : subtrack.rank.rating} +
+
+ {subtrack.name === 'MARATHON MATCH' && !subtrack.challenges && hasMM ? ' No Rating' : ' Rating'} +
+
+ ) + } + + { + (!subtrack.rank || _.isUndefined(subtrack.rank.rating)) + && !subtrack.fulfillment + && ( +
+
+ {subtrack.wins ? subtrack.wins : 0} +
+
+ Wins +
+
+ ) + } + + { + subtrack.fulfillment + && ( +
+
+ {`${subtrack.fulfillment}%`} +
+
+ Fulfillment +
+
+ ) + } + {subtrack.name.replace(' ', '_') + !== 'COPILOT' + ? ( + + + + ) : ( +
onShowModal(subtrack.name.replace(' ', '_'))} + > + +
+ )} +
+ +
+ )) + } +
+ ) + } +
+ ); +}; + +ActivityCard.defaultProps = { + subTracks: [], + stats: [], +}; + +ActivityCard.propTypes = { + stats: PT.arrayOf(PT.shape()), + trackName: PT.string.isRequired, + subTracks: PT.arrayOf(PT.shape()), + handle: PT.string.isRequired, + hasMM: PT.bool.isRequired, +}; + +export default ActivityCard; diff --git a/src/shared/components/ProfilePage/Activity/ActivityCard/styles.scss b/src/shared/components/ProfilePage/Activity/ActivityCard/styles.scss new file mode 100644 index 0000000000..8627eb9a38 --- /dev/null +++ b/src/shared/components/ProfilePage/Activity/ActivityCard/styles.scss @@ -0,0 +1,293 @@ +@import "~styles/mixins"; + +:global { + @include xs-to-sm { + body.scrolling-disabled-by-modal { + > div { + overflow: hidden; + } + } + } +} + +.activity-card { + padding: 32px; + background-color: $tc-white; + border-radius: 8px; + margin: 0 32px 8px 32px; + + @include xs-to-sm { + margin: 0 16px 8px 16px; + } + + &.hidden { + padding-bottom: 1px; + } + + .track-name { + margin-bottom: 32px; + display: flex; + justify-content: space-between; + cursor: pointer; + + span { + @include barlow-bold; + + font-weight: 600; + font-size: 22px; + line-height: 26px; + text-transform: uppercase; + } + + .arrow { + margin-top: 5px; + margin-right: 3px; + + &.down { + transform: rotate(180deg); + } + } + } +} + +.sub-track-wrapper { + display: grid; + grid-template-columns: 1fr 1fr; + grid-column-gap: 64px; + + &.full-width { + grid-template-columns: 1fr; + } + + @include xs-to-sm { + grid-template-columns: unset; + } +} + +.sub-track-item { + border-bottom: 1px solid $profile-border-gray; + padding: 16px 0; + display: flex; + justify-content: space-between; + + &:nth-child(-n + 2) { + border-top: 1px solid $profile-border-gray; + } + + span.title { + @include roboto-regular; + + font-weight: 400; + font-size: 16px; + line-height: 24px; + color: $tco-black; + text-transform: capitalize; + } +} + +.link-button { + border: 1.5px solid $listing-checkbox-green; + border-radius: 24px; + width: 35px; + height: 27px; + display: flex; + flex-direction: row; + justify-content: center; + margin-top: -2px; + + svg { + margin-top: 8px; + } +} + +.right { + display: flex; +} + +.ranking { + display: flex; + margin-right: 16px; + + .number { + @include roboto-bold; + + font-weight: 700; + font-size: 16px; + line-height: 24px; + text-transform: uppercase; + color: $profile-skill-badge; + } + + .tag { + @include roboto-bold; + + font-weight: 700; + font-size: 16px; + line-height: 24px; + text-transform: uppercase; + color: $profile-skill-badge; + margin-left: 5px; + } +} + +.stats { + padding-bottom: 10px; + border-radius: 8px; + margin: 25px 32px 32px; + + .stat-body { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + padding: 24px; + background: #f4f4f4; + border-radius: 4px; + + @include xs-to-sm { + flex-direction: column; + border-radius: 8px; + } + + .stat-item { + display: flex; + align-items: flex-end; + + @include xs-to-sm { + margin-bottom: 18px; + } + + .value { + @include barlow-condensed; + + font-style: normal; + font-weight: 500; + font-size: 44px; + line-height: 44px; + color: #1e94a3; + } + + .label { + @include roboto-regular; + + margin-left: 4px; + color: #2a2a2a; + font-weight: 400; + font-size: 15px; + line-height: 24px; + text-transform: capitalize; + } + } + } + + .table-body { + max-height: 49vh; + overflow-y: auto; + + @media screen and (max-width: $screen-sm) { + max-height: 65vh; + } + } + + @include xs-to-sm { + margin: 0 0; + } +} + +.stats-head { + display: flex; + margin-top: 24px; + width: 100%; + padding-bottom: 16px; + border-bottom: 1px solid #e9e9e9; + + @include xs-to-sm { + display: none; + } +} + +.tooltip { + font-size: 14px; + margin: 10px 10px 0 10px; + padding-top: 5px; +} + +.level-1 { + color: $member-gray !important; +} + +.level-2 { + color: $member-green !important; +} + +.level-3 { + color: $member-blue !important; +} + +.level-4 { + color: $member-yellow !important; +} + +.level-5 { + color: $member-red !important; +} + +.header { + display: flex; + justify-content: space-between; + margin-bottom: 25px; + + .title { + @include barlow-medium; + + font-weight: 600; + color: #2a2a2a; + font-size: 18px; + line-height: 22px; + text-transform: uppercase; + } + + .icon { + cursor: pointer; + margin-top: 5px; + } +} + +hr { + opacity: 0.5; +} + +.container { + width: 90vw !important; + border-radius: 8px; + + @include xs-to-sm { + width: 100vw !important; + height: 100vh !important; + margin: 0; + max-width: 100vw; + max-height: 100vh; + padding: 24px 16px; + z-index: 999999; + transform: initial; + left: 0; + top: 0; + } +} + +.mobile-header { + display: none; + + @include xs-to-sm { + display: block; + margin-top: 16px; + font-size: 11px; + line-height: 14px; + + @include barlow-bold; + + font-weight: 600; + color: #767676; + text-align: left; + } +} diff --git a/src/shared/components/ProfilePage/Activity/index.jsx b/src/shared/components/ProfilePage/Activity/index.jsx new file mode 100644 index 0000000000..31e250390e --- /dev/null +++ b/src/shared/components/ProfilePage/Activity/index.jsx @@ -0,0 +1,142 @@ +import React from 'react'; +import PT from 'prop-types'; +import _ from 'lodash'; + +import ActivityCard from './ActivityCard'; + +import './styles.scss'; + +/** + * Inspects a subtrack and determines if it should be hidden + * + * @param {Object} subtrack Subtrack object + * @returns {Boolean} + */ +const isHidden = (subtrack) => { + if (subtrack.name === 'DEVELOP_MARATHON_MATCH') { + return true; + } + + return false; +}; + +const Activity = ({ memberStats, hasMM, handle }) => { + /** + * Inspects a subtrack and determines if the member is active + * based on submissions and/or ranks. + * + * @param {Object} subtrack Subtrack object + * @return {Boolean} + */ + const isActiveSubtrack = (subtrack) => { + if (subtrack.name === 'COPILOT_POSTING') { + return false; + } + if (subtrack.rank && subtrack.rank.rating > 0) { + return true; + } + if (_.isNumber(subtrack.submissions)) { + return subtrack.submissions > 0; + } + return subtrack.submissions && subtrack.submissions.submissions > 0; + }; + + const QA_SUB_TRACKS = ['BUG_HUNT', 'TEST_SUITES', 'TEST_SCENARIOS']; + + const getActiveTracks = () => { + let stats; + if (_.isArray(memberStats)) { + // eslint-disable-next-line prefer-destructuring + stats = memberStats[0]; + } + const activeTracks = []; + const qaSubtracks = []; + + ['DESIGN', 'DEVELOP', 'DATA_SCIENCE'].forEach((track) => { + const active = []; + const subTracks = stats && stats[track] ? stats[track].subTracks || [] : []; + + if (stats && stats[track] && stats[track].SRM) { + subTracks.push({ ...stats[track].SRM, name: 'SRM' }); + } + if (stats && stats[track] && stats[track].MARATHON_MATCH) { + subTracks.push({ + ...stats[track].MARATHON_MATCH, + name: 'MARATHON MATCH', + }); + } + + subTracks.forEach((subtrack) => { + if (QA_SUB_TRACKS.includes(subtrack.name)) { + qaSubtracks.push({ ...subtrack, active: true }); + } else if ( + (isActiveSubtrack(subtrack) && !isHidden(subtrack)) + || (subtrack.name === 'MARATHON MATCH' && hasMM) + ) { + active.push({ ...subtrack, active: true }); + } + }); + if (active.length > 0) { + const sorted = _.orderBy( + active, + [s => s.wins, s => (s.rank ? s.rank.rating : 0)], + ['desc', 'desc'], + ); + activeTracks.push({ name: track, subTracks: sorted }); + } + }); + + if (qaSubtracks.length > 0) { + activeTracks.push({ + name: 'QA', + subTracks: qaSubtracks, + }); + } + + if (stats && stats.COPILOT && stats.COPILOT.fulfillment) { + activeTracks.push({ + name: 'COPILOT', + subTracks: [ + { + fulfillment: stats.COPILOT.fulfillment, + name: 'COPILOT', + }, + ], + }); + } + + return activeTracks; + }; + + const activeTracks = getActiveTracks(); + + return ( +
+
+ TOPCODER ACTIVITY +
+ + {activeTracks.map(activeTrack => ( + + ))} +
+ ); +}; + +Activity.defaultProps = { + memberStats: [], +}; + +Activity.propTypes = { + memberStats: PT.arrayOf(PT.shape()), + hasMM: PT.bool.isRequired, + handle: PT.string.isRequired, +}; + +export default Activity; diff --git a/src/shared/components/ProfilePage/Activity/styles.scss b/src/shared/components/ProfilePage/Activity/styles.scss new file mode 100644 index 0000000000..b1e6c9e9ec --- /dev/null +++ b/src/shared/components/ProfilePage/Activity/styles.scss @@ -0,0 +1,26 @@ +@import "~styles/mixins"; + +.activity { + background-color: $listing-filter-bg; + border-radius: 8px; + margin: 68px 0 16px 0; + padding-bottom: 32px; + + .header { + padding: 32px 0 40px 32px; + + @include xs-to-sm { + padding: 16px 0 16px 16px; + } + + span { + @include barlow-bold; + + font-weight: 600; + font-size: 22px; + line-height: 26px; + text-transform: uppercase; + color: $tco-black; + } + } +} diff --git a/src/shared/components/ProfilePage/Header/index.jsx b/src/shared/components/ProfilePage/Header/index.jsx index 96dde2f133..c87fe95f73 100644 --- a/src/shared/components/ProfilePage/Header/index.jsx +++ b/src/shared/components/ProfilePage/Header/index.jsx @@ -1,162 +1,92 @@ /** * Profile Header. Displays the name, country, potrait and quote for the member. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import PT from 'prop-types'; -import { noop, get, indexOf } from 'lodash'; -import moment from 'moment'; -import ReactSVG from 'react-svg'; -import { getRatingLevel } from 'utils/tc'; -import { config, isomorphy } from 'topcoder-react-utils'; +import { isomorphy } from 'topcoder-react-utils'; -import CopilotIcon from 'assets/images/profile/ico-track-copilot.svg'; -import DataScienceIcon from 'assets/images/profile/ico-track-data.svg'; -import DesignIcon from 'assets/images/profile/ico-track-design.svg'; -import DevelopIcon from 'assets/images/profile/ico-track-develop.svg'; +// import VerifiedBadge from 'assets/images/profile/verified-badge.svg'; +// import InfoIcon from 'assets/images/profile/ico-info.svg'; +// import Tooltip from 'components/Tooltip'; import './styles.scss'; -let assets; -if (isomorphy.isClientSide()) { - assets = require.context('assets/images', false, /svg/); -} - -const TRACK_LABELS = { - COPILOT: 'COPILOT', - DATA_SCIENCE: 'DATA SCIENTIST', - DESIGN: 'DESIGNER', - DEVELOP: 'DEVELOPER', -}; +const ProfileHeader = ({ info }) => { + const [imageUrl, setimageUrl] = useState(); -class ProfileHeader extends React.Component { - constructor(props) { - super(props); - const { - info, - } = this.props; - let photoURL = ''; - if (isomorphy.isClientSide()) { - ({ photoURL } = info); + useEffect(() => { + let url = ''; + const { photoURL } = info; + if (isomorphy.isClientSide() && photoURL) { + url = photoURL; } - this.state = { - imageUrl: photoURL, - }; - - this.loadImageError = this.loadImageError.bind(this); - } - - loadImageError() { - this.setState({ imageUrl: null }); - } - - render() { - const { - copilot, - country, - info, - onShowBadges, - showBadgesButton, - hasMM, - wins, - } = this.props; - const { imageUrl } = this.state; - return ( -
+ + setimageUrl(url); + }, []); + + const loadImageError = () => { + setimageUrl(null); + }; + + // const tooltipContent = ( + //
verified member
+ // ); + + const { handle } = info; + // const isMemberVerified = true; + + return ( +
+
+
- { imageUrl ? Member Portait : } -
-
-

- {info.handle} -

-

- {country} - {Boolean(wins) && ( - - {' '} - | - {' '} - {wins} - {' '} - Wins - - ) } -

-

- Member Since - {' '} - {moment(info.createdAt).format('MMMM, YYYY')} -

+ { imageUrl + ? Member Portait + : ( + // eslint-disable-next-line global-require + Member Portait + ) +}
- { - info.tracks && info.tracks.length > 0 - && ( -
-
- { - [...info.tracks, ...(indexOf(info.track, 'DATA_SCIENCE') === -1 && hasMM ? ['DATA_SCIENCE'] : []), ...(copilot ? ['COPILOT'] : [])].map(track => ( - - { track === 'COPILOT' && } - { track === 'DATA_SCIENCE' && } - { track === 'DESIGN' && } - { track === 'DEVELOP' && } -
- {TRACK_LABELS[track]} -
-
- )) - } -
+ +
+
+ {handle}
- ) - } - { info.description && ( -

- {info.description} -

- ) } -
- { - showBadgesButton ? ( - onShowBadges()} - onKeyPress={() => onShowBadges()} - role="link" - styleName="link badge-link" - tabIndex="0" - > - Badges - - ) : null - } - - Forum Posts - + + {/* { */} + {/* isMemberVerified && ( */} + {/*
*/} + {/* */} + + {/* verified member */} + + {/*
*/} + {/* */} + {/* */} + {/* */} + {/*
*/} + {/*
*/} + {/* ) */} + {/* } */}
+
- ); - } -} +
+ ); +}; + ProfileHeader.defaultProps = { - copilot: false, - hasMM: false, - country: '', info: {}, - onShowBadges: noop, - showBadgesButton: false, - wins: 0, }; ProfileHeader.propTypes = { - copilot: PT.bool, - hasMM: PT.bool, - country: PT.string, info: PT.shape(), - onShowBadges: PT.func, - showBadgesButton: PT.bool, - wins: PT.number, }; export default ProfileHeader; diff --git a/src/shared/components/ProfilePage/Header/styles.scss b/src/shared/components/ProfilePage/Header/styles.scss index 31c3d7ef79..7537655f7e 100644 --- a/src/shared/components/ProfilePage/Header/styles.scss +++ b/src/shared/components/ProfilePage/Header/styles.scss @@ -1,188 +1,120 @@ @import "~styles/mixins"; -.badge-link { - cursor: pointer; - outline: none; -} - -.track-icon { - height: 40px; - width: 40px; -} - .container { - display: flex; - flex-direction: column; - align-items: center; - - p.description { - word-wrap: break-word; - margin-top: 30px; - font-size: 15px; - line-height: 24px; + .curve { + background: linear-gradient(263.22deg, #038664 2.65%, #075f96 98.14%); + background-repeat: no-repeat; + background-position: center center; width: 100%; - - @include roboto-medium; - - text-align: center; - color: $tc-gray-90; - } - - .info { - display: flex; - flex-direction: column; - align-items: center; - - h1.handle { - text-align: center; - font-size: 28px; - line-height: 34px; - - @include roboto-medium; - - &.level-1 { - color: $tc-level-1; - } - - &.level-2 { - color: $tc-level-2; - } - - &.level-3 { - color: $tc-level-3; - } - - &.level-4 { - color: $tc-level-4; - } - - &.level-5 { - color: $tc-level-5; - } - } - - h3.tenure { - margin-top: 5px; - font-size: 12px; - line-height: 14px; - - @include roboto-light; - - color: $tc-gray-40; - text-align: center; - text-transform: uppercase; - } - - h3.location-challenges { - margin-top: 20px; - font-size: 12px; - line-height: 14px; - - @include roboto-medium; - - text-align: center; - text-transform: uppercase; + max-width: $screen-max; + margin: 0 auto; + height: 115px; + position: absolute; + top: 0; + + @include xs-to-sm { + height: 173px; } - p.description { - margin-top: 12px; - max-width: 600px; - text-align: left; + &::before { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 60px; + background: url(assets/images/profile/header-overlay.svg); + background-repeat: no-repeat; + + @include xs-to-sm { + height: 36px; + width: 100%; + bottom: -10px; + background-repeat: round; + } } } +} - img.profile-circle { - border-radius: 50%; - display: inline; - width: 140px; - height: 140px; - } - - h5 { - color: $tc-gray-50; +.header-container { + margin: 20px 32px 0 32px; + position: relative; + display: flex; - &.tracks { - text-transform: uppercase; - } + .profile-image { + width: 120px; + height: 120px; + border: 3px solid $tc-white; + border-radius: 50%; - &.location { - margin-top: 10px; + @include xs-to-sm { + position: absolute; + top: 88px; + left: 32vw; } } - .edit { - width: 99px; - margin-top: 30px; - text-align: center; - - @include roboto-bold; + @include xs-to-sm { + width: 100%; + margin: 20px 0 0 0; } +} - .tracks-links { - display: flex; - flex-direction: column; - align-items: center; - margin-top: 40px; - width: 100%; +.header-content { + margin-left: 28px; + + .member-handle { + @include roboto-medium; - .tracks { - display: flex; - flex-direction: row; - justify-content: center; + font-weight: 500; + color: $tc-white; + font-size: 32px; + line-height: 32px; - .track { - cursor: pointer; - display: flex; - width: 100%; - flex-wrap: wrap; - flex-direction: column; - align-items: center; - margin: 0 10px; - - .track-icon { - background-size: 40px; - width: 40px; - } - - .text { - @include roboto-light; - - color: $tc-gray-40; - font-size: 12px; - line-height: 12px; - margin-top: 11px; - text-transform: uppercase; - text-align: center; - word-wrap: break-word; - } - } + @include xs-to-sm { + font-size: 20px; + line-height: 28px; } } - .links { - margin-top: 30px; - width: 100%; + .verified-member { + margin-top: 10px; display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - .link, - .link:visited { - @include roboto-regular; + span { + @include roboto-bold; - color: $tc-gray-75; + font-weight: 700; + color: $tc-white; font-size: 12px; - line-height: 14px; - text-decoration: none; + line-height: 16px; text-transform: uppercase; - margin-left: 10px; - margin-right: 10px; - transition: color 0.2s; + margin-top: 5px; + margin-left: 4px; + letter-spacing: 1px; + + @include xs-to-sm { + font-size: 10px; + line-height: 12px; + } + } - &:hover { - color: $tc-dark-blue-110; + .info { + margin-top: 5px; + margin-left: 5px; + width: 13px; + height: 13px; + cursor: pointer; + + @include xs-to-sm { + margin-top: 3px; } } } } + +.tooltip-content { + padding: 10px; + background-color: #2a2a2a; + border-radius: 4px; +} diff --git a/src/shared/components/ProfilePage/MemberInfo/index.jsx b/src/shared/components/ProfilePage/MemberInfo/index.jsx new file mode 100644 index 0000000000..cf0a4bad47 --- /dev/null +++ b/src/shared/components/ProfilePage/MemberInfo/index.jsx @@ -0,0 +1,50 @@ +import React from 'react'; +import PT from 'prop-types'; +import moment from 'moment'; + +import './styles.scss'; + +const MemberInfo = ({ country, info, wins }) => ( +
+
+ { country } +
+ +

+ Member Since + {' '} + {moment(info.createdAt).format('MMM YYYY')} +

+ +
+

COMPETITION ACTIVITY

+ + {Boolean(wins) && ( +
+ + {wins} + {' '} + WINS + + +
+ ) + } +
+ +
+); + +MemberInfo.defaultProps = { + country: '', + info: {}, + wins: 0, +}; + +MemberInfo.propTypes = { + country: PT.string, + info: PT.shape(), + wins: PT.number, +}; + +export default MemberInfo; diff --git a/src/shared/components/ProfilePage/MemberInfo/styles.scss b/src/shared/components/ProfilePage/MemberInfo/styles.scss new file mode 100644 index 0000000000..2de245ca76 --- /dev/null +++ b/src/shared/components/ProfilePage/MemberInfo/styles.scss @@ -0,0 +1,50 @@ +@import "~styles/mixins"; + +.member-info { + .country { + span { + @include barlow-bold; + + font-weight: 600; + font-size: 18px; + line-height: 22px; + color: $tco-black; + text-transform: uppercase; + } + } + + .tenure { + @include roboto-regular; + + font-weight: 400; + font-size: 16px; + line-height: 24px; + color: $tco-black; + margin-top: 8px; + } + + .activity { + margin-top: 24px; + + h3 { + @include barlow-bold; + + font-weight: 600; + font-size: 18px; + line-height: 22px; + color: $tco-black; + text-transform: uppercase; + } + } + + .wins { + @include barlow-condensed; + + font-weight: 500; + color: $profile-member-wins; + font-size: 48px; + line-height: 50px; + margin-top: 8px; + text-transform: uppercase; + } +} diff --git a/src/shared/components/ProfilePage/MemberTracks/TrackItem/index.jsx b/src/shared/components/ProfilePage/MemberTracks/TrackItem/index.jsx new file mode 100644 index 0000000000..f664d31249 --- /dev/null +++ b/src/shared/components/ProfilePage/MemberTracks/TrackItem/index.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import PT from 'prop-types'; +import { v4 as uuidv4 } from 'uuid'; + +import './styles.scss'; + +const TrackItem = ({ trackName }) => ( +
+ {trackName} +
+); + + +TrackItem.propTypes = { + trackName: PT.string.isRequired, +}; + +export default TrackItem; diff --git a/src/shared/components/ProfilePage/MemberTracks/TrackItem/styles.scss b/src/shared/components/ProfilePage/MemberTracks/TrackItem/styles.scss new file mode 100644 index 0000000000..949ad63e24 --- /dev/null +++ b/src/shared/components/ProfilePage/MemberTracks/TrackItem/styles.scss @@ -0,0 +1,20 @@ +@import "~styles/mixins"; + +.track-item { + padding: 4px 10px; + border: 2px solid $listing-checkbox-green; + border-radius: 4px; + + @include xs-to-sm { + padding: 2px 10px; + } + + span { + @include roboto-medium; + + font-weight: 500; + font-size: 16px; + line-height: 24px; + text-align: center; + } +} diff --git a/src/shared/components/ProfilePage/MemberTracks/index.jsx b/src/shared/components/ProfilePage/MemberTracks/index.jsx new file mode 100644 index 0000000000..9b9d6436f1 --- /dev/null +++ b/src/shared/components/ProfilePage/MemberTracks/index.jsx @@ -0,0 +1,52 @@ +import React from 'react'; +import PT from 'prop-types'; +import { indexOf } from 'lodash'; + +import './styles.scss'; +import TrackItem from './TrackItem'; + +const MemberTracks = ({ + copilot, + info, + hasMM, +}) => { + const { tracks } = info; + + const trackMap = { + DEVELOP: 'Developer', + DESIGN: 'Designer', + DATA_SCIENCE: 'Data Scientist', + }; + + return ( +
+ { + tracks && tracks.length > 0 + && ( +
+ { + [...info.tracks, ...(indexOf(info.tracks, 'DATA_SCIENCE') === -1 && hasMM ? ['DATA_SCIENCE'] : []), ...(copilot ? ['COPILOT'] : [])].map(track => ( + + )) + } +
+ ) + } +
+ ); +}; + + +MemberTracks.defaultProps = { + copilot: false, + hasMM: false, + info: {}, +}; + +MemberTracks.propTypes = { + copilot: PT.bool, + hasMM: PT.bool, + info: PT.shape(), +}; + +export default MemberTracks; diff --git a/src/shared/components/ProfilePage/MemberTracks/styles.scss b/src/shared/components/ProfilePage/MemberTracks/styles.scss new file mode 100644 index 0000000000..25b67643b1 --- /dev/null +++ b/src/shared/components/ProfilePage/MemberTracks/styles.scss @@ -0,0 +1,11 @@ +@import "~styles/mixins"; + +.member-tracks { + display: flex; + margin-top: 19px; + gap: 16px; + + @include xs-to-sm { + gap: 8px; + } +} diff --git a/src/shared/components/ProfilePage/Skill/index.jsx b/src/shared/components/ProfilePage/Skill/index.jsx deleted file mode 100644 index ad2e27adee..0000000000 --- a/src/shared/components/ProfilePage/Skill/index.jsx +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Skill Component. Displays an icon and label dynamically based on data. - */ -import React from 'react'; -import PT from 'prop-types'; -import { truncate } from 'lodash'; - -import FallbackIcon from 'assets/images/profile/skills/id-develop.svg'; -import VerifiedBadgeIcon from 'assets/images/verified-skill-badge.svg'; -import { isomorphy } from 'topcoder-react-utils'; - -import './styles.scss'; - -let assets; -if (isomorphy.isClientSide()) { - assets = require.context('assets/images/profile/skills', false, /svg/); -} - -const Skill = ({ - tagId, - tagName, - isVerified, -}) => ( -
-
- { assets && assets.keys().includes(`./id-${tagId}.svg`) ? {`${tagName} : } -
-
-
- {truncate(tagName, 20)} -
- { isVerified &&
} -
-
-); - -Skill.propTypes = { - tagId: PT.string.isRequired, - tagName: PT.string.isRequired, - isVerified: PT.bool.isRequired, -}; - -export default Skill; diff --git a/src/shared/components/ProfilePage/Skill/styles.scss b/src/shared/components/ProfilePage/Skill/styles.scss deleted file mode 100644 index d41c275f87..0000000000 --- a/src/shared/components/ProfilePage/Skill/styles.scss +++ /dev/null @@ -1,70 +0,0 @@ -@import "~styles/mixins"; - -.container { - display: flex; - flex-direction: column; - align-items: center; - height: 106px; - width: 80px; - - @media (min-width: 768px) { - width: 100px; - height: 128px; - } - - a:hover { - text-decoration: none; - } - - .skill-icon { - position: relative; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background-color: $tc-gray-neutral-light; - padding: 10px; - border: 1px solid $tc-gray-10; - height: 80px; - width: 80px; - - @media (min-width: 768px) { - height: 100px; - width: 100px; - } - - img { - width: 48px; - height: 48px; - - @media only screen and (min-width: 768px) { - width: 60px; - height: 60px; - } - } - } - - .name-wrapper { - margin-top: 8px; - display: flex; - align-items: center; - justify-content: center; - - .name { - @include roboto-light; - - font-size: 12px; - color: $tc-gray-90; - text-align: center; - - @media (min-width: 768px) { - font-size: 14px; - } - } - - .verified-badge { - padding-left: 5px; - font-size: 0; - } - } -} diff --git a/src/shared/components/ProfilePage/Skills/List/index.jsx b/src/shared/components/ProfilePage/Skills/List/index.jsx new file mode 100644 index 0000000000..29234cdbfc --- /dev/null +++ b/src/shared/components/ProfilePage/Skills/List/index.jsx @@ -0,0 +1,75 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React, { useEffect, useState } from 'react'; +import PT from 'prop-types'; + +import VerifiedIconWhite from 'assets/images/profile/verified-icon-white.svg'; + +import './styles.scss'; + +const List = ({ + skills, + isVerified, + isMobile, +}) => { + const MAX_VISIBLE = isMobile ? 3 : 5; + + const [isWrapped, setIsWrapped] = useState(false); + const [visibleSkills, setVisibleSkills] = useState([]); + + useEffect(() => { + if (skills.length > MAX_VISIBLE) { + setIsWrapped(true); + setVisibleSkills(skills.slice(0, MAX_VISIBLE)); + } else { + setIsWrapped(false); + setVisibleSkills(skills); + } + }, []); + + const showHiddenSkills = () => { + setVisibleSkills(skills); + setIsWrapped(false); + }; + + return ( +
+ { + visibleSkills.map(({ + tagId, tagName, hidden, + }) => ( + !hidden + && ( +
+
+ { + isVerified &&
+ } + {tagName} +
+
+ ) + )) + } + { + isWrapped && ( +
+ +{skills.length - visibleSkills.length} +
+ ) + } +
+ ); +}; + +List.propTypes = { + skills: PT.arrayOf(PT.shape()).isRequired, + isVerified: PT.bool.isRequired, + isMobile: PT.bool.isRequired, +}; + +export default List; diff --git a/src/shared/components/ProfilePage/Skills/List/styles.scss b/src/shared/components/ProfilePage/Skills/List/styles.scss new file mode 100644 index 0000000000..e43df3f141 --- /dev/null +++ b/src/shared/components/ProfilePage/Skills/List/styles.scss @@ -0,0 +1,50 @@ +@import "~styles/mixins"; + +.list { + display: flex; + gap: 4px; + margin-top: 8px; + flex-wrap: wrap; +} + +.skill { + background-color: $profile-skill-badge; + width: fit-content; + padding: 4px 8px; + border-radius: 4px; + display: flex; + + .verified-icon { + margin-right: 5px; + display: flex; + } + + span { + @include roboto-medium; + + font-weight: 500; + font-size: 14px; + line-height: 16px; + letter-spacing: 0.5px; + color: $tc-white; + white-space: nowrap; + } +} + +.add-button { + border-radius: 4px; + border: 2px solid $profile-skill-badge; + cursor: pointer; + width: fit-content; + padding: 1px 6px; + + span { + @include roboto-medium; + + font-weight: 500; + font-size: 14px; + line-height: 16px; + letter-spacing: 0.5px; + color: $profile-skill-badge; + } +} diff --git a/src/shared/components/ProfilePage/Skills/index.jsx b/src/shared/components/ProfilePage/Skills/index.jsx new file mode 100644 index 0000000000..2ed82e4f0a --- /dev/null +++ b/src/shared/components/ProfilePage/Skills/index.jsx @@ -0,0 +1,50 @@ +import React from 'react'; +import PT from 'prop-types'; + +import './styles.scss'; +import _ from 'lodash'; +import VerifiedBadge from 'assets/images/profile/verified-icon.svg'; +import List from './List'; + +const Skills = ({ skills, isMobile }) => { + const verifiedSkills = _.filter(skills, skill => _.includes(skill.sources, 'CHALLENGE')); + const userEnteredSkills = _.filter(skills, skill => !_.includes(skill.sources, 'CHALLENGE')); + + return ( +
+

+ Skills +

+ + + + + +
+ + + = Topcoder Verified +
+ +
+ ); +}; + +Skills.defaultProps = { + skills: [], +}; + +Skills.propTypes = { + skills: PT.arrayOf(PT.shape), + isMobile: PT.bool.isRequired, +}; + +export default Skills; diff --git a/src/shared/components/ProfilePage/Skills/styles.scss b/src/shared/components/ProfilePage/Skills/styles.scss new file mode 100644 index 0000000000..2182c04193 --- /dev/null +++ b/src/shared/components/ProfilePage/Skills/styles.scss @@ -0,0 +1,30 @@ +@import "~styles/mixins"; + +.skills { + margin-top: 24px; + + .title { + @include barlow-bold; + + font-weight: 600; + font-size: 16px; + line-height: 18px; + color: $tco-black; + text-transform: uppercase; + } +} + +.info { + @include roboto-regular; + + font-weight: 400; + font-size: 14px; + line-height: 22px; + color: $tco-black; + margin-top: 8px; + display: flex; + + span { + margin-left: 4px; + } +} diff --git a/src/shared/components/ProfilePage/index.jsx b/src/shared/components/ProfilePage/index.jsx index 56be6153de..6ec47bde38 100644 --- a/src/shared/components/ProfilePage/index.jsx +++ b/src/shared/components/ProfilePage/index.jsx @@ -6,22 +6,16 @@ import _ from 'lodash'; import React from 'react'; import PT from 'prop-types'; -import { PrimaryButton } from 'topcoder-react-ui-kit'; -import Sticky from 'react-stickynode'; import { isomorphy } from 'topcoder-react-utils'; -import Robot from 'assets/images/robot-happy.svg'; - -import BadgesModal from './BadgesModal'; -import ExternalLink, { dataMap } from './ExternalLink'; +import { dataMap } from './ExternalLink'; import Header from './Header'; -import Skill from './Skill'; - -import style from './styles.scss'; -import StatsCategory from './StatsCategory'; +import MemberTracks from './MemberTracks'; -// Number of skills to show before a 'VIEW MORE' button is created -const MAX_SKILLS = 10; +import './styles.scss'; +import Skills from './Skills'; +import MemberInfo from './MemberInfo'; +import Activity from './Activity'; /** * Inspects a subtrack and determines if the member is active @@ -36,7 +30,8 @@ const isActiveSubtrack = (subtrack) => { } if (subtrack.rank && subtrack.rank.rating > 0) { return true; - } if (_.isNumber(subtrack.submissions)) { + } + if (_.isNumber(subtrack.submissions)) { return subtrack.submissions > 0; } return subtrack.submissions && subtrack.submissions.submissions > 0; @@ -46,9 +41,7 @@ class ProfilePage extends React.Component { constructor(props) { super(props); this.state = { - badgesModalOpen: false, isMobile: false, - skillsExpanded: false, }; this.handleResize = this.handleResize.bind(this); @@ -79,10 +72,12 @@ class ProfilePage extends React.Component { if (copilot && stats && stats.COPILOT && stats.COPILOT.fulfillment) { activeTracks.push({ name: 'COPILOT', - subTracks: [{ - fulfillment: stats.COPILOT.fulfillment, - name: 'COPILOT', - }], + subTracks: [ + { + fulfillment: stats.COPILOT.fulfillment, + name: 'COPILOT', + }, + ], }); } @@ -94,7 +89,10 @@ class ProfilePage extends React.Component { subTracks.push({ ...stats[track].SRM, name: 'SRM' }); } if (stats && stats[track] && stats[track].MARATHON_MATCH) { - subTracks.push({ ...stats[track].MARATHON_MATCH, name: 'MARATHON MATCH' }); + subTracks.push({ + ...stats[track].MARATHON_MATCH, + name: 'MARATHON MATCH', + }); } subTracks.forEach((subtrack) => { @@ -103,10 +101,11 @@ class ProfilePage extends React.Component { } }); if (active.length > 0) { - const sorted = _.orderBy(active, [ - s => s.wins, - s => (s.rank ? s.rank.rating : 0), - ], ['desc', 'desc']); + const sorted = _.orderBy( + active, + [s => s.wins, s => (s.rank ? s.rank.rating : 0)], + ['desc', 'desc'], + ); activeTracks.push({ name: track, subTracks: sorted }); } }); @@ -120,7 +119,6 @@ class ProfilePage extends React.Component { render() { const { - achievements, copilot, externalAccounts, externalLinks, @@ -130,11 +128,7 @@ class ProfilePage extends React.Component { lookupData, } = this.props; - const { - badgesModalOpen, - isMobile, - skillsExpanded, - } = this.state; + const { isMobile } = this.state; let { info } = this.props; @@ -146,169 +140,67 @@ class ProfilePage extends React.Component { let country = ''; if (_.has(lookupData, 'countries') && lookupData.countries.length > 0) { const countryCode = _.isEmpty(_.get(info, 'homeCountryCode')) - ? _.get(info, 'competitionCountryCode') : _.get(info, 'homeCountryCode'); + ? _.get(info, 'competitionCountryCode') + : _.get(info, 'homeCountryCode'); - const result = _.find(lookupData.countries, - c => countryCode && c.countryCode === countryCode.toUpperCase()); + const result = _.find( + lookupData.countries, + c => countryCode && c.countryCode === countryCode.toUpperCase(), + ); country = _.isEmpty(result) ? '' : result.country; } // Convert skills from object to an array for easier iteration - let skills = propSkills ? _.map(propSkills, (skill, tagId) => ({ tagId, ...skill })) : []; - const showMoreButton = skills.length > MAX_SKILLS; - if (!skillsExpanded) { - skills = skills.slice(0, MAX_SKILLS); - } - - let externals = externalAccounts ? _.map(_.pick(externalAccounts, _.map(dataMap, 'provider')), (data, type) => ({ type, data })) : []; + const skills = propSkills + ? _.map(propSkills, (skill, tagId) => ({ tagId, ...skill })) + : []; + + let externals = externalAccounts + ? _.map( + _.pick(externalAccounts, _.map(dataMap, 'provider')), + (data, type) => ({ type, data }), + ) + : []; if (externalLinks) { - externalLinks.map(data => externals.push(({ type: 'weblink', data }))); + externalLinks.map(data => externals.push({ type: 'weblink', data })); externals = _.filter(externals, 'data'); externals = _.sortBy(externals, 'type'); } - const activeTracks = this.getActiveTracks(); // no rating MM const hasMM = challenges && challenges.length; return (
- { - badgesModalOpen - && ( - this.setState({ badgesModalOpen: false })} - /> - ) - } -
-
-
- -
-
this.setState({ badgesModalOpen: true })} - showBadgesButton={achievements && achievements.length > 0} - wins={_.get((stats && stats[0]) || {}, 'wins', 0)} - /> -
-
+
+
+ +
+
+ + + {!_.isEmpty(skills) && ( + + )} + + {info.description && ( +

{info.description}

+ )}
-
- { - _.isEmpty(skills) && _.isEmpty(activeTracks) && _.isEmpty(externals) - && ( -
-

- BEEP. BEEP. HELLO! -

- -

- Seems like this member doesn’t have much information to share yet. -

-
- ) - } - { - !_.isEmpty(skills) - && ( -
-
-

- Skills -

-
- { - skills.map(({ - tagId, tagName, hidden, sources, - }) => ( - !hidden - && ( -
- -
- ) - )) - } -
- { - showMoreButton && !skillsExpanded - && ( - this.setState({ skillsExpanded: true })} - theme={style} - > - VIEW ALL - - ) - } - { - skillsExpanded - && ( - this.setState({ skillsExpanded: false })} - theme={style} - > - VIEW LESS - - ) - } -
-
- ) - } - { - !_.isEmpty(stats) && ( -
- -
- ) - } - { - !_.isEmpty(externals) - && ( -
-

- On The Web -

-
- { - externals.map(external => ( - - )) - } -
-
- ) - } +
+
+
); } @@ -317,14 +209,12 @@ class ProfilePage extends React.Component { ProfilePage.defaultProps = { externalAccounts: null, externalLinks: null, - achievements: [], challenges: null, skills: null, stats: null, }; ProfilePage.propTypes = { - achievements: PT.arrayOf(PT.shape()), copilot: PT.bool.isRequired, externalAccounts: PT.shape(), challenges: PT.arrayOf(PT.shape()), diff --git a/src/shared/components/ProfilePage/styles.scss b/src/shared/components/ProfilePage/styles.scss index fabab8207d..869f3efe00 100644 --- a/src/shared/components/ProfilePage/styles.scss +++ b/src/shared/components/ProfilePage/styles.scss @@ -1,517 +1,56 @@ @import "~styles/mixins"; -@mixin module-s { - margin-top: 6px; - background-color: $tc-white; -} - -@mixin module-l { - @include module-s; - - max-width: 1242px; - margin-left: 10px; - margin-right: 10px; - padding-top: 30px; - - @media only screen and (min-width: 900px) { - padding-top: 30px; - } -} - -.empty-profile { - @include roboto-light; - - align-items: center; - background-color: $tc-white; - color: $tc-gray-80; - display: flex; - flex-direction: column; - font-size: 18px; - height: 330px; - justify-content: space-around; - padding-top: 3 * $base-unit; - text-align: center; - - h2 { - font-size: 22px; - font-weight: 500; - } -} - .outer-container { - background: $tc-gray-neutral-light; - width: 100%; - - @include xs-to-sm { - padding: 8px 10px 20px; - } - - @include md { - padding: 30px 14px 30px 20px; - } - - @include lg-to-xl { - padding: 30px 64px; - } -} - -.button { - font-size: 12px; - height: 30px; - margin-bottom: 30px; - min-height: 30px; - line-height: 28px; - padding: 0 10px; -} - -.profile-container { - align-items: center; - display: flex; - justify-content: space-around; - flex-direction: column; width: 100%; + max-width: $screen-max; + margin: 0 auto; } -hr { - width: 100%; - border: 1px solid $tc-gray-20; - margin-bottom: 15px; -} - -// Main container -// This is the flex container; we convert it to 2 column layout on >767px width -.about-container { +.content { + margin: 0 32px 32px 32px; display: flex; - flex-direction: column; width: 100%; - @include module-l; - - background-color: transparent; - padding-top: 0; - - @media (min-width: 768px) { - padding-top: 30px; - flex-direction: row; + @include xs-to-md { + flex-direction: column; + margin: 180px 16px 16px 16px; } } -.external-links-container { - background-color: $tc-white; - margin-top: 0; - padding-top: 0; - text-align: center; - - h3 { - font-size: 18px; - line-height: 21px; - margin-bottom: 20px; - - @include roboto-medium; +.left-content { + width: 73%; + padding-right: 45px; - text-transform: uppercase; - } -} + .description { + @include roboto-regular; -.external-links { - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - margin-top: 1px; - padding: 0 30px 30px 30px; -} + font-weight: 400; + font-size: 16px; + line-height: 24px; + color: $tco-black; + margin-top: 24px; -// Both containers are flex column, then flex fows on >767px -// User data container -.profile-header-container { - background: transparent; - width: 100%; - max-width: 100%; - margin-bottom: 5px; - display: flex; - position: relative; - - @media (min-width: 768px) { - max-width: 368px; - margin: 0 auto; - padding-right: 10px; - display: block; - position: relative; - } - - // Sticky container - .sticky-container { - background-color: $tc-white; - display: flex; - justify-content: center; - width: calc(100vw - 40px); - - @media (min-width: 768px) { - display: block; - box-sizing: border-box; - width: 368px; - margin: 0; + @include xs-to-sm { + padding-bottom: 32px; + border-bottom: 2px solid $listing-gray; } - - padding: 30px 34px; - margin: 0 10px; - transition: 0.2s all; } -} - -// Where we show the info -.profile-about-container { - display: flex; - flex-direction: column; - width: 100%; - max-width: 883px; - padding: 0 10px; - .skills { + @include xs-to-md { + padding-right: 16px; width: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - align-self: center; - background: $tc-white; - text-align: center; - margin-left: auto; - margin-right: auto; - - .list { - display: flex; - flex-direction: row; - justify-content: center; - flex-wrap: wrap; - width: 100%; - margin-left: auto; - margin-bottom: 15px; - margin-right: 15px; - - .skill { - margin-top: 20px; - margin-right: 15px; - - &:first-of-type { - margin-left: 15px; - } - - a:hover { - cursor: default; - } - - + .skill { - margin-left: 15px; - } - } - } - - .dimmed { - width: 100%; - margin-top: -70px; - height: 100px; - background: rgba(0, 0, 0, 0.6); - opacity: 1; - transition: all 0.5s; - -webkit-transition: all 0.5s; - } - - h1 { - font-size: 25px; - margin: auto; - } - - button.more { - margin-bottom: 20px; - } - } - - .activity { - font-size: 18px; - line-height: 21px; - - @include roboto-medium; - - text-transform: uppercase; - margin-top: 20px; - } - - .categories { - @include module-l; - - margin-left: auto; - margin-right: auto; - padding-top: 15px; - align-self: center; - background: $tc-white; - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - - .track { - margin-top: 22.4px; - display: flex; - flex-direction: column; - align-items: center; - padding-bottom: 30px; - - &.noclick { - cursor: default; - } - - .name { - margin: auto; - font-size: 20px; - line-height: 24px; - margin-bottom: 10.6px; - - @include roboto-extra-light; - } - - svg { - height: 20px; - width: 20px; - margin-right: 7px; - margin-bottom: -2px; - } - - .subtrack { - text-decoration: none; - color: $tc-black; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - width: 300px; - height: 52px; - border-bottom: 1px solid $tc-gray-neutral-dark; - - &.first { - border-top: 1px solid $tc-gray-neutral-dark; - } - - &:hover { - background-color: $tc-gray-neutral-dark; - } - - .name { - margin-left: 15px; - align-self: center; - flex-basis: 250px; - text-align: left; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 14px; - line-height: 18px; - text-transform: uppercase; - display: flex; - flex-direction: row; - justify-content: flex-start; - margin-top: 12px; - - @include roboto-light; - } - - .ranking { - margin: auto; - margin-right: 15px; - color: $tc-gray-80; - display: flex; - flex-direction: column; - align-items: flex-end; - - @include roboto-medium; - - .number { - text-align: center; - font-size: 13.4px; - line-height: 16px; - - span.square { - position: absolute; - margin-left: 5px; - width: 6px; - height: 6px; - } - } - - .tag { - text-transform: uppercase; - margin: 5px auto 0 auto; - - @include roboto-regular; - - font-size: 12px; - line-height: 10px; - color: $tc-gray-60; - } - } - - .arrow { - @include roboto-extra-light; - - color: $tc-gray-80; - height: 12px; - display: flex; - flex-direction: column; - justify-content: center; - } - } - - .icon { - width: 80px; - height: 80px; - margin: auto; - } - } } } -@media (min-width: 768px) { - .external-links-container { - h3 { - font-size: 24px; - line-height: 30px; - } - } - - .profile-about-container { - .activity { - font-size: 24px; - line-height: 30px; - margin-top: 30px; - } - - .skills { - display: flex; - flex-direction: column; - padding-top: 0; - padding-bottom: 0; - - .list { - display: flex; - flex-direction: row; - width: 100%; - padding-left: 30px; - padding-right: 30px; - margin: 0 auto 30px auto; - - .skill { - margin-top: 30px; - margin-right: 15px; - margin-left: 15px; - } - - + .skill { - margin-left: 15px; - } - - &:nth-child(3n + 1) { - margin-left: 15px !important; - border: 1px solid lime; - } - - .leftButton, - .rightButton { - height: 100px; - display: flex; - flex-direction: column; - justify-content: center; - - &:hover { - cursor: pointer; - } - } - } - - button.more { - margin-bottom: 30px; - } - } - - .categories { - margin-top: 1px; - padding-top: 0; +.right-content { + border-left: 2px solid #e9e9e9; + padding-left: 50px; + margin-top: -27px; - .activity { - font-size: 28px; - line-height: 34px; - - @include roboto-bold; - - text-transform: uppercase; - } - - .track { - width: 100%; - padding-top: 30px; - - .name { - svg { - height: 30px; - width: 30px; - margin-right: 7px; - margin-bottom: -4px; - } - - background-size: 30px; - background-position: 16px 4px; - font-weight: 200; - font-size: 24px; - line-height: 30px; - margin-bottom: 30px; - margin-top: 0; - - @include roboto-medium; - } - - .subtrack { - align-items: center; - width: 75%; - height: 60px; - - .name { - font-size: 18px; - flex-basis: 350px; - line-height: 24px; - margin-top: 17px; - margin-bottom: 20px; - } - - .ranking { - display: flex; - flex-direction: column; - align-items: flex-end; - - .number { - font-size: 18px; - line-height: 23px; - } - - .tag { - margin-top: 2px; - font-size: 12px; - line-height: 14px; - color: $tc-gray-60; - } - } - - .arrow { - color: $tc-gray-30; - height: 15px; - margin-right: 10px; - } - } - - .icon { - width: 80px; - height: 80px; - margin: auto; - } - } - } + @include xs-to-sm { + border-left: 0; + margin-top: 0; + padding-left: 0; + padding-top: 32px; } } diff --git a/src/styles/_mixins/_variables.scss b/src/styles/_mixins/_variables.scss index 646c5a514b..1d0be4396c 100644 --- a/src/styles/_mixins/_variables.scss +++ b/src/styles/_mixins/_variables.scss @@ -4,6 +4,7 @@ $tco-black: #2a2a2a; /* @media */ $screem-md: 1376px; $screen-sm: 768px; +$screen-max: 1376px; /* member rating colors */ $member-gray: #555; @@ -39,3 +40,8 @@ $listing-checkbox-green: #137d60; $listing-checkbox-blue: #2c95d7; $listing-white: #fff; $listing-avatar-white: #f0f0f0; + +/* profile page colors */ +$profile-skill-badge: #227681; +$profile-member-wins: #1e94a3; +$profile-border-gray: #e9e9e9;