diff --git a/__tests__/shared/components/tc-communities/__snapshots__/JoinCommunity.jsx.snap b/__tests__/shared/components/tc-communities/__snapshots__/JoinCommunity.jsx.snap index 0d9dc3b3dd..653f417362 100644 --- a/__tests__/shared/components/tc-communities/__snapshots__/JoinCommunity.jsx.snap +++ b/__tests__/shared/components/tc-communities/__snapshots__/JoinCommunity.jsx.snap @@ -65,6 +65,7 @@ exports[`Matches shallow shapshot 2`] = ` Joining... + + diff --git a/src/shared/components/LoadingIndicator/index.jsx b/src/shared/components/LoadingIndicator/index.jsx index 69a109ddcf..18a98d0945 100644 --- a/src/shared/components/LoadingIndicator/index.jsx +++ b/src/shared/components/LoadingIndicator/index.jsx @@ -8,11 +8,12 @@ import PT from 'prop-types'; import React from 'react'; import { themr } from 'react-css-super-themr'; +import cn from 'classnames'; import style from './styles.scss'; -const LoadingIndicator = ({ theme }) => ( +const LoadingIndicator = ({ theme, className }) => ( ( ); +LoadingIndicator.defaultProps = { + className: '', +}; + LoadingIndicator.propTypes = { theme: PT.shape().isRequired, + className: PT.string, }; export default themr('LoadingIndicator', style)(LoadingIndicator); diff --git a/src/shared/containers/timeline-wall/index.jsx b/src/shared/containers/timeline-wall/index.jsx index 33d25e08d0..3b1cbf0f0f 100644 --- a/src/shared/containers/timeline-wall/index.jsx +++ b/src/shared/containers/timeline-wall/index.jsx @@ -1,14 +1,18 @@ -import React, { useState, useEffect } from 'react'; +import React, { + useState, useEffect, useRef, useMemo, +} from 'react'; import PT from 'prop-types'; import { connect } from 'react-redux'; import TopBanner from 'assets/images/timeline-wall/top-banner.png'; import TopBannerMobile from 'assets/images/timeline-wall/top-banner-mobile.png'; import IconCheveronDownBlue from 'assets/images/timeline-wall/cheveron-down-blue.svg'; +import IconArrowRight from 'assets/images/timeline-wall/icon-arrow-right.svg'; import { deleteEventById, approveEventById, rejectEventById } from 'services/timelineWall'; import cn from 'classnames'; import moment from 'moment'; import { useMediaQuery } from 'react-responsive'; import _ from 'lodash'; +import { config } from 'topcoder-react-utils'; import timelineActions from 'actions/timelineWall'; import LoadingIndicator from 'components/LoadingIndicator'; import TimelineEvents from './timeline-events'; @@ -16,8 +20,11 @@ import PendingApprovals from './pending-approvals'; import './styles.scss'; + +const FETCHING_PENDING_APPROVAL_EVENTS_INTERVAL = _.get(config, 'TIMELINE.FETCHING_PENDING_APPROVAL_EVENTS_INTERVAL', 0); function TimelineWallContainer(props) { const [tab, setTab] = useState(0); + const fetchingApprovalsInterval = useRef(null); const [showRightFilterMobile, setShowRightFilterMobile] = useState(false); const [selectedFilterValue, setSelectedFilterValue] = useState({ year: 0, @@ -36,13 +43,15 @@ function TimelineWallContainer(props) { getAvatar, userAvatars, pendingApprovals, + loadingApprovals, uploading, + uploadResult, } = props; const role = 'Admin User'; const authToken = _.get(auth, 'tokenV3'); const isMobile = useMediaQuery({ - query: '(max-device-width: 768px)', + query: '(max-width: 768px)', }); useEffect(() => { @@ -54,9 +63,25 @@ function TimelineWallContainer(props) { }, []); useEffect(() => { - if (authToken && isAdmin && !pendingApprovals.length) { + if (fetchingApprovalsInterval.current) { + clearInterval(fetchingApprovalsInterval.current); + fetchingApprovalsInterval.current = null; + } + if (authToken && isAdmin) { getPendingApprovals(authToken); + if (FETCHING_PENDING_APPROVAL_EVENTS_INTERVAL) { + fetchingApprovalsInterval.current = setInterval(() => { + getPendingApprovals(authToken); + }, FETCHING_PENDING_APPROVAL_EVENTS_INTERVAL); + } } + + return () => { + if (fetchingApprovalsInterval.current) { + clearInterval(fetchingApprovalsInterval.current); + fetchingApprovalsInterval.current = null; + } + }; }, [isAdmin]); useEffect(() => { @@ -71,7 +96,7 @@ function TimelineWallContainer(props) { }, [events]); useEffect(() => { - if ((pendingApprovals || []).length) { + if (pendingApprovals.length) { _.uniqBy(pendingApprovals, 'createdBy').forEach((eventItem) => { const photoURL = _.get(userAvatars, eventItem.createdBy); if (!photoURL) { @@ -89,7 +114,7 @@ function TimelineWallContainer(props) { let date = moment(`${currentYear}-${currentMonth + 1}`).format('YYYY-MM'); while (!target) { - target = document.getElementById(`${moment(date).year()}-${(moment(date).month()).toString().padStart(2, '0')}`); + target = document.getElementById(`${isMobile ? 'mobile-' : 'desktop-'}${moment(date).year()}-${(moment(date).month()).toString().padStart(2, '0')}`); if (target || !moment(date).isValid() || moment(date).year() > maxYear) { break; @@ -100,11 +125,7 @@ function TimelineWallContainer(props) { if (target) { const yOffset = -10; const coordinate = target.getBoundingClientRect().top + window.pageYOffset + yOffset; - if (isMobile) { - setTimeout(target.scrollTo(), 100); - } else { - window.scrollTo({ top: coordinate, behavior: 'smooth' }); - } + window.scrollTo({ top: coordinate, behavior: 'smooth' }); } else { window.scrollTo({ top: 0, behavior: 'smooth' }); } @@ -137,10 +158,23 @@ function TimelineWallContainer(props) { }; const sortedEvents = _.orderBy(events, ['eventDate'], ['desc']); + const shouldShowDiscuss = useMemo(() => { + if (tab !== 0) { + return false; + } + if (isAdmin) { + return !isMobile; + } + return true; + }, [isAdmin, isMobile, tab]); return (
-
+
top-banner top-banner @@ -170,6 +204,16 @@ function TimelineWallContainer(props) {
) : (

Topcoder Timeline Wall

)} + {shouldShowDiscuss ? ( + + ) : null} +
diff --git a/src/shared/containers/timeline-wall/styles.scss b/src/shared/containers/timeline-wall/styles.scss index cfb126ce8e..04e7ecebfa 100644 --- a/src/shared/containers/timeline-wall/styles.scss +++ b/src/shared/containers/timeline-wall/styles.scss @@ -17,10 +17,16 @@ @media (max-width: 768px) { min-height: 190px; } -} -.header-admin { - min-height: 207px; + &.header-with-discuss { + @media (max-width: 768px) { + min-height: 214px; + } + } + + &.header-admin { + min-height: 207px; + } } .header-content-1 { @@ -68,6 +74,40 @@ display: none !important; } +.btn-discuss { + margin-top: 11px; + display: flex; + align-items: center; + position: absolute; + top: 33px; + left: 1408px; + padding: 0; + background: transparent; + border: none; + color: white; + + @media (max-width: 1500px) { + left: unset; + right: 0; + } + + @media (max-width: 768px) { + position: relative; + right: unset; + top: 0; + margin-left: 27px; + } + + span { + @include roboto-bold; + + font-weight: 700; + font-size: 16px; + line-height: 16px; + margin-right: 4px; + } +} + .tab-item { border: none; background-color: transparent; diff --git a/src/shared/containers/timeline-wall/timeline-events/add-event/index.jsx b/src/shared/containers/timeline-wall/timeline-events/add-event/index.jsx index fc8f790e2a..35ab83d0fb 100644 --- a/src/shared/containers/timeline-wall/timeline-events/add-event/index.jsx +++ b/src/shared/containers/timeline-wall/timeline-events/add-event/index.jsx @@ -18,7 +18,7 @@ import ModalEventAdd from '../../modal-event-add'; import style from './styles.scss'; function AddEvents({ - className, isAuthenticated, createNewEvent, isAdmin, onDoneAddEvent, uploading, + className, isAuthenticated, createNewEvent, isAdmin, onDoneAddEvent, uploading, uploadResult, }) { const [formData, setFormData] = useState({ eventName: '', @@ -86,7 +86,7 @@ function AddEvents({ id="eventName" name="eventName" type="text" - placeholder="In 38 characters or less, write event name here" + placeholder="Enter event title" onChange={(e) => { setFormData({ ...formData, @@ -122,7 +122,7 @@ function AddEvents({ id="description" name="description" type="text" - placeholder="In 240 characters or less, tell the Topcoder community a bit about yourself" + placeholder="Tell your community about this memory" onChange={(e) => { setFormData({ ...formData, @@ -197,6 +197,7 @@ function AddEvents({ }} isAdmin={isAdmin} uploading={uploading} + uploadResult={uploadResult} /> ) : null } @@ -212,6 +213,7 @@ AddEvents.defaultProps = { isAuthenticated: false, isAdmin: false, uploading: false, + uploadResult: '', }; /** @@ -224,6 +226,7 @@ AddEvents.propTypes = { isAdmin: PT.bool, onDoneAddEvent: PT.func.isRequired, uploading: PT.bool, + uploadResult: PT.string, }; export default AddEvents; diff --git a/src/shared/containers/timeline-wall/timeline-events/events/event-item/index.jsx b/src/shared/containers/timeline-wall/timeline-events/events/event-item/index.jsx index c12b4c74c0..cd77aedc18 100644 --- a/src/shared/containers/timeline-wall/timeline-events/events/event-item/index.jsx +++ b/src/shared/containers/timeline-wall/timeline-events/events/event-item/index.jsx @@ -16,7 +16,7 @@ import './styles.scss'; import { DEFAULT_AVATAR_URL } from '../../../../../utils/url'; function EventItem({ - className, isLeft, eventItem, deleteEvent, isAdmin, userAvatars, + className, isLeft, eventItem, deleteEvent, isAdmin, userAvatars, idPrefix, }) { const [isExpanded, setIsExpanded] = useState(false); const [showModalPhoto, setShowModalPhoto] = useState(false); @@ -33,7 +33,7 @@ function EventItem({ 'color-red': eventItem.color === 'red', 'color-purple': eventItem.color === 'purple', })} - id={moment(eventItem.eventDate).format('YYYY-MM')} + id={`${idPrefix}${moment(eventItem.eventDate).format('YYYY-MM')}`} > {isLeft ? null : (
)} {isLeft ? null : ()} @@ -84,7 +84,13 @@ function EventItem({
avatar - {eventItem.createdBy} + {eventItem.createdBy} +   •  {moment(eventItem.eventDate).format('MMM DD, YYYY')}
{isAdmin ? ( @@ -137,6 +143,7 @@ EventItem.defaultProps = { }, isAdmin: false, userAvatars: {}, + idPrefix: '', }; /** @@ -149,6 +156,7 @@ EventItem.propTypes = { isAdmin: PT.bool, userAvatars: PT.shape(), deleteEvent: PT.func.isRequired, + idPrefix: PT.string, }; export default EventItem; diff --git a/src/shared/containers/timeline-wall/timeline-events/events/index.jsx b/src/shared/containers/timeline-wall/timeline-events/events/index.jsx index a53e0409ad..784f6d4ed1 100644 --- a/src/shared/containers/timeline-wall/timeline-events/events/index.jsx +++ b/src/shared/containers/timeline-wall/timeline-events/events/index.jsx @@ -67,6 +67,7 @@ function Events({ userAvatars={userAvatars} isAdmin={isAdmin} deleteEvent={deleteEvent} + idPrefix="desktop-" /> ))}
@@ -84,6 +85,7 @@ function Events({ userAvatars={userAvatars} isAdmin={isAdmin} deleteEvent={deleteEvent} + idPrefix="desktop-" /> ))}
@@ -101,6 +103,7 @@ function Events({ userAvatars={userAvatars} isAdmin={isAdmin} deleteEvent={deleteEvent} + idPrefix="mobile-" /> ))}
diff --git a/src/shared/containers/timeline-wall/timeline-events/index.jsx b/src/shared/containers/timeline-wall/timeline-events/index.jsx index ffb87e8166..582a74d478 100644 --- a/src/shared/containers/timeline-wall/timeline-events/index.jsx +++ b/src/shared/containers/timeline-wall/timeline-events/index.jsx @@ -22,6 +22,7 @@ function TimelineEvents({ userAvatars, onDoneAddEvent, uploading, + uploadResult, deleteEvent, }) { return ( @@ -34,6 +35,7 @@ function TimelineEvents({ isAdmin={isAdmin} onDoneAddEvent={onDoneAddEvent} uploading={uploading} + uploadResult={uploadResult} /> {events.length ? ( { }, body: form, }); + if (res.status >= 300) { + const result = await res.json(); + return result.message || 'There was an error during add event.'; + } - return res.json(); + return ''; } catch (error) { - return []; + return 'There was an error during add event.'; } };