From f08feb1ca673cd1764ed3f545505661f9d482bc8 Mon Sep 17 00:00:00 2001 From: Kien Vu Date: Mon, 18 Nov 2019 12:56:58 +0100 Subject: [PATCH 01/12] #602: get urlV2 instead of url coverImage --- packages/gdl-frontend/gql/QueryBookList.js | 2 +- packages/gdl-frontend/lib/offlineLibrary/OfflineLibrary.js | 2 +- packages/gdl-frontend/pages/books/_book.js | 4 ++-- packages/gdl-frontend/pages/books/_read.js | 2 +- packages/gdl-frontend/pages/books/_translate.js | 2 +- packages/gdl-frontend/pages/books/browse.js | 2 +- packages/gdl-frontend/pages/books/translations.js | 2 +- packages/gdl-frontend/pages/favorites.js | 2 +- packages/gdl-frontend/pages/index.js | 2 +- packages/gdl-frontend/pages/search.js | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/gdl-frontend/gql/QueryBookList.js b/packages/gdl-frontend/gql/QueryBookList.js index 2f4cea729..2c11aebba 100644 --- a/packages/gdl-frontend/gql/QueryBookList.js +++ b/packages/gdl-frontend/gql/QueryBookList.js @@ -40,7 +40,7 @@ const GET_BOOKS_QUERY = gql` bookId title coverImage { - url + url: urlV2 } language { code diff --git a/packages/gdl-frontend/lib/offlineLibrary/OfflineLibrary.js b/packages/gdl-frontend/lib/offlineLibrary/OfflineLibrary.js index 05c200d88..c25b3608e 100644 --- a/packages/gdl-frontend/lib/offlineLibrary/OfflineLibrary.js +++ b/packages/gdl-frontend/lib/offlineLibrary/OfflineLibrary.js @@ -253,7 +253,7 @@ const OfflineBookFragment = gql` isRTL } coverImage { - url + url: urlV2 } publisher { name diff --git a/packages/gdl-frontend/pages/books/_book.js b/packages/gdl-frontend/pages/books/_book.js index 44d83633e..01b60afe3 100644 --- a/packages/gdl-frontend/pages/books/_book.js +++ b/packages/gdl-frontend/pages/books/_book.js @@ -81,7 +81,7 @@ const BOOK_QUERY = gql` name } coverImage { - url + url: urlV2 } publisher { name @@ -537,7 +537,7 @@ const SIMILAR_BOOKS_QUERY = gql` code } coverImage { - url + url: urlV2 } } } diff --git a/packages/gdl-frontend/pages/books/_read.js b/packages/gdl-frontend/pages/books/_read.js index 85dac73e8..4ef72b1d9 100644 --- a/packages/gdl-frontend/pages/books/_read.js +++ b/packages/gdl-frontend/pages/books/_read.js @@ -244,7 +244,7 @@ const BOOK_QUERY = gql` chapterId } coverImage { - url + url: urlV2 } } } diff --git a/packages/gdl-frontend/pages/books/_translate.js b/packages/gdl-frontend/pages/books/_translate.js index 841f01825..5d3014075 100644 --- a/packages/gdl-frontend/pages/books/_translate.js +++ b/packages/gdl-frontend/pages/books/_translate.js @@ -37,7 +37,7 @@ const BOOK_QUERY = gql` name } coverImage { - url + url: urlV2 } } translationLanguages(languageCode: $languageCode) { diff --git a/packages/gdl-frontend/pages/books/browse.js b/packages/gdl-frontend/pages/books/browse.js index a398dd92a..b88fc6575 100644 --- a/packages/gdl-frontend/pages/books/browse.js +++ b/packages/gdl-frontend/pages/books/browse.js @@ -55,7 +55,7 @@ const BROWSE_BOOKS_QUERY = gql` bookId title coverImage { - url + url: urlV2 } language { code diff --git a/packages/gdl-frontend/pages/books/translations.js b/packages/gdl-frontend/pages/books/translations.js index 0c9c954e3..35433d4d4 100644 --- a/packages/gdl-frontend/pages/books/translations.js +++ b/packages/gdl-frontend/pages/books/translations.js @@ -79,7 +79,7 @@ const MY_TRANSLATION_QUERY = gql` name } coverImage { - url + url: urlV2 } language { name diff --git a/packages/gdl-frontend/pages/favorites.js b/packages/gdl-frontend/pages/favorites.js index eaf3ef1df..ef39c835c 100644 --- a/packages/gdl-frontend/pages/favorites.js +++ b/packages/gdl-frontend/pages/favorites.js @@ -174,7 +174,7 @@ const FAVORITES_QUERY = gql` code } coverImage { - url + url: urlV2 } } } diff --git a/packages/gdl-frontend/pages/index.js b/packages/gdl-frontend/pages/index.js index 017b0cd16..99d11343e 100644 --- a/packages/gdl-frontend/pages/index.js +++ b/packages/gdl-frontend/pages/index.js @@ -350,7 +350,7 @@ const HOME_CONTENT_QUERY = gql` bookId title coverImage { - url + url: urlV2 } language { code diff --git a/packages/gdl-frontend/pages/search.js b/packages/gdl-frontend/pages/search.js index a2143509c..9ab393c6b 100644 --- a/packages/gdl-frontend/pages/search.js +++ b/packages/gdl-frontend/pages/search.js @@ -215,7 +215,7 @@ const SEARCH_QUERY = gql` highlightDescription readingLevel coverImage { - url + url: urlV2 } language { code From 9b9918dca68c850837ec9de6578b1dba877a5238 Mon Sep 17 00:00:00 2001 From: Kien Vu Date: Mon, 18 Nov 2019 13:26:15 +0100 Subject: [PATCH 02/12] #602: changed chapter imageUrls to v2 --- packages/gdl-frontend/lib/offlineLibrary/OfflineLibrary.js | 2 +- packages/gdl-frontend/pages/books/_read.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gdl-frontend/lib/offlineLibrary/OfflineLibrary.js b/packages/gdl-frontend/lib/offlineLibrary/OfflineLibrary.js index c25b3608e..372d90117 100644 --- a/packages/gdl-frontend/lib/offlineLibrary/OfflineLibrary.js +++ b/packages/gdl-frontend/lib/offlineLibrary/OfflineLibrary.js @@ -237,7 +237,7 @@ const OfflineBookFragment = gql` seqNo chapterId content - imageUrls + imageUrls: imageUrlsV2 } downloads { epub diff --git a/packages/gdl-frontend/pages/books/_read.js b/packages/gdl-frontend/pages/books/_read.js index 4ef72b1d9..4d2d14a58 100644 --- a/packages/gdl-frontend/pages/books/_read.js +++ b/packages/gdl-frontend/pages/books/_read.js @@ -257,7 +257,7 @@ const CHAPTER_QUERY = gql` seqNo chapterId content - imageUrls + imageUrls: imageUrlsV2 } } `; From 663a73ca50c6483c621658a756948e9008bdd230 Mon Sep 17 00:00:00 2001 From: Kien Vu Date: Mon, 25 Nov 2019 14:30:37 +0100 Subject: [PATCH 03/12] Demo - working from user testing in Nepal (#415) * Issue#636 new category design (#409) * Issue#195 featured content caroussel (#397) * Issue#627 remove selected books favorits/offline (#405) --- package.json | 10 +- packages/admin-frontend/package.json | 4 +- .../BookListSection/PaginationArrowView.js | 6 +- .../components/EditBookLibrary/BookGrid.js | 34 + .../EditBookLibrary/BookSelectionLink.js | 127 +++ .../components/EditBookLibrary/EditBooks.js | 297 ++++++ .../FeaturedContentCarousel/Carousel.js | 272 ++++++ .../FeaturedContentCarousel/Pagination.js | 53 ++ .../FeaturedContentCarousel/PaginationDot.js | 57 ++ .../gdl-frontend/components/GamePage/index.js | 107 +++ .../gdl-frontend/components/HomePage/index.js | 216 ++--- .../gdl-frontend/components/Layout/Footer.js | 206 ++-- .../gdl-frontend/components/Layout/Main.js | 14 +- .../components/NavContextBar/NavContextBar.js | 19 +- .../components/Navbar/MobileBottomBar.js | 105 ++ .../components/Navbar/SideMenuBar.js | 86 ++ .../gdl-frontend/components/Navbar/helpers.js | 28 + .../gdl-frontend/components/Navbar/index.js | 39 +- .../gdl-frontend/context/RouteNameContext.js | 5 + packages/gdl-frontend/context/index.js | 1 + .../gdl-frontend/elements/SideMenuMargin.js | 13 + packages/gdl-frontend/elements/index.js | 1 + packages/gdl-frontend/gqlTypes.js | 136 ++- packages/gdl-frontend/locale/en/en.json | 108 ++- packages/gdl-frontend/next.config.js | 3 +- packages/gdl-frontend/package-lock.json | 898 ++++++++++++++++++ packages/gdl-frontend/package.json | 9 +- packages/gdl-frontend/pages/_app.js | 30 +- packages/gdl-frontend/pages/favorites.js | 281 ++++-- packages/gdl-frontend/pages/games/index.js | 104 ++ packages/gdl-frontend/pages/index.js | 27 +- packages/gdl-frontend/pages/offline.js | 230 +++-- packages/gdl-frontend/routes.js | 3 + packages/gdl-frontend/style/constants.js | 1 + yarn.lock | 173 +++- 35 files changed, 3111 insertions(+), 592 deletions(-) create mode 100644 packages/gdl-frontend/components/EditBookLibrary/BookGrid.js create mode 100644 packages/gdl-frontend/components/EditBookLibrary/BookSelectionLink.js create mode 100644 packages/gdl-frontend/components/EditBookLibrary/EditBooks.js create mode 100644 packages/gdl-frontend/components/FeaturedContentCarousel/Carousel.js create mode 100644 packages/gdl-frontend/components/FeaturedContentCarousel/Pagination.js create mode 100644 packages/gdl-frontend/components/FeaturedContentCarousel/PaginationDot.js create mode 100644 packages/gdl-frontend/components/GamePage/index.js create mode 100644 packages/gdl-frontend/components/Navbar/MobileBottomBar.js create mode 100644 packages/gdl-frontend/components/Navbar/SideMenuBar.js create mode 100644 packages/gdl-frontend/components/Navbar/helpers.js create mode 100644 packages/gdl-frontend/context/RouteNameContext.js create mode 100644 packages/gdl-frontend/context/index.js create mode 100644 packages/gdl-frontend/elements/SideMenuMargin.js create mode 100644 packages/gdl-frontend/package-lock.json create mode 100644 packages/gdl-frontend/pages/games/index.js create mode 100644 packages/gdl-frontend/style/constants.js diff --git a/package.json b/package.json index d39b05685..ee48ad82f 100644 --- a/package.json +++ b/package.json @@ -40,9 +40,9 @@ "copy-webpack-plugin": "4.5.4", "cross-env": "^5.0.1", "cypress": "^3.1.5", - "enzyme": "3.7.0", - "enzyme-adapter-react-16": "1.7.0", - "enzyme-to-json": "^3.3.4", + "enzyme": "3.10.0", + "enzyme-adapter-react-16": "1.14.0", + "enzyme-to-json": "3.4.0", "eslint": "^5.16.0", "eslint-config-react-app": "3.0.5", "eslint-plugin-chai-friendly": "^0.4.1", @@ -73,7 +73,7 @@ "@lingui/cli": "2.7.2", "@lingui/react": "2.7.2", "@material-ui/core": "3.6.1", - "@material-ui/icons": "3.0.1", + "@material-ui/icons": "4.4.1", "@sentry/browser": "4.4.2", "@sentry/node": "4.4.2", "@zeit/next-css": "1.0.1", @@ -113,6 +113,7 @@ "lscache": "1.2.0", "micro-proxy": "1.1.0", "next": "^8.1.0", + "next-images": "^1.1.2", "next-routes": "1.4.2", "next-transpile-modules": "^2.3.1", "polished": "3.0.3", @@ -129,6 +130,7 @@ "react-joyride": "2.0.5", "react-jss": "8.6.1", "react-swipeable": "^4.3.0", + "react-swipeable-views": "^0.13.3", "universal-cookie": "3.0.4", "workbox-webpack-plugin": "3.6.3" }, diff --git a/packages/admin-frontend/package.json b/packages/admin-frontend/package.json index c035a2e84..186dbff3d 100644 --- a/packages/admin-frontend/package.json +++ b/packages/admin-frontend/package.json @@ -12,7 +12,7 @@ "@emotion/core": "^10.0.14", "@emotion/styled": "^10.0.14", "@material-ui/core": "3.6.1", - "@material-ui/icons": "3.0.1", + "@material-ui/icons": "4.4.1", "cookie-parser": "^1.4.3", "downshift": "2.0.10", "express": "4.16.4", @@ -32,4 +32,4 @@ "devDependencies": { "@zeit/next-css": "1.0.1" } -} \ No newline at end of file +} diff --git a/packages/gdl-frontend/components/BookListSection/PaginationArrowView.js b/packages/gdl-frontend/components/BookListSection/PaginationArrowView.js index aab9d3900..843dda5ef 100644 --- a/packages/gdl-frontend/components/BookListSection/PaginationArrowView.js +++ b/packages/gdl-frontend/components/BookListSection/PaginationArrowView.js @@ -16,6 +16,7 @@ import colorMap from '../../style/colorMapping'; import GameLink from './GameLink'; import BookLink from './BookLink'; import PaginationScrollGrid from './PaginationScrollGrid'; +import { AMOUNT_OF_ITEMS_PER_LEVEL } from '../HomePage'; import type { Book } from './BookLink'; import type { @@ -72,7 +73,10 @@ const PaginationArrowView = ({ {items - .slice((currentIndex - 1) * 5, currentIndex * 5) + .slice( + (currentIndex - 1) * AMOUNT_OF_ITEMS_PER_LEVEL, + currentIndex * AMOUNT_OF_ITEMS_PER_LEVEL + ) .map((item: any) => (
{level === 'Games' ? ( diff --git a/packages/gdl-frontend/components/EditBookLibrary/BookGrid.js b/packages/gdl-frontend/components/EditBookLibrary/BookGrid.js new file mode 100644 index 000000000..314848f6a --- /dev/null +++ b/packages/gdl-frontend/components/EditBookLibrary/BookGrid.js @@ -0,0 +1,34 @@ +// @flow + +import * as React from 'react'; +import GridContainer from '../BookGrid/styledGridContainer'; +import BookLink, { type Book } from './BookSelectionLink'; + +type Props = { + books: $ReadOnlyArray, + selectedBooks: Array, + changeActive: () => void, + selectAll: boolean +}; + +class BookGrid extends React.Component { + render() { + const { books, selectedBooks } = this.props; + + return ( + + {books.map(book => ( + + ))} + + ); + } +} + +export default BookGrid; diff --git a/packages/gdl-frontend/components/EditBookLibrary/BookSelectionLink.js b/packages/gdl-frontend/components/EditBookLibrary/BookSelectionLink.js new file mode 100644 index 000000000..c1758ae1e --- /dev/null +++ b/packages/gdl-frontend/components/EditBookLibrary/BookSelectionLink.js @@ -0,0 +1,127 @@ +// @flow + +import * as React from 'react'; +import { CardContent, Typography } from '@material-ui/core'; +import { CheckCircle } from '@material-ui/icons'; +import styled from '@emotion/styled'; + +import CoverImage from '../CoverImage'; +import media from '../../style/media'; +import { coverWidths } from '../BookListSection/coverWidths'; + +export type Book = $ReadOnly<{ + id: string, + bookId: number, + title: string, + language: { + code: string + }, + coverImage: ?{ url: string } +}>; + +type Props = { + book: Book, + selectedBooks: Array, + changeActive: () => void, + selectAll: boolean +}; + +type State = { + active: boolean +}; + +export default class BookLink extends React.Component { + state = { + active: false + }; + + componentDidUpdate(prevProps: Props) { + if (prevProps.selectedBooks !== this.props.selectedBooks) { + this.props.selectAll + ? this.setState({ active: true }) + : this.setState({ active: false }); + } + } + + handleClick(id: string, selectedBooks: Array) { + if (!selectedBooks.some(item => id === item)) { + selectedBooks.push(id); + this.setState({ active: true }); + } else { + selectedBooks.splice(selectedBooks.indexOf(id), 1); + this.setState({ active: false }); + } + this.props.changeActive(); + } + + render() { + const { book, selectedBooks } = this.props; + const bookSelected = selectedBooks.some(item => book.id === item); + if (this.state.active && !bookSelected) { + selectedBooks.push(book.id); + } else if (!this.state.active && bookSelected) { + selectedBooks.splice(selectedBooks.indexOf(book.id), 1); + } + + return ( + this.handleClick(book.id, selectedBooks)}> + + + + + {book.title} + + {' '} + + ); + } +} + +const Card = styled('div')` + .selectBook { + color: rgb(68, 68, 68); + } + .isSelected { + color: #0277bd; + } + .active img { + transition: all 0.08s ease-in-out; + padding: 10px; + padding-bottom: 0px; + } + .active svg { + color: #0277bd; + } + .active { + background-color: #d7e2f5; + } + position: relative; + box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.1); + :hover { + .selectBook { + color: #8a8b91; + } + } + margin-right: 15px; + width: ${coverWidths.small}px; + ${media.tablet` + width: ${coverWidths.large}px; + `}; +`; diff --git a/packages/gdl-frontend/components/EditBookLibrary/EditBooks.js b/packages/gdl-frontend/components/EditBookLibrary/EditBooks.js new file mode 100644 index 000000000..8c5553920 --- /dev/null +++ b/packages/gdl-frontend/components/EditBookLibrary/EditBooks.js @@ -0,0 +1,297 @@ +// @flow +import * as React from 'react'; +import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import { Hidden, Typography, Button, Dialog, Tooltip } from '@material-ui/core'; +import { Delete, CheckCircle, Clear } from '@material-ui/icons'; +import { Container, Center, IconButton } from '../../elements'; +import EditBookGrid from './BookGrid'; +import { css } from '@emotion/core'; +import styled from '@emotion/styled'; +import { spacing, misc } from '../../style/theme'; +import type { intlShape } from 'react-intl'; + +type Props = { + books: Array, + onClick: () => void, + selectedBooks: Array, + onDelete: () => Promise, + dialog: () => void, + open: boolean, + selectAllBooks: () => void, + deselectAllBooks: () => void, + changeActive: () => void, + favorites: boolean, + intl: intlShape, + selectAll: boolean +}; + +const translations = defineMessages({ + books: { + id: 'books', + defaultMessage: 'books' + }, + book: { + id: 'book', + defaultMessage: 'book' + }, + favorites: { + id: 'favorites', + defaultMessage: 'favorites?' + }, + offlineLibrary: { + id: 'offline library', + defaultMessage: 'offline library?' + } +}); + +const EditBooks = ({ + books, + onClick, + selectedBooks, + onDelete, + dialog, + open, + selectAllBooks, + deselectAllBooks, + changeActive, + favorites, + intl, + selectAll +}: Props) => ( + <> + + + + } + /> + + {selectedBooks.length} books chosen + + + + + } + label={ + + } + /> + + } + label={} + /> + + + + +
+ + {' '} + {favorites ? 'favorites' : 'offline library'} + +
+ + } + > + + + } + /> + + {`${selectedBooks.length} `} + + + + + + } + label={ + + } + /> + + + } + label={} + /> + +
+
+ + + +
+ +
+ +
+

+ + {` ${selectedBooks.length} `} + {` ${ + selectedBooks.length > 1 + ? `${intl.formatMessage(translations.books)} ` + : `${intl.formatMessage(translations.book)} ` + }`} + + {`${ + favorites + ? ` ${intl.formatMessage(translations.favorites)}` + : ` ${intl.formatMessage(translations.offlineLibrary)}` + }`} +

+ + + +
+
+
+ +); + +const EditBooksBar = styled('div')` + display: flex; + height: 60px; + width: 100%; + position: fixed; + background-color: rgb(248, 248, 248); + z-index: 1; + max-width: ${misc.containers.large}px; + margin-left: auto; + margin-right: auto; + border-bottom: 1px solid lightgrey; + align-items: center; + padding: 0 20px; +`; + +const Right = styled('div')` + display: flex; + align-items: center; + justify-content: flex-end; + flex: 1; +`; + +const iconButtonStyle = css` + height: 60px; + width: fit-content; + padding: 0 12px; + span { + font-size: 0.8rem; + } +`; +const bottomIconButtonStyle = css` + height: 60px; + width: 35%; + border-radius: 10%; + span { + font-size: 0.8rem; + } +`; + +export default injectIntl(EditBooks); diff --git a/packages/gdl-frontend/components/FeaturedContentCarousel/Carousel.js b/packages/gdl-frontend/components/FeaturedContentCarousel/Carousel.js new file mode 100644 index 000000000..e60a4affc --- /dev/null +++ b/packages/gdl-frontend/components/FeaturedContentCarousel/Carousel.js @@ -0,0 +1,272 @@ +// @flow +/** + * Part of GDL gdl-frontend. + * Copyright (C) 2019 GDL + * + * See LICENSE + */ + +import * as React from 'react'; +import { Button, CardContent, Typography } from '@material-ui/core'; +import { View, Hidden } from '../../elements'; +import Pagination from '../FeaturedContentCarousel/Pagination'; +import SwipeableViews from 'react-swipeable-views'; +import { autoPlay, virtualize } from 'react-swipeable-views-utils'; +import { KeyboardArrowRight, KeyboardArrowLeft } from '@material-ui/icons/'; +import styled from '@emotion/styled'; + +import Head from '../../components/Head'; +import { + Banner, + HeroCovertitle, + HeroCardMobile, + HeroCardTablet +} from '../HomePage/index'; +import { logEvent } from '../../lib/analytics'; +import { FormattedMessage } from 'react-intl'; +import { css } from '@emotion/core'; +import type { HomeContent_featuredContent as FeaturedContent } from '../../gqlTypes'; +import { colors, misc } from '../../style/theme'; + +type State = { index: number }; +const AutoPlaySwipeableViews = autoPlay(virtualize(SwipeableViews)); + +type Props = {| + featuredContent: Array +|}; + +function mod(n, m) { + let remain = n % m; + return remain >= 0 ? remain : remain + m; +} +class Carousel extends React.Component { + state = { index: 0 }; + handleNextIndex = () => { + this.setState({ + index: this.state.index + 1 + }); + }; + + handlePrevIndex = () => { + this.setState({ + index: this.state.index - 1 + }); + }; + cardContent = (content: FeaturedContent) => { + return ( + // Specifying width here makes text in IE11 wrap + + + {content.title} + + + {content.description} + + + + ); + }; + card = (content: FeaturedContent) => { + return ( + <> + + + + + + + + + {/* Specifying width here makes text in IE11 wrap*/} + + {this.cardContent(content)} + + + + + ); + }; + slideRenderer = (params: { index: number, key: number }) => { + const { key, index } = params; + const { featuredContent } = this.props; + const indexPage = mod(index, featuredContent.length); + const content = featuredContent[indexPage]; + return ( +
+ {this.card(content)} + + + {this.cardContent(content)} + +
+ this.setState({ index })} + /> +
+
+
+
+
+ ); + }; + render() { + const { index } = this.state; + const { featuredContent } = this.props; + + return ( + + {featuredContent.length > 1 ? ( + <> + this.setState({ index })} + slideRenderer={this.slideRenderer} + /> + +
+ +
+ +
+ +
+
+ this.setState({ index })} + /> +
+
+ +
+ this.setState({ index })} + /> +
+
+ + ) : ( + <> + {this.card(featuredContent[0])} + + + {this.cardContent(featuredContent[0])} + + + )} +
+ ); + } +} + +export default Carousel; + +const Container = styled('div')` + position: relative; + max-width: ${misc.containers.small}px; + margin-left: auto; + margin-right: auto; +`; + +const fadeIn = css` + &:hover { + animation: fade-in ease 0.5s; + + @keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + } +`; + +const arrowRightContainer = css` + position: absolute; + width: 9.9%; + height: 100%; + top: 0; + right: 0; + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row-reverse; + &:hover { + background-image: linear-gradient( + 90deg, + rgba(240, 240, 240, 0.1), + rgba(0, 0, 0, 0.5) + ); + cursor: pointer; + } +`; +const arrowLeftContainer = css` + position: absolute; + width: 9.9%; + height: 100%; + top: 0; + left: 0; + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + &:hover { + background-image: linear-gradient( + -90deg, + rgba(240, 240, 240, 0.1), + rgba(0, 0, 0, 0.5) + ); + cursor: pointer; + } +`; +const dotsContainer = css` + position: relative; + width: 100%; + height: 100%; + top: 0; + left: 0; + display: flex; + justifycontent: center; +`; diff --git a/packages/gdl-frontend/components/FeaturedContentCarousel/Pagination.js b/packages/gdl-frontend/components/FeaturedContentCarousel/Pagination.js new file mode 100644 index 000000000..be8d05cf7 --- /dev/null +++ b/packages/gdl-frontend/components/FeaturedContentCarousel/Pagination.js @@ -0,0 +1,53 @@ +// @flow +/** + * Part of GDL gdl-frontend. + * Copyright (C) 2019 GDL + * + * See LICENSE + */ + +import React from 'react'; +import PaginationDot from './PaginationDot'; + +const styles = { + root: { + position: 'absolute', + bottom: '8px', + left: '0', + display: 'flex', + flexDirection: 'row', + width: '100%', + margin: 'auto', + justifyContent: 'center' + } +}; + +type Props = {| + dots: number, + index: number, + onChangeIndex: number => void +|}; + +class Pagination extends React.Component { + handleClick = (event: SyntheticEvent, index: number) => { + this.props.onChangeIndex(index); + }; + + render() { + const { index, dots } = this.props; + + const children = Array(dots) + .fill() + .map((e, i) => ( + + )); + + return
{children}
; + } +} +export default Pagination; diff --git a/packages/gdl-frontend/components/FeaturedContentCarousel/PaginationDot.js b/packages/gdl-frontend/components/FeaturedContentCarousel/PaginationDot.js new file mode 100644 index 000000000..fd2b6f5d2 --- /dev/null +++ b/packages/gdl-frontend/components/FeaturedContentCarousel/PaginationDot.js @@ -0,0 +1,57 @@ +// @flow +/** + * Part of GDL gdl-frontend. + * Copyright (C) 2019 GDL + * + * See LICENSE + */ + +import React from 'react'; + +const styles = { + root: { + height: 18, + width: 18, + cursor: 'pointer', + border: 0, + background: 'none', + padding: 0 + }, + dot: { + backgroundColor: '#e4e6e7', + height: 9, + width: 9, + borderRadius: 6, + margin: 3 + }, + active: { + backgroundColor: '#0277bd' + } +}; + +type Props = {| + active: boolean, + index: number, + onClick: any +|}; + +class PaginationDot extends React.Component { + handleClick = (event: SyntheticEvent) => { + this.props.onClick(event, this.props.index); + }; + + render() { + const { active } = this.props; + const styleDot = active + ? Object.assign({}, styles.dot, styles.active) + : styles.dot; + + return ( + + ); + } +} + +export default PaginationDot; diff --git a/packages/gdl-frontend/components/GamePage/index.js b/packages/gdl-frontend/components/GamePage/index.js new file mode 100644 index 000000000..030a1b1ad --- /dev/null +++ b/packages/gdl-frontend/components/GamePage/index.js @@ -0,0 +1,107 @@ +// @flow +/** + * Part of GDL gdl-frontend. + * Copyright (C) 2019 GDL + * + * See LICENSE + */ + +import * as React from 'react'; +import { css } from '@emotion/core'; +import { FormattedMessage } from 'react-intl'; + +import type { GameContent_games as Games } from '../../gqlTypes'; + +import ReadingLevelTrans from '../../components/ReadingLevelTrans'; +import Layout from '../../components/Layout'; +import Main from '../../components/Layout/Main'; +import { Container, View, Hidden, SideMenuMargin } from '../../elements'; +import PaginationSection from '../BookListSection/PaginationSection'; +import { spacing } from '../../style/theme'; +import { QueryGameList } from '../../gql'; +import MobileBottomBar from '../../components/Navbar/MobileBottomBar'; +import SideMenuBar from '../../components/Navbar/SideMenuBar'; +import { Typography } from '@material-ui/core'; + +export const AMOUNT_OF_ITEMS_PER_LEVEL = 5; + +type Props = {| + games: Games, + languageCode: string +|}; + +class GamePage extends React.Component { + render() { + const { games, languageCode } = this.props; + + return ( + + + + + +
+ + + {games.pageInfo.pageCount > 0 ? ( + + {({ games, loadMore, goBack, loading }) => ( + } + items={games.results} + /> + )} + + ) : ( +
+ + + +
+ )} +
+
+
+
+ + + +
+ ); + } +} + +const scrollStyle = css` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: ${spacing.medium} 0; + margin-top: 20px; +`; + +export default GamePage; diff --git a/packages/gdl-frontend/components/HomePage/index.js b/packages/gdl-frontend/components/HomePage/index.js index 1a8084eed..26af742c5 100644 --- a/packages/gdl-frontend/components/HomePage/index.js +++ b/packages/gdl-frontend/components/HomePage/index.js @@ -8,9 +8,8 @@ import * as React from 'react'; import { css } from '@emotion/core'; -import { FormattedMessage } from 'react-intl'; import styled from '@emotion/styled'; -import { Button, Card, CardContent, Typography } from '@material-ui/core'; +import { Card } from '@material-ui/core'; import type { Category, @@ -18,31 +17,32 @@ import type { HomeContent_featuredContent as FeaturedContent } from '../../gqlTypes'; -import { logEvent } from '../../lib/analytics'; import ReadingLevelTrans from '../../components/ReadingLevelTrans'; import Layout from '../../components/Layout'; import Main from '../../components/Layout/Main'; -import { Container, View } from '../../elements'; +import { Container, View, Hidden, SideMenuMargin } from '../../elements'; import { NavContextBar, CategoryNavigation } from '../../components/NavContextBar'; -import Head from '../../components/Head'; import PaginationSection from '../BookListSection/PaginationSection'; -import { colors, spacing } from '../../style/theme'; +import { spacing, misc } from '../../style/theme'; import media from '../../style/media'; import { flexCenter } from '../../style/flex'; -import { QueryBookList, QueryGameList } from '../../gql'; +import { QueryBookList } from '../../gql'; +import MobileBottomBar from '../../components/Navbar/MobileBottomBar'; +import SideMenuBar from '../../components/Navbar/SideMenuBar'; import type { ReadingLevel } from '../../gqlTypes'; +import Carousel from '../FeaturedContentCarousel/Carousel'; -const Banner = styled('div')` +export const Banner = styled('div')` background-image: ${p => (p.src ? `url(${p.src})` : 'none')}; background-size: cover; position: relative; display: flex; padding: 15px; - justify-content: center; + justify-content: flex; ${media.mobile` height: 210px; `} ${media.tablet` @@ -50,9 +50,15 @@ const Banner = styled('div')` padding: 20px; justify-content: flex-end; `}; + ${media.largerTablet` + max-width: ${misc.containers.small}px; + align-items: center; + margin-left: auto; + margin-right: auto; + `} `; -const HeroCovertitle = styled('div')` +export const HeroCovertitle = styled('div')` position: absolute; top: 0; left: 0; @@ -61,18 +67,20 @@ const HeroCovertitle = styled('div')` padding: 3px 12px; `; -const HeroCardMobile = styled(Card)` +export const HeroCardMobile = styled(Card)` ${flexCenter}; position: relative; margin-top: -50px; margin-left: ${spacing.large}; margin-right: ${spacing.large}; + margin-bottom: 1px; + ${media.tablet` display: none; `}; `; -const HeroCardTablet = styled(Card)` +export const HeroCardTablet = styled(Card)` ${flexCenter}; max-width: 375px; ${media.mobile` @@ -85,7 +93,7 @@ export const AMOUNT_OF_ITEMS_PER_LEVEL = 5; type Props = {| homeContent: HomeContent, languageCode: string, - featuredContent: FeaturedContent, + featuredContent: Array, categories: Array, category: Category |}; @@ -97,6 +105,7 @@ class HomePage extends React.Component { this.props.category !== nextProps.category ); } + render() { const { homeContent, @@ -106,49 +115,8 @@ class HomePage extends React.Component { languageCode } = this.props; - // Destructuring Games, otherwise apollo can't seperate it - const { Games, ...readingLevels } = homeContent; - - const cardContent = ( - // Specifying width here makes text in IE11 wrap - - - {featuredContent.title} - - - {featuredContent.description} - - - - ); - return ( - { languageCode={languageCode} /> -
- - - - - - - - {/* Specifying width here makes text in IE11 wrap*/} - {cardContent} - - - - {cardContent} - - {Object.entries(readingLevels) - // $FlowFixMe TODO: Get this properly typed. Maybe newer Flow versions understands this instead of turning into a mixed type - .filter( - ([_, data]: [ReadingLevel, any]) => - data.results && data.results.length > 0 - ) - .map(([level, data]: [ReadingLevel, any]) => ( - - - - {({ books, loadMore, goBack, loading }) => ( - } - browseLinkProps={{ - lang: languageCode, - readingLevel: level, - category: category, - route: 'browseBooks' - }} - items={books.results} - /> - )} - - - - ))} - - {Games.pageInfo.pageCount > 0 && ( - - - - {({ games, loadMore, goBack, loading }) => ( - } - items={games.results} - /> - )} - - - - )} -
+ + + + +
+ + + {Object.entries(homeContent) + // $FlowFixMe TODO: Get this properly typed. Maybe newer Flow versions understands this instead of turning into a mixed type + .filter( + ([_, data]: [ReadingLevel, any]) => + data.results && data.results.length > 0 + ) + .map(([level, data]: [ReadingLevel, any]) => ( + + + + {({ books, loadMore, goBack, loading }) => ( + } + browseLinkProps={{ + lang: languageCode, + readingLevel: level, + category: category, + route: 'browseBooks' + }} + items={books.results} + /> + )} + + + + ))} +
+
+ + +
); } @@ -255,7 +186,6 @@ const scrollStyle = css` align-items: center; justify-content: center; padding: ${spacing.medium} 0; - border-bottom: solid 1px ${colors.base.grayLight}; `; export default HomePage; diff --git a/packages/gdl-frontend/components/Layout/Footer.js b/packages/gdl-frontend/components/Layout/Footer.js index 147cf7e00..79c52b3b9 100644 --- a/packages/gdl-frontend/components/Layout/Footer.js +++ b/packages/gdl-frontend/components/Layout/Footer.js @@ -18,6 +18,7 @@ import Container from '../../elements/Container'; import CCLogo from './cc-logo.svg'; import { colors } from '../../style/theme'; import media from '../../style/media'; +import { SIDE_DRAWER_WIDTH } from '../../style/constants'; const { publicRuntimeConfig: { zendeskUrl } @@ -26,110 +27,122 @@ const { const Footer = ({ online }) => { if (!online) return null; return ( - - -
+ + + +
- -
  • - - - -
  • -
  • - - - -
  • -
  • - - + +
  • + + + +
  • +
  • + + + +
  • +
  • + + + +
  • +
  • + + + +
  • +
  • + + + +
  • +
  • + + + +
  • +
    + + + + - -
  • - - + + + + + -
  • -
  • - - + + -
  • -
  • - - + + -
  • - - - - - - - - - - - - - - - - - - - - - + + + + ); }; -const FooterStyle = styled('footer')` +const FooterWrapper = styled('footer')` + background-color: #f6f7f9; + border-top: solid 0.5px rgba(112, 112, 112, 0.22); + margin-top: 24px; +`; + +const FooterStyle = styled('div')` display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; margin-top: 40px; margin-bottom: 50px; + position: relative; a { text-decoration: none; color: ${colors.text.default}; @@ -137,6 +150,9 @@ const FooterStyle = styled('footer')` text-decoration: underline; } } + ${media.largerTablet` + left: ${SIDE_DRAWER_WIDTH / 2}px; + `} `; const LinkList = styled('ul')` @@ -156,6 +172,12 @@ const LinkList = styled('ul')` width: 33%; `} } + ${media.largerTablet` + li { + padding: 8px 0; + } + margin-left: 0; + `} `; const SocialMediaIcons = styled('div')` @@ -176,6 +198,10 @@ const SocialMediaIcons = styled('div')` ${media.tablet` order: 4; `}; + ${media.largerTablet` + order: 4; + margin: 14px 0 14px 36px; + `} `; const CreativeCommons = styled('div')` diff --git a/packages/gdl-frontend/components/Layout/Main.js b/packages/gdl-frontend/components/Layout/Main.js index 427463e8e..668e38091 100644 --- a/packages/gdl-frontend/components/Layout/Main.js +++ b/packages/gdl-frontend/components/Layout/Main.js @@ -11,18 +11,26 @@ import { css } from '@emotion/core'; import { Paper } from '@material-ui/core'; import { misc, colors } from '../../style/theme'; +import media from '../../style/media'; const styles = { default: css` - background: ${colors.container.background}; flex: 1 0 auto; width: 100%; max-width: ${misc.containers.large}px; margin-left: auto; margin-right: auto; + min-height: 100vh; `, white: css` background: ${colors.base.white}; + `, + container: css` + margin-left: 0; + ${media.largerTablet` + margin-left: 90px; + flex: 1 0 auto; + `} ` }; @@ -32,11 +40,11 @@ type Props = { background?: 'white' | 'gray' }; -const Main = ({ background, ...props }: Props) => ( +const Main = ({ background, ...rest }: Props) => ( ); diff --git a/packages/gdl-frontend/components/NavContextBar/NavContextBar.js b/packages/gdl-frontend/components/NavContextBar/NavContextBar.js index 658c493cf..c989bee57 100644 --- a/packages/gdl-frontend/components/NavContextBar/NavContextBar.js +++ b/packages/gdl-frontend/components/NavContextBar/NavContextBar.js @@ -10,20 +10,33 @@ import React, { type Node } from 'react'; import { Paper } from '@material-ui/core'; import Container from '../../elements/Container'; +import { SIDE_DRAWER_WIDTH } from '../../style/constants'; +import { misc } from '../../style/theme'; +import media from '../../style/media'; +import css from '@emotion/css'; + +const styles = { + paper: css` + z-index: 10; + margin-left: 0; + ${media.largerTablet` + margin-left: ${SIDE_DRAWER_WIDTH}px; + `} + ` +}; type Props = {| children: Node |}; const NavContextBar = (props: Props) => ( - + diff --git a/packages/gdl-frontend/components/Navbar/MobileBottomBar.js b/packages/gdl-frontend/components/Navbar/MobileBottomBar.js new file mode 100644 index 000000000..2d9bac598 --- /dev/null +++ b/packages/gdl-frontend/components/Navbar/MobileBottomBar.js @@ -0,0 +1,105 @@ +// @flow +import React from 'react'; +import { withStyles } from '@material-ui/core/styles'; +import BottomNavigation from '@material-ui/core/BottomNavigation'; +import BottomNavigationAction from '@material-ui/core/BottomNavigationAction'; +import { LibraryBooks, SportsEsports } from '@material-ui/icons'; +import { RouteNameContext } from '../../context'; +import { Link } from '../../routes'; +import { getTrigger } from './helpers'; +import { Slide } from '@material-ui/core'; + +const styles = theme => ({ + root: { + position: 'fixed', + width: '100%', + bottom: 0, + zIndex: theme.zIndex.appBar + } +}); + +const WrappedNavButton = ({ + label, + name, + value, + params, + children, + ...rest +}) => ( + + + +); + +type Props = { + classes: Object, + lang: string +}; + +class MobileBottomBar extends React.Component { + scrollerRef = React.createRef(); + + state = { + trigger: null + }; + + handleScroll = (event: SyntheticEvent) => + this.setState({ trigger: getTrigger(event, this.scrollerRef) }); + + componentDidMount() { + window.addEventListener('scroll', this.handleScroll); + } + + componentWillUnmount() { + window.removeEventListener('scroll', this.handleScroll); + } + + render() { + const { classes, lang } = this.props; + const { trigger } = this.state; + + return ( + + {pageRoute => ( + + + + + + + + + + + + )} + + ); + } +} + +export default withStyles(styles)(MobileBottomBar); diff --git a/packages/gdl-frontend/components/Navbar/SideMenuBar.js b/packages/gdl-frontend/components/Navbar/SideMenuBar.js new file mode 100644 index 000000000..1680caf1e --- /dev/null +++ b/packages/gdl-frontend/components/Navbar/SideMenuBar.js @@ -0,0 +1,86 @@ +// @flow +import React from 'react'; +import { withStyles } from '@material-ui/core/styles'; +import { + Drawer, + List, + ListItem, + ListItemIcon, + ListItemText +} from '@material-ui/core'; +import { LibraryBooks, SportsEsports } from '@material-ui/icons'; +import { SIDE_DRAWER_WIDTH } from '../../style/constants'; +import { RouteNameContext } from '../../context'; +import { Link } from '../../routes'; + +const styles = theme => ({ + root: {}, + menuButton: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + minHeight: 67 + }, + icon: { + marginRight: 0 + }, + drawer: { + width: SIDE_DRAWER_WIDTH, + flexShrink: 0, + whiteSpace: 'nowrap' + }, + drawerPaper: { + width: SIDE_DRAWER_WIDTH, + + overflowX: 'hidden' + } +}); + +const SideMenuBar = ({ classes, lang }: { classes: Object, lang: string }) => ( + + {pageRoute => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} + +); + +export default withStyles(styles)(SideMenuBar); diff --git a/packages/gdl-frontend/components/Navbar/helpers.js b/packages/gdl-frontend/components/Navbar/helpers.js new file mode 100644 index 000000000..869ec68bd --- /dev/null +++ b/packages/gdl-frontend/components/Navbar/helpers.js @@ -0,0 +1,28 @@ +// @flow +/** + * Since the application can't get react hooks to work, I reverse engineered: + * https://github.com/mui-org/material-ui/blob/89687f38cae750650555772ba4d821c9084d8dfc/packages/material-ui/src/useScrollTrigger/useScrollTrigger.js + */ + +// Change the trigger value when the vertical scroll strictly crosses this threshold +const THRESHOLD = 100; + +function getScrollY(ref: any): number { + return ref.pageYOffset !== undefined ? ref.pageYOffset : ref.scrollTop; +} + +export function getTrigger( + event: ?SyntheticEvent, + ref: Object +): boolean { + const previous = ref.current; + ref.current = event ? getScrollY(event.currentTarget) : previous; + + if (previous !== undefined) { + if (ref.current < previous) { + return false; + } + } + + return ref.current > THRESHOLD; +} diff --git a/packages/gdl-frontend/components/Navbar/index.js b/packages/gdl-frontend/components/Navbar/index.js index 3bc8721b5..b6327dfe6 100644 --- a/packages/gdl-frontend/components/Navbar/index.js +++ b/packages/gdl-frontend/components/Navbar/index.js @@ -23,6 +23,7 @@ import { WifiOff as WifiOffIcon } from '@material-ui/icons'; import { FormattedMessage } from 'react-intl'; +import { withStyles } from '@material-ui/core/styles'; import { withOnlineStatusContext } from '../OnlineStatusContext'; import SelectBookLanguage from '../GlobalMenu/SelectBookLanguage'; @@ -35,13 +36,27 @@ import { misc } from '../../style/theme'; import SearchInput from '../Search/components/SearchInput'; import SearchDrawer from '../Search/components/SearchDrawer'; import { Hidden } from '../../elements'; +import { SIDE_DRAWER_WIDTH } from '../../style/constants'; type Props = { onMenuClick(): void, online: boolean, - homeTutorialInProgress?: boolean + homeTutorialInProgress?: boolean, + classes: Object }; +const styles = theme => ({ + appBar: { + zIndex: theme.zIndex.drawer + 1 + }, + toolBar: { + minHeight: 56, + [`@media (min-width: 600px)`]: { + minHeight: 67 + } + } +}); + const BrandLink = styled('a')` margin-right: auto; svg { @@ -55,7 +70,12 @@ const BrandLink = styled('a')` } `; -const Navbar = ({ onMenuClick, online, homeTutorialInProgress }: Props) => { +const Navbar = ({ + onMenuClick, + online, + homeTutorialInProgress, + classes +}: Props) => { const offline = !online; const brandLink = ( @@ -71,8 +91,8 @@ const Navbar = ({ onMenuClick, online, homeTutorialInProgress }: Props) => { ); return ( - - + +