diff --git a/.circleci/config.yml b/.circleci/config.yml index 57c0822d50..5b87843434 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -379,6 +379,7 @@ workflows: only: - develop - old-mm-fix + - reskin-profile-settings # Production builds are exectuted # when PR is merged to the master # Don't change anything in this configuration diff --git a/src/assets/images/nav-active-item-blue.svg b/src/assets/images/nav-active-item-blue.svg new file mode 100644 index 0000000000..4590585532 --- /dev/null +++ b/src/assets/images/nav-active-item-blue.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/shared/components/MemberSearch/EndOfResults/index.jsx b/src/shared/components/MemberSearch/EndOfResults/index.jsx deleted file mode 100644 index f4369ea2ba..0000000000 --- a/src/shared/components/MemberSearch/EndOfResults/index.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import './style.scss'; - -const EndOfResults = ({ endOfResultsText }) => ( -
{endOfResultsText}
-); - -EndOfResults.propTypes = { - endOfResultsText: PropTypes.string, -}; - -EndOfResults.defaultProps = { - endOfResultsText: 'End of results', -}; - -export default EndOfResults; diff --git a/src/shared/components/MemberSearch/EndOfResults/style.scss b/src/shared/components/MemberSearch/EndOfResults/style.scss deleted file mode 100644 index f3f49d2a9e..0000000000 --- a/src/shared/components/MemberSearch/EndOfResults/style.scss +++ /dev/null @@ -1,8 +0,0 @@ -@import '~tc-ui/src/styles/tc-includes'; - -.end-of-results { - margin: 20px auto; - text-align: center; - font-size: 12px; - color: $tc-gray-50; -} diff --git a/src/shared/components/MemberSearch/LoadingListItem/style.scss b/src/shared/components/MemberSearch/LoadingListItem/style.scss index 15681625dd..ff5040111b 100644 --- a/src/shared/components/MemberSearch/LoadingListItem/style.scss +++ b/src/shared/components/MemberSearch/LoadingListItem/style.scss @@ -120,7 +120,6 @@ margin-top: 10px; margin-right: 5px; border-radius: 13px; - background-color: $tc-gray-10; &:last-child { margin-right: 0; diff --git a/src/shared/components/MemberSearch/MemberItem/UserInfo/index.jsx b/src/shared/components/MemberSearch/MemberItem/UserInfo/index.jsx deleted file mode 100644 index 42713f9a50..0000000000 --- a/src/shared/components/MemberSearch/MemberItem/UserInfo/index.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import _ from 'lodash'; -import UserAvatar from '../../User/UserAvatar'; -import UserBio from '../../User/UserBio'; -import UsernameAndDetails from '../../User/UsernameAndDetails'; - -import './style.scss'; - -const UserInfo = ({ user, userPlace, withBio }) => { - let userBio; - - if (withBio && user.description) { - userBio = ; - } - - let userPlaceNumber = null; - if (_.isFinite(userPlace)) { - userPlaceNumber =
{userPlace + 1}
; - } - - return ( -
-
- {userPlaceNumber} - - - - -
- - {userBio} -
- ); -}; - -UserInfo.propTypes = { - user: PropTypes.shape({ - maxRating: PropTypes.shape({ - rating: PropTypes.number, - }), - photoURL: PropTypes.string, - handle: PropTypes.string, - competitionCountryCode: PropTypes.string, - wins: PropTypes.number, - createdAt: PropTypes.number, - description: PropTypes.string, - }).isRequired, - userPlace: PropTypes.number, - withBio: PropTypes.bool, -}; - -UserInfo.defaultProps = { - userPlace: null, - withBio: false, -}; - -export default UserInfo; diff --git a/src/shared/components/MemberSearch/MemberItem/UserInfo/style.scss b/src/shared/components/MemberSearch/MemberItem/UserInfo/style.scss deleted file mode 100644 index 4e654220f2..0000000000 --- a/src/shared/components/MemberSearch/MemberItem/UserInfo/style.scss +++ /dev/null @@ -1,32 +0,0 @@ -@import '~tc-ui/src/styles/tc-includes'; - -.user-info { - background-color: $tc-white; - flex-basis: 60%; - padding: 20px; - position: relative; - - @media (max-width: 700px) { - flex-basis: 50%; - } -} - -.user-profile { - display: flex; - align-items: center; - - .list-number { - display: inline-block; - width: 40px; - vertical-align: top; - font-family: "Roboto", Helvetica, Arial, sans-serif; - font-size: 22px; - line-height: 28px; - color: $tc-gray-20; - background-color: $tc-white; - - @media (max-width: 700px) { - width: 25px; - } - } -} diff --git a/src/shared/components/MemberSearch/MemberItem/UserStats/index.jsx b/src/shared/components/MemberSearch/MemberItem/UserStats/index.jsx deleted file mode 100644 index a3f9ec4d32..0000000000 --- a/src/shared/components/MemberSearch/MemberItem/UserStats/index.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import _ from 'lodash'; -import { connect } from 'react-redux'; -import TagList from '../../TagList'; -import SubtrackList from '../../SubtrackList'; -import TrackList from '../../TrackList'; -import { getMostRecentSubtracks, sortSkillsByScoreAndTag } from '../../helpers'; - -import './style.scss'; - -const UserStats = ({ member, userPlace, searchTermTag }) => { - let userStatsList; - - const subtracks = getMostRecentSubtracks(member.stats, 5); - - if (subtracks.length) { - userStatsList = ; - } else { - userStatsList = ; - } - - // Highlight the skill that was searched for if the user has it - // but only in the leaderboard, which is indicated by having userPlace - const tag = _.isFinite(userPlace) ? searchTermTag : null; - - const skills = sortSkillsByScoreAndTag(member.skills, tag, 4); - - return ( -
-
- - - {userStatsList} -
-
- ); -}; - -UserStats.propTypes = { - member: PropTypes.shape({ - skills: PropTypes.arrayOf(PropTypes.shape({})), - tracks: PropTypes.arrayOf(PropTypes.string), - stats: PropTypes.arrayOf(PropTypes.shape({})), - }).isRequired, - userPlace: PropTypes.number, - searchTermTag: PropTypes.shape({}), -}; - -UserStats.defaultProps = { - userPlace: null, - searchTermTag: null, -}; - -const mapStateToProps = ({ memberSearch }) => ({ searchTermTag: memberSearch.searchTermTag }); - -export default connect(mapStateToProps)(UserStats); diff --git a/src/shared/components/MemberSearch/MemberItem/UserStats/style.scss b/src/shared/components/MemberSearch/MemberItem/UserStats/style.scss deleted file mode 100644 index 10a913540f..0000000000 --- a/src/shared/components/MemberSearch/MemberItem/UserStats/style.scss +++ /dev/null @@ -1,28 +0,0 @@ -@import '~tc-ui/src/styles/tc-includes'; - -.user-stats { - display: flex; - align-items: center; - flex-basis: 50%; - max-width: 440px; - margin-left: auto; - padding: 20px; - background-color: $tc-gray-neutral-light; - border-left: 1px solid $tc-gray-neutral-dark; - font-size: 12px; - line-height: 14px; - - @media (max-width: 700px) { - flex-basis: 100%; - display: block; - width: 100%; - max-width: 1000px; - padding: 15px; - border: none; - border-top: 1px solid $tc-gray-neutral-dark; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - font-size: 14px; - line-height: 16px; - } -} diff --git a/src/shared/components/MemberSearch/MemberItem/index.jsx b/src/shared/components/MemberSearch/MemberItem/index.jsx deleted file mode 100644 index 0326e87241..0000000000 --- a/src/shared/components/MemberSearch/MemberItem/index.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; -import classNames from 'classnames'; -import UserInfo from './UserInfo'; -import UserStats from './UserStats'; - -import './style.scss'; - -const MemberItem = ({ - member, - userPlace, - withBio, - shouldAnimate = false, -}) => { - const memberItemStyles = classNames( - 'member-item', - { 'with-bio': withBio }, - ); - - const memberItem = ( - - - - - - ); - - if (shouldAnimate) { - return ( - - {memberItem} - - ); - } - - return memberItem; -}; - -MemberItem.propTypes = { - member: PropTypes.shape({}).isRequired, - userPlace: PropTypes.number, - withBio: PropTypes.bool, - shouldAnimate: PropTypes.bool, -}; - -export default MemberItem; diff --git a/src/shared/components/MemberSearch/MemberItem/style.scss b/src/shared/components/MemberSearch/MemberItem/style.scss deleted file mode 100644 index d7c563556a..0000000000 --- a/src/shared/components/MemberSearch/MemberItem/style.scss +++ /dev/null @@ -1,31 +0,0 @@ -@import '~tc-ui/src/styles/tc-includes'; - -.member-item { - display: flex; - max-width: 960px; - border-bottom: 1px solid rgba(163, 163, 174, 0.3); - - @media (max-width: 700px) { - display: block; - } - - &:not(.with-bio):last-child { - border-bottom: none; - } -} - -.with-bio { - margin: 20px auto 0; - border: 1px solid rgba(163, 163, 174, 0.3); - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.13); -} - -// ReactCSSTransitionGroup transitions -.member-item-appear { - opacity: 0.01; - - &.member-item-appear-active { - opacity: 1; - transition: opacity 0.25s ease-in; - } -} diff --git a/src/shared/components/MemberSearch/MemberList/index.jsx b/src/shared/components/MemberSearch/MemberList/index.jsx deleted file mode 100644 index 7e12d2dadc..0000000000 --- a/src/shared/components/MemberSearch/MemberList/index.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import shortId from 'shortid'; -import MemberItem from '../MemberItem'; - -import './style.scss'; - -const MemberList = (({ members }) => ( -
- { - members.map(member => ) - } -
-)); - -MemberList.propTypes = { - members: PropTypes.arrayOf(PropTypes.object).isRequired, -}; - -export default MemberList; diff --git a/src/shared/components/MemberSearch/MemberList/style.scss b/src/shared/components/MemberSearch/MemberList/style.scss deleted file mode 100644 index 6ac3c29cef..0000000000 --- a/src/shared/components/MemberSearch/MemberList/style.scss +++ /dev/null @@ -1,10 +0,0 @@ -.member-list { - .username { - font-size: 16px; - line-height: 19px; - } - - .user-details { - line-height: 14px; - } -} diff --git a/src/shared/components/MemberSearch/MemberSearchView/MemberSearchItem/index.jsx b/src/shared/components/MemberSearch/MemberSearchView/MemberSearchItem/index.jsx new file mode 100644 index 0000000000..96093c6e78 --- /dev/null +++ b/src/shared/components/MemberSearch/MemberSearchView/MemberSearchItem/index.jsx @@ -0,0 +1,41 @@ +import React from 'react'; +import PT from 'prop-types'; +import UserAvatar from '../../User/UserAvatar'; +import UsernameAndDetails from '../../User/UsernameAndDetails'; +import UserStats from '../../User/UserStats'; + +import './styles.scss'; + +const MemberSearchItem = ({ index, item }) => ( +
+
+ + +
+ +
+ +
+ +
+); + +MemberSearchItem.defaultProps = { + index: 0, + item: {}, +}; + +MemberSearchItem.propTypes = { + index: PT.number, + item: PT.shape(), +}; + +export default MemberSearchItem; diff --git a/src/shared/components/MemberSearch/MemberSearchView/MemberSearchItem/styles.scss b/src/shared/components/MemberSearch/MemberSearchView/MemberSearchItem/styles.scss new file mode 100644 index 0000000000..edfcc4f974 --- /dev/null +++ b/src/shared/components/MemberSearch/MemberSearchView/MemberSearchItem/styles.scss @@ -0,0 +1,42 @@ +@import "~styles/mixins"; + +.member-search-item { + min-height: 128px; + background-color: #f4f4f4; + align-items: center; + display: flex; + justify-content: space-between; + position: relative; + border-radius: 8px; + + &.odd { + background-color: $tc-white; + } + + @include xs-to-sm { + flex-direction: column; + padding: 0 16px; + } +} + +.left-content { + display: flex; + + @include xs-to-sm { + width: 100%; + margin-top: 16px; + border-bottom: 2px solid #e9e9e9; + padding-bottom: 16px; + } +} + +.right-content { + border-left: 2px solid #e9e9e9; + width: 59%; + margin: 24px 0; + + @include xs-to-sm { + border-left: none; + width: 100%; + } +} diff --git a/src/shared/components/MemberSearch/MemberSearchView/MemberSearchTab/index.jsx b/src/shared/components/MemberSearch/MemberSearchView/MemberSearchTab/index.jsx new file mode 100644 index 0000000000..46f3ce7aea --- /dev/null +++ b/src/shared/components/MemberSearch/MemberSearchView/MemberSearchTab/index.jsx @@ -0,0 +1,105 @@ +import React, { useState } from 'react'; +import PT from 'prop-types'; +import { useMediaQuery } from 'react-responsive'; +import ArrowIcon from 'assets/images/ico-arrow-down.svg'; +import cn from 'classnames'; + +import './styles.scss'; + +const MemberSearchTab = ({ + currentTab, setCurrentTab, userNameCount, skillsCount, +}) => { + const tabs = ['USERNAMES MATCHING', 'SKILLS MATCHING']; + const [isTabClosed, setIsTabClosed] = useState(true); + + const matchingResults = [userNameCount, skillsCount]; + + const desktop = useMediaQuery({ minWidth: 1024 }); + + const tabDetail = tabs.map(((tab, index) => ( + { + setCurrentTab(index); + setIsTabClosed(true); + }} + styleName={`tab ${index === currentTab ? 'active' : ''}`} + key={tab} + > + {tab} + { + matchingResults[index] ? ( + { + index ? `Top ${matchingResults[index]}` : matchingResults[index] + } + + ) : null + } + + ))); + + return ( + + { + !desktop ? ( +
+
setIsTabClosed(!isTabClosed)} + > +
+

+ {tabs[currentTab]} + { + matchingResults[currentTab] ? ( + { + currentTab ? `Top ${matchingResults[currentTab]}` : matchingResults[currentTab] + } + + ) : null + } +

+ +
+ +
setIsTabClosed(!isTabClosed)} + > + +
+
+ { + !isTabClosed && ( +
+ {tabDetail} +
+ ) + } +
+ ) : ( +
+ {tabDetail} +
+ ) + } +
+ ); +}; + +MemberSearchTab.defaultProps = { + userNameCount: 0, + skillsCount: 0, + currentTab: 0, +}; + +MemberSearchTab.propTypes = { + userNameCount: PT.number, + skillsCount: PT.number, + currentTab: PT.number, + setCurrentTab: PT.func.isRequired, +}; + +export default MemberSearchTab; diff --git a/src/shared/components/MemberSearch/MemberSearchView/MemberSearchTab/styles.scss b/src/shared/components/MemberSearch/MemberSearchView/MemberSearchTab/styles.scss new file mode 100644 index 0000000000..7918047222 --- /dev/null +++ b/src/shared/components/MemberSearch/MemberSearchView/MemberSearchTab/styles.scss @@ -0,0 +1,168 @@ +@import "~styles/mixins"; + +.member-search-tab { + height: 42px; + background: #eaf6fd; + border-radius: 4px 4px 0 0; + padding: 0; + border-bottom: 1px solid #d4d4d4; + margin-top: 34px; + display: flex; +} + +.tab { + @include barlow; + + color: $tco-black; + font-size: 14px; + line-height: 20px; + padding: 0 16px; + display: flex; + align-items: center; + cursor: pointer; + position: relative; + font-weight: 600; + + &.active { + background: #bae1f9; + font-weight: 700; + + &::after { + content: ""; + background-image: url(assets/images/nav-active-item-blue.svg); + height: 10px; + width: 40px; + margin-top: 10px; + justify-content: center; + z-index: 100; + display: block; + position: absolute; + top: 31px; + left: 41%; + + @include xs-to-sm { + display: none; + } + } + } + + @include xs-to-sm { + padding: 12px 18px; + border-radius: 0 0 4px 4px; + } +} + +.count { + @include roboto-regular; + + background-color: #2c95d7; + border-radius: 50px; + padding: 0 8px; + text-align: center; + color: $tc-white; + font-weight: 500; + font-size: 14px; + line-height: 16px; + letter-spacing: 0.5px; + height: 16px; + margin-left: 4px; + + @include xs-to-sm { + &.count-title { + margin-top: 2px; + } + } +} + +.mobile-member-search-tab { + margin-top: 24px; + + .mobile-tab-container { + background-color: #eaf6fd; + height: 40px; + margin-top: 32px; + display: flex; + justify-content: space-between; + border-radius: 4px 4px 0 0; + border-bottom: 1px solid #d4d4d4; + + .title { + @include barlow-bold; + + display: flex; + justify-content: center; + align-self: center; + height: 100%; + font-weight: 700; + color: $tco-black; + font-size: 14px; + line-height: 20px; + text-transform: uppercase; + padding-left: 16px; + padding-top: 10px; + } + + .icon { + width: 16px; + height: 9px; + margin-right: 15px; + display: flex; + flex-direction: column; + justify-content: center; + align-self: center; + cursor: pointer; + transform: scale(1.5); + } + + .down { + transform: scale(1.5) rotate(180deg); + margin-right: 20px !important; + } + + @include xs-to-md { + width: 100%; + margin: 0; + } + } +} + +.mobile-tab-left-content { + display: flex; + align-items: center; +} + +.mobile-tab-expanded { + margin: 0 16px; + background-color: #eaf6fd; + flex-direction: column; + display: flex; + + a { + width: 100%; + height: 40px; + margin: 0; + + @include barlow-bold; + + font-weight: 600; + color: #555; + font-size: 14px; + line-height: 20px; + text-transform: uppercase; + padding-left: 16px; + padding-top: 10px; + } + + .active { + background-color: #bae1f9; + + p { + color: $tco-black; + font-weight: 700; + } + } + + @include xs-to-sm { + margin: 0; + } +} diff --git a/src/shared/components/MemberSearch/MemberSearchView/index.jsx b/src/shared/components/MemberSearch/MemberSearchView/index.jsx index 801a692ed2..5525fb8e2f 100644 --- a/src/shared/components/MemberSearch/MemberSearchView/index.jsx +++ b/src/shared/components/MemberSearch/MemberSearchView/index.jsx @@ -1,228 +1,68 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import _ from 'lodash'; -import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; -import ListContainer from '../ListContainer'; -import TopMemberList from '../TopMemberList'; -import MemberList from '../MemberList'; -import MemberItem from '../MemberItem'; -import LoadingListItem from '../LoadingListItem'; -import PageError from '../PageError'; -import NoResults from '../NoResults'; -import LoadMoreButton from '../LoadMoreButton'; -import EndOfResults from '../EndOfResults'; -import { getSearchTagPreposition } from '../helpers'; +import LoadingIndicator from 'components/LoadingIndicator'; +import MemberSearchTab from './MemberSearchTab'; +import MemberSearchItem from './MemberSearchItem'; import './style.scss'; const MemberSearchView = (props) => { - const { pageLoaded, loadingMore, error } = props; + const [currentTab, setCurrentTab] = useState(0); + const { + pageLoaded, usernameMatches, - totalCount, topMembers, - moreMatchesAvailable, - loadMemberSearch, } = props; - const { previousSearchTerm: searchTerm, searchTermTag: tag } = props; - - function renderPageState() { - let result = null; - if (error) { - result = ( - - - - ); - } else if (searchTerm && pageLoaded && !usernameMatches.length && !topMembers.length) { - result = ( - - - - ); - } else if (!pageLoaded && !usernameMatches.length && !topMembers.length) { - const loadingListItems = []; - - for (let i = 0; i < 10; i += 1) { - loadingListItems.push(); - } - - result = ( - - -
    - {loadingListItems} -
-
-
- ); - } - - return result; - } - - function renderTopMembers() { - if (pageLoaded && tag && topMembers.length) { - const preposition = getSearchTagPreposition(tag.domain); - - return ( - - - - ); - } - - return null; - } - - function renderUsernameMatches() { - let memberMatches; - let exactMemberMatch; - let restOfUsernameMatches; - - if (pageLoaded && usernameMatches.length) { - // Check if the first member in the array matches the search term - const isSearchTerm = _.isString(searchTerm); - const isExactMatch = isSearchTerm - && usernameMatches[0].handle.toLowerCase() === searchTerm.toLowerCase(); - - // If it's an exact match, and there is no leaderboard, - // show the exact match separately - if (isExactMatch && !tag) { - exactMemberMatch = ; - - restOfUsernameMatches = usernameMatches.slice(1); - } - - // If there is an exact match and no other matching usernames - if (restOfUsernameMatches && restOfUsernameMatches.length === 0) { - memberMatches = null; - } else { - memberMatches = ( - - - - - - ); - } - } - - return { - exactMemberMatch, - memberMatches, - }; - } - - function renderLoadMoreButton() { - const loadMoreMembers = () => { - loadMemberSearch(searchTerm); - }; + const { previousSearchTerm: searchTerm } = props; - if (moreMatchesAvailable && pageLoaded && !loadingMore - && !error && usernameMatches.length === 10) { - return ; - } - - if (moreMatchesAvailable && loadingMore && !error && usernameMatches.length === 10) { - return ; - } - - return null; - } - - function renderEndOfResults() { - const numResults = usernameMatches.length; - - // Don't show 'End of results' if the page is loading - let result; - if (!pageLoaded) { - result = null; - } else if (numResults !== totalCount) { // Or if there are more members to load - result = null; - } else if (numResults === 0 && topMembers.length === 0) { // Or if there are no results at all - result = null; - } else { - result = ; - } - - return result; - } - - const { - exactMemberMatch: exactMemberMatchItem, memberMatches: memberMatchItems, - } = renderUsernameMatches(); - const topMemberLeaderboard = renderTopMembers(); - const pageStatus = renderPageState(); - const loadMoreButton = renderLoadMoreButton(); - const endOfResults = renderEndOfResults(); + const currentItems = currentTab ? topMembers : usernameMatches; return ( -
- {pageStatus} - - {exactMemberMatchItem} - - {topMemberLeaderboard} - - {memberMatchItems} - - {loadMoreButton} - - {endOfResults} +
+
+

SEARCH RESULTS

+ +

ITEMS MATCHING “{searchTerm}”

+ + + +
+ { + pageLoaded + ? ( + + { + currentItems.length + ? currentItems.map((item, index) => ( + + )) :
No items available
+ } +
+ ) + : + } +
+ +
); }; MemberSearchView.propTypes = { pageLoaded: PropTypes.bool.isRequired, - loadingMore: PropTypes.bool.isRequired, - error: PropTypes.bool.isRequired, - usernameMatches: PropTypes.arrayOf(PropTypes.shape({ handle: PropTypes.string, })).isRequired, - moreMatchesAvailable: PropTypes.bool.isRequired, - totalCount: PropTypes.number.isRequired, topMembers: PropTypes.arrayOf(PropTypes.shape({})).isRequired, previousSearchTerm: PropTypes.string, searchTermTag: PropTypes.shape({}), - - loadMemberSearch: PropTypes.func.isRequired, }; MemberSearchView.defaultProps = { diff --git a/src/shared/components/MemberSearch/MemberSearchView/style.scss b/src/shared/components/MemberSearch/MemberSearchView/style.scss index aac41eea98..11eafceff0 100644 --- a/src/shared/components/MemberSearch/MemberSearchView/style.scss +++ b/src/shared/components/MemberSearch/MemberSearchView/style.scss @@ -1,10 +1,84 @@ -@import '~tc-ui/src/styles/tc-includes'; +@import "~styles/mixins"; -.member-search-view { - margin: 0 32px; - background-color: $tc-gray-neutral-dark; +.member-search-wrapper { + max-width: $screen-max; + width: 100%; + margin: 0 auto; + background-color: $tc-white; + margin-bottom: 32px; - @media (max-width: 700px) { - margin: 0 10px; + .member-search { + width: 100%; + margin-top: 32px; + + @include xs-to-sm { + margin: 16px; + } + } +} + +.title { + @include barlow-condensed; + + font-weight: 600; + font-size: 36px; + line-height: 32px; + letter-spacing: -1px; + padding-bottom: 24px; + border-bottom: 2px solid #eee; + color: $tco-black; + + @include xs-to-md { + font-size: 26px; + } +} + +.search-term { + @include barlow; + + font-size: 22px; + font-weight: 600; + line-height: 26px; + color: $tco-black; + margin-top: 32px; + + @include xs-to-sm { + font-size: 16px; + line-height: 18px; + margin-top: 24px; + } +} + +.member-search-item-wrapper { + margin-top: 42px; + display: flex; + flex-direction: column; + + @include xs-to-sm { + margin-top: 24px; + border-bottom: 2px solid #e9e9e9; + padding-bottom: 24px; + } +} + +.no-items { + text-align: center; + height: 50vh; + display: flex; + flex-direction: column; + justify-content: center; + + span { + @include roboto-regular; + + font-weight: 400; + font-size: 24px; + line-height: 32px; + color: #000; + + @include xs-to-sm { + font-size: 20px; + line-height: 28px; + } } } diff --git a/src/shared/components/MemberSearch/TopMemberList/index.jsx b/src/shared/components/MemberSearch/TopMemberList/index.jsx deleted file mode 100644 index 962bc631b2..0000000000 --- a/src/shared/components/MemberSearch/TopMemberList/index.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import _ from 'lodash'; -import shortId from 'shortid'; -import MemberItem from '../MemberItem'; - -const TopMemberList = ({ topMembers }) => { - const sortedTopMembers = _.orderBy(topMembers, 'wins', 'desc'); - - const topMemberItems = sortedTopMembers.map((member, i) => ( - - )); - - return ( -
- {topMemberItems} -
- ); -}; - -TopMemberList.propTypes = { - topMembers: PropTypes.arrayOf(PropTypes.object).isRequired, -}; - -export default TopMemberList; diff --git a/src/shared/components/MemberSearch/User/UserAvatar/default-avatar.svg b/src/shared/components/MemberSearch/User/UserAvatar/default-avatar.svg deleted file mode 100644 index 9a5883f8e0..0000000000 --- a/src/shared/components/MemberSearch/User/UserAvatar/default-avatar.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - ico-user-default - Created with Sketch. - - - - - - - - - - - - - - - diff --git a/src/shared/components/MemberSearch/User/UserAvatar/index.jsx b/src/shared/components/MemberSearch/User/UserAvatar/index.jsx index 27add3e31b..f833763d42 100644 --- a/src/shared/components/MemberSearch/User/UserAvatar/index.jsx +++ b/src/shared/components/MemberSearch/User/UserAvatar/index.jsx @@ -1,47 +1,31 @@ import React from 'react'; import PropTypes from 'prop-types'; -import LevelDesignatorIcon from '../../icons/LevelDesignatorIcon'; -import { memberLevelByRating } from '../../helpers'; import './style.scss'; +import { Link } from 'react-router-dom'; +import { getInitials } from '../../helpers'; + +const UserAvatar = ({ photoURL, handle }) => ( + + { + photoURL ? ( +
+ ) : ( +
{getInitials(handle)}
+ ) + } + +); -const UserAvatar = ({ showLevel, rating, photoURL }) => { - let levelIcon; - - if (showLevel) { - levelIcon = ( - - - - ); - } - - /* eslint-disable global-require */ - let backgroundImageUrl = `url(${require('./default-avatar.svg')})`; - - if (photoURL) { - backgroundImageUrl = `url(${photoURL}), ${backgroundImageUrl}`; - } - - // Delete -r when taking member search back out of the angular app - // Renamed to -r to avoid naming collisions - return ( -
- {levelIcon} -
- ); +UserAvatar.defaultProps = { + photoURL: '', + handle: '', }; - UserAvatar.propTypes = { - showLevel: PropTypes.bool, - rating: PropTypes.number.isRequired, photoURL: PropTypes.string, + handle: PropTypes.string, }; -UserAvatar.defaultProps = { - showLevel: '', - photoURL: '', -}; export default UserAvatar; diff --git a/src/shared/components/MemberSearch/User/UserAvatar/style.scss b/src/shared/components/MemberSearch/User/UserAvatar/style.scss index 1556476641..52aaf979ef 100644 --- a/src/shared/components/MemberSearch/User/UserAvatar/style.scss +++ b/src/shared/components/MemberSearch/User/UserAvatar/style.scss @@ -1,23 +1,39 @@ @import '~tc-ui/src/styles/tc-includes'; +@import "~styles/mixins"; -// Delete -r when taking member search back out of the angular app -// Renamed to -r to avoid naming collisions .user-avatar-r { - width: 60px; - height: 60px; - margin-right: 20px; + width: 80px; + height: 80px; + margin-left: 24px; position: relative; border-radius: 50%; background-size: cover; background-position: center; + border: 3px solid #fff; - @media (max-width: 700px) { - margin-right: 10px; + @include xs-to-sm { + width: 64px; + height: 64px; + margin-left: 0; } +} + +.user-avatar-default { + background: linear-gradient(84.45deg, #05456d 2.12%, #0a7ac0 97.43%); + position: relative; + + span { + @include barlow-condensed; - .user-rank { - position: absolute; - top: 0; - right: 0; + color: $tc-white; + font-size: 32px; + line-height: 32px; + font-weight: 600; + height: 100%; + display: flex; + justify-content: center; + text-align: center; + vertical-align: middle; + flex-direction: column; } } diff --git a/src/shared/components/MemberSearch/User/UserBio/index.jsx b/src/shared/components/MemberSearch/User/UserBio/index.jsx deleted file mode 100644 index 3b3b159cd2..0000000000 --- a/src/shared/components/MemberSearch/User/UserBio/index.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Dotdotdot from 'react-dotdotdot'; - -import './style.scss'; - -const UserBio = ({ bio }) => ( -
- - {bio} - -
-); - - -UserBio.propTypes = { - bio: PropTypes.string.isRequired, -}; - -export default UserBio; diff --git a/src/shared/components/MemberSearch/User/UserBio/style.scss b/src/shared/components/MemberSearch/User/UserBio/style.scss deleted file mode 100644 index 4561e9fc91..0000000000 --- a/src/shared/components/MemberSearch/User/UserBio/style.scss +++ /dev/null @@ -1,9 +0,0 @@ -@import '~tc-ui/src/styles/tc-includes'; - -.user-bio { - @include tc-body-small; - - font-family: "Roboto", Helvetica, Arial, sans-serif; - color: $tc-gray-80; - margin-top: 20px; -} diff --git a/src/shared/components/MemberSearch/User/UserStats/index.jsx b/src/shared/components/MemberSearch/User/UserStats/index.jsx new file mode 100644 index 0000000000..4cd17053bd --- /dev/null +++ b/src/shared/components/MemberSearch/User/UserStats/index.jsx @@ -0,0 +1,59 @@ +/* eslint-disable react/no-array-index-key */ +import React from 'react'; +import PT from 'prop-types'; +import _ from 'lodash'; + +import TrackItem from '../../../ProfilePage/MemberTracks/TrackItem'; +import { trackMap } from '../../../ProfilePage/MemberTracks'; +import List from '../../../ProfilePage/Skills/List'; + +import './style.scss'; + +const UserStats = ({ tracks, skills }) => { + const normalizeSkills = skills.map((skill, index) => ({ tagId: index, tagName: skill.name })); + const verifiedSkills = _.filter(normalizeSkills, skill => _.includes(skill.sources, 'CHALLENGE')); + const userEnteredSkills = _.filter(normalizeSkills, skill => !_.includes(skill.sources, 'CHALLENGE')); + + const renderTracks = ( +
+ { + tracks.map((track, index) => ( + + )) + } +
+ ); + + const renderSkills = ( +
+ + + +
+ ); + + return ( +
+
{tracks.length ? renderTracks : No track added}
+
{skills.length ? renderSkills : No skills added}
+
+ ); +}; + +UserStats.defaultProps = { + tracks: [], + skills: [], +}; + +UserStats.propTypes = { + tracks: PT.arrayOf(PT.shape()), + skills: PT.arrayOf(PT.shape()), +}; + +export default UserStats; diff --git a/src/shared/components/MemberSearch/User/UserStats/style.scss b/src/shared/components/MemberSearch/User/UserStats/style.scss new file mode 100644 index 0000000000..dce918bec2 --- /dev/null +++ b/src/shared/components/MemberSearch/User/UserStats/style.scss @@ -0,0 +1,47 @@ +@import "~styles/mixins"; + +.user-stats { + margin-left: 24px; + + @include xs-to-sm { + margin-left: 0; + } +} + +.not-found-text { + @include roboto-regular; + + font-weight: 400; + font-size: 14px; + line-height: 22px; + color: #767676; + + @include xs-to-sm { + &.track { + font-size: 12px; + line-height: 18px; + } + } +} + +.skills-wrapper { + margin-top: 16px; + + @include xs-to-sm { + margin-top: 8px; + } +} + +.tracks { + display: flex; + gap: 16px; + flex-wrap: wrap; + + @include xs-to-sm { + gap: 8px; + } +} + +.skills { + max-width: fit-content; +} diff --git a/src/shared/components/MemberSearch/User/UsernameAndDetails/index.jsx b/src/shared/components/MemberSearch/User/UsernameAndDetails/index.jsx index 1f60c712dd..a3e22d8b23 100644 --- a/src/shared/components/MemberSearch/User/UsernameAndDetails/index.jsx +++ b/src/shared/components/MemberSearch/User/UsernameAndDetails/index.jsx @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; import moment from 'moment'; +import { Link } from 'react-router-dom'; import { singlePluralFormatter } from '../../helpers'; import ISOCountries from '../../helpers/ISOCountries'; @@ -18,23 +19,27 @@ const UsernameAndDetails = ({ const numberWins = singlePluralFormatter(numWins, 'win'); - const memberSinceMMMYYYY = moment(memberSince).format('MMM YYYY'); + const memberSinceYYYY = moment(memberSince).format('YYYY'); return (

- {username} + {username}

{userCountry} - {numberWins && ` / ${numberWins}`} + { + numberWins + ? {numberWins} + : null + }
- Member since {memberSinceMMMYYYY} + Member since {memberSinceYYYY}
diff --git a/src/shared/components/MemberSearch/User/UsernameAndDetails/style.scss b/src/shared/components/MemberSearch/User/UsernameAndDetails/style.scss index 535de5e8bf..eecb2f911b 100644 --- a/src/shared/components/MemberSearch/User/UsernameAndDetails/style.scss +++ b/src/shared/components/MemberSearch/User/UsernameAndDetails/style.scss @@ -1,36 +1,59 @@ @import '~tc-ui/src/styles/tc-includes'; +@import "~styles/mixins"; .username-and-details { max-width: 225px; + margin-left: 16px; .username { - @include tc-heading; + @include roboto-bold; - font-family: "Roboto", Helvetica, Arial, sans-serif; - color: $tc-gray-80; - text-overflow: ellipsis; - overflow: hidden; + font-weight: 700; + font-size: 20px; + line-height: 26px; + color: $tco-black; - @media (max-width: 700px) { - max-width: 180px; + @include xs-to-sm { + font-size: 14px; + line-height: 20px; } } .user-details { .country-and-wins { - @include tc-label-small; + @include barlow; - color: $tc-gray-60; + font-weight: 600; + font-size: 16px; + line-height: 22px; + color: $tco-black; + margin-top: 1px; + text-transform: uppercase; .user-country { text-transform: uppercase; + padding-right: 8px; + } + + .number-wins { + padding-left: 8px; + border-left: 2px solid #e9e9e9; } } .member-since { - @include tc-label-small; + @include roboto-regular; - color: $tc-gray-30; + font-size: 14px; + font-weight: 400; + line-height: 22px; + color: #767676; + margin-top: 1px; + + @include xs-to-sm { + font-size: 12px; + line-height: 18px; + } } } } diff --git a/src/shared/components/MemberSearch/helpers/index.js b/src/shared/components/MemberSearch/helpers/index.js index a5adcec034..7b3842c7e3 100644 --- a/src/shared/components/MemberSearch/helpers/index.js +++ b/src/shared/components/MemberSearch/helpers/index.js @@ -244,3 +244,18 @@ export function getSearchTagPreposition(tagType) { return 'in'; } } + +/** + * Get Initials from handle + * @param {String} handle + * @returns {String} user initials + */ +export const getInitials = (handle) => { + const names = handle.split(' '); + let initials = names[0].substring(0, 1).toUpperCase(); + + if (names.length > 1) { + initials += names[names.length - 1].substring(0, 1).toUpperCase(); + } + return initials; +}; diff --git a/src/shared/components/MemberSearch/index.jsx b/src/shared/components/MemberSearch/index.jsx index 108c17b78b..4f6ca198f6 100644 --- a/src/shared/components/MemberSearch/index.jsx +++ b/src/shared/components/MemberSearch/index.jsx @@ -4,8 +4,6 @@ import qs from 'qs'; import MemberSearchView from './MemberSearchView'; import { isEndOfScreen } from './helpers'; -import './style.scss'; - export default class MemberSearch extends Component { constructor(props) { super(props); @@ -39,15 +37,7 @@ export default class MemberSearch extends Component { render() { return ( -
-
-
-
- -
-
-
-
+ ); } } diff --git a/src/shared/components/MemberSearch/style.scss b/src/shared/components/MemberSearch/style.scss deleted file mode 100644 index 1286420505..0000000000 --- a/src/shared/components/MemberSearch/style.scss +++ /dev/null @@ -1,43 +0,0 @@ -.page-wrapper { - display: block !important; // overrides .container>*:nth-child(2) - width: 100%; - min-height: 100%; - background-color: #f6f6f6; -} - -.fold-wrapper { - margin: 0; - min-height: 100%; -} - -.view-container { - min-height: 440px; - padding-bottom: 30px; -} - -:global { - #member-search-wrapper { - font-family: "Roboto", Helvetica, Arial, sans-serif !important; - - // Overrides .member-search-view - > div { - background-color: transparent; - } - - // Overrides old style guide - a { - cursor: auto !important; - transition: none !important; - text-decoration: none !important; - color: inherit !important; - - &:visited, - &:hover, - &:active { - color: inherit !important; - cursor: auto !important; - text-decoration: none !important; - } - } - } -} diff --git a/src/shared/components/ProfilePage/MemberTracks/TrackItem/styles.scss b/src/shared/components/ProfilePage/MemberTracks/TrackItem/styles.scss index 949ad63e24..8cbc603141 100644 --- a/src/shared/components/ProfilePage/MemberTracks/TrackItem/styles.scss +++ b/src/shared/components/ProfilePage/MemberTracks/TrackItem/styles.scss @@ -4,6 +4,7 @@ padding: 4px 10px; border: 2px solid $listing-checkbox-green; border-radius: 4px; + background-color: $tc-white !important; @include xs-to-sm { padding: 2px 10px; diff --git a/src/shared/components/ProfilePage/MemberTracks/index.jsx b/src/shared/components/ProfilePage/MemberTracks/index.jsx index d9202fb388..6bdfe36551 100644 --- a/src/shared/components/ProfilePage/MemberTracks/index.jsx +++ b/src/shared/components/ProfilePage/MemberTracks/index.jsx @@ -5,6 +5,12 @@ import { indexOf } from 'lodash'; import './styles.scss'; import TrackItem from './TrackItem'; +export const trackMap = { + DEVELOP: 'Developer', + DESIGN: 'Designer', + DATA_SCIENCE: 'Data Scientist', +}; + const MemberTracks = ({ copilot, info, @@ -12,12 +18,6 @@ const MemberTracks = ({ }) => { const { tracks } = info; - const trackMap = { - DEVELOP: 'Developer', - DESIGN: 'Designer', - DATA_SCIENCE: 'Data Scientist', - }; - return (
{ diff --git a/src/shared/components/ProfilePage/Skills/List/styles.scss b/src/shared/components/ProfilePage/Skills/List/styles.scss index e43df3f141..26698f8c38 100644 --- a/src/shared/components/ProfilePage/Skills/List/styles.scss +++ b/src/shared/components/ProfilePage/Skills/List/styles.scss @@ -2,7 +2,8 @@ .list { display: flex; - gap: 4px; + column-gap: 4px; + row-gap: 8px; margin-top: 8px; flex-wrap: wrap; } @@ -37,6 +38,7 @@ cursor: pointer; width: fit-content; padding: 1px 6px; + background-color: $tc-white; span { @include roboto-medium; diff --git a/src/shared/components/Settings/ExperienceAndSkills/WorkSkills/Skills/AddSkillsModal/index.jsx b/src/shared/components/Settings/ExperienceAndSkills/WorkSkills/Skills/AddSkillsModal/index.jsx index e7fa312d4d..357a115b92 100755 --- a/src/shared/components/Settings/ExperienceAndSkills/WorkSkills/Skills/AddSkillsModal/index.jsx +++ b/src/shared/components/Settings/ExperienceAndSkills/WorkSkills/Skills/AddSkillsModal/index.jsx @@ -37,7 +37,7 @@ export default function AddSkillsModal({ setDisplayingSkills([...userSkills]); }, [userSkills]); - const find = (arr, i) => arr && arr.indexOf(i) !== -1; + const find = (arr, i) => arr && _.findIndex(arr, e => e.toLowerCase() === i.toLowerCase()) !== -1; const findSkill = (arr, skill) => arr && arr.find(a => a.id === skill.id); const popularSkills = React.useMemo(() => allSkills diff --git a/src/shared/components/challenge-detail/Header/TabSelector/index.jsx b/src/shared/components/challenge-detail/Header/TabSelector/index.jsx index b01cb53702..14175c3264 100644 --- a/src/shared/components/challenge-detail/Header/TabSelector/index.jsx +++ b/src/shared/components/challenge-detail/Header/TabSelector/index.jsx @@ -300,7 +300,7 @@ export default function ChallengeViewSelector(props) { return ''; })()} { - isMM && ( + (isMM || challenge.track.toLowerCase() === 'data science') && (