diff --git a/App.css b/App.css new file mode 100644 index 0000000..74b5e05 --- /dev/null +++ b/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/App.js b/App.js new file mode 100644 index 0000000..7985c3a --- /dev/null +++ b/App.js @@ -0,0 +1,31 @@ +import './App.css'; +import { Header } from "./components/Header/Header"; +import { Home } from "./components/Home/Home"; +import { Footer } from "./components/Footer/Footer"; +import { Catalog } from './components/Catalog/Catalog'; +import {BrowserRouter, Switch, Route,} from "react-router-dom"; +import { Filter } from "./components/Filter/Filter"; + +function App() { + return ( + +
+ + +
+ + + + + + +
+ + +
+
+ ); +} + +export default App; diff --git a/App.test.js b/App.test.js new file mode 100644 index 0000000..1f03afe --- /dev/null +++ b/App.test.js @@ -0,0 +1,8 @@ +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/README.md b/README.md index fbbc647..5d74c0a 100644 --- a/README.md +++ b/README.md @@ -1 +1,29 @@ -# React_Labs \ No newline at end of file +# React.js: Item page +## Description: Continue work on your React App by adding a page for your +Item (see the link to wireframe of Item page above). Also, now, you have to +make all your previous pages (Home & Catalog) more interactive. +### Variants - (products that you are ‘selling’) the same as for previous works. +(see the description to 3rd work) +## Requirements: +- All of the requirements for previous React.js works should be kept. +### Code style: +- Your items should be stored inside the state or context (your +choice) of your page +https://uk.reactjs.org/docs/hooks-state.html +https://uk.reactjs.org/docs/hooks-reference.html#usecontext +- For your state management use useState() inside Functional +Component instead of this.state and Class component +- If you decided to use context, use useContext() hook instead of +Context.Consumer +https://www.robinwieruch.de/react-usecontext-hook +## Functionality (IMPORTANT): +- Home page: “View more” button should display more elements +on the same page Tip: Elements can be just random paragraph +& heading, use your imagination ;) +- Catalog page: You should be able to filter your items list, by +applying different filters by item's properties (i.e size/color/type) +- Catalog page: Search by any text property option should also +work +- Catalog & Item pages: “View more” action on every item +should refer to corresponding Item page, with correct +information about item (get the info from your state/context) diff --git a/components/Catalog/Catalog.js b/components/Catalog/Catalog.js new file mode 100644 index 0000000..c5ca4c7 --- /dev/null +++ b/components/Catalog/Catalog.js @@ -0,0 +1,71 @@ +import { CatalogItem } from "../CatalogItem/CatalogItem"; +import { Wrapper } from "./Catalog.styles"; +import GoldCreekPond from "../../icons/GoldCreekPond.jpg"; +import AppenzellDistrict from "../../icons/AppenzellDistrict.jpg"; +import Uttarakhand from "../../icons/Uttarakhand.jpg"; +import Algeria from "../../icons/Algeria.jpg"; +import { useState } from "react"; +import { Filter } from "../Filter/Filter"; + + +export const Catalog = () => { + const catalogItems = [ + { + name: "Gold Creek Pond", + price: 1200, + image: GoldCreekPond + }, + { + name: "Appenzell District", + price: 700, + image: AppenzellDistrict + }, + { + name: "Uttarakhand", + price: 890, + image: Uttarakhand + }, + { + name: "Algeria", + price: 2500, + image: Algeria + } + ]; + const [items, update] = useState(catalogItems); + + function updateItems(name, order, price, input) { + let resultArray = catalogItems; + if (price == 1) + resultArray = catalogItems.filter(item => item.price <= 1500); + else if (price == 2) + resultArray = catalogItems.filter(item => item.price > 1500); + if (name == "name") + resultArray.sort((a, b) => a.name.localeCompare(b.name)); + else if (name == "price") + resultArray.sort((a, b) => a.price - b.price) + sortByOrder(resultArray, order, input); + } + + function sortByOrder(array, order, input) { + let resultArray; + if (order == 2) + resultArray = Array.from(array).reverse(); + else + resultArray = Array.from(array); + filterInput(resultArray, input); + } + + function filterInput(array, input) { + update(array.filter(item => item.name.search(input.value) !== -1)); + } + + return ( + <> + + + {items.map(item => + )} + + + ); +} diff --git a/components/Catalog/Catalog.styles.js b/components/Catalog/Catalog.styles.js new file mode 100644 index 0000000..a7f2364 --- /dev/null +++ b/components/Catalog/Catalog.styles.js @@ -0,0 +1,8 @@ +import styled from 'styled-components'; + +export const Wrapper = styled.div` + display: flex; + justify-content: space-around; + padding: 20px 50px; + flex-wrap: wrap; +` diff --git a/components/CatalogItem/CatalogItem.js b/components/CatalogItem/CatalogItem.js new file mode 100644 index 0000000..8dfbd91 --- /dev/null +++ b/components/CatalogItem/CatalogItem.js @@ -0,0 +1,14 @@ +import {Image, Wrapper, Label, SmallText, TextWrapper, Button} from './CatalogItem.styles' + +export function CatalogItem(props) { + return ( + + + + + Price: {props.price}$ + + + + ); +} \ No newline at end of file diff --git a/components/CatalogItem/CatalogItem.styles.js b/components/CatalogItem/CatalogItem.styles.js new file mode 100644 index 0000000..4ab96ee --- /dev/null +++ b/components/CatalogItem/CatalogItem.styles.js @@ -0,0 +1,46 @@ +import styled from 'styled-components'; + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + background-color: #f3f3f3; + border-radius: 25px; + padding: 10px; + text-align: center; + margin: 10px; +` + +export const TextWrapper = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + padding: 5px 20px; +` + +export const Image = styled.img` + width: 400px; + height: 300px; + border-radius: 15px; +` + +export const Label = styled.h2` + margin: 10px; +` +export const SmallText = styled.h3` + font-weight: 400; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + align-self: center; +` + +export const Button = styled.button` + padding: 10px 6px; + left: 50%; + background-color: #f3f3f3; + border: solid #c4c4c4 1px; + border-radius: 10px; + width: 30%; + transform: translate(120%, 0); + &:hover { + box-shadow: 0 0 8px #777676; + } +` \ No newline at end of file diff --git a/components/Filter/Filter.js b/components/Filter/Filter.js new file mode 100644 index 0000000..7310e95 --- /dev/null +++ b/components/Filter/Filter.js @@ -0,0 +1,80 @@ +import { useState } from "react"; +import { Logo, Nav, Search, NavItem, NavWrapper, Wrapper } from "../Header/Header.styles"; +import { + ApplyButton, + FilterSelector, + FilterWrapper, + HorLine, + LabelText, + OuterWrapper +} from "./Filter.styles"; +import SearchIcon from "../../icons/search.png"; +import { Link } from "react-router-dom"; + + +export const Filter = (props) => { + const [name, changeName] = useState("DEFAULT"); + const [order, changeOrder] = useState("DEFAULT"); + const [price, changePrice] = useState("DEFAULT"); + const input = document.getElementById("search_input"); + + function setName(nameSelector) { + changeName(nameSelector.target.value); + } + + function setOrder(orderSelector) { + changeOrder(orderSelector.target.value); + } + + function setPrice(priceSelector) { + changePrice(priceSelector.target.value); + } + + function updateItems() { + props.function(name, order, price, input); + } + + return ( + <> + + Voucher + + +
+ + {"search"} +
+
+
+ + + + Sort by: + + + + + + Order by: + + + + + + Price: + + + + + + + Apply + + + + ); +} diff --git a/components/Filter/Filter.styles.js b/components/Filter/Filter.styles.js new file mode 100644 index 0000000..e832d5f --- /dev/null +++ b/components/Filter/Filter.styles.js @@ -0,0 +1,44 @@ +import styled from "styled-components"; + +export const OuterWrapper = styled.div` + display: flex; + padding: 5px 80px; +` + +export const Wrapper = styled.div` + display: flex; + padding: 5px 80px; +` + +export const FilterWrapper = styled.div` + display: flex; + flex: 10; +` + +export const FilterSelector = styled.div` + margin-right: 40px; + padding: 10px 32px; + border-radius: 10px; + border: solid #c4c4c4 1px; +` + +export const LabelText = styled.h4` + margin: 0 30px; + align-self: center; +` + +export const ApplyButton = styled.button` + padding: 18px 32px; + background-color: #f3f3f3; + flex: 1; + border: solid #c4c4c4 1px; + border-radius: 10px; + &:hover { + box-shadow: 0 0 8px #777676; + } +` + +export const HorLine = styled.hr` + margin-top: 0; +` + diff --git a/components/Footer/Footer.js b/components/Footer/Footer.js new file mode 100644 index 0000000..5007c49 --- /dev/null +++ b/components/Footer/Footer.js @@ -0,0 +1,21 @@ +import { FooterWrapper, HorizontalLine, Links, StyledText, WrapperLogo } from "./Footer.styles"; +import {InstagramOutlined, YoutubeOutlined, FacebookOutlined} from '@ant-design/icons' +import Logo from "../../icons/logo.jpg"; + +export const Footer = () => { + return ( +
+ + + + + + + + + + + 2021 IoT @ Copyright all rights reserved +
+ ); +} diff --git a/components/Footer/Footer.styles.js b/components/Footer/Footer.styles.js new file mode 100644 index 0000000..063d321 --- /dev/null +++ b/components/Footer/Footer.styles.js @@ -0,0 +1,24 @@ +import styled from 'styled-components'; + +export const FooterWrapper = styled.div` + display: flex; + justify-content: space-around; +` +export const HorizontalLine = styled.hr` + margin-top: 110px; +` + +export const WrapperLogo = styled.img` + width: 80px; + height: 80px; + border-radius: 50px; +` + +export const Links = styled.ul` + display: flex; + list-style: none; + padding: 0; +` +export const StyledText = styled.p` + font-size: 12px; +` \ No newline at end of file diff --git a/components/Header/Header.js b/components/Header/Header.js new file mode 100644 index 0000000..a4c0191 --- /dev/null +++ b/components/Header/Header.js @@ -0,0 +1,27 @@ +import { StyleHeader, Wrapper, Nav, NavItem, Logo, Links, Search, NavWrapper } from "./Header.styles"; +import { + InstagramOutlined, + YoutubeOutlined, + FacebookOutlined, +} from "@ant-design/icons"; +import { Link } from "react-router-dom"; + +export const Header = () => { + return ( + + Voucher + + + + + + + + + + ); +}; diff --git a/components/Header/Header.styles.js b/components/Header/Header.styles.js new file mode 100644 index 0000000..b8b2b54 --- /dev/null +++ b/components/Header/Header.styles.js @@ -0,0 +1,58 @@ +import styled from 'styled-components'; + +export const StyleHeader = styled.div` + padding: 16px 20px 4px; + display: flex; + justify-content: space-around; + width: 100%; + font-size: 18px; +` + +export const Wrapper = styled.div` + display: flex; + justify-content: space-around; + align-items: center; + font-size: 18px; + background-color: #f3f0f0e7; + width: 100%; +` + + +export const Logo = styled.a` + color: black; + text-decoration: none; + padding-left: 50px; +` + +export const Nav = styled.ul` + display: flex; + list-style: none; + justify-content: center; + padding: 0; + text-align: center; + flex: 5; +` +export const NavItem = styled.li` + margin: 10px 30px; + text-align: center; +` + +export const Links = styled.ul` + display: flex; + list-style: none; + justify-content: center; + padding: 0; +` +export const NavWrapper = styled.div` + width: 200px; + flex: 1; +` + +export const Search = styled.input` + border-color: #c4c4c4; + border-style: solid; + border-radius: 10px; + color: #c4c4c4; + padding: 8px 8px; + margin: 0; +` diff --git a/components/Home/Home.js b/components/Home/Home.js new file mode 100644 index 0000000..95590af --- /dev/null +++ b/components/Home/Home.js @@ -0,0 +1,80 @@ +import { HeroWrapper, Image, ImageText, ItemLabel, ItemList, Label, TextWrapper, Button, InformWrapper, Inform, InformLabel, AllInform, InformText } from "./Home.styles"; +import Hero from "../../icons/hero.jpg"; +import GoldCreekPond from "../../icons/GoldCreekPond.jpg"; +import AppenzellDistrict from "../../icons/AppenzellDistrict.jpg"; +import Uttarakhand from "../../icons/Uttarakhand.jpg"; +import { VoucherItems } from "../VoucherItems/VoucherItems"; +import { useState } from "react"; +import { HomeItem } from "../HomeItem/HomeItem"; + +export const Home = () => { + const [viewMoreVouchers, toggle] = useState(false); + const [vouchers, showAllVouchers] = useState([ + { + name: "Gold Creek Pond", + image: GoldCreekPond, + + }, + { + name: "Appenzell District", + image: AppenzellDistrict, + }, + { + name: "Uttarakhand", + image: Uttarakhand, + } + ]); + + function changeVouchers() { + if (!viewMoreVouchers) { + showAllVouchers(vouchers.concat(vouchers)); + } else { + showAllVouchers(vouchers.slice(0, 3)); + } + } + + function toggleView() { + toggle(!viewMoreVouchers); + changeVouchers(); + } + + return ( +
+ + + + Your guide to adventures.
Come fly with us into a fantasy
+
+ tourists image +
+ + Propositions for you + + {vouchers.map(voucher => ())} + + + + + + About us + + + 500 + countries + + + 2000 + places + + + 5000 + feedbacks + + + + + Your trip + +
+ ); +} \ No newline at end of file diff --git a/components/Home/Home.styles.js b/components/Home/Home.styles.js new file mode 100644 index 0000000..80436aa --- /dev/null +++ b/components/Home/Home.styles.js @@ -0,0 +1,75 @@ +import styled from "styled-components"; + +export const HeroWrapper = styled.div` + display: flex; + justify-content: space-around; + align-items: center; + padding: 65px 100px; + margin-bottom: 100px; +` + +export const Image = styled.img` + width: 600px; + height: 600px; + border-radius: 25px; +` + +export const TextWrapper = styled.div` + padding: 60px; +` + +export const Label = styled.h2` + font-weight: 500; + font-size: 68px; +` + +export const ImageText = styled.p` + font-size: 24px; + padding-left: 30px; +` + +export const ItemLabel = styled.h3` + font-weight: 500; + font-size: 68px; + text-align: center; +` + +export const ItemList = styled.div` + display: flex; + justify-content: space-around; + flex-wrap: wrap; +` + +export const Button = styled.button` + margin-top: 30px; + padding: 18px 32px; + position: absolute; + left: 50%; + background-color: #f3f3f3; + border: none; + border-radius: 10px; + transform: translate(-50%, 0); + &:hover { + box-shadow: 0 0 8px #777676; + } +` +export const InformWrapper = styled.div` + margin-top: 200px; +` +export const AllInform = styled.div` + display: flex; + justify-content: space-around; + text-align: center; +` +export const Inform = styled.div` + display: flex; + flex-direction: column; +` + +export const InformLabel = styled.h3` + margin: 0; + font-size: 44px; +` +export const InformText = styled.a` + font-weight: 400; +` diff --git a/components/HomeItem/HomeItem.js b/components/HomeItem/HomeItem.js new file mode 100644 index 0000000..77a1b59 --- /dev/null +++ b/components/HomeItem/HomeItem.js @@ -0,0 +1,14 @@ +import { + Image, + Text, + Wrapper +} from "./HomeItem.styles"; + +export const HomeItem = ({name, image}) => { + return ( + + + {name} + + ); +} diff --git a/components/HomeItem/HomeItem.styles.js b/components/HomeItem/HomeItem.styles.js new file mode 100644 index 0000000..d02d643 --- /dev/null +++ b/components/HomeItem/HomeItem.styles.js @@ -0,0 +1,25 @@ +import styled from 'styled-components'; + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + background-color: #f3f3f3; + border-radius: 25px; + padding: 10px; + text-align: center; + margin: 10px; + &:hover { + box-shadow: 0 0 8px #777676; + } +` + +export const Image = styled.img` + width: 400px; + height: 300px; + border-radius: 15px; +` + +export const Text = styled.p` + font-size: 24px; + margin: 10px; +` \ No newline at end of file diff --git a/components/Item/Item.js b/components/Item/Item.js new file mode 100644 index 0000000..585cbc3 --- /dev/null +++ b/components/Item/Item.js @@ -0,0 +1,10 @@ +import { Image, Wrapper, Text } from "./Item.styles"; + +export const Item = (props) => { + return ( + + + {props.name} + + ); +} diff --git a/components/Item/Item.styles.js b/components/Item/Item.styles.js new file mode 100644 index 0000000..e8d89f8 --- /dev/null +++ b/components/Item/Item.styles.js @@ -0,0 +1,32 @@ +import styled from 'styled-components'; + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + background-color: #f3f3f3; + border-radius: 25px; + padding: 10px; + text-align: center; + &:hover { + box-shadow: 0 0 8px #777676; + } display: flex; + flex-direction: column; + background-color: #f3f3f3; + border-radius: 25px; + padding: 10px; + text-align: center; + &:hover { + box-shadow: 0 0 8px #777676; + } +` + +export const Image = styled.img` + width: 400px; + height: 300px; + border-radius: 15px; +` + +export const Text = styled.p` + font-size: 24px; + margin: 10px; +` \ No newline at end of file diff --git a/components/VoucherItems/VoucherItems.js b/components/VoucherItems/VoucherItems.js new file mode 100644 index 0000000..276ab2b --- /dev/null +++ b/components/VoucherItems/VoucherItems.js @@ -0,0 +1,33 @@ +import { Wrapper, Image, TextWrapper, Text } from "./VoucherItems.styles"; +import GoldCreekPond from "../../icons/GoldCreekPond.jpg"; +import AppenzellDistrict from "../../icons/AppenzellDistrict.jpg"; +import Uttarakhand from "../../icons/Uttarakhand.jpg"; +import { Label } from "../Home/Home.styles"; + +export const VoucherItems = () => { + return ( +
+ + + + + USA + + + + + + + Switzerland + + + + + + + India + + +
+ ); +} \ No newline at end of file diff --git a/components/VoucherItems/VoucherItems.styles.js b/components/VoucherItems/VoucherItems.styles.js new file mode 100644 index 0000000..ba28755 --- /dev/null +++ b/components/VoucherItems/VoucherItems.styles.js @@ -0,0 +1,41 @@ +import styled from 'styled-components'; + +export const Wrapper = styled.div` + display: flex; + justify-content: space-around; + align-items: center; + padding: 30px 200px; +` +export const Image = styled.img` + width: 600px; + height: 500px; + border-radius: 25px; +` + +export const TextWrapper = styled.div` + display: flex; + flex-direction: column; + padding: 40px 100px; +` +export const Label = styled.h3` + font-weight: 400; + font-size: 54px; + margin-bottom: 0; +` +export const Text = styled.h4` + font-weight: 300; + font-size: 26px; +` +export const Button = styled.button` + margin-top: 30px; + padding: 18px 32px; + position: absolute; + left: 50%; + background-color: #f3f3f3; + border: none; + border-radius: 10px; + transform: translate(-50%, 0); + &:hover { + box-shadow: 0 0 8px #777676; + } +` \ No newline at end of file diff --git a/icons/Algeria.jpg b/icons/Algeria.jpg new file mode 100644 index 0000000..45bd8f7 Binary files /dev/null and b/icons/Algeria.jpg differ diff --git a/icons/AppenzellDistrict.jpg b/icons/AppenzellDistrict.jpg new file mode 100644 index 0000000..4934e83 Binary files /dev/null and b/icons/AppenzellDistrict.jpg differ diff --git a/icons/GoldCreekPond.jpg b/icons/GoldCreekPond.jpg new file mode 100644 index 0000000..f80f574 Binary files /dev/null and b/icons/GoldCreekPond.jpg differ diff --git a/icons/Uttarakhand.jpg b/icons/Uttarakhand.jpg new file mode 100644 index 0000000..67ac96d Binary files /dev/null and b/icons/Uttarakhand.jpg differ diff --git a/icons/hero.jpg b/icons/hero.jpg new file mode 100644 index 0000000..f6abef0 Binary files /dev/null and b/icons/hero.jpg differ diff --git a/icons/logo.jpg b/icons/logo.jpg new file mode 100644 index 0000000..81abd24 Binary files /dev/null and b/icons/logo.jpg differ diff --git a/icons/search.png b/icons/search.png new file mode 100644 index 0000000..533de1b Binary files /dev/null and b/icons/search.png differ diff --git a/index.css b/index.css new file mode 100644 index 0000000..ec2585e --- /dev/null +++ b/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..ef2edf8 --- /dev/null +++ b/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import reportWebVitals from './reportWebVitals'; + +ReactDOM.render( + + + , + document.getElementById('root') +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/logo.svg b/logo.svg new file mode 100644 index 0000000..9dfc1c0 --- /dev/null +++ b/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/reportWebVitals.js b/reportWebVitals.js new file mode 100644 index 0000000..5253d3a --- /dev/null +++ b/reportWebVitals.js @@ -0,0 +1,13 @@ +const reportWebVitals = onPerfEntry => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/setupTests.js b/setupTests.js new file mode 100644 index 0000000..8f2609b --- /dev/null +++ b/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom';