diff --git a/README.md b/README.md index 9751a3a..71f9887 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ -# React TypeScript Starter Pack +Description: + - News page with ability to search articles and choose categories. -To use this template click `Use this template` -### Available Scripts - -`Deploy` - available to deploy your application to gh-pages - -`SCSS Preprocessor` - available to write your styles with modern style language +- [DEMO](https://uran-web.github.io/react-typescript-starter-pack/); diff --git a/package-lock.json b/package-lock.json index a3d5e77..bc2dcf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4556,6 +4556,11 @@ } } }, + "classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + }, "clean-css": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", diff --git a/package.json b/package.json index aae404f..8169a66 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@types/node": "^12.20.10", "@types/react": "^17.0.4", "@types/react-dom": "^17.0.3", + "classnames": "^2.3.1", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", diff --git a/public/images/header.jpg b/public/images/header.jpg new file mode 100644 index 0000000..f43051d Binary files /dev/null and b/public/images/header.jpg differ diff --git a/public/index.html b/public/index.html index aa069f2..9f82a10 100644 --- a/public/index.html +++ b/public/index.html @@ -24,6 +24,12 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> + React App diff --git a/public/search.svg b/public/search.svg new file mode 100644 index 0000000..ae4687e --- /dev/null +++ b/public/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/App.scss b/src/App.scss index 8f4cd9f..0358dde 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,3 +1,155 @@ .starter { font-size: 18px; } + +.menu { + position: sticky; + top: 0; + width: 100%; + z-index: 1020; +} + +.container { + padding: 14px 60px 0; + background-color: #fff; +} + +.card-body--head-article { + padding: 41px 82px 43px 57px; +} + +.card-title { + font-family: Inter; + font-style: normal; + font-weight: bold; + font-size: 20px; + line-height: 24px; + + &--head-title { + font-size: 30px; + line-height: 36px; + } +} + +.mb-27 { + margin-bottom: 27px; +} + +.mb-30 { + margin-bottom: 30px; +} + +.h-100 { + height: 382px; +} + +.card-inner { + display: flex; + align-items: center; + justify-content: space-between; +} + +.element { + margin: 0; +} + +.gu-image { + width: 100%; + height: 179px; + + object-fit: fill; +} + +.element-image__caption, .element-image__credit { + display: none; +} + +.card-text { + font-family: Inter; + font-style: normal; + font-weight: normal; + font-size: 14px; + line-height: 17px; + + color: #718096; + + &--bot { + margin: 0; + + font-size: 12px; + line-height: 15px; + } + + &--small-card { + height: 85px; + overflow: hidden; + } +} + +.card-date { + margin: 0; + + font-family: Inter; + font-style: normal; + font-weight: bold; + font-size: 12px; + line-height: 15px; + + color: #2D3748; +} + +.article-details { + display: flex; + align-items: center; + justify-content: space-between; + + &__text { + margin: 0; + } +} + +.article-link { + color: black; + font-weight: bold; + text-decoration: none; + + &__text { + margin: 0; + } +} + +.article-link:hover { + text-decoration: underline; +} + +.footer { + padding-bottom: 50px; +} + +.footer-copyright { + margin: 0; + padding-bottom: 1px; + + font-family: Inter; + font-style: normal; + font-weight: normal; + font-size: 16px; + line-height: 19px; +} + +.logo-footer { + display: flex; + margin-right: 50px; + + font-family: Inter; + font-style: normal; + font-weight: bold; + font-size: 16px; + line-height: 19px; +} + +.logo-bar--rights { + align-items: flex-end; + font-size: 16px; + line-height: 19px; +} diff --git a/src/App.tsx b/src/App.tsx index 7bd15e8..4fe1ca4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,27 +1,129 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import './App.scss'; - -interface Props { - onClick: () => void; -} - -export const Provider: React.FC = React.memo( - ({ onClick, children }) => ( - - ), -); +import { SearchBar } from './components/SearchBar/SearchBar'; +import { getData } from './api/api'; export const App: React.FC = () => { + const [articleData, setArticleData] = useState([]); + const [currentCategory, setCurrentCategory] = useState(''); + const [query, setQuery] = useState(''); + const [articles, setArticles] = useState(); + + useEffect(() => { + getData(currentCategory).then(response => { + setArticleData(response.response.results); + setArticles(response.response.results); + }); + }, [currentCategory]); + + const sortedByDate = () => { + const sorted = [...articleData].sort((article1: Article, article2: Article) => { + return ( + article1.webPublicationDate.localeCompare(article2.webPublicationDate) + ); + }); + + return sorted; + }; + + const sortedArticles = sortedByDate(); + + useEffect(() => { + const filtered = sortedArticles.filter(article => { + if (query.length === 0) { + return ( + article.webTitle?.includes(' ') + ); + } + + return ( + article.webTitle?.includes(query) + ); + }); + + setArticles(filtered); + }, [query]); + + const renderArticleCard = (article: Article) => { + const createdDateYear = new Date(article.webPublicationDate); + const dateToday = new Date(); + const oneDay = 1000 * 60 * 60 * 24; + const daysAgo = Math.floor(+(dateToday.getTime() - +createdDateYear.getTime()) / oneDay); + + return ( +
+
+
+
+
{article.webTitle}
+

{article.fields?.bodyText}

+
+
+

{`Created ${daysAgo} days ago`}

+
+ +

Read more

+
+
+
+
+
+ ); + }; + return ( -
- ({})}> - - -
+ <> + + +
+
+
+
+
+
+
Always intreating news!
+

Look at these amazing world actions!

+
+

Last updated 3 mins ago

+

Last updated 3 mins ago

+
+
+
+
+ img +
+
+
+
+
+ +
+
+
+ {articles?.map(renderArticleCard)} +
+
+
+ + +
+
+ ); }; diff --git a/src/api/api.tsx b/src/api/api.tsx new file mode 100644 index 0000000..f520219 --- /dev/null +++ b/src/api/api.tsx @@ -0,0 +1,11 @@ +export const BASE_URL = 'https://content.guardianapis.com/search?q='; + +export const getData = async (query: string) => { + const response = await fetch(`${BASE_URL}${query}&show-tags=all&page-size=20&show-fields=all&order-by=relevance&api-key=141f90d4-5cce-4f13-8f4e-2b6cf69cd8cb`); + + if (!response.ok) { + throw new Error(`${response.status} - ${response.statusText}`); + } + + return response.json(); +}; diff --git a/src/components/BurgerMenu/BurgerMenu.scss b/src/components/BurgerMenu/BurgerMenu.scss new file mode 100644 index 0000000..c5e3d49 --- /dev/null +++ b/src/components/BurgerMenu/BurgerMenu.scss @@ -0,0 +1,80 @@ +.burger-menu { + display: flex; + position: relative; + + &__catagories { + margin-right: 60px; + } + + &__trend { + margin-right: 141px; + cursor: pointer; + border: none; + background-color: #fff; + align-self: flex-start; + } + + &__trend:hover { + color: #2F80ED; + } +} + +.drop { + font-family: Inter; + font-size: 14px; + line-height: 17px; + border: none; + color: #000000; +} + +.dropdown-content { + display: none; + position: absolute; + margin-top: 5px; + background-color: #f1f1f1; + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1000; +} + +.category { + color: #000000; + padding: 12px 16px; + text-decoration: none; + display: block; + cursor: pointer; + border: none; + z-index: 1000; +} + +.burger-menu__catagories:hover .drop { + color: #2F80ED; +} + +.burger-menu__catagories:hover .dropdown-content { + display: grid; + grid-template-columns: repeat(2, 1fr); +} + +.category:hover { + background-color: #ddd; + color: #2F80ED; +} + +.bar1, .bar2, .bar3 { + width: 18px; + height: 2px; + background-color: #333; + margin: 6px 0; + transition: 0.4s; +} + +.change .bar1 { + transform: rotate(-45deg) translate(-5px, 4px); +} + +.change .bar2 {opacity: 0;} + +.change .bar3 { + transform: rotate(45deg) translate(-7px, -7px); +} diff --git a/src/components/BurgerMenu/BurgerMenu.tsx b/src/components/BurgerMenu/BurgerMenu.tsx new file mode 100644 index 0000000..a75594a --- /dev/null +++ b/src/components/BurgerMenu/BurgerMenu.tsx @@ -0,0 +1,138 @@ +import React, { useState } from 'react'; +import './BurgerMenu.scss'; +import classNames from 'classnames'; + +type Props = { + setCurrentCategory: (category: string) => void; +}; + +export const BurgerMenu: React.FC = (props) => { + const { setCurrentCategory } = props; + + const [clicked, setClicked] = useState(false); + const [selectedCategory, setSelectedCategory] = useState('trending'); + + const showMenu = () => { + setClicked(!clicked); + }; + + const handleClick = (event: React.MouseEvent) => { + const { id } = event.currentTarget; + + setSelectedCategory(id); + }; + + setCurrentCategory(selectedCategory); + + const handleKeyPress = () => { + + }; + + return ( +
+ + {!clicked || ( + <> +
+
Categories
+
+ + + + + + + + + + + + + + + + +
+
+ + + + )} + +
+
+
+
+
+
+ ); +}; diff --git a/src/components/SearchBar/SearchBar.scss b/src/components/SearchBar/SearchBar.scss new file mode 100644 index 0000000..199aeb9 --- /dev/null +++ b/src/components/SearchBar/SearchBar.scss @@ -0,0 +1,69 @@ +.input-bar { + display: flex; + align-items: center; + position: relative; + width: 100%; + + background-color: #fff; + z-index: 1; +} + +.logo-bar { + display: flex; + margin-right: 50px; + + font-family: Inter; + font-weight: bold; + font-size: 20px; + line-height: 24px; + + &__article { + color: #000000; + text-decoration: none; + } +} + +.input-group { + display: flex; + align-items: center; + justify-content: center; + + width: 350px; + border: 1px solid transparent; + border-radius: 4px; + background-color: #f2f2f2; + +} + +.btn--bar { + padding: 7px 12px 7px 19px; + display: flex; + align-items: center; + + cursor: pointer; + border: none; +} + +.magnifier { + width: 18px; + height: 18px; +} + +.form-control { + background: #f2f2f2; + border-radius: 4px; + width: 100%; + + border: none; + outline: none; +} + +.form-control { + outline: none; +} + +.burger { + display: flex; + position: absolute; + right: 0; +} diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx new file mode 100644 index 0000000..63ec204 --- /dev/null +++ b/src/components/SearchBar/SearchBar.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import './SearchBar.scss'; +import { BurgerMenu } from '../BurgerMenu/BurgerMenu'; + +type Props = { + setCurrentCategory: (gotCategory: string) => void; + setQueryFromBar: (gotCategory: string) => void; +}; + +export const SearchBar: React.FC = (props) => { + const { setCurrentCategory, setQueryFromBar } = props; + + const [query, setQuery] = useState(''); + + const handleChange = (event: React.ChangeEvent) => { + const { value } = event.target; + + setQuery(value); + }; + + setQueryFromBar(query.toLowerCase()); + + return ( +
+ + +
+ + +
+ +
+ +
+
+ ); +}; diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index 6431bc5..72fe492 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1 +1,13 @@ /// + +interface Article { + id?: string; + webPublicationDate: string; + webTitle?: string; + fields?: Fields; +} + +interface Fields { + bodyText?: string; + main?: string; +}