diff --git a/src/assets/icons/icon-check-light.svg b/src/assets/icons/icon-check-light.svg deleted file mode 100644 index 0c9a0f387..000000000 --- a/src/assets/icons/icon-check-light.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/src/components/ActionCard/CommentMobile.jsx b/src/components/ActionCard/CommentMobile.jsx index dea0c764d..6a5104192 100644 --- a/src/components/ActionCard/CommentMobile.jsx +++ b/src/components/ActionCard/CommentMobile.jsx @@ -16,7 +16,7 @@ const CommentMobile = ({ messageId, author, date, children }) => { const messageLink = window.location.pathname.substr(0, window.location.pathname.indexOf('#')) + `#comment-${messageId}` return ( -
+
{moment(date).fromNow()} diff --git a/src/components/ActionCard/CommentMobile.scss b/src/components/ActionCard/CommentMobile.scss index 80fa6bb34..4d98d8b33 100644 --- a/src/components/ActionCard/CommentMobile.scss +++ b/src/components/ActionCard/CommentMobile.scss @@ -3,11 +3,8 @@ .comment { background-color: $tc-gray-neutral-light; border-radius: 4px; + margin-top: 3 * $base-unit; padding: 2 * $base-unit 2 * $base-unit 3 * $base-unit 2 * $base-unit; - - & + & { - margin-top: 3 * $base-unit; - } } .header { diff --git a/src/components/ColorSelector/ColorSelector.scss b/src/components/ColorSelector/ColorSelector.scss index b8b98adfe..d4abfeb57 100644 --- a/src/components/ColorSelector/ColorSelector.scss +++ b/src/components/ColorSelector/ColorSelector.scss @@ -13,6 +13,10 @@ margin: 0 15px; position: relative; + @media screen and (max-width: $screen-md - 1px) { + position: static; + } + .remove-color { background-color: $tc-gray-60; opacity: 0.7; @@ -31,9 +35,10 @@ } svg { + display: block; position: relative; border-color: $tc-gray-30; - margin: 10px; + margin: 11px; } } @@ -48,6 +53,18 @@ padding: 10px; box-shadow: rgba($tc-black, 0.14902) 0px 0px 0px 1px, rgba($tc-black, 0.14902) 0px 8px 16px; + @media screen and (max-width: $screen-md - 1px) { + left: 50%; + transform: translateX(-50%); + top: auto; + width: 340px; + } + + /* if picker doesn't fit screen, make it even smaller */ + @media screen and (max-width: 350px - 1px) { + width: 310px; + } + > div { // box-shadow: none !important; } @@ -63,4 +80,3 @@ } } } - \ No newline at end of file diff --git a/src/components/Feed/FeedComments.jsx b/src/components/Feed/FeedComments.jsx index 078ecf786..0fb06e651 100644 --- a/src/components/Feed/FeedComments.jsx +++ b/src/components/Feed/FeedComments.jsx @@ -8,7 +8,7 @@ import cn from 'classnames' import {markdownToHTML} from '../../helpers/markdownToState' import MediaQuery from 'react-responsive' import CommentMobile from '../ActionCard/CommentMobile' -// import { THREAD_MESSAGES_PAGE_SIZE } from '../../config/constants' +import { SCREEN_BREAKPOINT_MD } from '../../config/constants' const getCommentCount = (totalComments) => { if (!totalComments) { @@ -72,7 +72,7 @@ class FeedComments extends React.Component {
- + {(matches) => (matches ? (
{comments.map((item, idx) => ( diff --git a/src/components/Feed/FeedMobile.jsx b/src/components/Feed/FeedMobile.jsx index d85cf3e4c..6a5c73d60 100644 --- a/src/components/Feed/FeedMobile.jsx +++ b/src/components/Feed/FeedMobile.jsx @@ -27,6 +27,33 @@ class FeedMobile extends React.Component { } } + componentWillReceiveProps(nextProps) { + // if comments just has been loaded, check if have to open the thread + if (this.props.comments.length === 0 && nextProps.comments.length > 0) { + this.openCommentFromHash(nextProps.comments) + } + } + + componentWillMount() { + this.openCommentFromHash(this.props.comments) + } + + /** + * If there is hash in the URL referencing some comment, + * open the comments thread by default + * + * @param {Array} comments list of comments + */ + openCommentFromHash(comments) { + const { isCommentsShown } = this.state + const commentInHash = window.location.hash.match(/#comment-(\d+)/) + const isCommentInTheFeed = !!(commentInHash && _.find(comments, (comment) => (comment.id.toString() === commentInHash[1]))) + + if (!isCommentsShown && isCommentInTheFeed) { + this.toggleComments() + } + } + toggleComments() { this.setState({ isCommentsShown: !this.state.isCommentsShown }) } @@ -62,14 +89,6 @@ class FeedMobile extends React.Component {

{title}

-
- {totalComments > 0 ? ( - - ) : ( -
No posts yet
- )} - {allowComments && } -
{isCommentsShown && } +
+ {totalComments > 0 ? ( + + ) : ( +
No posts yet
+ )} + {allowComments && } +
{children} {isNewCommentMobileOpen && h4 { @include roboto-medium; font-size: 15px; @@ -18,29 +18,41 @@ margin-bottom: 2px; } } - + .file-list-item { display: flex; border-top: 1px dashed $tc-gray-30; padding: 15px 0; width: 100%; align-items: stretch; - + + @media screen and (max-width: $screen-md - 1px) { + padding-top: 13px + 20px + 11px; /* date line height and its margins */ + position: relative; + } + .icon-col { padding-top: 3px; width: 60px; } - + .content-col { flex: 2; - + h4 { @include roboto-bold; font-size: 15px; color: $tc-gray-80; max-width: 380px; + + @media screen and (max-width: $screen-md - 1px) { + margin-right: 67px; + max-width: calc(100vw - 160px); + overflow: hidden; + text-overflow: ellipsis; + } } - + p { @include roboto; font-size: 13px; @@ -48,44 +60,70 @@ color: $tc-gray-70; line-height: 20px; } - + .title { width: 100%; display: flex; line-height: 25px; + + @media screen and (max-width: $screen-md - 1px) { + flex-direction: column; + } } - + .size { @include roboto; font-size: 15px; color: $tc-gray-60; margin-left: auto; text-align: right; - + + @media screen and (max-width: $screen-md - 1px) { + color: $tc-gray-40; + margin-left: 0; + text-align: left; + } + .Tooltip { line-height: 100%; + + @media screen and (max-width: $screen-md - 1px) { + left: 0; + position: absolute; + top: 13px; + } } - + .date { font-size: 13px; - line-height: normal; + line-height: 20px; margin-top: 5px; + + @media screen and (max-width: $screen-md - 1px) { + margin-top: 0; + } } } - + .edit-icons { margin-left: 5px; - + + @media screen and (max-width: $screen-md - 1px) { + right: 0; + position: absolute; + top: 46px; + } + i { cursor: pointer; } } - + .title-edit { display: flex; align-items: center; margin-bottom: 10px; - + input { @include roboto-bold; font-size: 15px; @@ -97,7 +135,7 @@ background: $tc-gray-neutral-light; } } - + .tc-textarea { margin-left: 0; padding: 5px 10px; @@ -110,27 +148,34 @@ resize: none; } } - + .icon-trash, .icon-edit { margin-left: 13px; vertical-align: middle; } - + .icon-trash { margin-left: 22px; } - + .icon-save { margin-right: 25px; + + @media screen and (max-width: $screen-md - 1px) { + margin-right: 15px; + } } - + .icon-close { margin-right: 10px; + + @media screen and (max-width: $screen-md - 1px) { + margin-right: 0; + } } } - + .delete-file-modal { } } - \ No newline at end of file diff --git a/src/components/Footer/Footer.jsx b/src/components/Footer/Footer.jsx index 3139bf3f3..6cf128749 100644 --- a/src/components/Footer/Footer.jsx +++ b/src/components/Footer/Footer.jsx @@ -3,7 +3,7 @@ import MenuBar from 'appirio-tech-react-components/components/MenuBar/MenuBar' import moment from 'moment' import MediaQuery from 'react-responsive' import FooterV2 from '../FooterV2/FooterV2' -import { NEW_PROJECT_PATH } from '../../config/constants' +import { NEW_PROJECT_PATH, SCREEN_BREAKPOINT_MD } from '../../config/constants' require('./Footer.scss') @@ -25,14 +25,14 @@ const Footer = () => { const shouldHideOnMobile = window.location.pathname !== '/' return ( - + {(matches) => { if (matches) { return (shouldHideOnDesktop ? null :

© Topcoder { currentYear }

- +
) diff --git a/src/components/Grid/GridView.jsx b/src/components/Grid/GridView.jsx index d74c7c650..ae6064388 100644 --- a/src/components/Grid/GridView.jsx +++ b/src/components/Grid/GridView.jsx @@ -6,6 +6,7 @@ import PaginationBar from './PaginationBar' import Placeholder from './Placeholder' import InfiniteScroll from 'react-infinite-scroller' import LoadingIndicator from '../../components/LoadingIndicator/LoadingIndicator' +import NewProjectCard from '../../projects/list/components/Projects/NewProjectCard' import { PROJECTS_LIST_PER_PAGE } from '../../config/constants' import './GridView.scss' @@ -81,6 +82,7 @@ const GridView = props => {
} { !isLoading && !hasMore &&
No more {projectsStatus} projects
} +
) } diff --git a/src/components/Grid/GridView.scss b/src/components/Grid/GridView.scss index d510bca8c..f7ec2c34d 100644 --- a/src/components/Grid/GridView.scss +++ b/src/components/Grid/GridView.scss @@ -1,30 +1,46 @@ @import '~tc-ui/src/styles/tc-includes'; +@import '../../styles/variables'; :global { .gridview-content.content { overflow: visible; width: 100%; - + .container { width: auto; - min-width: 1120px; padding: 0; background: $tc-white; margin: 0 20px 20px 20px; border-radius: 6px; border: 1px solid $tc-gray-20; + + @media screen and (max-width: $screen-md - 1px) { + border: 0; + border-radius: 0; + margin: 0; + min-width: 0; + } + } + + .project-card-new { + display: none; + padding: 0 2 * $base-unit 2 * $base-unit; + + @media screen and (max-width: $screen-md - 1px) { + display: block; + } } } - + .flex-data { .hide { display: none; } - + .show { display: block; } - + .txt-black { color: $tc-black; } @@ -32,21 +48,21 @@ .row { margin: 0; border-bottom: 1px solid $tc-gray-10; - + &.header:first-child { border-color: $tc-gray-20; } } - + .flex-row { display: flex; position: relative; transition: 200ms all; - + &:hover { background-color: rgba($tc-dark-blue-10, .5); } - + .flex-item-title, .flex-item { -webkit-align-items: center; @@ -54,17 +70,17 @@ display: -webkit-inline-flex; display: inline-flex; min-height: 71px; - + &.item-id .spacing { color: $tc-gray-30; } - + .spacing { @include roboto-medium; font-size: $tc-label-md; color: $tc-gray-50; line-height: $base-unit + 11; - + .sort-txt { &.icon-up, &.icon-down { @@ -72,7 +88,7 @@ position: relative; padding: 0 20px 0 0; } - + &.icon-up::after, &.icon-down::after { font-size: 0; @@ -86,19 +102,19 @@ right: 0; margin-top: -12px; } - + &a:hover { cursor: pointer; } } } } - + .flex-item-title.item-type, .flex-item.item-type .blue-block { padding-left: 15px; } - + .blue-border { background: $tc-dark-blue-70; width: 2px; @@ -115,12 +131,12 @@ height: 70px; border-radius: 6px 6px 0 0; user-select: none; - + &:hover { background-color: rgba($tc-gray-10, .5); } } - + .mask-layer { background: rgba($tc-white, 0.7); width: 100%; @@ -130,11 +146,11 @@ left: 0; z-index: 99; } - + .spacing-grid { overflow: hidden; } - + .link-title { @include roboto-medium; @include ellipsis; @@ -147,12 +163,12 @@ padding-right: 20px; overflow: hidden; text-overflow: ellipsis; - + &:hover { text-decoration: underline; } } - + .txt-time { font-size: $tc-label-sm; color: $tc-gray-50; @@ -161,12 +177,12 @@ display: block; font-weight: normal; } - + .txt-normal { font-weight: normal; } } - + /* .txt-italic */ .txt-italic { @include roboto; @@ -174,39 +190,43 @@ font-size: $tc-label-md; color: $tc-gray-30; line-height: $base-unit * 3; - + &.txt-normal { font-style: normal; } - + &.txt-red { color: $tc-red; } } - + .item-projects { .spacing { width: 100%; } } - + .user-block { white-space: nowrap; - + .txt-box span { font-weight: normal; } - + .linnk-black { white-space: nowrap; text-overflow: ellipsis; } } - + .gridview-load-more { text-align: center; + + @media screen and (max-width: $screen-md - 1px) { + padding: 2 * $base-unit 0; + } } - + .gridview-no-more { @include roboto; color: $tc-gray-40; @@ -214,7 +234,7 @@ padding: 10px 0; text-align: center; } - + /* .txt-status */ .txt-status { @include roboto-medium; @@ -224,7 +244,7 @@ padding-left: 30px; position: relative; display: inline-block; - + &:after { font-size: 0; line-height: 0; @@ -238,34 +258,34 @@ margin-top: -12px; } } - + .project-status-title { position: relative; display: block; background-position: center center; height: 16px; width: 16px; - + & * { fill: $tc-gray-40; } } - + .drop-down { position: relative; display: inline-block; margin-left: 15px; - + .txt-link { color: $tc-gray-50; position: relative; padding-right: 14px; display: block; - + &.active { color: $tc-dark-blue; } - + &:after { font-size: 0; line-height: 0; @@ -278,7 +298,7 @@ right: 0; } } - + .down-layer { background: $tc-white; border-radius: 5px; @@ -289,10 +309,10 @@ left: -20px; box-shadow: 0 0 10px rgba($tc-black, 0.2); z-index: 599; - + ul { padding: 10px 0; - + li { a { font-size: $tc-label-md; @@ -300,12 +320,12 @@ line-height: $base-unit * 6; display: block; padding: 0 10px 0 20px; - + &:hover { background-color: $tc-dark-blue-10; } } - + &.active, &.active:hover { @include roboto-bold; @@ -315,14 +335,14 @@ } } } - + /* .pages */ .pages { background: $tc-gray-neutral-light; min-height: 70px; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; - + .right-page { background: $tc-white; border: 1px solid $tc-gray-30; @@ -331,11 +351,11 @@ margin: 20px 20px 0 0; float: right; display: flex; - + ul { display: flex; } - + ul, li, .btn-prev, @@ -343,16 +363,16 @@ text-align: center; // float: left; } - + li { border-right: 1px solid $tc-gray-30; position: relative; - + &.active a { background: $tc-gray-20; box-shadow: inset -1px 1px 2px 0px rgba($tc-gray-80, 0.20); } - + .down-layer { background: $tc-white; border-radius: 5px; @@ -366,7 +386,7 @@ margin-left: -70px; box-shadow: 0 0 10px rgba($tc-black, 0.2); z-index: 99; - + &::before { font-size: 0; line-height: 0; @@ -382,7 +402,7 @@ left: 50%; margin-left: -7px; } - + .txt { @include roboto; font-size: $tc-label-md; @@ -392,13 +412,13 @@ display: block; float: left; } - + .inputs { width: 34px; height: 30px; display: block; margin: 13px 0 0 90px; - + input { @include roboto; font-size: $tc-label-md; @@ -412,19 +432,19 @@ } } } - + &.go-to-page-pill { .tooltip-target { min-width: 27px; height: 28px; } - + .go-to-page-tooltip { display: flex; align-items: center; @include tc-label-md; line-height: $base-unit * 6; - + input[type="number"] { width: 50px; height: $base-unit * 6; @@ -434,7 +454,7 @@ } } } - + a { @include roboto; font-size: $tc-label-md; @@ -444,13 +464,13 @@ padding: 1px 8px; line-height: $base-unit + 23; display: block; - + &.btn-prev { border-right: 1px solid $tc-gray-30; min-width: 70px; padding-left: 24px; position: relative; - + &:before { font-size: 0; line-height: 0; @@ -463,12 +483,12 @@ left: 12px; } } - + &.btn-next { min-width: 70px; padding-right: 24px; position: relative; - + &:after { font-size: 0; line-height: 0; @@ -484,8 +504,8 @@ } } } - - + + /*fix layout issue when smaller than 1024px width*/ @media (max-width: 1003px) { /* .flex-data */ @@ -494,11 +514,11 @@ font-size: $tc-label-sm; line-height: $base-unit * 3; } - + .txt-black { display: block; } - + .flex-59 { .flex-item-title .spacing, .flex-item .spacing { @@ -506,13 +526,13 @@ padding-bottom: 5px; line-height: $base-unit * 3; } - + .item-ref-code .spacing { padding: 0 30px 0 0; min-width: 85px; } } - + .txt-box .link-black, .txt-gray-md, .txt-italic { @@ -531,5 +551,148 @@ margin-left: 0; } } + + @media screen and (max-width: 1180px - 1px) { + .flex-data { + .flex-row .flex-item-title.item-id { + display: none; + } + + .flex-row:not(.row-th) .flex-item-title.item-icon { + justify-content: center; + width: 62px; + } + } + } + + @media screen and (max-width: 1160px - 1px) { + .flex-data { + .flex-row .flex-item-title.item-manager { + display: none; + } + } + } + + @media screen and (max-width: 1024px - 1px) { + .flex-data { + .flex-row .flex-item-title.item-status { + display: none; + } + } + } + + @media screen and (max-width: $screen-rg - 1px) { + .flex-data { + .row .flex-item-title { + display: none; + } + + .row:not(.header) .flex-row { + flex-direction: column; + padding-left: 62px; /* space for absolute icon */ + padding-bottom: 2 * $base-unit; + } + + .row:not(.header) .flex-item-title.item-projects { + display: flex; + flex: 1; + min-height: 0; + min-width: 0; + padding-bottom: 0; + padding-right: 9 * $base-unit; + } + + .row:not(.header) .flex-item-title.item-customer { + display: flex; + flex: 1; + min-height: 0; + + /* as block inside has additional paddings + compensate position by negative margins */ + .user-block { + margin-bottom: -3px; + margin-top: -4px; + margin-left: -10px; + } + + /* add same paddings for unknown user + as has user with tooltip */ + .user-block.txt-italic { + padding: 6px 10px; + } + + .project-customer { + line-height: 4 * $base-unit; + } + } + + .row:not(.header) .flex-item-title.item-icon { + align-items: flex-start; + justify-content: center; + left: 0; + padding-top: 4 * $base-unit; + position: absolute; + top: 0; + width: 62px; + } + + .row.header .flex-item-title.item-status-date { + display: flex; + flex: 1; + justify-content: space-between; + min-height: 50px; + min-width: 0; + padding-left: 20px; + padding-right: 20px; + + &::before { + @include roboto-bold; + color: $tc-gray-40; + content: 'Sort by'; + display: block; + font-size: 14px; + } + + .project-drop-down .down-layer { + right: -15px; + left: auto; + min-width: 0; + width: calc(100% + 35px); + } + } + + .flex-row:not(.row-th) .item-projects .project-container .project-title { + flex-direction: column; + display: inline-block; + align-items: flex-start; + + .link-title { + line-height: 4 * $base-unit; + width: 100%; + } + + .item-ref-code { + display: inline-block; + margin-top: 2px; + margin-bottom: 3px; + } + } + + .flex-row:not(.row-th) .item-projects .project-container .project-description { + margin-right: 0; + } + + .row.header .flex-row.row-th { + height: 50px; + } + + .row.header .flex-row.row-th:hover { + background-color: $tc-gray-neutral-light; + } + + .flex-row .flex-item-title .spacing { + line-height: 4 * $base-unit; + } + } + } } - \ No newline at end of file diff --git a/src/components/Layout/Layout.scss b/src/components/Layout/Layout.scss index 8480506c7..4c2344f29 100644 --- a/src/components/Layout/Layout.scss +++ b/src/components/Layout/Layout.scss @@ -4,7 +4,6 @@ .main-wrapper { min-height: calc(100vh - 40px); // minus footer height padding-top: 60px; - transition: all 150ms ease-in-out; overflow: hidden; @media screen and (max-width: $screen-md - 1px) { @@ -14,6 +13,10 @@ &.with-filters { padding-top: 120px; + + @media screen and (max-width: $screen-md - 1px) { + padding-top: 0; + } } &[data-route*='new-project'] { diff --git a/src/components/MobileMenu/MobileMenuToggle.scss b/src/components/MobileMenu/MobileMenuToggle.scss index 31d859acb..0c9880284 100644 --- a/src/components/MobileMenu/MobileMenuToggle.scss +++ b/src/components/MobileMenu/MobileMenuToggle.scss @@ -1,6 +1,8 @@ @import '~tc-ui/src/styles/tc-includes'; .toggle { + align-items: center; + display: flex; padding-right: $base-unit * 3; @media screen and (min-width: $screen-md) { diff --git a/src/components/ModalControl/ModalControl.scss b/src/components/ModalControl/ModalControl.scss index eee918121..6a6027bff 100644 --- a/src/components/ModalControl/ModalControl.scss +++ b/src/components/ModalControl/ModalControl.scss @@ -13,13 +13,13 @@ width: 40px; } .icon-wrapper { - display: table-cell; - vertical-align: middle; - height: 43px; - text-align: center; + align-items: center; + display: flex; + justify-content: center; + height: 40px; width: 40px; color: $tc-gray-70; - + svg { fill: $tc-gray-70; } @@ -29,6 +29,11 @@ color: $tc-gray-70; font-size: 13px; text-transform: uppercase; + + + @media screen and (max-width: $screen-md - 1px) { + display: none; + } } &:hover { color: $tc-white; @@ -38,11 +43,10 @@ .label { color: $tc-black; } - + svg { fill: $tc-white; } } } } - \ No newline at end of file diff --git a/src/components/NotificationItem/NotificationItem.jsx b/src/components/NotificationItem/NotificationItem.jsx index 3a8e378fa..d22143e6d 100644 --- a/src/components/NotificationItem/NotificationItem.jsx +++ b/src/components/NotificationItem/NotificationItem.jsx @@ -10,10 +10,8 @@ import cn from 'classnames' import { NOTIFICATION_TYPE } from '../../config/constants' import moment from 'moment' import { Link } from 'react-router-dom' -import MediaQuery from 'react-responsive' import './NotificationItem.scss' import Check from '../../assets/icons/check.svg' -import CheckLight from '../../assets/icons/icon-check-light.svg' import IconNotificationMememberAdded from '../../assets/icons/notification-member-added.svg' import IconNotificationNewPosts from '../../assets/icons/notification-new-posts.svg' import IconNotificationNewProject from '../../assets/icons/notification-new-project.svg' @@ -77,7 +75,6 @@ const formatDate = (date) => { return format } - const NotificationItem = (props) => { const { id, onLinkClick } = props const notificationItem = ( @@ -96,22 +93,16 @@ const NotificationItem = (props) => { props.onReadToggleClick(props.id) }} > - - {(matches) => (matches ? : )} - + ) return ( - - {(matches) => ( - matches && props.goto - ? props.seen || onLinkClick(id)}>{notificationItem} - : notificationItem - )} - + props.goto + ? onLinkClick(id)}>{notificationItem} + : notificationItem ) } diff --git a/src/components/NotificationItem/NotificationItem.scss b/src/components/NotificationItem/NotificationItem.scss index 3bd040ccd..cf8f78d09 100644 --- a/src/components/NotificationItem/NotificationItem.scss +++ b/src/components/NotificationItem/NotificationItem.scss @@ -54,7 +54,7 @@ @media screen and (max-width: $screen-md - 1px) { font-size: 15px; - line-height: 25px; + line-height: 22px; } > strong { @@ -100,7 +100,9 @@ width: 42px; svg { + height: auto; fill: $tc-gray-20; + width: 16px; } } } diff --git a/src/components/NotificationsDropdown/NotificationsDropdown.jsx b/src/components/NotificationsDropdown/NotificationsDropdown.jsx index 7502ff044..18f11c3f1 100644 --- a/src/components/NotificationsDropdown/NotificationsDropdown.jsx +++ b/src/components/NotificationsDropdown/NotificationsDropdown.jsx @@ -17,7 +17,7 @@ const NotificationsDropdown = (props) => {
diff --git a/src/components/NotificationsDropdown/NotificationsDropdown.scss b/src/components/NotificationsDropdown/NotificationsDropdown.scss index bbaaeb407..e8dab7488 100644 --- a/src/components/NotificationsDropdown/NotificationsDropdown.scss +++ b/src/components/NotificationsDropdown/NotificationsDropdown.scss @@ -56,24 +56,16 @@ width: 480px; } - .notifications-read-all { - @include roboto; - background-color: $tc-gray-neutral-light; - border-top: 1px solid $tc-gray-10; - color: $tc-gray-50; - display: block; - font-size: 12px; - line-height: 40px; - letter-spacing: 0; - text-align: center; - } - .notifications-empty { - padding: 50px 0 60px; + justify-content: center; + display: flex; + height: 100%; + flex-direction: column; + padding: 0; + } - .notification-settings { - margin-top: 30px; - } + .notifications-empty-note { + max-width: 270px; } } } diff --git a/src/components/NotificationsDropdown/NotificationsDropdownContainer.jsx b/src/components/NotificationsDropdown/NotificationsDropdownContainer.jsx index e74a28ca1..bcf40caf9 100644 --- a/src/components/NotificationsDropdown/NotificationsDropdownContainer.jsx +++ b/src/components/NotificationsDropdown/NotificationsDropdownContainer.jsx @@ -7,20 +7,32 @@ import React from 'react' import { Link } from 'react-router-dom' import { connect } from 'react-redux' import _ from 'lodash' -import { getNotifications, visitNotifications, toggleNotificationSeen, markAllNotificationsRead, toggleNotificationRead, toggleBundledNotificationRead } from '../../routes/notifications/actions' -import { splitNotificationsBySources, filterReadNotifications, limitQuantityInSources } from '../../routes/notifications/helpers/notifications' +import { getNotifications, visitNotifications, toggleNotificationSeen, markAllNotificationsRead, + toggleNotificationRead, toggleBundledNotificationRead, viewOlderNotifications, toggleNotificationsDropdownMobile } from '../../routes/notifications/actions' +import { splitNotificationsBySources, filterReadNotifications, limitQuantityInSources, filterOldNotifications } from '../../routes/notifications/helpers/notifications' import NotificationsSection from '../NotificationsSection/NotificationsSection' import NotificationsEmpty from '../NotificationsEmpty/NotificationsEmpty' import NotificationsDropdownHeader from '../NotificationsDropdownHeader/NotificationsDropdownHeader' import NotificationsDropdown from './NotificationsDropdown' import NotificationsMobilePage from './NotificationsMobilePage' +import NotificationsReadAll from './NotificationsReadAll' import ScrollLock from 'react-scroll-lock-component' import MediaQuery from 'react-responsive' -import { NOTIFCATIONS_DROPDOWN_PER_SOURCE, NOTIFCATIONS_DROPDOWN_MAX_TOTAL, REFRESH_NOTIFICATIONS_INTERVAL } from '../../config/constants' +import { NOTIFICATIONS_DROPDOWN_PER_SOURCE, NOTIFICATIONS_DROPDOWN_MAX_TOTAL, REFRESH_NOTIFICATIONS_INTERVAL, SCREEN_BREAKPOINT_MD } from '../../config/constants' import './NotificationsDropdown.scss' class NotificationsDropdownContainer extends React.Component { + constructor(props) { + super(props) + + this.state = { + isViewAll: false + } + + this.viewAll = this.viewAll.bind(this) + } + componentDidMount() { this.props.getNotifications() this.autoRefreshNotifications = setInterval(() => this.props.getNotifications(), REFRESH_NOTIFICATIONS_INTERVAL) @@ -28,6 +40,23 @@ class NotificationsDropdownContainer extends React.Component { componentWillUnmount() { clearInterval(this.autoRefreshNotifications) + // hide notifications dropdown for mobile, when this component is unmounted + this.props.toggleNotificationsDropdownMobile(false) + } + + componentWillReceiveProps(nextProps) { + const currentPathname = this.props.location.pathname + const nextPathname = nextProps.location.pathname + + if (currentPathname !== nextPathname) { + // hide notifications dropdown for mobile, + // when this component persist but URL changed + this.props.toggleNotificationsDropdownMobile(false) + } + } + + viewAll() { + this.setState({isViewAll: true}) } render() { @@ -35,7 +64,10 @@ class NotificationsDropdownContainer extends React.Component { return } - const {lastVisited, sources, notifications, markAllNotificationsRead, toggleNotificationRead, toggleNotificationSeen, pending, toggleBundledNotificationRead, visitNotifications } = this.props + const {lastVisited, sources, notifications, markAllNotificationsRead, toggleNotificationRead, toggleNotificationSeen, + pending, toggleBundledNotificationRead, visitNotifications, oldSourceIds, viewOlderNotifications, isDropdownMobileOpen, + toggleNotificationsDropdownMobile } = this.props + const {isViewAll} = this.state const getPathname = link => link.split(/[?#]/)[0].replace(/\/?$/, '') // mark notifications with url mathc current page's url as seen @@ -48,15 +80,26 @@ class NotificationsDropdownContainer extends React.Component { } const notReadNotifications = filterReadNotifications(notifications) - const notificationsBySources = limitQuantityInSources( - splitNotificationsBySources(sources, notReadNotifications), - NOTIFCATIONS_DROPDOWN_PER_SOURCE, - NOTIFCATIONS_DROPDOWN_MAX_TOTAL - ) + const notOldNotifications = filterOldNotifications(notReadNotifications, oldSourceIds) + const allNotificationsBySources = splitNotificationsBySources(sources, notOldNotifications) + let notificationsBySources + + if (!isViewAll) { + notificationsBySources = limitQuantityInSources( + allNotificationsBySources, + NOTIFICATIONS_DROPDOWN_PER_SOURCE, + NOTIFICATIONS_DROPDOWN_MAX_TOTAL + ) + } else { + notificationsBySources = allNotificationsBySources + } + + const hiddenByLimitCount = _.sumBy(allNotificationsBySources, 'notifications.length') - _.sumBy(notificationsBySources, 'notifications.length') + const globalSource = notificationsBySources.length > 0 && notificationsBySources[0].id === 'global' ? notificationsBySources[0] : null const projectSources = notificationsBySources.length > 1 && globalSource ? notificationsBySources.slice(1) : notificationsBySources const hasUnread = notReadNotifications.length > 0 - const olderNotificationsCount = _.sumBy(projectSources, 'total') - _.sumBy(projectSources, 'notifications.length') + const olderNotificationsCount = notReadNotifications.length - notOldNotifications.length // we have to give Dropdown component some time // before removing notification item node from the list // otherwise dropdown thinks we clicked outside and closes dropdown @@ -75,18 +118,36 @@ class NotificationsDropdownContainer extends React.Component { } const hasNew = hasUnread && lastVisited < _.maxBy(_.map(notifications, n => new Date(n.date))) + const notificationsEmpty = ( + +

+ Maybe you need to check your notification settings to + get up to date with the latest activity from your projects? +

+
+ Notification Settings +
+
+ ) + + // this function checks that notification is not seen yet, + // before marking it as seen + const markNotificationSeen = (notificationId) => { + const notification = _.find(notifications, { id: notificationId }) + + if (notification && !notification.seen) { + toggleNotificationSeen(notificationId) + } + } + return ( - + {(matches) => (matches ? ( !pending && markAllNotificationsRead()} hasUnread={hasUnread}/> {!hasUnread ? (
- -
- Notification Settings -
-
+ {notificationsEmpty}
) : ([ @@ -96,8 +157,8 @@ class NotificationsDropdownContainer extends React.Component { {...globalSource} isGlobal isSimple - onReadToggleClick={document.body.classList.remove('noScroll'), toggleNotificationReadWithDelay} - onLinkClick={toggleNotificationSeen} + onReadToggleClick={toggleNotificationReadWithDelay} + onLinkClick={markNotificationSeen} /> } {projectSources.filter(source => source.notifications.length > 0).map(source => ( @@ -105,44 +166,64 @@ class NotificationsDropdownContainer extends React.Component { {...source} key={source.id} isSimple - onReadToggleClick={document.body.classList.remove('noScroll'), toggleNotificationReadWithDelay} - onLinkClick={toggleNotificationSeen} + onReadToggleClick={toggleNotificationReadWithDelay} + onLinkClick={markNotificationSeen} /> ))}
, - + { olderNotificationsCount > 0 ? `View ${olderNotificationsCount} older notification${olderNotificationsCount > 1 ? 's' : ''}` : 'View all notifications' } - + ])} ) : ( - + { + toggleNotificationsDropdownMobile() + visitNotifications() + }} + isOpen={isDropdownMobileOpen} + > {!hasUnread ? ( - + notificationsEmpty ) : (
- {globalSource && globalSource.notifications.length && + {globalSource && (globalSource.notifications.length || isViewAll && globalSource.total) && viewOlderNotifications(globalSource.id) : null} + onLinkClick={(notificationId) => { + toggleNotificationsDropdownMobile() + markNotificationSeen(notificationId) + }} />} - {projectSources.filter(source => source.notifications.length > 0).map(source => ( + {projectSources.filter(source => source.notifications.length || isViewAll && source.total).map(source => ( viewOlderNotifications(source.id) : null} + onLinkClick={(notificationId) => { + toggleNotificationsDropdownMobile() + markNotificationSeen(notificationId) + }} /> ))} + {!isViewAll && (olderNotificationsCount > 0 || hiddenByLimitCount > 0) && + Read all notifications}
)}
@@ -160,7 +241,9 @@ const mapDispatchToProps = { toggleNotificationSeen, markAllNotificationsRead, toggleNotificationRead, - toggleBundledNotificationRead + toggleBundledNotificationRead, + viewOlderNotifications, + toggleNotificationsDropdownMobile } export default connect(mapStateToProps, mapDispatchToProps)(NotificationsDropdownContainer) diff --git a/src/components/NotificationsDropdown/NotificationsMobilePage.jsx b/src/components/NotificationsDropdown/NotificationsMobilePage.jsx index 7156f664f..26d5babae 100644 --- a/src/components/NotificationsDropdown/NotificationsMobilePage.jsx +++ b/src/components/NotificationsDropdown/NotificationsMobilePage.jsx @@ -5,56 +5,32 @@ import React from 'react' import PropTypes from 'prop-types' import MobilePage from '../MobilePage/MobilePage' import { Link } from 'react-router-dom' -import ConnectLogoMono from '../../assets/icons/connect-logo-mono.svg' import NotificationsBell from './NotificationsBell' import XMartIcon from '../../assets/icons/x-mark-white.svg' +import SettingsIcon from '../../assets/icons/ui-16px-1_settings-gear-64.svg' import './NotificationsMobilePage.scss' -class NotificationsDropdown extends React.Component { - constructor(props) { - super(props) - - this.state = { - isOpen: false - } - - this.toggle = this.toggle.bind(this) - } - - toggle() { - this.setState({ isOpen: !this.state.isOpen }) - } - - render() { - const { isOpen } = this.state - const { onToggle, children, hasUnread, hasNew } = this.props - - return ( -
- { - this.toggle() - onToggle() - }} - /> - {isOpen && ( - -
- -
Notifications
-
-
-
- {children} -
-
- )} -
- ) - } -} +const NotificationsDropdown = ({ onToggle, children, hasUnread, hasNew, isOpen }) => ( +
+ + {isOpen && ( + +
+ +
Notifications
+
+
+
+ {children} +
+
+ )} +
+) NotificationsDropdown.propTypes = { hasUnread: PropTypes.bool, diff --git a/src/components/NotificationsDropdown/NotificationsMobilePage.scss b/src/components/NotificationsDropdown/NotificationsMobilePage.scss index 0e38f6c47..40b0edf7a 100644 --- a/src/components/NotificationsDropdown/NotificationsMobilePage.scss +++ b/src/components/NotificationsDropdown/NotificationsMobilePage.scss @@ -3,7 +3,11 @@ .container { align-items: center; display: flex; - padding-right: $base-unit * 2; + padding-right: 14px; + + :global(.notifications-empty-note) { + max-width: 270px; + } } .header { @@ -12,7 +16,7 @@ display: flex; justify-content: space-between; height: 50px; - padding: 0 $base-unit * 3; + padding: 0 ($base-unit * 3 - 8); /* -8px to align buttons */ } .logo { @@ -42,16 +46,31 @@ text-transform: uppercase; } -.close-wrapper { +.btn { align-items: center; display: flex; - justify-content: flex-end; - /* same width as notification bell icon together with hamburger menu - minus header padding right */ - width: 71px - $base-unit * 3; + justify-content: center; + height: 32px; + width: 32px; +} + +.settings-icon { + fill: $tc-white; } .body { background-color: #fff; height: calc(100vh - 50px); /* window height - header height */ } + +.read-all { + @include roboto; + background-color: $tc-gray-neutral-light; + border-top: 1px solid $tc-gray-10; + color: $tc-gray-50; + display: block; + font-size: 12px; + line-height: 40px; + letter-spacing: 0; + text-align: center; +} diff --git a/src/components/NotificationsDropdown/NotificationsReadAll.jsx b/src/components/NotificationsDropdown/NotificationsReadAll.jsx new file mode 100644 index 000000000..64acceeae --- /dev/null +++ b/src/components/NotificationsDropdown/NotificationsReadAll.jsx @@ -0,0 +1,20 @@ +/** + * Notifications "read all" footer button + * + * FIXME reimplement this component using general Button component from `topcoder-react-utils` + * after update to the latest version of `topcoder-react-utils` + */ +import React from 'react' +import { Link } from 'react-router-dom' + +import './NotificationsReadAll.scss' + +const NotificationsReadAll = ({ children, to, onClick }) => ( + to ? ( + {children} + ) : ( + + ) +) + +export default NotificationsReadAll diff --git a/src/components/NotificationsDropdown/NotificationsReadAll.scss b/src/components/NotificationsDropdown/NotificationsReadAll.scss new file mode 100644 index 000000000..b276843a8 --- /dev/null +++ b/src/components/NotificationsDropdown/NotificationsReadAll.scss @@ -0,0 +1,20 @@ +@import '~tc-ui/src/styles/tc-includes'; + +.read-all { + @include roboto; + background-color: $tc-gray-neutral-light; + border: 0; + border-top: 1px solid $tc-gray-10; + color: $tc-gray-50; + cursor: pointer; + display: block; + font-size: 12px; + line-height: 40px; + letter-spacing: 0; + text-align: center; + width: 100%; + + @media screen and (max-width: $screen-md - 1px) { + font-size: 13px; + } +} diff --git a/src/components/NotificationsEmpty/NotificationsEmpty.scss b/src/components/NotificationsEmpty/NotificationsEmpty.scss index 6d42c4cea..653c8f92b 100644 --- a/src/components/NotificationsEmpty/NotificationsEmpty.scss +++ b/src/components/NotificationsEmpty/NotificationsEmpty.scss @@ -40,5 +40,26 @@ line-height: 30px; margin-top: 14px; } + + > .notification-settings { + margin-top: 30px; + } + + .notifications-empty-note { + @include roboto; + color: $tc-gray-50; + font-size: 13px; + line-height: 20px; + margin: 10 * $base-unit auto 6 * $base-unit; + max-width: 440px; + + > a { + color: #55a5ff; + + &:hover { + color: $tc-dark-blue; + } + } + } } } diff --git a/src/components/NotificationsSection/NotificationsSection.jsx b/src/components/NotificationsSection/NotificationsSection.jsx index 4bcd08ffe..fa7b3716c 100644 --- a/src/components/NotificationsSection/NotificationsSection.jsx +++ b/src/components/NotificationsSection/NotificationsSection.jsx @@ -51,6 +51,7 @@ NotificationsSection.propTypes = { onViewOlderClick: PropTypes.func, total: PropTypes.number, notifications: PropTypes.array.isRequired, + isLoading: PropTypes.bool } export default NotificationsSection diff --git a/src/components/ProjectInfo/ProjectInfo.jsx b/src/components/ProjectInfo/ProjectInfo.jsx index 97041dd9d..121d7a3be 100644 --- a/src/components/ProjectInfo/ProjectInfo.jsx +++ b/src/components/ProjectInfo/ProjectInfo.jsx @@ -7,6 +7,7 @@ import ProjectCardBody from '../../projects/list/components/Projects/ProjectCard import ProjectDirectLinks from '../../projects/list/components/Projects/ProjectDirectLinks' import MobileExpandable from '../MobileExpandable/MobileExpandable' import MediaQuery from 'react-responsive' +import { SCREEN_BREAKPOINT_MD } from '../../config/constants' import './ProjectInfo.scss' @@ -54,7 +55,7 @@ class ProjectInfo extends Component { /> } - + {(matches) => ( a.order - b.order).map((item) => ({val: item.value, label: item.name})) +] + +class StatusFiltersMobile extends React.Component { + constructor(props) { + super(props) + + this.state = { + isOpen: false + } + + this.toggle = this.toggle.bind(this) + this.onStatusClick = this.onStatusClick.bind(this) + } + + toggle() { + this.setState({ isOpen: !this.state.isOpen }) + } + + onStatusClick(statusVal) { + this.toggle() + this.props.onStatusClick(statusVal) + } + + render() { + const { currentStatus } = this.props + const { isOpen } = this.state + const currentSatusLabel = _.find(projectStatuses, { val: currentStatus }).label + + return ( +
+
+ {currentSatusLabel} + +
+ {isOpen &&
    + {projectStatuses.map((status) => ( +
  • this.onStatusClick(status.val)} + > + {status.label} +
  • + ))} +
} +
+ ) + } +} + +StatusFiltersMobile.defaultProps = { + currentStatus: null +} + +StatusFiltersMobile.propTypes = { + currentStatus: PropTypes.string, + statuses: PropTypes.arrayOf(PropTypes.shape({ + val: PropTypes.string, + label: PropTypes.string.isRequired + })), + onStatusClick: PropTypes.func.isRequired +} + +export default StatusFiltersMobile diff --git a/src/components/StatusFilters/StatusFiltersMobile.scss b/src/components/StatusFilters/StatusFiltersMobile.scss new file mode 100644 index 000000000..cb6bab593 --- /dev/null +++ b/src/components/StatusFilters/StatusFiltersMobile.scss @@ -0,0 +1,42 @@ +@import '~tc-ui/src/styles/tc-includes'; + +.handle { + @include roboto-medium; + align-items: center; + background-color: $tc-white; + cursor: pointer; + display: flex; + height: 10 * $base-unit; + font-size: 15px; + justify-content: space-between; + padding: 0 4 * $base-unit; + + > svg { + width: 12px; + } + + &.is-open > svg { + transform: rotate(180deg); + } +} + +.list { + background-color: $tc-white; +} + +.item { + @include roboto-medium; + align-items: center; + border-top: 1px solid $tc-gray-10; + color: $tc-gray-40; + cursor: pointer; + display: flex; + font-size: 15px; + height: 50px; + padding: 0 4 * $base-unit; + + &.active, + &:hover { + color: $tc-black; + } +} diff --git a/src/components/TopBar/NewProjectNavLink.scss b/src/components/TopBar/NewProjectNavLink.scss index 109dd2b4c..680129e09 100644 --- a/src/components/TopBar/NewProjectNavLink.scss +++ b/src/components/TopBar/NewProjectNavLink.scss @@ -11,6 +11,12 @@ margin-left: 20px; @media screen and (max-width: $screen-md - 1px) { + margin-left: 2 * $base-unit; + padding-left: 2 * $base-unit; + padding-right: 2 * $base-unit; + } + + @media screen and (max-width: $screen-sm - 1px) { display: none; } @@ -22,6 +28,7 @@ display: flex; justify-content: center; align-items: center; + svg { fill: $tc-black; width: 10px; diff --git a/src/components/TopBar/ProjectToolBar.js b/src/components/TopBar/ProjectToolBar.js index 4213f7642..573db1fab 100644 --- a/src/components/TopBar/ProjectToolBar.js +++ b/src/components/TopBar/ProjectToolBar.js @@ -144,7 +144,7 @@ class ProjectToolBar extends React.Component { render() { // TODO: removing isPowerUser until link challenges is needed once again. - const { renderLogoSection, userMenu, project, user, mobileMenu } = this.props + const { renderLogoSection, userMenu, project, user, mobileMenu, location } = this.props const { isTooltipVisible, isMobileMenuOpen } = this.state this.setActivePage() @@ -191,7 +191,9 @@ class ProjectToolBar extends React.Component { } {userMenu} - + {/* pass location, to make sure that component is re-rendered when location is changed + it's necessary to hide notification dropdown on mobile when users uses browser history back/forward buttons */} + diff --git a/src/components/TopBar/ProjectsToolBar.js b/src/components/TopBar/ProjectsToolBar.js index 4ae48f41f..9c8010214 100644 --- a/src/components/TopBar/ProjectsToolBar.js +++ b/src/components/TopBar/ProjectsToolBar.js @@ -15,6 +15,7 @@ import NewProjectNavLink from './NewProjectNavLink' import MobileMenu from '../MobileMenu/MobileMenu' import MobileMenuToggle from '../MobileMenu/MobileMenuToggle' import SearchFilter from '../../assets/icons/ui-filters.svg' +import SearchIcon from '../../assets/icons/ui-16px-1_zoom.svg' import { projectSuggestions, loadProjects, setInfiniteAutoload } from '../../projects/actions/loadProjects' @@ -25,7 +26,8 @@ class ProjectsToolBar extends Component { this.state = { errorCreatingProject: false, isFilterVisible: false, - isMobileMenuOpen: false + isMobileMenuOpen: false, + isMobileSearchVisible: false } this.state.isFilterVisible = sessionStorage.getItem('isFilterVisible') === 'true' this.applyFilters = this.applyFilters.bind(this) @@ -33,6 +35,7 @@ class ProjectsToolBar extends Component { this.handleTermChange = this.handleTermChange.bind(this) this.handleSearch = this.handleSearch.bind(this) this.toggleMobileMenu = this.toggleMobileMenu.bind(this) + this.toggleMobileSearch = this.toggleMobileSearch.bind(this) this.onLeave = this.onLeave.bind(this) } @@ -99,7 +102,7 @@ class ProjectsToolBar extends Component { } toggleFilter() { - const {isFilterVisible} = this.state + const {isFilterVisible, isMobileSearchVisible} = this.state const contentDiv = document.getElementById('wrapper-main') this.setState({isFilterVisible: !isFilterVisible}, () => { sessionStorage.setItem('isFilterVisible', (!isFilterVisible).toString()) @@ -109,12 +112,27 @@ class ProjectsToolBar extends Component { contentDiv.classList.remove('with-filters') } }) + // if open filters, close search panel on mobile + if (!isFilterVisible && isMobileSearchVisible) { + this.toggleMobileSearch() + } } toggleMobileMenu() { this.setState({ isMobileMenuOpen: !this.state.isMobileMenuOpen }) } + toggleMobileSearch() { + const { isFilterVisible, isMobileSearchVisible } = this.state + + // if open mobile search and filter is visible, then close filter + if (!isMobileSearchVisible && isFilterVisible) { + this.toggleFilter() + } + + this.setState({ isMobileSearchVisible: !isMobileSearchVisible }) + } + routeWithParams(criteria) { // because criteria is changed disable infinite autoload this.props.setInfiniteAutoload(false) @@ -129,7 +147,7 @@ class ProjectsToolBar extends Component { shouldComponentUpdate(nextProps, nextState) { const { user, criteria, creatingProject, projectCreationError, searchTermTag } = this.props - const { errorCreatingProject, isFilterVisible, isMobileMenuOpen } = this.state + const { errorCreatingProject, isFilterVisible, isMobileMenuOpen, isMobileSearchVisible } = this.state return nextProps.user.handle !== user.handle || JSON.stringify(nextProps.criteria) !== JSON.stringify(criteria) || nextProps.creatingProject !== creatingProject @@ -138,11 +156,12 @@ class ProjectsToolBar extends Component { || nextState.errorCreatingProject !== errorCreatingProject || nextState.isFilterVisible !== isFilterVisible || nextState.isMobileMenuOpen !== isMobileMenuOpen + || nextState.isMobileSearchVisible !== isMobileSearchVisible } render() { - const { renderLogoSection, userMenu, userRoles, criteria, isPowerUser, user, mobileMenu } = this.props - const { isFilterVisible, isMobileMenuOpen } = this.state + const { renderLogoSection, userMenu, userRoles, criteria, isPowerUser, user, mobileMenu, location } = this.props + const { isFilterVisible, isMobileMenuOpen, isMobileSearchVisible } = this.state const isLoggedIn = !!(userRoles && userRoles.length) let excludedFiltersCount = 1 // 1 for default sort criteria @@ -181,46 +200,60 @@ class ProjectsToolBar extends Component { />
{ renderLogoSection(menuBar) } - { isLoggedIn &&
MY PROJECTS
} + { isLoggedIn && !isPowerUser &&
MY PROJECTS
} { - isLoggedIn && + isLoggedIn && !!isPowerUser &&
- { !!isPowerUser && - - } - { - !!isPowerUser && - - } + +
}
{ isLoggedIn && } { userMenu } - { isLoggedIn && } + {/* pass location, to make sure that component is re-rendered when location is changed + it's necessary to hide notification dropdown on mobile when users uses browser history back/forward buttons */} + { isLoggedIn && } { isLoggedIn && }
+ { isMobileSearchVisible && isLoggedIn && +
+ +
+ } { isFilterVisible && isLoggedIn && -
- -
+
+ +
} {isMobileMenuOpen && } diff --git a/src/components/TopBar/ProjectsToolBar.scss b/src/components/TopBar/ProjectsToolBar.scss index b153d98d4..2a49dc6f0 100644 --- a/src/components/TopBar/ProjectsToolBar.scss +++ b/src/components/TopBar/ProjectsToolBar.scss @@ -78,6 +78,64 @@ display: flex; min-height: 60px; height: 60px; + + .SearchBar { + display: none; + } + + @media screen and (max-width: $screen-md - 1px) { + height: auto; + min-height: 50px; + + /* update styles for SearchBar + if same mobile styles has to be used at some other places, + this style has to be updated in SearchBar component in react-components + */ + .SearchBar { + background-color: $tc-gray-90; + display: block; + height: 50px; + padding: 0; + + &::before { + @include roboto; + color: $tc-gray-40; + font-size: 15px; + } + + .search-bar__text { + @include roboto; + border: 0; + border-radius: 0; + color: $tc-gray-40; + height: 50px; + font-size: 15px; + margin-bottom: 0; + padding: 0 3 * $base-unit; + text-indent: 0; + + &:focus { + box-shadow: none; + } + } + + .search-icon-wrap { + display: none; + } + + .search-bar__clear { + right: 4 * $base-unit; + } + + &.state-focused, + &.state-filled { + border: 0; + border-radius: 0; + background-color: $tc-gray-90; + padding-right: 0; + } + } + } } .MenuBar li { @@ -101,7 +159,8 @@ display: flex; @media screen and (max-width: $screen-md - 1px) { - display: none; + height: 32px; + margin-top: 9px; } input::-ms-clear{ @@ -109,12 +168,27 @@ height: 0; display: none; } + + .SearchBar { + @media screen and (max-width: $screen-md - 1px) { + display: none; + } + } } .search-filter { width: 97px; margin-left: 2 * $base-unit; + @media screen and (max-width: $screen-md - 1px) { + border: 1px solid $tc-gray-80; + border-radius: 2px; + display: flex; + height: 32px; + margin: 0 auto; + width: auto; + } + .tc-btn { display: flex; align-items: center; @@ -128,8 +202,41 @@ font-size: 12px; position: relative; + @media screen and (max-width: $screen-md - 1px) { + text-align: center; + width: 46px; + + &:first-child { + border-right: 1px solid $tc-gray-80; + } + + > svg { + margin: 0 auto; + } + } + + &.mobile-search-toggle { + display: none; + + @media screen and (max-width: $screen-md - 1px) { + display: flex; + } + + > svg { + fill: $tc-gray-50; + } + + &.active > svg { + fill: $tc-white; + } + } + svg.icon-search-filter { margin-right: 10px; + + @media screen and (max-width: $screen-md - 1px) { + margin-right: auto; + } } &.active { @@ -142,6 +249,12 @@ } } + .filter-text { + @media screen and (max-width: $screen-md - 1px) { + display: none; + } + } + .filter-indicator { width: 15px; height: 15px; @@ -152,6 +265,12 @@ display: flex; justify-content: center; align-items: center; + + @media screen and (max-width: $screen-md - 1px) { + right: -8px; + position: absolute; + top: 3px; + } } } @@ -169,10 +288,21 @@ justify-content: space-between; width: 100%; + @media screen and (max-width: $screen-md - 1px) { + border-bottom: 0; + box-shadow: none; + min-height: 50px; + padding: 0; + } + h2 { font-size: $tc-body-large; color: $tc-black; white-space: nowrap; + + @media screen and (max-width: $screen-md - 1px) { + display: none; + } } .bar-control { @@ -188,6 +318,12 @@ } } + > .tc-btn { + @media screen and (max-width: $screen-md - 1px) { + display: none; + } + } + .search-panel { flex: 1; margin: 0 auto; @@ -196,12 +332,20 @@ justify-content: space-between; padding-right: 30px; + @media screen and (max-width: $screen-md - 1px) { + padding-right: 0; + } + .search-select-widget { align-items: center; display: flex; margin-left: 40px; flex: 1; + @media screen and (max-width: $screen-md - 1px) { + margin-left: 0; + } + label { @include roboto-medium; display: inline-block; @@ -212,11 +356,106 @@ padding-right: 15px; text-align: right; white-space: nowrap; + + @media screen and (max-width: $screen-md - 1px) { + display: none; + } } } .search-select-field { flex: 1; + + /* update styles for Select, + it could make sense to update styles for Select globally + if somewhere else we use it for mobile */ + @media screen and (max-width: $screen-md - 1px) { + .Select-control { + background-color: $tc-gray-90; + border: 0; + border-radius: 0; + height: 50px; + padding-left: 2 * $base-unit; + } + + .Select-arrow-zone { + display: none; + } + + .Select-clear-zone { + padding-right: 4 * $base-unit; + } + + .Select-clear { + background: url(~appirio-tech-react-components/components/SearchBar/x-mark.svg) no-repeat center; + height: 10px; + overflow: hidden; + text-indent: -9999px; + width: 10px; + } + + .Select-placeholder { + color: $tc-gray-40; + font-size: 15px; + line-height: 50px; + padding-left: 3 * $base-unit; + } + + .Select-item { + background-color: $tc-gray-70; + border-radius: 2px; + height: 30px; + margin-left: $base-unit; + margin-top: 2 * $base-unit; + padding: $base-unit 2 * $base-unit; + + .Select-item-icon { + display: none; + } + + .Select-item-label { + color: $tc-white; + padding-left: 0; + } + } + + .Select-input { + height: 50px; + margin-left: $base-unit; + + > input:not([type="checkbox"]) { + color: $tc-gray-40; + background-color: transparent; + height: 50px; + + &:focus { + /* because we have global style for all inputs with !important */ + background-color: transparent !important; + } + } + } + + .Select.is-open .Select-input > input:not([type="checkbox"]) { + background-color: transparent; + } + + .Select-menu { + padding: 2 * $base-unit 0; + } + + .Select-option { + @include roboto; + color: $tc-black; + height: 50px; + font-size: 15px; + line-height: 50px; + padding: 0 4 * $base-unit; + + &.is-focused { + background-color: $tc-white; + } + } + } } } } diff --git a/src/components/TopBar/SectionToolBar.scss b/src/components/TopBar/SectionToolBar.scss index 5f5641176..a543a81ae 100644 --- a/src/components/TopBar/SectionToolBar.scss +++ b/src/components/TopBar/SectionToolBar.scss @@ -20,6 +20,17 @@ padding: 0 20px; width: 100%; + @media screen and (max-width: $screen-md - 1px) { + padding: 0; + + /* add pseudo element to center title */ + &::before { + content: ''; + display: block; + width: 39px; /* close button width with padding */ + } + } + > .section { align-items: center; display: flex; @@ -34,6 +45,10 @@ > .section > .title { margin-left: 24px; + + @media screen and (max-width: $screen-md - 1px) { + margin-left: 0; + } } .close { @@ -45,6 +60,13 @@ justify-content: center; width: 30px; + @media screen and (max-width: $screen-md - 1px) { + background-color: transparent; + margin-right: 7px; + height: 32px; + width: 32px; + } + &:hover { background-color: #55a5ff; @@ -54,10 +76,14 @@ } } - .icon-x-mark { - fill: $tc-black; - margin: -6px; + .icon-x-mark { + fill: $tc-black; + margin: -6px; + + @media screen and (max-width: $screen-md - 1px) { + fill: $tc-white; } + } } > .section > .menu { @@ -81,6 +107,10 @@ align-items: center; display: flex; + @media screen and (max-width: $screen-md - 1px) { + display: none; + } + .icon-connect-logo-mono { height: auto; width: 53px; diff --git a/src/components/TopBar/TopBarContainer.js b/src/components/TopBar/TopBarContainer.js index b9a8227a8..4328d161d 100644 --- a/src/components/TopBar/TopBarContainer.js +++ b/src/components/TopBar/TopBarContainer.js @@ -51,7 +51,7 @@ class TopBarContainer extends React.Component { } render() { - const { user, toolbar, userRoles } = this.props + const { user, toolbar, userRoles, isPowerUser } = this.props const userHandle = _.get(user, 'handle') const userImage = _.get(user, 'profile.photoURL') @@ -90,7 +90,7 @@ class TopBarContainer extends React.Component { { style: 'big', items: [ - { label: 'My projects', link: '/projects?sort=updatedAt%20desc&status=active' }, + { label: 'All projects', link: isPowerUser ? '/projects?sort=updatedAt%20desc' : '/projects' }, { label: 'Getting Started', link: 'https://www.topcoder.com/about-topcoder/connect/', absolute: true }, { label: 'Help', link: 'https://help.topcoder.com/hc/en-us', absolute: true }, ] diff --git a/src/components/User/UserTooltip.scss b/src/components/User/UserTooltip.scss index 6d0fac237..d09ac8777 100644 --- a/src/components/User/UserTooltip.scss +++ b/src/components/User/UserTooltip.scss @@ -78,6 +78,10 @@ padding-top: 20px; padding-left: 20px; + @media screen and (max-width: $screen-md - 1px) { + width: 300px; + } + .tooltip-body { color: $tc-black; overflow: hidden; diff --git a/src/components/Wizard/Wizard.jsx b/src/components/Wizard/Wizard.jsx index 517658b95..33b15bb9e 100644 --- a/src/components/Wizard/Wizard.jsx +++ b/src/components/Wizard/Wizard.jsx @@ -31,7 +31,7 @@ function Wizard(props) { ) } return ( -
+
{backControl} { modalCloseControl } diff --git a/src/components/Wizard/Wizard.scss b/src/components/Wizard/Wizard.scss index addbbab79..e5f8ebe4b 100644 --- a/src/components/Wizard/Wizard.scss +++ b/src/components/Wizard/Wizard.scss @@ -13,17 +13,34 @@ /*position: relative;*/ text-align: left; width: 100%; + + @media screen and (max-width: $screen-md - 1px) { + padding: 10px; + } } + .back-button { position: absolute; left: 12px; top: 12px; + z-index: 1; + + @media screen and (max-width: $screen-md - 1px) { + left: 2px; + top: 2px; + } } + .escape-button { position: absolute; right: 12px; top: 12px; + z-index: 1; + + @media screen and (max-width: $screen-md - 1px) { + right: 2px; + top: 2px; + } } } } - \ No newline at end of file diff --git a/src/config/constants.js b/src/config/constants.js index 4885627fb..061cd389c 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -15,6 +15,7 @@ export const TOGGLE_NOTIFICATION_READ = 'TOGGLE_NOTIFICATION_READ' export const TOGGLE_NOTIFICATION_SEEN = 'TOGGLE_NOTIFICATION_SEEN' export const VIEW_OLDER_NOTIFICATIONS_SUCCESS = 'VIEW_OLDER_NOTIFICATIONS_SUCCESS' export const NOTIFICATIONS_PENDING = 'NOTIFICATIONS_PENDING' +export const TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE = 'TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE' // Settings export const CHECK_EMAIL_AVAILABILITY_PENDING = 'CHECK_EMAIL_AVAILABILITY_PENDING' @@ -377,14 +378,14 @@ export const SORT_OPTIONS = [ // Notifications export const REFRESH_NOTIFICATIONS_INTERVAL = 1000 * 60 * 1 // 1 minute interval export const REFRESH_UNREAD_UPDATE_INTERVAL = 1000 * 10 * 1 // 10 second interval -export const NOTIFCATIONS_DROPDOWN_PER_SOURCE = 5 -export const NOTIFCATIONS_DROPDOWN_MAX_TOTAL = Infinity +export const NOTIFICATIONS_DROPDOWN_PER_SOURCE = 5 +export const NOTIFICATIONS_DROPDOWN_MAX_TOTAL = Infinity export const NOTIFICATIONS_LIMIT = 1000 // old notification time in minutes, a notification is old if its date is later than this time export const OLD_NOTIFICATION_TIME = 60 * 48 // 2 day2 -export const SCROLL_TO_MARGIN = 70 // px - 60px of toolbar height + 10px to make sume margin +export const SCROLL_TO_MARGIN = 70 // px - 60px of toolbar height + 10px to make some margin export const SCROLL_TO_DURATION = 500 // ms // Settings @@ -392,3 +393,10 @@ export const MAX_USERNAME_LENGTH = 15 export const EMAIL_AVAILABILITY_CHECK_DEBOUNCE = 300 /* in ms */ export const PASSWORD_MIN_LENGTH = 8 export const PASSWORD_REG_EXP = /^(?=.*[a-z])(?=.*[^a-z]).+$/i + +// Screen breakpoints +export const SCREEN_BREAKPOINT_LG = 1360 +export const SCREEN_BREAKPOINT_RG = 992 +export const SCREEN_BREAKPOINT_MD = 768 +export const SCREEN_BREAKPOINT_SM = 640 +export const SCREEN_BREAKPOINT_XS = 320 diff --git a/src/projects/create/components/FillProjectDetails.js b/src/projects/create/components/FillProjectDetails.js index 7de58745d..44e660a1b 100644 --- a/src/projects/create/components/FillProjectDetails.js +++ b/src/projects/create/components/FillProjectDetails.js @@ -8,6 +8,8 @@ import './FillProjectDetails.scss' import ProjectBasicDetailsForm from '../components/ProjectBasicDetailsForm' import ProjectOutline from '../components/ProjectOutline' import typeToSpecification from '../../../config/projectSpecification/typeToSpecification' +import ModalControl from '../../../components/ModalControl' +import TailLeft from '../../../assets/icons/arrows-16px-1_tail-left.svg' class FillProjectDetails extends Component { constructor(props) { @@ -38,9 +40,10 @@ class FillProjectDetails extends Component { } render() { - const { project, dirtyProject, processing, submitBtnText } = this.props + const { project, dirtyProject, processing, submitBtnText, onBackClick } = this.props const productId = _.get(project, 'details.products[0]') const product = findProduct(productId) + const formDesclaimer = _.get(product, 'formDesclaimer') let specification = 'topcoder.v1' if (productId) @@ -51,6 +54,12 @@ class FillProjectDetails extends Component {
+ } + label="back" + onClick={onBackClick} + />

@@ -67,9 +76,11 @@ class FillProjectDetails extends Component { submitBtnText={ submitBtnText } />
-
- { _.get(product, 'formDesclaimer') } -
+ {formDesclaimer && ( +
+ {formDesclaimer} +
+ )}
@@ -87,6 +98,7 @@ class FillProjectDetails extends Component { FillProjectDetails.propTypes = { // onProjectChange: PT.func.isRequired, + onBackClick: PT.func.isRequired, onCreateProject: PT.func.isRequired, onChangeProjectType: PT.func.isRequired, project: PT.object.isRequired, diff --git a/src/projects/create/components/FillProjectDetails.scss b/src/projects/create/components/FillProjectDetails.scss index 729a2562d..a30e60749 100644 --- a/src/projects/create/components/FillProjectDetails.scss +++ b/src/projects/create/components/FillProjectDetails.scss @@ -2,25 +2,33 @@ @import 'layout'; @import '~tc-ui/src/styles/tc-includes'; +@import '~styles/_variables.scss'; :global { .FillProjectDetailsWrapper { // @extend .wizardWrapper; } - + .headerFillProjectDetails { @extend .wizardHeader; } - + .FillProjectDetails { h1 { @extend .wizardHeadline; + + @media screen and (max-width: $screen-md - 1px) { + font-size: 20px; + line-height: 24px; + margin-bottom: 0; + padding: 4 * $base-unit 4 * $base-unit 4 * $base-unit 12 * $base-unit; + text-align: left; + } } - + .two-col-content { padding: 0 !important; - min-width: $minimumPageWidth; - + &.content{ .container { @include flexBox; @@ -28,53 +36,68 @@ background-color: none; display: flex; padding: 0; - max-width: 1110px; margin: 0 auto; - + /* .left-area */ .left-area { flex: auto; width: auto; position: relative; - + .left-area-content { border-radius: 4px; background: $tc-white; box-shadow: 0px 1px 3px 0px $tc-gray-30; padding: 50px; - + + @media screen and (max-width: $screen-md - 1px) { + background-color: $tc-white; + border-radius: 0; + box-shadow: none; + padding: 3 * $base-unit 4 * $base-unit; + } + .title { color: $tc-gray-80; @include roboto-bold; } - + .section-footer { margin-top: $base_unit * 4; margin-bottom: 0; background: none; height: 40px; + + @media screen and (max-width: $screen-md - 1px) { + margin-bottom: 10 * $base-unit; + margin-top: 10 * $base-unit; + } } } - + .left-area-footer { @include tc-label-sm; color: $tc-gray-50; margin-top: 4 * $base_unit; - + span { clear: both; float: none; } } } - + .right-area { flex: auto !important; width: 320px; min-width: 320px; max-width: 320px; margin-left: 4 * $base_unit; - + + @media screen and (max-width: $screen-rg - 1px) { + display: none; + } + .right-area-footer { @include roboto; padding: 10px 0 0 0; @@ -82,7 +105,7 @@ line-height: 20px; color: $tc-gray-50; font-style: italic; - + span { float: right; } @@ -92,14 +115,13 @@ } } } - + .ProjectWizard { .FillProjectDetails { - .left-area-content .title span, .section-header .section-number { display: none; } - + .gray-text { @include roboto; font-size: 15px; @@ -107,10 +129,38 @@ line-height: 25px; } } - - .spec-question-list-item .content-col > h5 span { - display: none; + } + + .ProjectWizard-step-3 { + .FillProjectDetails .header { + align-items: center; + display: flex; + justify-content: space-between; + + h1 { + padding-left: 2px; + } + + .back-button { + display: none; + margin-left: 12px; + position: static; + + @media screen and (max-width: $screen-md - 1px) { + display: block; + } + } + } + + @media screen and (max-width: $screen-md - 1px) { + .content { + padding: 0; + + > .escape-button, + > .back-button { + display: none; + } + } } } } - \ No newline at end of file diff --git a/src/projects/create/components/IncompleteProjectConfirmation.scss b/src/projects/create/components/IncompleteProjectConfirmation.scss index 927b60041..0e013aec9 100644 --- a/src/projects/create/components/IncompleteProjectConfirmation.scss +++ b/src/projects/create/components/IncompleteProjectConfirmation.scss @@ -8,12 +8,12 @@ display: flex; flex-direction: column; align-items: center; - + .header { @extend .wizardHeader; width: 100%; } - + h3 { @include roboto-light; margin-top: 8 * $base_unit; @@ -21,38 +21,55 @@ color: $tc-black; line-height: 40px; font-size: 36px; + + @media screen and (max-width: $screen-md - 1px) { + font-size: 20px; + margin-top: 0; + margin-bottom: 6 * $base_unit; + text-align: center; + } } - + h5 { @include roboto-medium; margin-bottom: 4 * $base_unit; color: $tc-gray-80; line-height: 30px; font-size: 20px; + + @media screen and (max-width: $screen-md - 1px) { + font-size: 15px; + margin-bottom: 0; + text-align: center; + } } - + p { @include roboto; margin-bottom: 10 * $base_unit; color: $tc-gray-60; line-height: 25px; font-size: 15px; + + @media screen and (max-width: $screen-md - 1px) { + font-size: 13px; + text-align: center; + } } - + .actions { button { display: block; margin: 0 auto; } - + button + button { margin-top: 4 * $base_unit; } } } - + .Footer { position: relative; } } - \ No newline at end of file diff --git a/src/projects/create/components/ProductCard.scss b/src/projects/create/components/ProductCard.scss index 97f734a79..8ba6da4c6 100644 --- a/src/projects/create/components/ProductCard.scss +++ b/src/projects/create/components/ProductCard.scss @@ -4,7 +4,7 @@ @mixin transition { transition: 150ms ease-in-out all; } - + .ProductCard { width: 185px; height: 185px; @@ -15,7 +15,13 @@ text-align: center; box-shadow: 0 0 0 1px $tc-gray-20; @include transition; - + + @media screen and (max-width: $screen-md - 1px) { + margin: 5px; + max-width: 185px; + width: calc(50% - 10px); + } + .Icon { display: flex; flex-direction: column; @@ -24,7 +30,7 @@ width: 32px; height: 32px; } - + h3 { @include tc-heading; @include roboto-bold; @@ -35,7 +41,7 @@ margin-top: 0px; @include transition; } - + .details { margin: 0; color: $tc-gray-50; @@ -46,37 +52,37 @@ letter-spacing: -0.4px; @include transition; } - + .icon-wrapper { svg { fill: $tc-gray-70; } - + &:hover { svg { fill: $tc-dark-blue; } } } - + .list-cards { user-select: none; } - + &:not(.active):hover { box-shadow: 0 0 0 1px $tc-dark-blue-70; background-color: $tc-dark-blue-10; cursor: pointer; - + h3 { color: $tc-black; } - + p { color: $tc-gray-80; } } - + &:active, &.active { background-color: $tc-dark-blue-30; @@ -84,4 +90,3 @@ } } } - \ No newline at end of file diff --git a/src/projects/create/components/ProjectTypeCard.scss b/src/projects/create/components/ProjectTypeCard.scss index 60554e33b..20a9cd458 100644 --- a/src/projects/create/components/ProjectTypeCard.scss +++ b/src/projects/create/components/ProjectTypeCard.scss @@ -15,15 +15,27 @@ transition: 300ms all ease; -moz-transition: 300ms all ease; -webkit-transition: 300ms all ease; - + + @media screen and (max-width: $screen-md - 1px) { + border-radius: 6px; + height: 207px; + margin: 5px; + max-width: 172px; + width: calc(50% - 10px); + } + .sub-type-details { padding: 0 20px; font-size: 13px; color: $tc-gray-50; line-height: 20px; width: 100%; + + @media screen and (max-width: $screen-md - 1px) { + padding: 0 10px; + } } - + .header { @include tc-body; @include roboto-bold; @@ -32,12 +44,12 @@ margin-bottom: 10px; margin-top: 0px; } - + .icon-wrapper { height: 32px; display: flex; margin: 6 * $base_unit 0 4 * $base_unit; - + .Icon { /* TODO .Icon is svg, so style to svg is never applied, probably we should remove it */ svg { @@ -45,39 +57,45 @@ } } } - + &.disabled { opacity: 0.3; } - + &.enabled:hover { border-color: $tc-dark-blue-70; cursor: pointer; - + .button { background: $tc-dark-blue; border-color: transparent; color: $tc-white; } - + svg { fill: $tc-dark-blue; } + + @media screen and (max-width: $screen-md - 1px) { + .tc-btn { + display: none; + } + } } - + &.selected { background: $tc-gray-10; border-color: transparent; - + &:hover { border-color: transparent; } } - + .tc-btn { margin-top: 2 * $base_unit; } - + .tc-btn { position: absolute; bottom: 30px; @@ -85,7 +103,7 @@ transform: translateX(-50%); opacity: 0; } - + &:hover { transition: 300ms all ease; -moz-transition: 300ms all ease; @@ -93,19 +111,18 @@ position: relative; background: $tc-white; box-shadow: 0 2px 9px 0 rgba($tc-gray-90, 0.1); - + h1 { color: $tc-dark-blue; } - + .tc-btn { opacity: 1; } } - + &:active { background: $tc-dark-blue-10; } } } - \ No newline at end of file diff --git a/src/projects/create/components/ProjectWizard.jsx b/src/projects/create/components/ProjectWizard.jsx index 2ac56ccf0..37b1d4c47 100644 --- a/src/projects/create/components/ProjectWizard.jsx +++ b/src/projects/create/components/ProjectWizard.jsx @@ -234,7 +234,7 @@ class ProjectWizard extends Component { if (!details) { updateQuery = { details: { $set : { utm : { code : projectRef }}}} } - this.setState({ + this.setState({ project: update(this.state.project, updateQuery), dirtyProject: update(this.state.project, updateQuery) }) @@ -253,7 +253,7 @@ class ProjectWizard extends Component { updateQuery.details = { $set : { products : [products[0].id]} } } } - this.setState({ + this.setState({ project: update(this.state.project, updateQuery), dirtyProject: update(this.state.project, updateQuery), wizardStep: products.length === 1 ? WZ_STEP_FILL_PROJ_DETAILS : WZ_STEP_SELECT_PROD_TYPE @@ -275,7 +275,7 @@ class ProjectWizard extends Component { if (projectType) { updateQuery.type = {$set : projectType } } - this.setState({ + this.setState({ project: update(this.state.project, updateQuery), dirtyProject: update(this.state.project, updateQuery), wizardStep: WZ_STEP_FILL_PROJ_DETAILS @@ -287,7 +287,7 @@ class ProjectWizard extends Component { /** * Restores common details of the project while changing product type. - * + * * Added for Github issue#1037 */ restoreCommonDetails(updatedProduct, updateQuery, detailsQuery) { @@ -394,14 +394,14 @@ class ProjectWizard extends Component { render() { const { processing, showModal, userRoles } = this.props - const { project, dirtyProject } = this.state + const { project, dirtyProject, wizardStep } = this.state return ( step > 1 } > this.handleStepChange(wizardStep - 1)} /> ) diff --git a/src/projects/create/components/ProjectWizard.scss b/src/projects/create/components/ProjectWizard.scss index 2face905a..6b10d0ccf 100644 --- a/src/projects/create/components/ProjectWizard.scss +++ b/src/projects/create/components/ProjectWizard.scss @@ -2,6 +2,15 @@ :global { .ProjectWizard { + @media screen and (max-width: $screen-md - 1px) { + align-items: center; + display: flex; + + &.ProjectWizard-step-1 { + align-items: flex-start; + } + } + .content { background: transparent; box-shadow: none; @@ -9,10 +18,14 @@ display: block; margin: 0px; padding-top: 0px;//to align the wizard header to the bottom of logo - + + @media screen and (max-width: $screen-md - 1px) { + padding-top: 0; + } + .ProductCard { background: $tc-white; - + &.selected { background: $tc-gray-10; } @@ -20,4 +33,3 @@ } } } - \ No newline at end of file diff --git a/src/projects/create/components/SelectProduct.js b/src/projects/create/components/SelectProduct.js index 8b949da6e..5c206f286 100644 --- a/src/projects/create/components/SelectProduct.js +++ b/src/projects/create/components/SelectProduct.js @@ -151,7 +151,7 @@ function SelectProduct(props) {

{ projectCategory.question }

{cards}
- Looking for something else? Get in touch with us. + Looking for something else? Get in touch with us →
diff --git a/src/projects/create/components/SelectProduct.scss b/src/projects/create/components/SelectProduct.scss index c7d7606f3..4c8b52f30 100644 --- a/src/projects/create/components/SelectProduct.scss +++ b/src/projects/create/components/SelectProduct.scss @@ -7,13 +7,21 @@ .headerSelectProduct { @extend .wizardHeader; } - + .SelectProduct { @extend .wizardCardContainer; - + h1 { @extend .wizardHeadline; - + + @media screen and (max-width: $screen-md - 1px) { + font-size: 20px; + line-height: 30px; + margin-bottom: 0; + margin-top: 10px; + padding: 5px 40px 0; + } + + h2 { font-size: 15px; line-height: 20px; @@ -22,30 +30,35 @@ color: $tc-gray-80; margin-bottom: 13px; margin-top: 0px; + + @media screen and (max-width: $screen-md - 1px) { + @include roboto-medium; + font-size: 15px; + margin-top: 4 * $base-unit; + } } } - + .header { display: block; } } - + @media screen and (max-width: 1100px) { .SelectProduct { max-width: 750px; } } - + @media screen and (max-width: 850px) { .SelectProduct { max-width: 500px; } } - - @media screen and (max-width: 500px) { + + @media screen and (max-width: $screen-md - 1px) { .SelectProduct { - max-width: 250px; + max-width: 100%; } } } - \ No newline at end of file diff --git a/src/projects/create/components/SelectProjectType.jsx b/src/projects/create/components/SelectProjectType.jsx index 8435cd859..eed4e1d5c 100644 --- a/src/projects/create/components/SelectProjectType.jsx +++ b/src/projects/create/components/SelectProjectType.jsx @@ -145,7 +145,7 @@ function SelectProjectType(props) {

Create a new project

{cards}
- Looking for something else? Get in touch with us. + Looking for something else? Get in touch with us →
diff --git a/src/projects/create/components/SelectProjectType.scss b/src/projects/create/components/SelectProjectType.scss index 27f892cc9..06384be66 100644 --- a/src/projects/create/components/SelectProjectType.scss +++ b/src/projects/create/components/SelectProjectType.scss @@ -7,18 +7,34 @@ .ProjectWizard { @extend .wizardWrapper; } - + .headerSelectProjectType { @extend .wizardHeader; } - + .SelectProjectType { @extend .wizardCardContainer; - - h1 { + + .cards { + margin: 0 auto; + max-width: 900px; + + @media screen and (max-width: $screen-md - 1px) { + max-width: 600px; + } + } + + > h1 { @extend .wizardHeadline; margin-bottom: 30px; + + @media screen and (max-width: $screen-md - 1px) { + font-size: 20px; + margin-left: 10px; + margin-bottom: 0; + padding-top: 10px; + text-align: left; + } } } } - \ No newline at end of file diff --git a/src/projects/create/components/_layout.scss b/src/projects/create/components/_layout.scss index 3178fd75b..1f5c31cd7 100644 --- a/src/projects/create/components/_layout.scss +++ b/src/projects/create/components/_layout.scss @@ -14,34 +14,37 @@ $minimumPageWidth: 984px; min-height: 100vh; background-color: $tc-gray-neutral-light; margin-top: -60px; // Counteract the push from the main wrapper, top of vewport - min-width: $minimumPageWidth; - // FIXME: This is hack to stop the div of growing - border-top: 1px solid $tc-gray-neutral-light; - // end hack; - overflow-x: scroll; + + @media screen and (max-width: $screen-md - 1px) { + margin-top: 0; + } } - + .ReactModalPortal .wizardWrapper { margin-top: 0; } - + .wizardHeader { display: flex; justify-content: space-between; margin: 20px 0 0; height: 43px; - + + @media screen and (max-width: $screen-md - 1px) { + display: none; + } + button { align-self: flex-start; white-space: nowrap; - + .Icon { vertical-align: middle; margin-right: 10px; } } } - + .wizardHeadline { @include tc-heading; width: 100%; @@ -52,11 +55,11 @@ $minimumPageWidth: 984px; color: $tc-black; line-height: 40px; } - + .ReactModalPortal .header > h1:first-child { margin-top: 40px; } - + .wizardCardContainer { @include tc-body; color: $tc-gray-50; @@ -66,7 +69,7 @@ $minimumPageWidth: 984px; min-height: calc(100vh - 130px); margin: 0 auto; padding-bottom: 90px; - + a, a:visited, a:hover, @@ -74,44 +77,50 @@ $minimumPageWidth: 984px; color: $tc-dark-blue; cursor: pointer; } - + a:hover { text-decoration: underline; } - + .cards { // margin-top: 50px; display: flex; justify-content: center; flex-flow: row wrap; + + @media screen and (max-width: $screen-md - 1px) { + margin-left: -5px; + margin-right: -5px; + } } - - .ProjectTypeCard:nth-child(1) { - margin-left: 40px; - } - - .ProjectTypeCard:nth-child(3) { - margin-right: 40px; - } - + .footer { position: absolute; bottom: 0; left: 0; right: 0; margin-bottom: 15px; - margin-top: 50px; + + @media screen and (max-width: $screen-md - 1px) { + margin-bottom: 2 * $base-unit; + padding: 0 6 * $base-unit; + } + + > a { + @media screen and (max-width: $screen-md - 1px) { + white-space: nowrap; + } + } } } - + sup { font-size: 0.5em; vertical-align: text-top; } - + sub { font-size: 0.5em; vertical-align: text-bottom; } } - \ No newline at end of file diff --git a/src/projects/create/containers/CreateContainerMobilePlug.jsx b/src/projects/create/containers/CreateContainerMobilePlug.jsx deleted file mode 100644 index eba55abcf..000000000 --- a/src/projects/create/containers/CreateContainerMobilePlug.jsx +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Plug for create new project page for mobile resolutions - */ -import React from 'react' -import { Link } from 'react-router-dom' -import './CreateContainerMobilePlug.scss' -import Spaceship from '../../../assets/icons/spaceship.svg' - -const CreateContainerMobilePlug = () => ( -
-
- -
-

CREATE NEW PROJECT

-

For the full experience, Please log in to connect.topcoder.com using your desktop browser

-
- Back to Homepage -
-
-) - -export default CreateContainerMobilePlug diff --git a/src/projects/create/containers/CreateContainerMobilePlug.scss b/src/projects/create/containers/CreateContainerMobilePlug.scss deleted file mode 100644 index 9b283b007..000000000 --- a/src/projects/create/containers/CreateContainerMobilePlug.scss +++ /dev/null @@ -1,40 +0,0 @@ -@import '~tc-ui/src/styles/tc-includes'; - -.container { - background-color: $tc-white; - height: 100vh; - padding: 7 * $base-unit 4 * $base-unit; -} - -.illustration { - margin-bottom: 10 * $base-unit; - text-align: center; - - > svg { - max-width: 100%; - } -} - -.title { - @include roboto-bold; - color: $tc-black; - font-size: 20px; - text-align: center; -} - -.text { - @include roboto; - color: $tc-gray-80; - font-size: 15px; - line-height: 25px; - text-align: center; - - > a { - color: $tc-dark-blue-70; - } -} - -.back-home { - margin-top: 6 * $base-unit; - text-align: center; -} diff --git a/src/projects/detail/Dashboard.jsx b/src/projects/detail/Dashboard.jsx index 52a0a05ed..eb5a7ea26 100644 --- a/src/projects/detail/Dashboard.jsx +++ b/src/projects/detail/Dashboard.jsx @@ -3,8 +3,10 @@ import { connect } from 'react-redux' import MediaQuery from 'react-responsive' import ProjectInfoContainer from './containers/ProjectInfoContainer' import FeedContainer from './containers/FeedContainer' +import MobileNavigationTabs from './components/MobileNavigationTabs' import Sticky from 'react-stickynode' import spinnerWhileLoading from '../../components/LoadingSpinner' +import { SCREEN_BREAKPOINT_MD } from '../../config/constants' require('./Dashboard.scss') @@ -22,8 +24,9 @@ const DashboardView = ({project, currentMemberRole, route, params, isSuperUser } return (
+
- + {(matches) => { if (matches) { return {leftArea} diff --git a/src/projects/detail/Dashboard.scss b/src/projects/detail/Dashboard.scss index e1c011f48..7767f04bd 100644 --- a/src/projects/detail/Dashboard.scss +++ b/src/projects/detail/Dashboard.scss @@ -34,6 +34,7 @@ @media screen and (max-width: $screen-md - 1px) { flex-direction: column; margin-bottom: 0; + margin-top: 0; /* leave space for new message flying button and space around it */ padding: 0 0 (56px + 8 * $base-unit) 0; } diff --git a/src/projects/detail/components/MobileNavigationTabs.jsx b/src/projects/detail/components/MobileNavigationTabs.jsx new file mode 100644 index 000000000..978874b32 --- /dev/null +++ b/src/projects/detail/components/MobileNavigationTabs.jsx @@ -0,0 +1,23 @@ +/** + * Tabs for project page: details / specification + * + * Displayed on mobile only + */ +import React from 'react' +import PropTypes from 'prop-types' +import { NavLink } from 'react-router-dom' + +import style from './MobileNavigationTabs.scss' + +const MobileNavigationTabs = ({ projectId }) => ( +
    +
  • Dashboard
  • +
  • Specifications
  • +
+) + +MobileNavigationTabs.propTypes = { + projectId: PropTypes.number.isRequired +} + +export default MobileNavigationTabs diff --git a/src/projects/detail/components/MobileNavigationTabs.scss b/src/projects/detail/components/MobileNavigationTabs.scss new file mode 100644 index 000000000..6578d3811 --- /dev/null +++ b/src/projects/detail/components/MobileNavigationTabs.scss @@ -0,0 +1,39 @@ +@import '~tc-ui/src/styles/tc-includes'; + +.tabs { + align-items: center; + background-color: $tc-gray-neutral-light; + border-bottom: 1px solid $tc-gray-10; + display: none; + height: 50px; + justify-content: center; + + @media screen and (max-width: $screen-md - 1px) { + display: flex; + } +} + +.link { + @include roboto; + color: $tc-gray-50; + font-size: 14px; + padding: 0 4 * $base-unit; + position: relative; + text-transform: uppercase; + + &.active { + @include roboto-bold; + + &::after { + background-color: $tc-dark-blue-70; + bottom: - $base-unit; + content: ''; + display: block; + height: 2px; + left: 50%; + margin-left: - (5 * $base-unit) / 2; + position: absolute; + width: 5 * $base-unit; + } + } +} diff --git a/src/projects/detail/components/ProjectSpecSidebar.jsx b/src/projects/detail/components/ProjectSpecSidebar.jsx index 4c580b7b3..8b8ed95ad 100644 --- a/src/projects/detail/components/ProjectSpecSidebar.jsx +++ b/src/projects/detail/components/ProjectSpecSidebar.jsx @@ -3,10 +3,14 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { withRouter } from 'react-router-dom' import { connect } from 'react-redux' +import cn from 'classnames' +import Sticky from 'react-stickynode' +import MediaQuery from 'react-responsive' import SidebarNav from './SidebarNav' import VisualDesignProjectEstimateSection from './VisualDesignProjectEstimateSection' -import { PROJECT_ROLE_OWNER, PROJECT_ROLE_CUSTOMER } from '../../../config/constants' +import { PROJECT_ROLE_OWNER, PROJECT_ROLE_CUSTOMER, SCREEN_BREAKPOINT_MD } from '../../../config/constants' import { updateProject } from '../../actions/project' +import { findProduct } from '../../../config/projectWizard' import './ProjectSpecSidebar.scss' const calcProgress = (project, subSection) => { @@ -128,15 +132,27 @@ class ProjectSpecSidebar extends Component { ) } + const submitButton = ( + + ) + + // check if project has estimation the same way as VisualDesignProjectEstimateSection does + // probably this can be done in more elegant way + const { products } = project.details + const productId = products ? products[0] : null + const product = findProduct(productId) + const hasEstimation = product && typeof product.basePriceEstimate !== 'undefined' + return ( -
+

Specifications

-
- {getProjectEstimateSection()} { showReviewBtn && @@ -149,14 +165,39 @@ class ProjectSpecSidebar extends Component { you a good estimate.

-
- -
+ + {(matches) => { + if (matches) { + return ( +
+ {submitButton} +
+ ) + } else { + return ( + +
+ {getProjectEstimateSection()} + {submitButton} +
+
+ ) + } + }} +
} + {!showReviewBtn && hasEstimation && + +
+ +
+ {getProjectEstimateSection()} +
+
+
+
+ }
) } diff --git a/src/projects/detail/components/ProjectSpecSidebar.scss b/src/projects/detail/components/ProjectSpecSidebar.scss index bd18c77b1..d36bb1a51 100644 --- a/src/projects/detail/components/ProjectSpecSidebar.scss +++ b/src/projects/detail/components/ProjectSpecSidebar.scss @@ -12,7 +12,7 @@ $hr-color: lighten($hr-color-base, 33.5%); $hr-color-light: lighten($hr-color-base, 46.7%); $hr-color-lighter: lighten($hr-color-base, 93.5%); - + .projectSpecSidebar { margin-right: 20px; margin-bottom: 10px; @@ -21,7 +21,21 @@ padding: 20px 0; border-radius: 4px; min-width: 280px; - + position: relative; + + @media screen and (max-width: $screen-md - 1px) { + border-radius: 0; + box-shadow: none; + margin-bottom: 0; + margin-right: 0; + padding: 0; + + &.has-review-btn { + padding-bottom: 2 * $base-unit; + padding-top: 2 * $base-unit; + } + } + .titles { @include roboto-medium; font-size: 12px; @@ -30,15 +44,44 @@ padding: 6px 0; margin: 0 20px; text-transform: uppercase; - + + @media screen and (max-width: $screen-md - 1px) { + display: none; + } + &.normal-txt { color: $tc-black; } } + + .visual-design-project-estimate-section { + @media screen and (max-width: $screen-md - 1px) { + padding-bottom: 5 * $base-unit; + } + + @media screen and (max-width: $screen-md - 1px) { + .titles { + display: block; + } + } + } + + &.has-review-btn { + .visual-design-project-estimate-section { + @media screen and (max-width: $screen-md - 1px) { + padding-bottom: 2 * $base-unit; + } + } + } + /* .text-box 8/29 */ .text-box { margin: 0 20px; - + + @media screen and (max-width: $screen-md - 1px) { + display: none; + } + hr { display: block; height: 1px; @@ -46,8 +89,12 @@ border-top: 1px solid $hr-color-lighter; margin: 1em 0; padding: 0; + + @media screen and (max-width: $screen-md - 1px) { + display: none; + } } - + p { @include roboto; font-size: 13px; @@ -55,18 +102,31 @@ color: $gray-color; } } + /* text-box End */ .btn-boxs { margin-top: 20px; text-align: center; + + @media screen and (max-width: $screen-md - 1px) { + background-color: $tc-white; + margin-top: 0; + padding: 3 * $base-unit 4 * $base-unit; + } + } + + .list-group:first-of-type { + @media screen and (max-width: $screen-md - 1px) { + display: none; + } } - + &.no-number { .list-group { .title { padding-left: 0; } - + li { .boxs { padding: 0 20px; @@ -74,6 +134,86 @@ } } } + + /* styles related to animations when .btn-boxs sticks using Sticky module */ + @media screen and (max-width: $screen-md - 1px) { + /* restyle estimations component */ + .btn-boxs { + .list-group { + display: block; + } + + .visual-design-project-estimate-section { + flex-direction: column-reverse; + left: 4 * $base-unit; + margin: 0; + opacity: 0; + position: absolute; + transition: opacity 0.5s ease; + width: auto; + + h4, + .label { + display: none; + } + + .estimate { + margin-top: 0; + text-align: left; + + &:not(:last-child) { + color: $tc-gray-50; + font-size: 13px; + margin-top: 2px; + } + } + } + } + + .btn-boxs button { + display: block; + left: 50%; + position: relative; + transform: translateX(-50%); + transition: all 0.5s ease; + } + + .sticky-outer-wrapper.active { + .sticky-inner-wrapper { + z-index: 10; + } + + .btn-boxs { + box-shadow: 0px 1px 3px 0px rgba($tc-black, 0.2); + } + + .btn-boxs.has-estimation button { + left: 100%; + transform: translateX(-100%); + } + + .visual-design-project-estimate-section { + opacity: 1; + } + } + } + + .sticky-estimation-only { + .sticky-outer-wrapper { + bottom: 0; + left: 0; + position: absolute; + width: 100%; + z-index: -1; + + &.active { + z-index: 10; + } + } + + .btn-boxs { + height: 70px; + } + } } } - \ No newline at end of file diff --git a/src/projects/detail/components/SpecFeatureQuestion.scss b/src/projects/detail/components/SpecFeatureQuestion.scss index c1c77cb37..c570392a2 100644 --- a/src/projects/detail/components/SpecFeatureQuestion.scss +++ b/src/projects/detail/components/SpecFeatureQuestion.scss @@ -10,7 +10,20 @@ height: 60px; background-color: $tc-gray-neutral-light; border: 1px solid $tc-gray-10; + + @media screen and (max-width: $screen-md - 1px) { + &::after { + @include roboto; + content: 'Editing features is not available on mobile devices.'; + display: block; + font-size: 12px; + font-style: italic; + } + + button { + display: none; + } + } } } } - \ No newline at end of file diff --git a/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss b/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss index eb6f50b5f..5f24c7911 100644 --- a/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss +++ b/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss @@ -3,33 +3,41 @@ :global { .spec-question-list { } - + .spec-question-list-item { padding: 0; margin-bottom: 30px; - + + @media screen and (max-width: $screen-md - 1px) { + margin-top: 9 * $base-unit; + } + .icon-col { padding-top: 3px; padding-right: 10px; display: none; } - + .content-col { width: 100%; - + > h5 { - @include roboto; + @include roboto-medium; font-size: 15px; line-height: 25px; color: $tc-gray-70; margin-bottom: 0; - + + @media screen and (max-width: $screen-md - 1px) { + margin-bottom: 2 * $base-unit; + } + span { color: $tc-orange; padding-left: $base-unit; } } - + > p, .description { @include roboto-light; @@ -38,38 +46,42 @@ font-weight: 400; font-style: italic; line-height: 20px; - + &.bigger { font-size: 15px; } + + @media screen and (max-width: $screen-md - 1px) { + margin-top: 2 * $base-unit; + } } - + .description { margin-bottom: $base-unit * 4; } - + .child-component { .colorSelector { margin-bottom: $base-unit * 4; } - + .checkbox-group-item.checkbox-item-checked { background: $tc-dark-blue-10; border-radius: 4px; } - + .dropdown-wrap.default { background: $tc-gray-neutral-light; border-color: $tc-gray-20; max-width: 300px; margin-left: 0px; - + &:after { border-bottom-color: $tc-gray-70; border-right-color: $tc-gray-70; } } - + textarea { min-height: 45px; max-width: 100%; @@ -78,7 +90,7 @@ margin-bottom: 0; line-height: 24.5px; } - + .radio-group-input, .checkbox-group-item { width: 100%; @@ -86,33 +98,131 @@ margin-bottom: 20px; margin-right: 0; } - + + .radio-group-input { + @media screen and (max-width: $screen-md - 1px) { + margin-bottom: 0; + padding: 4 * $base-unit 0; + } + } + + .radio-group-options { + @media screen and (max-width: $screen-md - 1px) { + flex-direction: column; + } + + .radio label { + @media screen and (max-width: $screen-md - 1px) { + @include roboto; + color: $tc-gray-80; + font-size: 15px; + } + } + + .radio:not(:first-child) { + @media screen and (max-width: $screen-md - 1px) { + margin-top: 4 * $base-unit; + } + } + } + .checkbox-group-item { border-radius: 4px; background: $tc-gray-neutral-light; padding-left: 10px; - display: block; - + padding-right: 10px; + display: flex; + + .tc-checkbox { + flex: 0 0 20px; + } + label { font-weight: 400; color: $tc-black; font-size: 12px; } } - + .checkbox-group-options .checkbox-group-item { width: auto; background: none; padding: 0; margin: 15px 30px 15px 0; + + @media screen and (max-width: $screen-md - 1px) { + margin-bottom: 4 * $base-unit; + margin-top: 4 * $base-unit; + } + + label { + @media screen and (max-width: $screen-md - 1px) { + @include roboto; + color: $tc-gray-80; + font-size: 15px; + } + } } - + .SliderRadioGroup { - margin: 40px auto 15px auto; - width: 80%; + height: auto; + margin: 10px auto; + padding-bottom: 20px; + + .rc-slider-rail { + display: none; + } + + .rc-slider-step { + display: flex; + justify-content: space-around; + bottom: 8px; + left: 0; + width: 100%; + } + + /* use important to override inline styles which comes from rc-slider */ + .rc-slider-dot { + bottom: 8px; + left: auto !important; + position: relative; + z-index: 1; + } + + .rc-slider-mark { + display: flex; + justify-content: space-between; + top: 0; + } + + /* use important to override inline styles which comes from rc-slider */ + .rc-slider-mark-text { + align-items: flex-end; + display: flex; + flex: 1; + justify-content: center; + left: auto !important; + margin-left: 0 !important; + position: relative; + width: 0 !important; /* 0 to force equal width with flex */ + + &::before { + background-color: $tc-gray-10; + bottom: -12px; + content: ''; + display: block; + height: 4px; + left: 50%; + position: absolute; + width: 100%; + } + + &:last-child::before { + display: none; + } + } } } } } } - \ No newline at end of file diff --git a/src/projects/detail/components/VisualDesignProjectEstimateSection.scss b/src/projects/detail/components/VisualDesignProjectEstimateSection.scss index 0a62a90db..fcffb8abb 100644 --- a/src/projects/detail/components/VisualDesignProjectEstimateSection.scss +++ b/src/projects/detail/components/VisualDesignProjectEstimateSection.scss @@ -8,7 +8,7 @@ margin-right: 20px; flex-wrap: wrap; flex-direction: row; - + h4.titles { width: 100%; margin: 0; @@ -16,23 +16,42 @@ font-size: 12px; color: $tc-gray-50; text-transform: uppercase; + + @media screen and (max-width: $screen-md - 1px) { + @include roboto-bold; + font-size: 14px; + margin-bottom: 0; + margin-top: 5 * $base-unit; + padding: 0; + } } - + .label, .estimate { flex-basis: 50%; @include tc-label-md; @include roboto-bold; margin-bottom: $base-unit * 2; + + @media screen and (max-width: $screen-md - 1px) { + font-size: 15px; + margin-bottom: 0; + margin-top: 3 * $base-unit; + } } - + .label { color: $tc-gray-50; } - + .estimate { text-align: right; flex: auto; } + + .estimate:last-child { + @media screen and (max-width: $screen-md - 1px) { + font-size: 22px; + } + } } } - \ No newline at end of file diff --git a/src/projects/detail/containers/FeedContainer.js b/src/projects/detail/containers/FeedContainer.js index 628b79f9e..99a87a901 100644 --- a/src/projects/detail/containers/FeedContainer.js +++ b/src/projects/detail/containers/FeedContainer.js @@ -11,7 +11,8 @@ import { CODER_BOT_USERID, CODER_BOT_USER_FNAME, CODER_BOT_USER_LNAME, - TC_SYSTEM_USERID + TC_SYSTEM_USERID, + SCREEN_BREAKPOINT_MD } from '../../../config/constants' import { connect } from 'react-redux' import Sticky from 'react-stickynode' @@ -32,10 +33,12 @@ import FeedMobile from '../../../components/Feed/FeedMobile' import './Specification.scss' import Refresh from '../../../assets/icons/icon-refresh.svg' -import { ScrollElement, scroller } from 'react-scroll' +import { ScrollElement } from 'react-scroll' +import { scrollToHash } from '../../../components/ScrollToAnchors' /*eslint-disable new-cap */ const ScrollableFeed = ScrollElement(Feed) +const ScrollableFeedMobile = ScrollElement(FeedMobile) const SYSTEM_USER = { firstName: CODER_BOT_USER_FNAME, @@ -241,15 +244,8 @@ class FeedView extends React.Component { return } const scrollTo = window.location.hash ? window.location.hash.substring(1) : null - // const scrollTo = _.get(props, 'params.statusId', null) if (scrollTo) { - scroller.scrollTo(scrollTo, { - spy: true, - smooth: true, - offset: -80, // 60px for top bar and 20px for margin from nav bar - duration: 500, - activeClass: 'active' - }) + scrollToHash(scrollTo) } }) } @@ -416,7 +412,7 @@ class FeedView extends React.Component { const anchorId = 'feed-' + item.id return (
- + {(matches) => (matches ? ( } ) : ( -
} - + ) )} @@ -494,7 +490,7 @@ class FeedView extends React.Component { />
} - + { feeds.map(renderFeed) }
- - - + { !isNewPostMobileOpen && + + + + } { isNewPostMobileOpen && svg { padding-top: 3px; transition: all 200ms ease-in-out; } } - + &:hover { color: $tc-black; cursor: pointer; - + .x-mark-icon > svg { fill: $tc-black; } } } - + .two-col-content.content { .tc-file-field__inputs { height: 45px; @@ -80,19 +80,19 @@ /*background: $tc-gray-neutral-light; border-color: $tc-gray-20; color: $tc-black;*/ - + &::-webkit-input-placeholder { color: $tc-gray-50; text-transform: none; } - + &::-moz-placeholder { line-height: 45px; color: $tc-gray-50; opacity: 1; text-transform: none; } - + &:-ms-input-placeholder { color: $tc-gray-50; text-transform: none; @@ -105,22 +105,22 @@ justify-content: space-between; padding: 15px 0; position: relative; - + .screen-title, .project-name { @include roboto; color: black; font-weight: bold; } - + .screen-title { font-size: 18px; } - + .project-name { font-size: 20px; } - + button.tc-btn-xs {//TODO ideally we should fix it in react-components repo padding-top: 2px; padding-bottom: 2px; @@ -132,33 +132,33 @@ padding-bottom: $base-unit; margin: 30px 0 20px 0; position: relative; - + .title { @include roboto; font-size: 20px; line-height: 30px; } - + .section-actions { position: absolute; right: 0; top: 0; padding: 0 15px; - + &.hide { display: none; } } } - + .sub-title { margin: 30px 0 10px; - + .title { font-size: 15px; line-height: 25px; color: $tc-gray-80; - + span { color: $tc-orange; font-size: 15px; @@ -166,7 +166,7 @@ } } } - + .container { @include flexBox; background-color: transparent; @@ -177,46 +177,55 @@ /* .left-area */ .left-area { flex: auto; - + .left-area-footer { @include roboto; padding: 0 10px; font-size: 11px; line-height: 30px; color: $tc-gray-50; - + span { float: right; } } } - + .right-area { flex: auto; border-radius: 4px; position: relative; - + /* this selector is too common and it's applied to .spec-question-list thought it shouldn't if also has big priority so we have to fix it here */ form > div:not(.spec-question-list) { box-shadow: 0px 1px 3px 0px rgba($tc-black, 0.2); } - + .title { color: $tc-gray-80; @include roboto-bold; } - + .right-area-item { padding: 0 0 70px 0; margin-bottom: 20px; border-radius: 4px; background-color: $tc-white; - + + @media screen and (max-width: $screen-md - 1px) { + margin-bottom: 0; + padding-bottom: 4 * $base-unit; + } + .boxes { margin: 0 50px; + + @media screen and (max-width: $screen-md - 1px) { + margin: 0 4 * $base-unit; + } } - + .big-titles { @include roboto-light; font-size: 28px; @@ -227,7 +236,18 @@ border-radius: 4px 4px 0 0; background: $tc-gray-neutral-light; border-bottom: 1px dashed $tc-gray-20; - + + @media screen and (max-width: $screen-md - 1px) { + margin: 0 -4 * $base-unit 20px; + padding: 4 * $base-unit; + } + + h2 { + @media screen and (max-width: $screen-md - 1px) { + line-height: 8 * $base-unit; + } + } + .optional, .required { @include roboto; @@ -238,42 +258,42 @@ padding: 0 20px; border-radius: 20px 0 0 20px; } - + .required { background: $tc-orange-70; color: $tc-white; } - + .optional { background: $tc-gray-10; color: $gray-color; } } - + .gray-text { @include roboto; font-size: 15px; color: $gray-color; line-height: 25px; - + strong { font-weight: bold; } } - + &.design-specification-two { padding-bottom: 1px; } - + &.option-groups { padding-bottom: 46px; } } - + .tab-nav { .center-area { margin: 0 0 0 111px; - + .btn-nav { padding: 0 15px; float: left; @@ -284,7 +304,7 @@ } } } - + .section-additional-notes { .textarea-title { @include roboto; @@ -293,7 +313,7 @@ line-height: 20px; padding-bottom: 10px; } - + textarea { height: 45px; max-width: 100%; @@ -302,25 +322,29 @@ resize: vertical; } } - + .right-area-footer { @include tc-label-sm; color: $tc-gray-50; margin-top: 4 * $base_unit; + + @media screen and (max-width: $screen-md - 1px) { + display: none; + } } } - + .contents-list { .right-area { padding-left: 40px; - + .title { @include roboto-bold; font-size: 15px; letter-spacing: 0px; line-height: 25px; } - + .txt { @include roboto; font-size: 13px; @@ -328,7 +352,7 @@ line-height: 25px; } } - + &.part-one { .icons { width: 16px; @@ -336,37 +360,41 @@ float: left; margin-top: 3px; } - + .icon-box { background-position: -28px -29px; } - + .icon-magic { background-position: -4px -52px; } } } } - + /* .section-features-module */ .section-features-module { margin-top: 20px; - + + @media screen and (max-width: $screen-md - 1px) { + margin-top: 8 * $base-unit; + } + &.open { .content-boxes { display: block; } - + .bg-contents, .btn-gray-border { display: none; } } - + .row { margin-bottom: 5px; } - + .bg-contents { @include roboto-medium; font-size: 15px; @@ -378,11 +406,11 @@ line-height: 40px; text-align: center; } - + .content-boxes { display: none; } - + .textarea-title { @include roboto; font-size: 13px; @@ -390,7 +418,7 @@ line-height: 20px; padding-bottom: 10px; } - + textarea { min-height: 45px; max-width: 100%; @@ -400,18 +428,22 @@ overflow: hidden; } } - + .section-header { display: flex; justify-content: space-between; - + .section-number { @include tc-label-lg; color: $tc-gray-30; padding-right: 6 * $base-unit; + + @media screen and (max-width: $screen-md - 1px) { + padding-right: 0; + } } } - + .section-footer { display: flex; align-items: center; @@ -421,18 +453,24 @@ margin-top: -70px; margin-bottom: 20px; border-radius: $corner-radius * 2; + + @media screen and (max-width: $screen-md - 1px) { + border-radius: 0; + height: 80px; + margin-top: 0; + } } - + .section-footer-spec { border-radius: 0 0 $corner-radius * 2 $corner-radius * 2; // margin-top: -10px; // margin-bottom: $base-unit * 4; - + // &:last-child { // margin-bottom: 0; // } } - + /* .section-file-interface */ .section-file-interface { .tc-textarea { @@ -444,16 +482,16 @@ line-height: 20px; resize: vertical; } - + .dashed-bottom-border { border-top: 1px dashed $tc-gray-20; border-bottom: none; } - + .textarea-boxes { display: none; } - + .row { &.hover-after { .edit-box { @@ -461,26 +499,26 @@ float: right; } } - + &.edit-status { .tc-file-field__inputs, .textarea-boxes, .edit-box { display: block; } - + .title, .txt, .right-txt { display: none; } - + .status { .icon-pen { background-position: -24px 0; margin-top: 4px; } - + .icon-delete { background-position: -24px -48px; margin-top: 4px; @@ -488,10 +526,10 @@ } } } - + .edit-box { display: none; - + .icons { width: 24px; height: 24px; @@ -499,16 +537,16 @@ margin-top: -2px; margin-left: 10px; } - + .icon-pen { background-position: -48px 0; } - + .icon-delete { background-position: -72px 0; } } - + .contents-list { .titles { @include roboto-medium; @@ -518,7 +556,7 @@ margin-bottom: 2px; } } - + .icon-zip { background-position: -51px -27px; width: 42px; @@ -526,38 +564,38 @@ float: left; margin-top: 3px; } - + .icon-pen { background-position: 0 0; } - + .right-area { &.padding-left60 { padding-left: 60px; } - + .tc-file-field__inputs { width: 80%; margin-right: 100px; display: none; @include roboto-bold; - + &.height100 { height: 100px; line-height: 10px; } - + &.width { width: 100%; } } - + .zip-title-bar { position: relative; - + .status { float: right; - + .txt { @include roboto; font-size: 15px; @@ -566,7 +604,7 @@ line-height: 20px; } } - + &:after { clear: right; } @@ -580,25 +618,25 @@ margin-bottom: 20px; background: $tc-gray-neutral-light; border-radius: 2px; - + .title { @include roboto-medium; font-size: 15px; letter-spacing: 0px; line-height: 20px; } - + .gray-txt { @include roboto; font-size: 15px; color: $tc-gray-60; line-height: 20px; } - + .btn-boxes { margin-top: 10px; } - + .tc-btn-primary { position: relative; cursor: pointer; @@ -607,7 +645,7 @@ padding: 0 13px; display: inline-block; } - + .file-box { opacity: 0; width: 100%; @@ -619,28 +657,28 @@ margin: 0; z-index: 555; } - + .icon-file { background-position: -4px -28px; width: 15px; height: 17px; vertical-align: middle; } - + &.drag-boxes { padding: 20px 0; } - + &.flie-list-boxes { .flie-list { border-bottom: 1px dashed $tc-gray-30; padding-bottom: 5px; } - + .row { text-align: left; padding: 10px 20px; - + .file-name { @include roboto-bold; font-size: 15px; @@ -649,7 +687,7 @@ line-height: 20px; padding-left: 7px; } - + .upload-status { @include roboto; font-size: 15px; @@ -658,7 +696,7 @@ line-height: 20px; float: right; } - + .white-bar { width: 100%; height: 4px; @@ -666,7 +704,7 @@ background: $tc-gray-20; border-radius: 12px; position: relative; - + .blue-bar { background: $tc-dark-blue-70; border-right: 2px solid $tc-gray-neutral-light; @@ -675,18 +713,18 @@ position: absolute; left: 0; top: 0; - + &.width23 { width: 23%; } - + &.width75 { width: 75%; } } } } - + .file-list-bottom { .txt { color: $title-color; @@ -701,7 +739,7 @@ .bottom-border-titles { margin-bottom: 20px; } - + .titles { text-align: left; @include roboto-bold; @@ -709,44 +747,44 @@ color: $title-color; line-height: 25px; } - + .gray-txt { @include roboto; font-size: 15px; color: $tc-gray-60; line-height: 20px; } - + .content-boxes { margin-top: -20px; } - + .checkbox-group { margin-top: 10px; } - + .group-item { display: inline-block; vertical-align: middle; margin-right: 42px; } - + .radios-group { margin-top: 10px; } - + .radio { float: left; } - + .ul-list { .hide { display: none; } - + li { position: relative; - + .text, .point { @include roboto-medium; @@ -758,45 +796,45 @@ color: $title-color; } } - + .btn-delete { position: absolute; right: 0; top: 50%; margin-top: -14px; - + &:hover .icon-delete { background-position: -96px 0; width: 24px; height: 24px; } } - + .point { display: inline-block; margin-right: 20px; } - + .icons.icon-delete { background-position: -24px -48px; width: 24px; height: 24px; } } - + .input-box { margin-top: 15px; } - + .btn-input-boxes { position: relative; margin-top: 15px; padding-right: 64px; - + .tc-file-field__inputs { width: 100%; } - + .btn-gray-border { width: 54px; position: absolute; @@ -805,33 +843,75 @@ } } } - + .two-col-content.specificationContainer { padding: 0 20px; min-width: 960px; max-width: 1120px; width: auto; margin: 0 auto; - + + @media screen and (max-width: $screen-md - 1px) { + min-width: 0; + padding: 0; + } + + .container { + @media screen and (max-width: $screen-md - 1px) { + flex-direction: column; + margin-bottom: 0; + margin-top: 0; + } + } + .left-area { min-width: $sideBarWidth; max-width: $sideBarMaxWidth; margin-right: 20px; + + @media screen and (max-width: $screen-md - 1px) { + margin-right: 0; + max-width: 100%; + } + + .footer-v2 { + @media screen and (max-width: $screen-md - 1px) { + display: none; + } + } } - + .right-area { min-width: 500px; + + @media screen and (max-width: $screen-md - 1px) { + min-width: 0; + padding-top: 4 * $base-unit; + } + + form div:last-of-type .section-footer-spec { + @media screen and (max-width: $screen-md - 1px) { + margin-bottom: 0; + } + } + } + + .section-footer-spec { + @media screen and (max-width: $screen-md - 1px) { + border-top: 1px dashed $tc-gray-20; + border-radius: 0; + } } } - + .tc-link { @include roboto; } - + .SelectDropdown { margin-bottom: 20px; } - + .edit-screen-footer { display: flex; align-items: center; @@ -840,12 +920,12 @@ margin-bottom: 40px; position: relative; } - + .screen-number-reached-message { @include roboto; color: white; } - + .prompt { text-align: center; z-index: 999; @@ -863,23 +943,32 @@ display: flex; align-items: flex-start; margin-bottom: 20px; - + + @media screen and (max-width: $screen-sm - 1px) { + flex-direction: column; + margin-bottom: 11 * $base-unit; + } + .editable-project-name { flex: 1; + + @media screen and (max-width: $screen-sm - 1px) { + width: 100%; + } } - + .dashed-bottom-border { flex: 1; min-height: 40px; border-bottom: 1px dashed $tc-gray-20; - + .project-name { word-break: break-all; font-size: 20px; /*margin-top: 10px;*/ } } - + .read-only-refcode { height: 40px; align-self: flex-end; @@ -887,7 +976,7 @@ padding: 10px; position: relative; margin-left: 20px; - + .refcode-desc { @include roboto; font-size: 13px; @@ -901,13 +990,19 @@ white-space: nowrap; } } - + .textinput-refcode { position: relative; width: 100px; height: 100%; margin-left: 10px; - + + @media screen and (max-width: $screen-sm - 1px) { + margin-left: 0; + margin-top: 4 * $base-unit; + width: 100%; + } + .refcode-desc { @include roboto; font-size: 12px; @@ -917,12 +1012,39 @@ bottom: -20px; font-style: italic; } - + .project-refcode { + input { + background-color: $tc-gray-neutral-light; + border-bottom: 1px solid $tc-gray-40; + } } } } - + + .two-col-content.content .container .tiled-group-row { + @media screen and (max-width: $screen-md - 1px) { + margin: -4 * $base-unit -2 * $base-unit 0 -2 * $base-unit; + } + } + + .two-col-content.content .container .tiled-group-row > span { + @media screen and (max-width: $screen-md - 1px) { + display: block; + margin-top: 2 * $base-unit; + max-width: 180px; + width: 25%; + + > a { + width: calc(100% - 20px); + } + } + + @media screen and (max-width: $screen-sm - 1px) { + width: 50%; + } + } + .two-col-content.content .container .tiled-group-item { position: relative; -webkit-flex: initial; @@ -939,24 +1061,24 @@ margin: 10px 10px 0; position: relative; vertical-align: top; - + .check-mark { display: none; } - + &.disabled { opacity: 0.3; } - + &:hover { border-color: $tc-dark-blue-70; cursor: pointer; } - + &.active { background: $tc-gray-10; border-color: $tc-gray-10; - + &:after { content: ''; display: block; @@ -968,7 +1090,7 @@ background: $tc-dark-blue; border-radius: 2px; } - + &:before { content: ''; position: absolute; @@ -985,12 +1107,12 @@ -ms-transform: rotate(45deg); -webkit-transform: rotate(45deg); } - + &:hover { border-color: $tc-gray-10; } } - + span.title { display: block; margin-top: 20px; @@ -999,14 +1121,14 @@ font-size: 13px; text-align: center; } - + span.icon { text-align: center; display: block; height: 50px; margin: 30px auto 20px auto; } - + small { display: block; font-size: 11px; @@ -1016,4 +1138,8 @@ margin-top: 10px; } } + + .SliderRadioGroup .rc-slider-mark { + position: relative; + } } diff --git a/src/projects/detail/containers/SpecificationContainer.jsx b/src/projects/detail/containers/SpecificationContainer.jsx index cfd1e7c1c..9a0401442 100644 --- a/src/projects/detail/containers/SpecificationContainer.jsx +++ b/src/projects/detail/containers/SpecificationContainer.jsx @@ -5,11 +5,14 @@ import PropTypes from 'prop-types' import { connect } from 'react-redux' import _ from 'lodash' import Sticky from 'react-stickynode' +import MediaQuery from 'react-responsive' import ProjectSpecSidebar from '../components/ProjectSpecSidebar' +import MobileNavigationTabs from '../components/MobileNavigationTabs' import FooterV2 from '../../../components/FooterV2/FooterV2' import EditProjectForm from '../components/EditProjectForm' import { findProduct } from '../../../config/projectWizard' +import { SCREEN_BREAKPOINT_MD } from '../../../config/constants' import { updateProject, fireProjectDirty, fireProjectDirtyUndo } from '../../actions/project' import spinnerWhileLoading from '../../../components/LoadingSpinner' import typeToSpecification from '../../../config/projectSpecification/typeToSpecification' @@ -59,14 +62,27 @@ class SpecificationContainer extends Component { specification = typeToSpecification[project.details.products[0]] const sections = require(`../../../config/projectQuestions/${specification}`).default + const leftArea = ( +
+ + +
+ ) + return (
+
- - - - + + {(matches) => { + if (matches) { + return {leftArea} + } else { + return leftArea + } + }} +
diff --git a/src/projects/list/components/Projects/ProjectCard.jsx b/src/projects/list/components/Projects/ProjectCard.jsx index bdc43f51b..efde30da2 100644 --- a/src/projects/list/components/Projects/ProjectCard.jsx +++ b/src/projects/list/components/Projects/ProjectCard.jsx @@ -6,6 +6,7 @@ import ProjectCardHeader from './ProjectCardHeader' import ProjectCardBody from './ProjectCardBody' import ProjectManagerAvatars from './ProjectManagerAvatars' import MediaQuery from 'react-responsive' +import { SCREEN_BREAKPOINT_MD } from '../../../../config/constants' import './ProjectCard.scss' function ProjectCard({ project, duration, disabled, currentUser, history, onChangeStatus}) { @@ -23,7 +24,7 @@ function ProjectCard({ project, duration, disabled, currentUser, history, onChan />
- + {(matches) => (
- + + {(matches) => ( + matches ? ( + + ) : ( + + ) + )} +
diff --git a/src/projects/list/components/Projects/ProjectListNavHeader.scss b/src/projects/list/components/Projects/ProjectListNavHeader.scss index 9d6710845..deba0c2e1 100644 --- a/src/projects/list/components/Projects/ProjectListNavHeader.scss +++ b/src/projects/list/components/Projects/ProjectListNavHeader.scss @@ -12,7 +12,11 @@ margin-right: 20px; @media screen and (max-width: $screen-md - 1px) { - display: none; + margin-top: 4 * $base-unit; + } + + @media screen and (max-width: $screen-sm - 1px) { + margin: 0 0 4 * $base-unit 0; } .left-wrapper { @@ -26,6 +30,14 @@ flex-wrap: nowrap; justify-content: flex-end; + @media screen and (max-width: $screen-md - 1px) { + flex: 0 0 120px; + } + + @media screen and (max-width: $screen-sm - 1px) { + display: none; + } + .list-nav-item { flex: 1; } @@ -37,12 +49,20 @@ &.nav-icon { flex: 0 0 46px; + + @media screen and (max-width: $screen-md - 1px) { + text-align: right; + } } } .primary-filter { margin-right: 20px; + @media screen and (max-width: $screen-md - 1px) { + display: none; + } + .tc-switch .label { white-space: nowrap; } diff --git a/src/projects/list/components/Projects/Projects.jsx b/src/projects/list/components/Projects/Projects.jsx index 31ef357b8..a986ad319 100755 --- a/src/projects/list/components/Projects/Projects.jsx +++ b/src/projects/list/components/Projects/Projects.jsx @@ -7,7 +7,6 @@ import CoderBot from '../../../../components/CoderBot/CoderBot' import ProjectListNavHeader from './ProjectListNavHeader' import ProjectsGridView from './ProjectsGridView' import ProjectsCardView from './ProjectsCardView' -import MediaQuery from 'react-responsive' import { loadProjects, setInfiniteAutoload, setProjectsListView } from '../../../actions/loadProjects' import { sortProjects } from '../../../actions/sortProjects' import _ from 'lodash' @@ -220,15 +219,7 @@ class Projects extends Component { ( ) : ( - - {(matches) => { - if (matches) { - return projectsView - } else { - return cardView - } - }} - + projectsView ) }
diff --git a/src/routes.jsx b/src/routes.jsx index 088fe35de..ce069ad3b 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -12,8 +12,6 @@ import TopBarContainer from './components/TopBar/TopBarContainer' import ProjectsToolBar from './components/TopBar/ProjectsToolBar' import RedirectComponent from './components/RedirectComponent' import CreateContainer from './projects/create/containers/CreateContainer' -import CreateContainerMobilePlug from './projects/create/containers/CreateContainerMobilePlug' -import MediaQuery from 'react-responsive' import LoadingIndicator from './components/LoadingIndicator/LoadingIndicator' import {ACCOUNTS_APP_LOGIN_URL, PROJECT_FEED_TYPE_PRIMARY, PROJECT_FEED_TYPE_MESSAGES } from './config/constants' import { getTopic } from './api/messages' @@ -96,12 +94,6 @@ class RedirectToProject extends React.Component { const topBarWithProjectsToolBar = -const CreateContainerResponsive = () => ( - - {(matches) => (matches ? : )} - -) - class Routes extends React.Component { componentWillMount() { redirectToConnectIfNeed() @@ -137,8 +129,8 @@ class Routes extends React.Component { return ( )} /> - )} /> - )} /> + )} /> + )} /> )} /> )} /> diff --git a/src/routes/notifications/actions/index.js b/src/routes/notifications/actions/index.js index e23c54243..f6e011772 100644 --- a/src/routes/notifications/actions/index.js +++ b/src/routes/notifications/actions/index.js @@ -9,7 +9,8 @@ import { MARK_ALL_NOTIFICATIONS_READ, TOGGLE_NOTIFICATION_READ, VIEW_OLDER_NOTIFICATIONS_SUCCESS, - NOTIFICATIONS_PENDING + NOTIFICATIONS_PENDING, + TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE } from '../../../config/constants' import notificationsService from '../services/notifications.js' import Alert from 'react-s-alert' @@ -110,3 +111,8 @@ export const viewOlderNotifications = (sourceId) => (dispatch) => dispatch({ type: VIEW_OLDER_NOTIFICATIONS_SUCCESS, payload: sourceId }) + +export const toggleNotificationsDropdownMobile = (isOpen) => (dispatch) => dispatch({ + type: TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE, + payload: isOpen +}) diff --git a/src/routes/notifications/containers/NotificationsContainer.jsx b/src/routes/notifications/containers/NotificationsContainer.jsx index e9a073a3b..95c04cdd2 100644 --- a/src/routes/notifications/containers/NotificationsContainer.jsx +++ b/src/routes/notifications/containers/NotificationsContainer.jsx @@ -15,7 +15,7 @@ import NotificationsSectionTitle from '../../../components/NotificationsSectionT import SideFilter from '../../../components/SideFilter/SideFilter' import NotificationsEmpty from '../../../components/NotificationsEmpty/NotificationsEmpty' import spinnerWhileLoading from '../../../components/LoadingSpinner' -import { getNotificationsFilters, splitNotificationsBySources, filterReadNotifications } from '../helpers/notifications' +import { getNotificationsFilters, splitNotificationsBySources, filterReadNotifications, filterOldNotifications } from '../helpers/notifications' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' import { REFRESH_NOTIFICATIONS_INTERVAL } from '../../../config/constants' import './NotificationsContainer.scss' @@ -39,7 +39,8 @@ class NotificationsContainer extends React.Component { markAllNotificationsRead, toggleNotificationRead, viewOlderNotifications, oldSourceIds, pending, toggleBundledNotificationRead } = this.props const notReadNotifications = filterReadNotifications(notifications) - const notificationsBySources = splitNotificationsBySources(sources, notReadNotifications, oldSourceIds) + const notOldNotifications = filterOldNotifications(notReadNotifications, oldSourceIds) + const notificationsBySources = splitNotificationsBySources(sources, notOldNotifications) let globalSource = notificationsBySources.length > 0 && notificationsBySources[0].id === 'global' ? notificationsBySources[0] : null let projectSources = globalSource ? notificationsBySources.slice(1) : notificationsBySources if (filterBy) { diff --git a/src/routes/notifications/containers/NotificationsContainer.scss b/src/routes/notifications/containers/NotificationsContainer.scss index be2b999b4..6441560da 100644 --- a/src/routes/notifications/containers/NotificationsContainer.scss +++ b/src/routes/notifications/containers/NotificationsContainer.scss @@ -9,12 +9,12 @@ max-width: 1150px; min-width: 960px; padding: 30px 20px 0; - + > .content { flex: 1; margin-right: 30px; max-width: 720px; - + > .end-of-list { @include roboto-medium; color: $tc-gray-50; @@ -24,28 +24,10 @@ padding: 20px 0; } } - + > .filters { flex: 1; max-width: 360px; } - - .notifications-empty-note { - @include roboto; - color: $tc-gray-50; - font-size: 13px; - line-height: 20px; - margin: 50px auto 0; - max-width: 440px; - - > a { - color: #55a5ff; - - &:hover { - color: $tc-dark-blue; - } - } - } } } - \ No newline at end of file diff --git a/src/routes/notifications/helpers/notifications.js b/src/routes/notifications/helpers/notifications.js index aedebbd18..781d14c94 100644 --- a/src/routes/notifications/helpers/notifications.js +++ b/src/routes/notifications/helpers/notifications.js @@ -102,11 +102,10 @@ export const getNotificationsFilters = (sources) => { * * @param {Array} sources list of sources * @param {Array} notifications list of notifications - * @param {Array} oldSourceIds list of ids of sources that will also show old notifications * * @return {Array} list of sources with related notifications */ -export const splitNotificationsBySources = (sources, notifications, oldSourceIds = []) => { +export const splitNotificationsBySources = (sources, notifications) => { const notificationsBySources = [] sources.filter(source => source.total > 0).forEach(source => { @@ -114,9 +113,6 @@ export const splitNotificationsBySources = (sources, notifications, oldSourceIds if (n.sourceId !== source.id) return false return true }) - if (_.indexOf(oldSourceIds, source.id) < 0) { - source.notifications = source.notifications.slice(0, 10) - } notificationsBySources.push(source) }) @@ -128,10 +124,23 @@ export const splitNotificationsBySources = (sources, notifications, oldSourceIds * * @param {Array} notifications list of notifications * - * @return {Array} notifications list filtered of notifications + * @return {Array} list of filtered notifications */ export const filterReadNotifications = (notifications) => _.filter(notifications, { isRead: false }) +/** + * Filter notifications to only not old or if their source in special oldSourceIds array + * + * @param {Array} notifications list of notifications + * @param {Array} oldSourceIds list of ids of sources that will also show old notifications + * + * @return {Array} list of filtered notifications + */ +export const filterOldNotifications = (notifications, oldSourceIds) => _.filter(notifications, (notification) => ( + _.includes(oldSourceIds, notification.sourceId) || + new Date().getTime() - OLD_NOTIFICATION_TIME * MILLISECONDS_IN_MINUTE < new Date(notification.date).getTime() +)) + /** * Filter notifications that belongs to project:projectId * @@ -350,7 +359,6 @@ export const prepareNotifications = (rawNotifications) => { date: rawNotification.createdAt, isRead: rawNotification.read, seen: rawNotification.seen, - isOld: new Date().getTime() - OLD_NOTIFICATION_TIME * MILLISECONDS_IN_MINUTE > new Date(rawNotification.createdAt).getTime(), contents: rawNotification.contents, version: rawNotification.version })).map((notification) => { diff --git a/src/routes/notifications/reducers/index.js b/src/routes/notifications/reducers/index.js index 0188a005a..bc045f48a 100644 --- a/src/routes/notifications/reducers/index.js +++ b/src/routes/notifications/reducers/index.js @@ -9,7 +9,8 @@ import { MARK_ALL_NOTIFICATIONS_READ, TOGGLE_NOTIFICATION_READ, VIEW_OLDER_NOTIFICATIONS_SUCCESS, - NOTIFICATIONS_PENDING + NOTIFICATIONS_PENDING, + TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE } from '../../../config/constants' import _ from 'lodash' @@ -23,7 +24,9 @@ const initialState = { // ids of sources that will also show old notifications oldSourceIds: [], lastVisited: new Date(0), - pending: false + pending: false, + // indicates if notifications dropdown opened for mobile devices + isDropdownMobileOpen: false } // get sources from notifications @@ -102,6 +105,11 @@ export default (state = initialState, action) => { oldSourceIds: [...state.oldSourceIds, action.payload] } + case TOGGLE_NOTIFICATIONS_DROPDOWN_MOBILE: + return {...state, + isDropdownMobileOpen: !_.isUndefined(action.payload) ? action.payload : !state.isDropdownMobileOpen + } + default: return state } diff --git a/src/routes/settings/components/SettingsPanel.scss b/src/routes/settings/components/SettingsPanel.scss index 1862adb2a..0a9481a41 100644 --- a/src/routes/settings/components/SettingsPanel.scss +++ b/src/routes/settings/components/SettingsPanel.scss @@ -6,61 +6,77 @@ margin: 20px auto 0; padding: 0 20px; min-width: 960px; - + + @media screen and (max-width: $screen-md - 1px) { + margin: 0; + min-width: 0; + padding: 0; + } + > .inner { background-color: $tc-white; border-radius: 6px; margin: 0 auto; max-width: 720px; padding: 50px 60px 40px; - + + @media screen and (max-width: $screen-md - 1px) { + padding: 4 * $base-unit; + } + > .title { @include roboto-light; color: $tc-gray-50; font-size: 28px; line-height: 30px; text-align: center; + + @media screen and (max-width: $screen-md - 1px) { + display: none; + } } - + > .text { @include roboto; color: $tc-gray-50; font-size: 15px; line-height: 25px; margin-top: 20px; + + @media screen and (max-width: $screen-md - 1px) { + margin-top: 0; + } } - + > .content { border-top: 1px solid $tc-gray-50; margin-top: 30px; } } - + &.wide { > .inner { max-width: 960px; - + > .content { border-top: 0; margin-top: 0; } } } - + a { @include roboto; - text-decoration: underline; } // Link colors a:link, a:visited { color: $tc-dark-blue; } - + a:hover, a:active { color: $tc-dark-blue-70; } } } - \ No newline at end of file diff --git a/src/routes/settings/routes/notifications/components/NotificationSettingsForm.scss b/src/routes/settings/routes/notifications/components/NotificationSettingsForm.scss index 198f82d14..af606e84c 100644 --- a/src/routes/settings/routes/notifications/components/NotificationSettingsForm.scss +++ b/src/routes/settings/routes/notifications/components/NotificationSettingsForm.scss @@ -6,13 +6,13 @@ > .table { margin-top: 22px; width: 100%; - + td { padding: 0; text-align: left; vertical-align: top; } - + tr > th, tr .th { @include roboto; @@ -23,43 +23,61 @@ padding: 0; vertical-align: top; } - + tbody > tr { border-top: 1px dashed $tc-gray-30; - + > th { padding: 20px 0; } - + > td { padding: 22.5px 0; + + @media screen and (max-width: $screen-md - 1px) { + text-align: center; + + & > .SwitchButton > label { + margin: 0 auto; + } + } } } - + thead > tr { border-bottom: 1px solid $tc-gray-50; - + > th { padding-bottom: 11px; } - + > th:last-child { width: 90px; + + @media screen and (max-width: $screen-md - 1px) { + text-align: center; + width: auto; + } } - + > th:nth-child(2) { width: 170px; + + @media screen and (max-width: $screen-md - 1px) { + text-align: center; + width: auto; + } } - + > th:first-child { font-size: 20px; } } - + tbody > tr:last-child { border-bottom: 1px solid $tc-gray-50; } - + .fixed-value { @include roboto-medium; background-color: $tc-gray-40; @@ -71,23 +89,23 @@ line-height: 20px; padding: 0 10px; } - + .th-with-icon { display: inline-block; height: 0; - + > img, > span { display: inline-block; vertical-align: middle; } - + > span { margin-left: 10px; margin-bottom: 10px; } } - + .none { background-color: $tc-gray-40; display: inline-block; @@ -95,41 +113,39 @@ margin-top: 9px; width: 21px; } - + .bundle-emails { align-items: center; display: flex; justify-content: space-between; margin: -3px 0; } - + .SwitchButton label { margin-top: 0; } - + .SwitchButton .label { display: none; } } - + > .controls { - margin-top: 10px; + margin-bottom: 3 * $base-unit; + margin-top: 6 * $base-unit; text-align: center; } - + > .email-settings { @include roboto; margin: 20px 0; - - a { - text-decoration: underline; - } + // Link colors a:link, a:visited { color: $tc-dark-blue; } - + a:hover, a:active { color: $tc-dark-blue-70; @@ -137,4 +153,3 @@ } } } - \ No newline at end of file