From 359612dbca22e8c36da54dcef4d9b3f4b1f1a7c5 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 28 Oct 2022 15:09:50 +0300 Subject: [PATCH 1/5] GAME-205 profile pages updates --- src/shared/actions/page/profile.js | 4 +- .../components/ProfileBadgesPage/index.jsx | 114 ++++++++++++++ .../components/ProfileBadgesPage/styles.scss | 144 ++++++++++++++++++ .../ProfilePage/Awards/AwardBadge/styles.scss | 1 + .../ProfilePage/Awards/AwardModal/styles.scss | 4 + .../components/ProfilePage/Awards/index.jsx | 11 +- .../components/ProfilePage/Awards/styles.scss | 9 +- src/shared/components/ProfilePage/index.jsx | 2 +- src/shared/containers/ProfileBadges.jsx | 69 +++++++++ src/shared/routes/ProfileBadges.jsx | 27 ++++ src/shared/routes/Topcoder/Routes.jsx | 10 ++ 11 files changed, 390 insertions(+), 5 deletions(-) create mode 100644 src/shared/components/ProfileBadgesPage/index.jsx create mode 100644 src/shared/components/ProfileBadgesPage/styles.scss create mode 100644 src/shared/containers/ProfileBadges.jsx create mode 100644 src/shared/routes/ProfileBadges.jsx diff --git a/src/shared/actions/page/profile.js b/src/shared/actions/page/profile.js index cd8d10f6fe..60e15d117b 100644 --- a/src/shared/actions/page/profile.js +++ b/src/shared/actions/page/profile.js @@ -21,11 +21,11 @@ async function getGamificationBadgesInit(handle) { * @param {String} handle Topcoder member handle. * @return {Action} */ -async function getGamificationBadgesDone(handle) { +async function getGamificationBadgesDone(handle, limit) { try { const memberInfo = await fetch(`${config.API.V5}/members/${handle}`) .then(response => response.json()); - const badges = await fetch(`${config.API.V5}/gamification/badges/assigned/${memberInfo.userId}?organization_id=${config.GAMIFICATION.ORG_ID}`) + const badges = await fetch(`${config.API.V5}/gamification/badges/assigned/${memberInfo.userId}?organization_id=${config.GAMIFICATION.ORG_ID}&limit=${limit || 4}`) .then(response => response.json()); return { diff --git a/src/shared/components/ProfileBadgesPage/index.jsx b/src/shared/components/ProfileBadgesPage/index.jsx new file mode 100644 index 0000000000..bf16439ea0 --- /dev/null +++ b/src/shared/components/ProfileBadgesPage/index.jsx @@ -0,0 +1,114 @@ +import React, { useState } from 'react'; +import PT from 'prop-types'; +import { Link } from 'react-router-dom'; +import { get } from 'lodash'; +import { Modal } from 'topcoder-react-ui-kit'; +import IconClose from 'assets/images/tc-edu/icon-close-big.svg'; +import FallBackAwardIcon from 'assets/images/default-award.svg'; +import md from 'utils/markdown'; +import AwardModal from '../ProfilePage/Awards/AwardModal'; + +import style from './styles.scss'; + +const ProfileBadges = ({ badges, handleParam }) => { + const [showModal, setShowModal] = useState(false); + const [modalData, setModalData] = useState({}); + + return ( +
+ + + + + Return to Profile + +
+
COMMUNITY AWARDS & HONORS
+
+ { + badges.rows.map((reward) => { + const title = get(reward, 'org_badge.badge_name'); + const imageUrl = get(reward, 'org_badge.badge_image_url'); + let description = get(reward, 'org_badge.badge_description'); + if (description) { + description = md(description); + } + + return ( +
{ + setShowModal(true); + setModalData({ + title, + description, + imageUrl, + }); + }} + > + { + imageUrl ? ( + award-badge + ) : ( + + ) + } +
+ +
+ +
+
+ ); + }) + } +
+
+ { + showModal && ( + setShowModal(false)} theme={style}> +
+
+

Community Awards & Honors

+
setShowModal(false)}> + +
+
+
+ + +
+
+ ) + } +
+ ); +}; + +ProfileBadges.defaultProps = { + badges: {}, +}; + +ProfileBadges.propTypes = { + badges: PT.shape(), + handleParam: PT.string.isRequired, +}; + +export default ProfileBadges; diff --git a/src/shared/components/ProfileBadgesPage/styles.scss b/src/shared/components/ProfileBadgesPage/styles.scss new file mode 100644 index 0000000000..df6c570438 --- /dev/null +++ b/src/shared/components/ProfileBadgesPage/styles.scss @@ -0,0 +1,144 @@ +/* stylelint-disable no-descending-specificity */ +@import "~styles/mixins"; + +.outer-container { + width: 100%; + max-width: $screen-max; + margin: 0 auto; + display: flex; + flex-direction: column; + + @include xs-to-md { + margin: 0 32px; + } + + .memberPageBackLink { + text-transform: uppercase; + color: $listing-checkbox-green; + font-weight: 700; + font-family: Roboto, sans-serif; + margin: 32px 0; + display: flex; + align-items: center; + + svg { + margin-right: 6px; + } + } + + .badgesWrap { + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2); + border-radius: 8px; + display: flex; + flex-direction: column; + padding: 32px; + margin-bottom: 32px; + + .seactionTitle { + @include barlow-medium; + + font-weight: 600; + color: #2a2a2a; + font-size: 22px; + line-height: 26px; + text-transform: uppercase; + padding-bottom: 24px; + border-bottom: 2px solid #e9e9e9; + } + + .badgesGrid { + display: grid; + grid-template-columns: repeat(6, 1fr); + + @include xs-to-sm { + grid-template-columns: repeat(2, 1fr); + } + + @include md { + grid-template-columns: repeat(4, 1fr); + } + + .awardBadge { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + padding-top: 32px; + + .image { + width: 100px; + height: 100px; + } + + .title { + @include roboto-bold; + $color: $tco-black; + + font-size: 12px; + font-weight: 700; + line-height: 16px; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + align-items: center; + max-width: 130px; + margin-top: 17px; + text-transform: uppercase; + + @include xs-to-sm { + max-width: unset; + } + } + } + } + } +} + +.award-modal { + padding-bottom: 10px; + border-radius: 8px; + margin: 25px 32px 32px; + + .header { + display: flex; + justify-content: space-between; + margin-bottom: 25px; + + .title { + @include barlow-medium; + + font-weight: 600; + color: #2a2a2a; + font-size: 22px; + line-height: 26px; + text-transform: uppercase; + } + + .icon { + cursor: pointer; + margin-top: 5px; + } + } +} + +hr { + opacity: 0.5; +} + +.container { + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2); + border-radius: 8px; + min-width: 600px; + + @include xs-to-sm { + width: 90%; + min-width: unset; + } +} + +.overlay { + background-color: #0c0c0c; + opacity: 0.85; +} diff --git a/src/shared/components/ProfilePage/Awards/AwardBadge/styles.scss b/src/shared/components/ProfilePage/Awards/AwardBadge/styles.scss index a8dd313416..1f9c43019a 100644 --- a/src/shared/components/ProfilePage/Awards/AwardBadge/styles.scss +++ b/src/shared/components/ProfilePage/Awards/AwardBadge/styles.scss @@ -6,6 +6,7 @@ border-radius: 8px; display: flex; cursor: pointer; + min-width: 316px; .image { width: 48px; diff --git a/src/shared/components/ProfilePage/Awards/AwardModal/styles.scss b/src/shared/components/ProfilePage/Awards/AwardModal/styles.scss index 0a230c30e9..48cb7a33f6 100644 --- a/src/shared/components/ProfilePage/Awards/AwardModal/styles.scss +++ b/src/shared/components/ProfilePage/Awards/AwardModal/styles.scss @@ -27,6 +27,10 @@ justify-content: center; flex-direction: column; + @include xs-to-md { + align-items: center; + } + .title { @include roboto-bold; diff --git a/src/shared/components/ProfilePage/Awards/index.jsx b/src/shared/components/ProfilePage/Awards/index.jsx index 7551d74071..0d4fa08d66 100644 --- a/src/shared/components/ProfilePage/Awards/index.jsx +++ b/src/shared/components/ProfilePage/Awards/index.jsx @@ -4,12 +4,14 @@ import { Modal } from 'topcoder-react-ui-kit'; import IconClose from 'assets/images/tc-edu/icon-close-big.svg'; import _ from 'lodash'; import md from 'utils/markdown'; +import { Link } from 'react-router-dom'; import style from './styles.scss'; import AwardBadge from './AwardBadge'; import AwatarModal from './AwardModal'; -const Awards = ({ badges }) => { + +const Awards = ({ badges, info }) => { const [showModal, setShowModal] = useState(false); const [modalData, setModalData] = useState({}); @@ -18,6 +20,12 @@ const Awards = ({ badges }) => {
Community Awards & Honors + + View All Badges +
@@ -77,6 +85,7 @@ Awards.defaultProps = { Awards.propTypes = { badges: PT.arrayOf(PT.shape()), + info: PT.shape().isRequired, }; export default Awards; diff --git a/src/shared/components/ProfilePage/Awards/styles.scss b/src/shared/components/ProfilePage/Awards/styles.scss index 2fc895902b..8b272ce659 100644 --- a/src/shared/components/ProfilePage/Awards/styles.scss +++ b/src/shared/components/ProfilePage/Awards/styles.scss @@ -19,6 +19,13 @@ cursor: pointer; margin-top: 5px; } + + .badgesPageLink { + text-transform: uppercase; + color: $listing-checkbox-green; + font-weight: 700; + font-family: Roboto, sans-serif; + } } .awards { @@ -28,7 +35,7 @@ padding-bottom: 32px; .header { - padding: 32px 0 20px 32px; + padding: 32px 32px 20px 32px; @include xs-to-sm { padding: 16px 0 16px 16px; diff --git a/src/shared/components/ProfilePage/index.jsx b/src/shared/components/ProfilePage/index.jsx index c981a9fd20..60953d0d5d 100644 --- a/src/shared/components/ProfilePage/index.jsx +++ b/src/shared/components/ProfilePage/index.jsx @@ -229,7 +229,7 @@ class ProfilePage extends React.Component {
{ (config.GAMIFICATION.ENABLE_BADGE_UI && badges && (badges.rows || [])).length ? ( - + ) : null } {tcAcademyCertifications.length > 0 && ( diff --git a/src/shared/containers/ProfileBadges.jsx b/src/shared/containers/ProfileBadges.jsx new file mode 100644 index 0000000000..fefd2a8a4c --- /dev/null +++ b/src/shared/containers/ProfileBadges.jsx @@ -0,0 +1,69 @@ +import React, { useEffect } from 'react'; +import PT from 'prop-types'; +import { connect } from 'react-redux'; +import profileActions from 'actions/page/profile'; +import MetaTags from 'components/MetaTags'; +import LoadingIndicator from 'components/LoadingIndicator'; +import ProfileBadgesPage from 'components/ProfileBadgesPage'; +import { isEmpty } from 'lodash'; + +function ProfileBadgesContainer(props) { + const { handleParam, badges, loadBadges } = props; + const title = `${handleParam} | Community Profile | Topcoder`; + const description = `Meet Topcoder member ${handleParam} and view their community awards and honors.`; + + useEffect(() => { + loadBadges(handleParam); + }, []); + + return ( + + + { + !isEmpty(badges) ? ( + + ) : + } + + ); +} + +ProfileBadgesContainer.defaultProps = { + profile: null, +}; + +ProfileBadgesContainer.propTypes = { + profile: PT.shape(), + loadBadges: PT.func.isRequired, + handleParam: PT.string.isRequired, + badges: PT.shape().isRequired, +}; + +function mapStateToProps(state, ownProps) { + const profile = state.auth && state.auth.profile ? { ...state.auth.profile } : {}; + return { + handleParam: ownProps.match.params.handle, + badges: state.page.profile[ownProps.match.params.handle] + ? state.page.profile[ownProps.match.params.handle].badges : {}, + profile, + }; +} + +function mapDispatchToActions(dispatch) { + return { + loadBadges: (handle) => { + dispatch(profileActions.page.profile.getGamificationBadgesInit(handle)); + dispatch(profileActions.page.profile.getGamificationBadgesDone(handle, 36)); + }, + }; +} + +export default connect( + mapStateToProps, + mapDispatchToActions, +)(ProfileBadgesContainer); diff --git a/src/shared/routes/ProfileBadges.jsx b/src/shared/routes/ProfileBadges.jsx new file mode 100644 index 0000000000..d53ea77376 --- /dev/null +++ b/src/shared/routes/ProfileBadges.jsx @@ -0,0 +1,27 @@ +/** + * The loader of Profile webpack chunks. + */ +import path from 'path'; +import React from 'react'; + +import LoadingPagePlaceholder from 'components/LoadingPagePlaceholder'; +import { AppChunk, webpack } from 'topcoder-react-utils'; + +export default function ProfileLoader(props) { + return ( + import(/* webpackChunkName: "profile-badges/chunk" */ 'containers/ProfileBadges') + .then(({ default: ProfileBadgesContainer }) => ( + + )) + } + renderPlaceholder={() => } + renderServer={() => { + const p = webpack.resolveWeak('containers/Profile'); + const ProfileBadgesContainer = webpack.requireWeak(path.resolve(__dirname, p)); + return ; + }} + /> + ); +} diff --git a/src/shared/routes/Topcoder/Routes.jsx b/src/shared/routes/Topcoder/Routes.jsx index c45868ad5c..769904af87 100644 --- a/src/shared/routes/Topcoder/Routes.jsx +++ b/src/shared/routes/Topcoder/Routes.jsx @@ -31,6 +31,7 @@ import Notifications from './Notifications'; import Settings from '../Settings'; import HallOfFame from '../HallOfFame'; import Profile from '../Profile'; +import ProfileBadges from '../ProfileBadges'; import Scoreboard from '../tco/scoreboard'; import MemberSearch from '../../containers/MemberSearch'; @@ -89,6 +90,15 @@ export default function Topcoder() { exact path="/members/:handle([\w\-\[\].{} ]{2,15})" /> + { + config.GAMIFICATION.ENABLE_BADGE_UI && ( + + ) + } } path="/settings" From 63113e9af2182930cf9de78ae1726ad32e8a842f Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 28 Oct 2022 15:11:46 +0300 Subject: [PATCH 2/5] ci: on test --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index aa0445c1ed..20e30546dc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -356,7 +356,7 @@ workflows: filters: branches: only: - - free + - feat/badges-box # This is alternate dev env for parallel testing - "build-qa": context : org-global From fa46579baefacdbf61606c354f7b4b491cbf535f Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Mon, 31 Oct 2022 15:10:24 +0200 Subject: [PATCH 3/5] up badges to 100 --- src/shared/containers/ProfileBadges.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/containers/ProfileBadges.jsx b/src/shared/containers/ProfileBadges.jsx index fefd2a8a4c..889fa40962 100644 --- a/src/shared/containers/ProfileBadges.jsx +++ b/src/shared/containers/ProfileBadges.jsx @@ -58,7 +58,7 @@ function mapDispatchToActions(dispatch) { return { loadBadges: (handle) => { dispatch(profileActions.page.profile.getGamificationBadgesInit(handle)); - dispatch(profileActions.page.profile.getGamificationBadgesDone(handle, 36)); + dispatch(profileActions.page.profile.getGamificationBadgesDone(handle, 100)); }, }; } From e50b62b0c07c8629c73baff9d58a70803b683cce Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Tue, 1 Nov 2022 08:26:53 +0200 Subject: [PATCH 4/5] add awardedAt date --- src/shared/components/ProfileBadgesPage/index.jsx | 6 ++++++ .../ProfilePage/Awards/AwardModal/index.jsx | 6 +++++- .../ProfilePage/Awards/AwardModal/styles.scss | 12 +++++++++++- src/shared/components/ProfilePage/Awards/index.jsx | 6 ++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/shared/components/ProfileBadgesPage/index.jsx b/src/shared/components/ProfileBadgesPage/index.jsx index bf16439ea0..fd69254b3a 100644 --- a/src/shared/components/ProfileBadgesPage/index.jsx +++ b/src/shared/components/ProfileBadgesPage/index.jsx @@ -6,6 +6,7 @@ import { Modal } from 'topcoder-react-ui-kit'; import IconClose from 'assets/images/tc-edu/icon-close-big.svg'; import FallBackAwardIcon from 'assets/images/default-award.svg'; import md from 'utils/markdown'; +import { format } from 'date-fns'; import AwardModal from '../ProfilePage/Awards/AwardModal'; import style from './styles.scss'; @@ -47,6 +48,10 @@ const ProfileBadges = ({ badges, handleParam }) => { if (description) { description = md(description); } + let awardedAt = get(reward, 'awarded_at'); + if (awardedAt) { + awardedAt = format(new Date(awardedAt), 'PPP'); + } return (
{ title, description, imageUrl, + awardedAt, }); }} > diff --git a/src/shared/components/ProfilePage/Awards/AwardModal/index.jsx b/src/shared/components/ProfilePage/Awards/AwardModal/index.jsx index 4eb1257d25..5d52322494 100644 --- a/src/shared/components/ProfilePage/Awards/AwardModal/index.jsx +++ b/src/shared/components/ProfilePage/Awards/AwardModal/index.jsx @@ -8,7 +8,7 @@ const AwatarModal = ({ modalData, }) => { const { - title, description, imageUrl, + title, description, imageUrl, awardedAt, } = modalData; return ( @@ -25,6 +25,9 @@ const AwatarModal = ({
{title}
+ { + awardedAt &&
{`Awarded on ${awardedAt}`}
+ }
{description}
@@ -41,6 +44,7 @@ AwatarModal.propTypes = { modalData: PT.shape( { title: PT.string, + awardedAt: PT.string, description: PT.string, imageUrl: PT.string, }, diff --git a/src/shared/components/ProfilePage/Awards/AwardModal/styles.scss b/src/shared/components/ProfilePage/Awards/AwardModal/styles.scss index 48cb7a33f6..953e239a88 100644 --- a/src/shared/components/ProfilePage/Awards/AwardModal/styles.scss +++ b/src/shared/components/ProfilePage/Awards/AwardModal/styles.scss @@ -44,6 +44,16 @@ } } + .awardedAt { + @include roboto-bold; + + color: $listing-placeholder-gray; + font-size: 12px; + line-height: 16px; + text-transform: uppercase; + margin-top: 8px; + } + .description { @include brackets-content; @@ -51,7 +61,7 @@ color: $tco-black; font-size: 16px; line-height: 24px; - margin-top: 10px; + margin-top: 16px; } @include xs-to-md { diff --git a/src/shared/components/ProfilePage/Awards/index.jsx b/src/shared/components/ProfilePage/Awards/index.jsx index 0d4fa08d66..b5b9b0109a 100644 --- a/src/shared/components/ProfilePage/Awards/index.jsx +++ b/src/shared/components/ProfilePage/Awards/index.jsx @@ -5,6 +5,7 @@ import IconClose from 'assets/images/tc-edu/icon-close-big.svg'; import _ from 'lodash'; import md from 'utils/markdown'; import { Link } from 'react-router-dom'; +import { format } from 'date-fns'; import style from './styles.scss'; import AwardBadge from './AwardBadge'; @@ -37,6 +38,10 @@ const Awards = ({ badges, info }) => { if (description) { description = md(description); } + let awardedAt = _.get(reward, 'awarded_at'); + if (awardedAt) { + awardedAt = format(new Date(awardedAt), 'PPP'); + } return ( { title, description, imageUrl, + awardedAt, }); }} /> From afe679d50c74257afba78271d272b53a2ea107e6 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 2 Nov 2022 08:50:18 +0200 Subject: [PATCH 5/5] ci: on QA env --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 20e30546dc..30e6fbba04 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -356,14 +356,14 @@ workflows: filters: branches: only: - - feat/badges-box + - free # This is alternate dev env for parallel testing - "build-qa": context : org-global filters: branches: only: - - feature/dice-setup + - feat/badges-box # This is beta env for production soft releases - "build-prod-beta": context : org-global