diff --git a/.circleci/config.yml b/.circleci/config.yml index 8492622fba..f99b3a4db1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -363,7 +363,7 @@ workflows: filters: branches: only: - - jan-updates-1 + - jan-fixes-2 # This is stage env for production QA releases - "build-prod-staging": context : org-global diff --git a/src/shared/components/Contentful/Article/Article.jsx b/src/shared/components/Contentful/Article/Article.jsx index f00c373f90..61abb934d3 100644 --- a/src/shared/components/Contentful/Article/Article.jsx +++ b/src/shared/components/Contentful/Article/Article.jsx @@ -39,7 +39,7 @@ const CONTENT_PREVIEW_LENGTH = 110; // Votes local storage key const LOCAL_STORAGE_KEY = 'VENBcnRpY2xlVm90ZXM='; // def banner image -const DEFAULT_BANNER_IMAGE = 'https://images.ctfassets.net/piwi0eufbb2g/7v2hlDsVep7FWufHw0lXpQ/2505e61a880e68fab4e80cd0e8ec1814/0C37CB5E-B253-4804-8935-78E64E67589E.png'; +const DEFAULT_BANNER_IMAGE = 'https://images.ctfassets.net/piwi0eufbb2g/7v2hlDsVep7FWufHw0lXpQ/2505e61a880e68fab4e80cd0e8ec1814/0C37CB5E-B253-4804-8935-78E64E67589E.png?w=1200&h=630'; // random ads banner - left sidebar const RANDOM_BANNERS = ['6G8mjiTC1mzeSQ2YoUG1gB', '1DnDD02xX1liHfSTf5Vsn8', 'HQZ3mN0rR92CbNTkKTHJ5', '1OLoX8ZsvjAnn4TdGbZESD', '77jn01UGoQe2gqA7x0coQD']; const RANDOM_BANNER = RANDOM_BANNERS[_.random(0, 4)]; @@ -167,9 +167,10 @@ class Article extends React.Component { + - - + +
diff --git a/src/shared/components/Contentful/ContentSlider/ContentSlider.jsx b/src/shared/components/Contentful/ContentSlider/ContentSlider.jsx index fd84bf3b46..b0fc1796d5 100644 --- a/src/shared/components/Contentful/ContentSlider/ContentSlider.jsx +++ b/src/shared/components/Contentful/ContentSlider/ContentSlider.jsx @@ -36,8 +36,66 @@ class ContentSlider extends Component { const { children, theme, autoStart, duration, id, containerStyle, slidesToShow, framePadding, withoutControls, vertical, cellSpacing, - cellAlign, wrapAround, heightMode, arrowTheme, + cellAlign, wrapAround, heightMode, arrowTheme, hideSliderDots, themeName, + arrowLeftImage, arrowRightImage, } = this.props; + const renderControls = { + renderCenterLeftControls: ({ previousSlide }) => ( + + {arrowRightImage && Slider left arrow} + {!arrowRightImage && (arrowTheme === 'Gray' ? : )} + + ), + renderCenterRightControls: ({ nextSlide }) => ( + + {arrowLeftImage && Slider right arrow} + {!arrowLeftImage && (arrowTheme === 'Gray' ? : )} + + ), + }; + if (hideSliderDots) { + renderControls.renderBottomCenterControls = () => null; + } + if (themeName === 'Controls Bottom Right') { + renderControls.renderCenterLeftControls = () => null; + renderControls.renderCenterRightControls = () => null; + renderControls.renderBottomRightControls = ({ previousSlide, nextSlide }) => ( +
+ + {arrowLeftImage && Slider left arrow} + {!arrowLeftImage && (arrowTheme === 'Gray' ? : )} + + + {arrowRightImage && Slider right arrow} + {!arrowRightImage && (arrowTheme === 'Gray' ? : )} + +
+ ); + } return (
( - - {arrowTheme === 'Gray' ? : } - - )} - renderCenterRightControls={({ nextSlide }) => ( - - {arrowTheme === 'Gray' ? : } - - )} + {...renderControls} > {children} @@ -103,6 +140,10 @@ ContentSlider.defaultProps = { wrapAround: true, heightMode: 'max', arrowTheme: 'Gray', + hideSliderDots: false, + themeName: 'Default', + arrowLeftImage: null, + arrowRightImage: null, }; ContentSlider.propTypes = { @@ -110,14 +151,7 @@ ContentSlider.propTypes = { children: PT.node.isRequired, autoStart: PT.bool, duration: PT.number, - theme: PT.shape({ - container: PT.string, - content: PT.string, - controlLeft: PT.string, - controlRight: PT.string, - multiContent: PT.any, - singleContent: PT.any, - }), + theme: PT.shape(), containerStyle: PT.shape(), slidesToShow: PT.number, framePadding: PT.string, @@ -128,6 +162,10 @@ ContentSlider.propTypes = { wrapAround: PT.bool, heightMode: PT.string, arrowTheme: PT.string, + hideSliderDots: PT.bool, + themeName: PT.string, + arrowLeftImage: PT.shape(), + arrowRightImage: PT.shape(), }; export default themr('Contentful-Slider', defaultTheme)(ContentSlider); diff --git a/src/shared/components/Contentful/ContentSlider/index.jsx b/src/shared/components/Contentful/ContentSlider/index.jsx index 0eabebf054..f90e740c44 100644 --- a/src/shared/components/Contentful/ContentSlider/index.jsx +++ b/src/shared/components/Contentful/ContentSlider/index.jsx @@ -36,6 +36,9 @@ function ContentSliderItemsLoader(props) { environment, heightMode, arrowTheme, + hideSliderDots, + arrowLeftImage, + arrowRightImage, } = props; return ( @@ -58,6 +61,10 @@ function ContentSliderItemsLoader(props) { wrapAround={wrapAround} heightMode={heightMode} arrowTheme={arrowTheme} + hideSliderDots={hideSliderDots} + themeName={theme} + arrowLeftImage={arrowLeftImage} + arrowRightImage={arrowRightImage} > { ids.map(itemId => ( @@ -94,6 +101,9 @@ ContentSliderItemsLoader.defaultProps = { environment: null, heightMode: 'current', arrowTheme: 'Gray', + hideSliderDots: false, + arrowLeftImage: null, + arrowRightImage: null, }; ContentSliderItemsLoader.propTypes = { @@ -115,6 +125,9 @@ ContentSliderItemsLoader.propTypes = { wrapAround: PT.bool, heightMode: PT.string, arrowTheme: PT.string, + hideSliderDots: PT.bool, + arrowLeftImage: PT.shape(), + arrowRightImage: PT.shape(), }; export default function ContentfulSlider(props) { @@ -152,6 +165,9 @@ export default function ContentfulSlider(props) { wrapAround={fields.wrapAround} heightMode={fields.heightMode} arrowTheme={fields.arrowTheme} + hideSliderDots={fields.hideSliderDots} + arrowLeftImage={fields.arrowLeftImage} + arrowRightImage={fields.arrowRightImage} /> ); }} diff --git a/src/shared/components/Contentful/ContentSlider/themes/default.scss b/src/shared/components/Contentful/ContentSlider/themes/default.scss index 59f0d8d5c4..e1d07d595b 100644 --- a/src/shared/components/Contentful/ContentSlider/themes/default.scss +++ b/src/shared/components/Contentful/ContentSlider/themes/default.scss @@ -46,3 +46,19 @@ margin-right: 15px; } } + +.bottomRightControls { + display: flex; + position: absolute; + right: 0; + bottom: -50px; + + a.controlLeft { + margin: 0; + margin-right: 20px; + } + + a.controlRight { + margin: 0; + } +} diff --git a/src/shared/components/Contentful/Viewport/index.jsx b/src/shared/components/Contentful/Viewport/index.jsx index 52a79427c2..30d3a78e28 100644 --- a/src/shared/components/Contentful/Viewport/index.jsx +++ b/src/shared/components/Contentful/Viewport/index.jsx @@ -17,7 +17,7 @@ import BlogFeed from 'containers/Contentful/BlogFeed'; import { errors } from 'topcoder-react-lib'; import LoadingIndicator from 'components/LoadingIndicator'; import PT from 'prop-types'; -import React from 'react'; +import React, { useState } from 'react'; import Countdown from 'components/Contentful/Countdown'; import Tabs from 'components/Contentful/Tabs'; import AppComponentLoader from 'components/Contentful/AppComponent'; @@ -30,11 +30,13 @@ import Article from 'components/Contentful/Article'; import { isomorphy } from 'topcoder-react-utils'; import MemberTalkCloud from 'components/Contentful/MemberTalkCloud'; import Masonry from 'react-masonry-css'; +import { PrimaryButton } from 'topcoder-react-ui-kit'; // AOS import AOS from 'aos'; import 'aos/dist/aos.css'; +import tc from 'components/buttons/themed/tc.scss'; import Viewport from './Viewport'; import columnTheme from './themes/column.scss'; @@ -78,6 +80,10 @@ const THEMES = { Masonry: masonryTheme, }; +const buttonThemes = { + tc, +}; + /* Loads viewport content assets. */ function ViewportContentLoader(props) { const { @@ -91,12 +97,19 @@ function ViewportContentLoader(props) { viewportId, animationOnScroll, masonryConfig, + itemsPerPage, + loadMoreButtonText, + loadMoreButtonTheme, + loadMoreButtonContainerStyles, } = props; let { extraStylesForContainer, } = props; + const [page, setPage] = useState(1); - const getInner = data => contentIds.map((id) => { + const getInner = data => contentIds.slice( + 0, (itemsPerPage ? itemsPerPage * page : contentIds.length), + ).map((id) => { const type = data.entries.items[id].sys.contentType.sys.id; const Component = COMPONENTS[type]; if (Component) { @@ -146,7 +159,44 @@ function ViewportContentLoader(props) { AOS.init(); } } - return ( + return itemsPerPage ? ( +
+ + { + themeName === 'Masonry' && masonryConfig ? ( + + {getInner(data)} + + ) : getInner(data) + } + + { + page * itemsPerPage < contentIds.length && ( +
+ { + setPage(page + 1); + }} + theme={{ + button: buttonThemes.tc[loadMoreButtonTheme], + disabled: buttonThemes.tc.themedButtonDisabled, + }} + > + {loadMoreButtonText} + +
+ ) + } +
+ ) : ( ); })} diff --git a/src/shared/components/Contentful/Viewport/themes/column.scss b/src/shared/components/Contentful/Viewport/themes/column.scss index 2c30bf0518..56f62c8cac 100644 --- a/src/shared/components/Contentful/Viewport/themes/column.scss +++ b/src/shared/components/Contentful/Viewport/themes/column.scss @@ -2,3 +2,14 @@ flex-direction: column; width: 100%; } + +.loadMoreWrapper { + display: flex; + flex-direction: column; + + .loadMoreButtonContainer { + display: flex; + justify-content: center; + align-items: center; + } +} diff --git a/src/shared/components/Contentful/Viewport/themes/grid.scss b/src/shared/components/Contentful/Viewport/themes/grid.scss index 4a9d33cc93..6e2ad2ab95 100644 --- a/src/shared/components/Contentful/Viewport/themes/grid.scss +++ b/src/shared/components/Contentful/Viewport/themes/grid.scss @@ -24,3 +24,14 @@ } } } + +.loadMoreWrapper { + display: flex; + flex-direction: column; + + .loadMoreButtonContainer { + display: flex; + justify-content: center; + align-items: center; + } +} diff --git a/src/shared/components/Contentful/Viewport/themes/masonry.scss b/src/shared/components/Contentful/Viewport/themes/masonry.scss index 6f3dd73412..a03628ce60 100644 --- a/src/shared/components/Contentful/Viewport/themes/masonry.scss +++ b/src/shared/components/Contentful/Viewport/themes/masonry.scss @@ -21,3 +21,14 @@ $gutterSize: 20px; } } } + +.loadMoreWrapper { + display: flex; + flex-direction: column; + + .loadMoreButtonContainer { + display: flex; + justify-content: center; + align-items: center; + } +}