├── .gitignore
├── Materials
├── Colours.pdf
└── wavey-design-by-piotr-medycki.jpg
├── README.md
├── code-along
├── .gitignore
├── .prettierrc
├── craco.config.js
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.js
│ ├── components
│ │ ├── Benefits
│ │ │ ├── Benefits.jsx
│ │ │ └── index.js
│ │ ├── BenefitsItem
│ │ │ ├── BenefitsItem.jsx
│ │ │ └── index.js
│ │ ├── BrandLink
│ │ │ ├── BrandLink.css
│ │ │ ├── BrandLink.jsx
│ │ │ └── index.js
│ │ ├── ButtonLink
│ │ │ ├── ButtonLink.jsx
│ │ │ └── index.js
│ │ ├── CartButton
│ │ │ ├── CartButton.css
│ │ │ ├── CartButton.jsx
│ │ │ └── index.js
│ │ ├── CategoriesBar
│ │ │ ├── CategoriesBar.jsx
│ │ │ └── index.js
│ │ ├── Dropdown
│ │ │ ├── Dropdown.jsx
│ │ │ └── index.js
│ │ ├── FactNumber
│ │ │ ├── FactNumber.jsx
│ │ │ └── index.js
│ │ ├── Footer
│ │ │ ├── Footer.css
│ │ │ ├── Footer.jsx
│ │ │ └── index.js
│ │ ├── HeaderBanner
│ │ │ ├── HeaderBanner.jsx
│ │ │ ├── banner.png
│ │ │ └── index.js
│ │ ├── Icons
│ │ │ ├── CartIcon.jsx
│ │ │ ├── FacebookIcon.jsx
│ │ │ ├── HeartIcon.jsx
│ │ │ ├── InstagramIcon.jsx
│ │ │ └── TwitterIcon.jsx
│ │ ├── Loader
│ │ │ ├── Loader.jsx
│ │ │ └── index.js
│ │ ├── MainBanner
│ │ │ ├── MainBanner.css
│ │ │ ├── MainBanner.jsx
│ │ │ ├── banner.png
│ │ │ └── index.js
│ │ ├── NavLinks
│ │ │ ├── NavLinks.css
│ │ │ ├── NavLinks.jsx
│ │ │ └── index.js
│ │ ├── Navbar
│ │ │ ├── Navbar.css
│ │ │ ├── Navbar.jsx
│ │ │ └── index.js
│ │ ├── Price
│ │ │ ├── Price.jsx
│ │ │ └── index.js
│ │ ├── PriceRange
│ │ │ ├── PriceRange.jsx
│ │ │ └── index.js
│ │ ├── Product
│ │ │ ├── Description.jsx
│ │ │ ├── Image.jsx
│ │ │ ├── Info.jsx
│ │ │ ├── Product.jsx
│ │ │ ├── Title.jsx
│ │ │ └── index.js
│ │ ├── ProductCard
│ │ │ ├── ProductCard.jsx
│ │ │ └── index.js
│ │ ├── Products
│ │ │ ├── Products.jsx
│ │ │ └── index.js
│ │ ├── RecommendedProducts
│ │ │ ├── RecommendedProducts.jsx
│ │ │ └── index.js
│ │ ├── Search
│ │ │ ├── Search.jsx
│ │ │ └── index.js
│ │ ├── SecondaryBanner
│ │ │ ├── SecondaryBanner.css
│ │ │ ├── SecondaryBanner.jsx
│ │ │ ├── banner.png
│ │ │ └── index.js
│ │ ├── SectionTitle
│ │ │ ├── SectionTitle.jsx
│ │ │ └── index.js
│ │ ├── SideBar
│ │ │ ├── SideBar.jsx
│ │ │ └── index.js
│ │ ├── TopProducts
│ │ │ ├── TopProducts.jsx
│ │ │ └── index.js
│ │ └── UniqueSellingPoint
│ │ │ ├── UniqueSellingPoint.jsx
│ │ │ ├── img.jpg
│ │ │ └── index.js
│ ├── constants
│ │ ├── brands.js
│ │ ├── categories.js
│ │ ├── sorting.js
│ │ └── tags.js
│ ├── data
│ │ └── footerLinks.json
│ ├── hooks
│ │ ├── useGetProduct.js
│ │ ├── useGetRecommendedProducts.js
│ │ ├── useGetTopProducts.js
│ │ └── useSearch.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── pages
│ │ ├── About.jsx
│ │ ├── Home.jsx
│ │ ├── Product.jsx
│ │ └── Search.jsx
│ ├── reportWebVitals.js
│ ├── setupTests.js
│ └── state
│ │ ├── actionCreators.js
│ │ ├── actionTypes.js
│ │ ├── search-context.js
│ │ ├── search-reducer.js
│ │ └── search-state.js
└── tailwind.config.js
└── starter-files
├── .DS_Store
├── package.json
├── public
├── favicon.ico
├── index.html
├── manifest.json
└── robots.txt
└── src
├── .DS_Store
├── App.js
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
└── setupTests.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Materials/Colours.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/Materials/Colours.pdf
--------------------------------------------------------------------------------
/Materials/wavey-design-by-piotr-medycki.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/Materials/wavey-design-by-piotr-medycki.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## REACT MAKEUP APP
2 |
3 | This is a code along project where we code live on streams an Makeup Website using the following:
4 |
5 | - ReactJS (+ Context API, hooks and routing)
6 | - TailwindCSS
7 | - Axios
8 | - Makeup API (REST)
9 | - and probably some more things
10 |
11 | You can find the code-along streams on my [YouTube channel](https://www.youtube.com/c/danascript/).
12 |
13 | Here is a list of recommended reading/watching material to help you kickstart this project with a bit of knowledge:
14 |
15 | 1. [Tailwind CSS 101](https://blog.tailwindcss.com/tailwindcss-from-zero-to-production)
16 | 2. [Flux: explanation about Store, Dispatcher, View and Action](https://www.freecodecamp.org/news/an-introduction-to-the-flux-architectural-pattern-674ea74775c9/)
17 |
18 |
19 | If some of the things are too complex, then don't worry! We're here to learn and grow together, feel free to ask question during the stream to help yourself and everyone else too! :heart:
--------------------------------------------------------------------------------
/code-along/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/code-along/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "bracketSpacing": true,
4 | "printWidth": 120,
5 | "semi": true,
6 | "singleQuote": true,
7 | "tabWidth": 4,
8 | "trailingComma": "es5",
9 | "useTabs": false
10 | }
--------------------------------------------------------------------------------
/code-along/craco.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | style: {
3 | postcss: {
4 | plugins: [require('tailwindcss'), require('autoprefixer')],
5 | },
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/code-along/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-makeup-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "craco start",
7 | "build": "craco build",
8 | "test": "craco test",
9 | "eject": "react-scripts eject"
10 | },
11 | "dependencies": {
12 | "@craco/craco": "^6.2.0",
13 | "@testing-library/jest-dom": "^5.11.4",
14 | "@testing-library/react": "^11.1.0",
15 | "@testing-library/user-event": "^12.1.10",
16 | "axios": "^0.21.1",
17 | "prop-types": "^15.7.2",
18 | "react": "^17.0.2",
19 | "react-dom": "^17.0.2",
20 | "react-router-dom": "^5.2.0",
21 | "react-scripts": "4.0.3",
22 | "web-vitals": "^1.0.1"
23 | },
24 | "devDependencies": {
25 | "autoprefixer": "^9.8.6",
26 | "postcss": "^7.0.35",
27 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.7"
28 | },
29 | "eslintConfig": {
30 | "extends": [
31 | "react-app",
32 | "react-app/jest"
33 | ]
34 | },
35 | "browserslist": {
36 | "production": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ],
41 | "development": [
42 | "last 1 chrome version",
43 | "last 1 firefox version",
44 | "last 1 safari version"
45 | ]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/code-along/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/code-along/public/favicon.ico
--------------------------------------------------------------------------------
/code-along/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 |
28 |
29 |
30 |
31 | React App
32 |
33 |
34 | You need to enable JavaScript to run this app.
35 |
36 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/code-along/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
--------------------------------------------------------------------------------
/code-along/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/code-along/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
3 |
4 | import Navbar from './components/Navbar';
5 | import Footer from './components/Footer';
6 |
7 | import Home from './pages/Home';
8 | import About from './pages/About';
9 | import Search from './pages/Search';
10 | import Product from './pages/Product';
11 |
12 | export default function App() {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/code-along/src/components/Benefits/Benefits.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import BenefitsItem from '../BenefitsItem';
4 |
5 | const benefits = [
6 | {
7 | title: 'Delivery',
8 | text: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Tempora repellendus excepturi',
9 | },
10 | {
11 | title: 'Products',
12 | text: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit',
13 | },
14 | {
15 | title: 'Payments',
16 | text: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Tempora repellendus excepturi, consequatur sed inventore ut',
17 | },
18 | ];
19 |
20 | const Benefits = () => (
21 |
22 | {benefits.map(item => (
23 |
24 | ))}
25 |
26 | );
27 |
28 | export default Benefits;
29 |
--------------------------------------------------------------------------------
/code-along/src/components/Benefits/index.js:
--------------------------------------------------------------------------------
1 | import Benefits from './Benefits';
2 |
3 | export default Benefits;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/BenefitsItem/BenefitsItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const BenefitsItem = ({ item }) => (
4 |
5 |
6 |
7 |
8 | {item.title === 'Products' && (
9 |
16 |
22 |
23 | )}
24 |
25 | {item.title === 'Payments' && (
26 |
33 |
39 |
40 | )}
41 |
42 | {item.title === 'Delivery' && (
43 |
50 |
56 |
57 | )}
58 |
59 |
60 |
{item.title}
61 |
{item.text}
62 |
63 |
64 | );
65 |
66 | export default BenefitsItem;
67 |
--------------------------------------------------------------------------------
/code-along/src/components/BenefitsItem/index.js:
--------------------------------------------------------------------------------
1 | import BenefitsItem from './BenefitsItem';
2 |
3 | export default BenefitsItem;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/BrandLink/BrandLink.css:
--------------------------------------------------------------------------------
1 | /* your css code */
--------------------------------------------------------------------------------
/code-along/src/components/BrandLink/BrandLink.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const BrandLink = ({ classes }) => (
5 |
6 | Welly
7 |
8 | );
9 |
10 | export default BrandLink;
11 |
--------------------------------------------------------------------------------
/code-along/src/components/BrandLink/index.js:
--------------------------------------------------------------------------------
1 | import BrandLink from './BrandLink';
2 |
3 | export default BrandLink;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/ButtonLink/ButtonLink.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const ButtonLink = ({ path, text, isMain, className }) => (
5 |
11 | {text}
12 |
13 | );
14 |
15 | export default ButtonLink;
16 |
--------------------------------------------------------------------------------
/code-along/src/components/ButtonLink/index.js:
--------------------------------------------------------------------------------
1 | import ButtonLink from './ButtonLink';
2 |
3 | export default ButtonLink;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/CartButton/CartButton.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/code-along/src/components/CartButton/CartButton.css
--------------------------------------------------------------------------------
/code-along/src/components/CartButton/CartButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const CartButton = () => {
4 | return (
5 |
6 | add to basket
7 |
8 | );
9 | };
10 |
11 | export default CartButton;
12 |
--------------------------------------------------------------------------------
/code-along/src/components/CartButton/index.js:
--------------------------------------------------------------------------------
1 | import CartButton from './CartButton';
2 |
3 | export default CartButton;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/CategoriesBar/CategoriesBar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import { CATEGORIES } from '../../constants/categories';
4 |
5 | const CategoriesBar = ({ onSelect }) => {
6 | const [activeCategory, setActiveCategory] = useState(null);
7 |
8 | const setCategory = e => {
9 | const value = e.target.innerText;
10 |
11 | onSelect(value);
12 | setActiveCategory(value);
13 | };
14 |
15 | return (
16 | <>
17 | {CATEGORIES.map(cat => (
18 |
19 |
{cat.name}
20 |
21 | {cat.subCategories.map(subCat => (
22 |
29 | {subCat}
30 |
31 | ))}
32 |
33 |
34 | ))}
35 | >
36 | );
37 | };
38 |
39 | export default CategoriesBar;
40 |
--------------------------------------------------------------------------------
/code-along/src/components/CategoriesBar/index.js:
--------------------------------------------------------------------------------
1 | import CategoriesBar from './CategoriesBar';
2 |
3 | export default CategoriesBar;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/Dropdown/Dropdown.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Dropdown = ({ type, values, onChange }) => {
4 | return (
5 |
6 |
7 | {type}
8 |
9 |
15 | {values.map((value, index) => (
16 | {value}
17 | ))}
18 |
19 |
20 | );
21 | };
22 |
23 | export default Dropdown;
24 |
--------------------------------------------------------------------------------
/code-along/src/components/Dropdown/index.js:
--------------------------------------------------------------------------------
1 | import Dropdown from './Dropdown';
2 |
3 | export default Dropdown;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/FactNumber/FactNumber.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Icon = ({ icon }) => {
4 | if (icon === 'people') {
5 | return (
6 |
13 |
19 |
20 | );
21 | }
22 |
23 | if (icon === 'revenue') {
24 | return (
25 |
32 |
38 |
39 | );
40 | }
41 |
42 | if (icon === 'shop') {
43 | return (
44 |
51 |
57 |
58 | );
59 | }
60 | };
61 |
62 | const FactNumber = ({ icon, number, title, description }) => {
63 | return (
64 |
65 |
66 |
67 |
{number}
68 |
{title}
69 |
{description}
70 |
71 |
72 | );
73 | };
74 |
75 | export default FactNumber;
76 |
--------------------------------------------------------------------------------
/code-along/src/components/FactNumber/index.js:
--------------------------------------------------------------------------------
1 | import FactNumber from './FactNumber';
2 |
3 | export default FactNumber;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/Footer/Footer.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/code-along/src/components/Footer/Footer.css
--------------------------------------------------------------------------------
/code-along/src/components/Footer/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import links from '../../data/footerLinks.json';
5 |
6 | import BrandLink from '../BrandLink';
7 | import FacebookIcon from '../Icons/FacebookIcon';
8 | import TwitterIcon from '../Icons/TwitterIcon';
9 | import InstagramIcon from '../Icons/InstagramIcon';
10 |
11 | import './Footer.css';
12 |
13 | const Footer = () => (
14 |
54 | );
55 |
56 | export default Footer;
57 |
--------------------------------------------------------------------------------
/code-along/src/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import Footer from './Footer';
2 |
3 | export default Footer;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/HeaderBanner/HeaderBanner.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import bannerImg from './banner.png';
4 |
5 | const HeaderBanner = () => {
6 | return (
7 |
8 |
9 |
10 |
11 | Be natural, be smart, be you
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default HeaderBanner;
28 |
--------------------------------------------------------------------------------
/code-along/src/components/HeaderBanner/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/code-along/src/components/HeaderBanner/banner.png
--------------------------------------------------------------------------------
/code-along/src/components/HeaderBanner/index.js:
--------------------------------------------------------------------------------
1 | import HeaderBanner from './HeaderBanner';
2 |
3 | export default HeaderBanner;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/Icons/CartIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const CartIcon = () => (
4 |
5 |
11 |
12 | );
13 |
14 | export default CartIcon;
15 |
--------------------------------------------------------------------------------
/code-along/src/components/Icons/FacebookIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const FacebookIcon = () => (
4 |
5 |
9 |
10 | );
11 |
12 | export default FacebookIcon;
13 |
--------------------------------------------------------------------------------
/code-along/src/components/Icons/HeartIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const HeartIcon = () => (
4 |
11 |
17 |
18 | );
19 |
20 | export default HeartIcon;
21 |
--------------------------------------------------------------------------------
/code-along/src/components/Icons/InstagramIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const InstagramIcon = () => (
4 |
5 |
9 |
10 | );
11 |
12 | export default InstagramIcon;
13 |
--------------------------------------------------------------------------------
/code-along/src/components/Icons/TwitterIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const TwitterIcon = () => (
4 |
5 |
9 |
10 | );
11 |
12 | export default TwitterIcon;
13 |
--------------------------------------------------------------------------------
/code-along/src/components/Loader/Loader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Loader = ({ classes }) => (
4 |
5 |
6 |
19 |
26 |
31 |
40 |
41 |
42 |
43 |
44 | );
45 |
46 | export default Loader;
47 |
--------------------------------------------------------------------------------
/code-along/src/components/Loader/index.js:
--------------------------------------------------------------------------------
1 | import Loader from './Loader';
2 |
3 | export default Loader;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/MainBanner/MainBanner.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/code-along/src/components/MainBanner/MainBanner.css
--------------------------------------------------------------------------------
/code-along/src/components/MainBanner/MainBanner.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import ButtonLink from '../ButtonLink';
4 |
5 | import bannerImg from './banner.png';
6 |
7 | const MainBanner = () => (
8 |
9 |
10 |
The Makeup e-commerce project
11 |
12 | This project is built with data from a MakeUp API using Axios, Tailwind CSS, ReactJS, PropTypes, React
13 | Context API and React Router.
14 |
15 |
16 | Webtwo ipsum divvyshot. disqus elgg klout. Jumo wufoo hulu spotify trulia, twitter nuvvo. Omgpop tumblr
17 | odeo mog palantir squidoo balihoo nuvvo, etsy yuntaa elgg reddit kiko oovoo. Kno revver oovoo, blyve.
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 |
27 | export default MainBanner;
28 |
--------------------------------------------------------------------------------
/code-along/src/components/MainBanner/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/code-along/src/components/MainBanner/banner.png
--------------------------------------------------------------------------------
/code-along/src/components/MainBanner/index.js:
--------------------------------------------------------------------------------
1 | import MainBanner from './MainBanner';
2 |
3 | export default MainBanner;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/NavLinks/NavLinks.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/code-along/src/components/NavLinks/NavLinks.css
--------------------------------------------------------------------------------
/code-along/src/components/NavLinks/NavLinks.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from 'react-router-dom';
3 |
4 | const links = [
5 | {
6 | path: '/',
7 | name: 'Home',
8 | },
9 | {
10 | path: '/search',
11 | name: 'Search',
12 | },
13 | {
14 | path: '/about',
15 | name: 'About',
16 | },
17 | ];
18 |
19 | const NavLinks = () => {
20 | return (
21 | <>
22 | {links.map((link, index) => (
23 |
24 | {link.name}
25 |
26 | ))}
27 | >
28 | );
29 | };
30 |
31 | export default NavLinks;
32 |
--------------------------------------------------------------------------------
/code-along/src/components/NavLinks/index.js:
--------------------------------------------------------------------------------
1 | import NavLinks from './NavLinks';
2 |
3 | export default NavLinks;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/Navbar/Navbar.css:
--------------------------------------------------------------------------------
1 | .navbar__brand {
2 | font-family: 'Krona One', sans-serif;
3 | @apply p-4;
4 | }
5 |
--------------------------------------------------------------------------------
/code-along/src/components/Navbar/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import BrandLink from '../BrandLink';
4 | import CartIcon from '../Icons/CartIcon';
5 | import HeartIcon from '../Icons/HeartIcon';
6 | import NavLinks from '../NavLinks';
7 |
8 | import './Navbar.css';
9 |
10 | const Navbar = () => (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 |
25 | export default Navbar;
26 |
--------------------------------------------------------------------------------
/code-along/src/components/Navbar/index.js:
--------------------------------------------------------------------------------
1 | import Navbar from './Navbar';
2 |
3 | export default Navbar;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/Price/Price.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Price = ({ price, isLarge }) => (
5 |
9 | );
10 |
11 | export default Price;
12 |
13 | Price.propTypes = {
14 | price: PropTypes.string,
15 | isLarge: PropTypes.bool,
16 | };
17 |
18 | Price.defaultProps = {
19 | price: '0.0',
20 | isLarge: false,
21 | };
22 |
--------------------------------------------------------------------------------
/code-along/src/components/Price/index.js:
--------------------------------------------------------------------------------
1 | import Price from './Price';
2 |
3 | export default Price;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/PriceRange/PriceRange.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const PriceRange = ({ onChange }) => {
4 | let currentTimeout;
5 |
6 | const onChangeHandler = e => {
7 | clearTimeout(currentTimeout);
8 |
9 | currentTimeout = setTimeout(() => {
10 | onChange(e);
11 | }, 1500);
12 | };
13 |
14 | return (
15 |
16 |
select price
17 |
18 |
19 |
27 |
28 | min
29 |
30 |
31 |
32 |
33 |
41 |
42 | max
43 |
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default PriceRange;
51 |
--------------------------------------------------------------------------------
/code-along/src/components/PriceRange/index.js:
--------------------------------------------------------------------------------
1 | import PriceRange from './PriceRange';
2 |
3 | export default PriceRange;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/Product/Description.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Description = ({ text }) => {text}
;
5 |
6 | export default Description;
7 |
8 | Description.propTypes = {
9 | text: PropTypes.string.isRequired,
10 | };
11 |
--------------------------------------------------------------------------------
/code-along/src/components/Product/Image.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Image = ({ name, img }) => (
5 |
6 |
7 |
8 | );
9 |
10 | export default Image;
11 |
12 | Image.propTypes = {
13 | name: PropTypes.string.isRequired,
14 | img: PropTypes.string.isRequired,
15 | };
16 |
--------------------------------------------------------------------------------
/code-along/src/components/Product/Info.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Info = ({ category, name, type }) => (
5 | <>
6 | {type}
7 | {name}
8 | {category}
9 | >
10 | );
11 |
12 | export default Info;
13 |
14 | Info.propTypes = {
15 | name: PropTypes.string.isRequired,
16 | type: PropTypes.string.isRequired,
17 | category: PropTypes.string.isRequired,
18 | };
19 |
--------------------------------------------------------------------------------
/code-along/src/components/Product/Product.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import CartButton from '../CartButton';
5 | import Price from '../Price';
6 | import Image from './Image';
7 | import Info from './Info';
8 | import Description from './Description';
9 | import Title from './Title';
10 |
11 | const Product = ({ name, img, type, category, price, description }) => (
12 | <>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
28 | >
29 | );
30 |
31 | export default Product;
32 |
33 | Product.propTypes = {
34 | name: PropTypes.string.isRequired,
35 | img: PropTypes.string.isRequired,
36 | type: PropTypes.string.isRequired,
37 | category: PropTypes.string.isRequired,
38 | price: PropTypes.string.isRequired,
39 | description: PropTypes.string.isRequired,
40 | };
41 |
--------------------------------------------------------------------------------
/code-along/src/components/Product/Title.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Title = ({ name, type }) => (
5 |
6 |
{name}
7 |
{type}
8 |
9 | );
10 |
11 | export default Title;
12 |
13 | Title.propTypes = {
14 | name: PropTypes.string.isRequired,
15 | type: PropTypes.string.isRequired,
16 | };
17 |
--------------------------------------------------------------------------------
/code-along/src/components/Product/index.js:
--------------------------------------------------------------------------------
1 | import Product from './Product';
2 |
3 | export default Product;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/ProductCard/ProductCard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import Price from '../Price';
5 |
6 | const ProductCard = ({ id, name, brand, imgUrl, price }) => (
7 |
11 |
12 |
13 |
14 |
15 |
16 |
{name.replace(/^(.{12}[^\s]*).*/, '$1')}
17 |
18 |
{brand}
19 |
20 |
21 |
22 |
23 | );
24 |
25 | export default ProductCard;
26 |
--------------------------------------------------------------------------------
/code-along/src/components/ProductCard/index.js:
--------------------------------------------------------------------------------
1 | import ProductCard from './ProductCard';
2 |
3 | export default ProductCard;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/Products/Products.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSearchState } from '../../state/search-context';
3 |
4 | import ProductCard from '../ProductCard';
5 |
6 | const Products = () => {
7 | const [{ products }] = useSearchState();
8 |
9 | return (
10 |
11 | {products.length === 0 ? (
12 |
13 |
Sorry, no products
14 |
Please try changing your filters
15 |
16 | ) : (
17 | <>
18 | {products.map(product => (
19 |
27 | ))}
28 | >
29 | )}
30 |
31 | );
32 | };
33 |
34 | export default Products;
35 |
--------------------------------------------------------------------------------
/code-along/src/components/Products/index.js:
--------------------------------------------------------------------------------
1 | import Products from './Products';
2 |
3 | export default Products;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/RecommendedProducts/RecommendedProducts.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import useGetRecommendedProducts from '../../hooks/useGetRecommendedProducts';
4 | import Loader from '../Loader/Loader';
5 |
6 | import ProductCard from '../ProductCard';
7 | import SectionTitle from '../SectionTitle';
8 |
9 | const RecommendedProducts = ({ product }) => {
10 | const products = useGetRecommendedProducts(product);
11 |
12 | return (
13 | <>
14 |
15 | {products.length === 0 ? (
16 |
17 | ) : (
18 |
19 | {products.map(item => (
20 |
28 | ))}
29 |
30 | )}
31 | >
32 | );
33 | };
34 |
35 | export default RecommendedProducts;
36 |
--------------------------------------------------------------------------------
/code-along/src/components/RecommendedProducts/index.js:
--------------------------------------------------------------------------------
1 | import RecommendedProducts from './RecommendedProducts';
2 |
3 | export default RecommendedProducts;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/Search/Search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import useSearch from '../../hooks/useSearch';
4 |
5 | import Loader from '../Loader';
6 | import Products from '../Products';
7 | import SideBar from '../SideBar';
8 |
9 | const Search = () => {
10 | const { isLoading } = useSearch();
11 |
12 | return (
13 |
19 | );
20 | };
21 |
22 | export default Search;
23 |
--------------------------------------------------------------------------------
/code-along/src/components/Search/index.js:
--------------------------------------------------------------------------------
1 | import Search from './Search';
2 |
3 | export default Search;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/SecondaryBanner/SecondaryBanner.css:
--------------------------------------------------------------------------------
1 | .banner__secondary {
2 | position: relative;
3 | bottom: -100px;
4 | }
5 |
--------------------------------------------------------------------------------
/code-along/src/components/SecondaryBanner/SecondaryBanner.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import ButtonLink from '../ButtonLink';
4 |
5 | import bannerImg from './banner.png';
6 |
7 | import './SecondaryBanner.css';
8 |
9 | const SecondaryBanner = () => {
10 | return (
11 |
12 |
13 |
14 | Be natural, be smart, be you
15 |
16 |
17 |
18 |
19 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default SecondaryBanner;
31 |
--------------------------------------------------------------------------------
/code-along/src/components/SecondaryBanner/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/code-along/src/components/SecondaryBanner/banner.png
--------------------------------------------------------------------------------
/code-along/src/components/SecondaryBanner/index.js:
--------------------------------------------------------------------------------
1 | import SecondaryBanner from './SecondaryBanner';
2 |
3 | export default SecondaryBanner;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/SectionTitle/SectionTitle.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const SectionTitle = ({ text }) => {text} ;
4 |
5 | export default SectionTitle;
6 |
--------------------------------------------------------------------------------
/code-along/src/components/SectionTitle/index.js:
--------------------------------------------------------------------------------
1 | import SectionTitle from './SectionTitle';
2 |
3 | export default SectionTitle;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/SideBar/SideBar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { BRANDS } from '../../constants/brands';
4 | import { setFilter } from '../../state/actionCreators';
5 | import { useSearchState } from '../../state/search-context';
6 |
7 | import Dropdown from '../Dropdown';
8 | import CategoriesBar from '../CategoriesBar';
9 | import PriceRange from '../PriceRange';
10 |
11 | const SideBar = () => {
12 | const [, dispatch] = useSearchState();
13 |
14 | const handleOnChange = e => {
15 | const { name, value } = e.target;
16 | dispatch(setFilter(name, value));
17 | };
18 |
19 | const handleOnSelect = value => {
20 | dispatch(setFilter('productType', value));
21 | };
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default SideBar;
37 |
--------------------------------------------------------------------------------
/code-along/src/components/SideBar/index.js:
--------------------------------------------------------------------------------
1 | import SideBar from './SideBar';
2 |
3 | export default SideBar;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/TopProducts/TopProducts.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import SecondaryBanner from '../SecondaryBanner';
4 | import ButtonLink from '../ButtonLink';
5 | import SectionTitle from '../SectionTitle';
6 | import useGetTopProducts from '../../hooks/useGetTopProducts';
7 | import ProductCard from '../ProductCard';
8 | import Loader from '../Loader';
9 |
10 | const TopProducts = () => {
11 | const products = useGetTopProducts();
12 |
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 | {/* Products */}
21 |
22 | {products.length === 0 ? (
23 |
24 | ) : (
25 | <>
26 | {products.map(product => (
27 |
35 | ))}
36 | >
37 | )}
38 |
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | export default TopProducts;
46 |
--------------------------------------------------------------------------------
/code-along/src/components/TopProducts/index.js:
--------------------------------------------------------------------------------
1 | import TopProducts from './TopProducts';
2 |
3 | export default TopProducts;
4 |
--------------------------------------------------------------------------------
/code-along/src/components/UniqueSellingPoint/UniqueSellingPoint.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import bannerImg from './img.jpg';
4 |
5 | const UniqueSellingPoint = () => (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
Clean code that you deserve
13 |
14 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Dicta expedita saepe quae mollitia? Quam
15 | odio accusamus facere!
16 |
17 |
18 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Dicta expedita saepe quae mollitia? Quam
19 | odio accusamus facere deleniti nostrum.
20 |
21 |
22 |
23 |
24 | );
25 |
26 | export default UniqueSellingPoint;
27 |
--------------------------------------------------------------------------------
/code-along/src/components/UniqueSellingPoint/img.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/code-along/src/components/UniqueSellingPoint/img.jpg
--------------------------------------------------------------------------------
/code-along/src/components/UniqueSellingPoint/index.js:
--------------------------------------------------------------------------------
1 | import UniqueSellingPoint from './UniqueSellingPoint';
2 |
3 | export default UniqueSellingPoint;
4 |
--------------------------------------------------------------------------------
/code-along/src/constants/brands.js:
--------------------------------------------------------------------------------
1 | export const BRANDS = [
2 | 'almay',
3 | 'alva',
4 | 'anna sui',
5 | 'annabelle',
6 | 'benefit',
7 | 'boosh',
8 | "burt's bees",
9 | 'butter london',
10 | "c'est moi",
11 | 'cargo cosmetics',
12 | 'china glaze',
13 | 'clinique',
14 | 'coastal classic creation',
15 | 'colourpop',
16 | 'covergirl',
17 | 'dalish',
18 | 'deciem',
19 | 'dior',
20 | 'dr. hauschka',
21 | 'e.l.f.',
22 | 'essie',
23 | 'fenty',
24 | 'glossier',
25 | 'green people',
26 | 'iman',
27 | "l'oreal",
28 | 'lotus cosmetics usa',
29 | "maia's mineral galaxy",
30 | 'marcelle',
31 | 'marienatie',
32 | 'maybelline',
33 | 'milani',
34 | 'mineral fusion',
35 | 'misa',
36 | 'mistura',
37 | 'moov',
38 | 'nudus',
39 | 'nyx',
40 | 'orly',
41 | 'pacifica',
42 | 'penny lane organics',
43 | 'physicians formula',
44 | 'piggy paint',
45 | 'pure anada',
46 | 'rejuva minerals',
47 | 'revlon',
48 | "sally b's skin yummies",
49 | 'salon perfect',
50 | 'sante',
51 | 'sinful colours',
52 | 'smashbox',
53 | 'stila',
54 | 'suncoat',
55 | 'w3llpeople',
56 | 'wet n wild',
57 | 'zorah',
58 | 'zorah biocosmetiques',
59 | ];
60 |
--------------------------------------------------------------------------------
/code-along/src/constants/categories.js:
--------------------------------------------------------------------------------
1 | export const CATEGORIES = [
2 | {
3 | name: 'skin',
4 | subCategories: ['foundation', 'blush', 'bronzer'],
5 | },
6 | {
7 | name: 'eyes',
8 | subCategories: ['eyebrow', 'eyeliner', 'eyeshadow', 'mascara'],
9 | },
10 | {
11 | name: 'lips',
12 | subCategories: ['lipstick', 'lip liner'],
13 | },
14 | {
15 | name: 'nails',
16 | subCategories: ['nail polish'],
17 | },
18 | ];
19 |
--------------------------------------------------------------------------------
/code-along/src/constants/sorting.js:
--------------------------------------------------------------------------------
1 | export const SORTING = ['price - ascending', 'price - descending', 'rating - ascending', 'rating - descending'];
2 |
--------------------------------------------------------------------------------
/code-along/src/constants/tags.js:
--------------------------------------------------------------------------------
1 | export const PRODUCT_TAGS = [
2 | 'canadian',
3 | 'certclean',
4 | 'chemical free',
5 | 'dairy free',
6 | 'ewg verified',
7 | 'ecocert',
8 | 'fair trade',
9 | 'gluten free',
10 | 'hypoallergenic',
11 | 'natural',
12 | 'no talc',
13 | 'non-gmo',
14 | 'organic',
15 | 'peanut free product',
16 | 'sugar free',
17 | 'usda organic',
18 | 'vegan',
19 | 'alcohol free',
20 | 'cruelty free',
21 | 'oil free',
22 | 'purpicks',
23 | 'silicone free',
24 | 'water free',
25 | ];
26 |
--------------------------------------------------------------------------------
/code-along/src/data/footerLinks.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "title": "about",
4 | "links": [
5 | {
6 | "name": "About Welly",
7 | "path": "/about"
8 | },
9 | {
10 | "name": "Careers",
11 | "path": "/"
12 | },
13 | {
14 | "name": "Social Media",
15 | "path": "/"
16 | },
17 | {
18 | "name": "Affiliates",
19 | "path": "/"
20 | },
21 | {
22 | "name": "Supply Chain",
23 | "path": "/"
24 | },
25 | {
26 | "name": "Sitemap",
27 | "path": "/"
28 | },
29 | {
30 | "name": "Global Sites",
31 | "path": "/"
32 | }
33 | ]
34 | },
35 | {
36 | "title": "skincare",
37 | "links": [
38 | {
39 | "name": "Makeup",
40 | "path": "/"
41 | },
42 | {
43 | "name": "Skincare",
44 | "path": "/"
45 | },
46 | {
47 | "name": "Fragrance",
48 | "path": "/"
49 | }
50 | ]
51 | },
52 | {
53 | "title": "makeup",
54 | "links": [
55 | {
56 | "name": "Base",
57 | "path": "/"
58 | },
59 | {
60 | "name": "Contour",
61 | "path": "/"
62 | },
63 | {
64 | "name": "Eye makeup",
65 | "path": "/"
66 | },
67 | {
68 | "name": "Lips",
69 | "path": "/"
70 | }
71 | ]
72 | },
73 | {
74 | "title": "supplements",
75 | "links": [
76 | {
77 | "name": "Hair and nails",
78 | "path": "/"
79 | },
80 | {
81 | "name": "Vitamins",
82 | "path": "/"
83 | },
84 | {
85 | "name": "Nutrients",
86 | "path": "/"
87 | }
88 | ]
89 | },
90 | {
91 | "title": "luxury",
92 | "links": [
93 | {
94 | "name": "Makeup",
95 | "path": "/"
96 | },
97 | {
98 | "name": "Skincare",
99 | "path": "/"
100 | },
101 | {
102 | "name": "Fragrance",
103 | "path": "/"
104 | }
105 | ]
106 | }
107 | ]
--------------------------------------------------------------------------------
/code-along/src/hooks/useGetProduct.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from 'react';
2 | import { useParams } from 'react-router-dom';
3 | import axios from 'axios';
4 |
5 | const BASE_URL = 'http://makeup-api.herokuapp.com/api/v1/products';
6 |
7 | const formatProduct = data => ({
8 | ...data,
9 | category: data.category?.replace(/_/g, ' '),
10 | product_type: data.product_type?.replace(/_/g, ' '),
11 | description: data.description?.replace(/<\/?[^>]+(>|$)/g, ''),
12 | api_featured_image: `https://${data.api_featured_image}`,
13 | });
14 |
15 | const useGetProduct = () => {
16 | const { id } = useParams();
17 | const [singleProduct, setSingleProduct] = useState(null);
18 | const [isLoading, setIsLoading] = useState(false);
19 |
20 | const getSingleProduct = useCallback(() => {
21 | setIsLoading(true);
22 | axios.get(`${BASE_URL}/${id}.json`).then(response => {
23 | const data = formatProduct(response.data);
24 | setSingleProduct(data);
25 | });
26 | }, [id]);
27 |
28 | useEffect(() => {
29 | getSingleProduct();
30 | }, [id, getSingleProduct]);
31 |
32 | useEffect(() => {
33 | setIsLoading(false);
34 | }, [singleProduct]);
35 |
36 | return {
37 | isLoading,
38 | singleProduct,
39 | };
40 | };
41 |
42 | export default useGetProduct;
43 |
--------------------------------------------------------------------------------
/code-along/src/hooks/useGetRecommendedProducts.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, useCallback } from 'react';
2 | import axios from 'axios';
3 |
4 | const PRODUCTS_COUNT = 4;
5 | const BASE_URL = 'http://makeup-api.herokuapp.com/api/v1/products';
6 |
7 | const getRandomIndex = (max, min = 0) => {
8 | return Math.floor(Math.random() * (max - min) + min);
9 | };
10 |
11 | const getRandomProducts = (randomIndex, data) => {
12 | let i = randomIndex;
13 | const products = [];
14 |
15 | for (let index = 0; index < PRODUCTS_COUNT; index++) {
16 | products.push(data[i]);
17 | i++;
18 | }
19 |
20 | return products;
21 | };
22 |
23 | const useGetRecommendedProducts = product => {
24 | const [products, setProducts] = useState([]);
25 |
26 | const getRecommendedProducts = useCallback(() => {
27 | setProducts([]);
28 | const key = product.product_type ? 'product_type' : 'brand';
29 | const value = product.product_type ? product.product_type : product.brand;
30 |
31 | axios
32 | .get(`${BASE_URL}.json`, {
33 | params: {
34 | [key]: value,
35 | },
36 | })
37 | .then(response => {
38 | const { data } = response;
39 |
40 | if (data.length > PRODUCTS_COUNT) {
41 | const index = getRandomIndex(data.length - PRODUCTS_COUNT);
42 | const randomProducts = getRandomProducts(index, data);
43 |
44 | setProducts(randomProducts);
45 | return;
46 | }
47 |
48 | setProducts(data);
49 | });
50 | }, [product]);
51 |
52 | useEffect(() => {
53 | if (!product) return;
54 | getRecommendedProducts();
55 | }, [product, getRecommendedProducts]);
56 |
57 | return products;
58 | };
59 |
60 | export default useGetRecommendedProducts;
61 |
--------------------------------------------------------------------------------
/code-along/src/hooks/useGetTopProducts.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import axios from 'axios';
3 |
4 | import { PRODUCT_TAGS } from '../constants/tags';
5 |
6 | const PRODUCTS_COUNT = 8;
7 | const BASE_URL = 'http://makeup-api.herokuapp.com/api/v1/products';
8 |
9 | const getRandomProductTag = () => {
10 | const randomNumber = Math.floor(Math.random() * PRODUCT_TAGS.length);
11 |
12 | return PRODUCT_TAGS[randomNumber];
13 | };
14 |
15 | const useGetTopProducts = () => {
16 | const [products, setProducts] = useState([]);
17 |
18 | const getTopProducts = () => {
19 | axios
20 | .get(`${BASE_URL}.json`, {
21 | params: {
22 | product_tags: getRandomProductTag(),
23 | },
24 | })
25 | .then(response => {
26 | const { data } = response;
27 | data.length = data.length > PRODUCTS_COUNT ? PRODUCTS_COUNT : data.length;
28 |
29 | setProducts(data);
30 | });
31 | };
32 |
33 | useEffect(() => {
34 | getTopProducts();
35 | }, []);
36 |
37 | return products;
38 | };
39 |
40 | export default useGetTopProducts;
41 |
--------------------------------------------------------------------------------
/code-along/src/hooks/useSearch.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import axios from 'axios';
3 |
4 | import { useSearchState } from '../state/search-context';
5 | import { setProducts } from '../state/actionCreators';
6 |
7 | const BASE_URL = 'https://makeup-api.herokuapp.com/api/v1/products';
8 |
9 | const useSearch = () => {
10 | const [state, dispatch] = useSearchState();
11 | const [isLoading, setIsLoading] = useState(true);
12 |
13 | const getProducts = () => {
14 | setIsLoading(true);
15 | dispatch(setProducts([]));
16 |
17 | const params = {
18 | product_type: state.filters.productType,
19 | brand: state.filters.brand,
20 | price_greater_than: state.filters.minPrice,
21 | price_less_than: state.filters.maxPrice,
22 | };
23 |
24 | axios
25 | .get(`${BASE_URL}.json`, {
26 | params,
27 | })
28 | .then(({ data }) => {
29 | dispatch(setProducts(data));
30 | setIsLoading(false);
31 | });
32 | };
33 |
34 | useEffect(() => {
35 | getProducts();
36 | }, [state.filters]);
37 |
38 | return {
39 | isLoading,
40 | };
41 | };
42 |
43 | export default useSearch;
44 |
--------------------------------------------------------------------------------
/code-along/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | /* font-family: 'Krona One', sans-serif;
6 | font-family: 'Poppins', sans-serif; */
7 |
8 | body {
9 | @apply font-base text-dark;
10 | }
11 |
12 | .search {
13 | display: grid;
14 | grid-template-columns: 250px auto;
15 | grid-template-areas:
16 | 'sidebar results'
17 | 'sidebar results';
18 | }
19 |
20 | .search .sidebar {
21 | grid-area: sidebar;
22 | }
23 |
24 | .search .results {
25 | grid-area: results;
26 | }
27 |
--------------------------------------------------------------------------------
/code-along/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/code-along/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/code-along/src/pages/About.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 |
3 | import BrandLink from './../components/BrandLink';
4 | import FactNumber from './../components/FactNumber';
5 |
6 | const About = () => {
7 | return (
8 |
9 |
15 |
16 |
17 |
18 | Welly is the leading premium beauty platform in Europe. Offering more than 130,000 beauty and
19 | lifestyle products in online shops, the beauty marketplace and more than 2,000 stores, Welly
20 | inspires customers to live their own kind of beauty by a previously unparalleled assortment.
21 |
22 |
23 | The continued expansion of the fast-growing e‑commerce business is the focus of the
24 | #forwardbeauty . DigitalFirst strategy programme. In fiscal
25 | year 2019/2020, Welly generated sales of 3.2 billion euros in the areas of perfumery, decorative
26 | cosmetics, skin and hair care as well as nutritional supplements and accessories.
27 | #letsdobeautiful
28 |
29 |
30 |
31 |
32 |
33 |
34 | {/* Facts and figures */}
35 |
41 |
47 |
53 |
54 |
55 |
56 |
Imprint
57 |
Welly Inc. Welly-Street 7-11 40235 Welland
58 |
59 | You can reach us by email at: service@welly.wellly. Our deliveries go out every Sunday evening so
60 | you can get them as early in the week as possible
61 |
62 |
63 |
64 | );
65 | };
66 |
67 | export default About;
68 |
--------------------------------------------------------------------------------
/code-along/src/pages/Home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Benefits from '../components/Benefits';
3 |
4 | import MainBanner from '../components/MainBanner';
5 | import TopProducts from '../components/TopProducts';
6 | import UniqueSellingPoint from '../components/UniqueSellingPoint/UniqueSellingPoint';
7 |
8 | const Home = () => {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 | >
16 | );
17 | };
18 |
19 | export default Home;
20 |
--------------------------------------------------------------------------------
/code-along/src/pages/Product.jsx:
--------------------------------------------------------------------------------
1 | import React, { useLayoutEffect } from 'react';
2 | import { useLocation } from 'react-router-dom';
3 |
4 | import useGetProduct from '../hooks/useGetProduct';
5 |
6 | import Benefits from '../components/Benefits';
7 | import ProductPreview from '../components/Product';
8 | import RecommendedProducts from '../components/RecommendedProducts';
9 | import Loader from '../components/Loader/Loader';
10 |
11 | const Product = () => {
12 | const location = useLocation();
13 | const { singleProduct, isLoading } = useGetProduct();
14 |
15 | useLayoutEffect(() => {
16 | window.scrollTo(0, 0);
17 | }, [location.pathname]);
18 |
19 | if (!singleProduct) return ;
20 |
21 | return (
22 |
23 | {isLoading ? (
24 |
25 | ) : (
26 |
34 | )}
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default Product;
43 |
--------------------------------------------------------------------------------
/code-along/src/pages/Search.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import HeaderBanner from '../components/HeaderBanner';
4 | import Search from '../components/Search';
5 |
6 | import SearchProvider from '../state/search-context';
7 |
8 | const SearchPage = () => {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 | >
16 | );
17 | };
18 |
19 | export default SearchPage;
20 |
--------------------------------------------------------------------------------
/code-along/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/code-along/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/code-along/src/state/actionCreators.js:
--------------------------------------------------------------------------------
1 | import { SET_FILTER, SET_PRODUCTS } from './actionTypes';
2 |
3 | export const setProducts = products => ({
4 | type: SET_PRODUCTS,
5 | payload: products,
6 | });
7 |
8 | export const setFilter = (name, value) => ({
9 | type: SET_FILTER,
10 | payload: { name, value },
11 | });
12 |
--------------------------------------------------------------------------------
/code-along/src/state/actionTypes.js:
--------------------------------------------------------------------------------
1 | export const SET_PRODUCTS = 'setProducts';
2 | export const SET_FILTER = 'setFilter';
3 |
--------------------------------------------------------------------------------
/code-along/src/state/search-context.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useReducer } from 'react';
2 |
3 | import { initialSearchState } from './search-state';
4 | import { searchReducer } from './search-reducer';
5 |
6 | const SearchStateContext = createContext('searchState');
7 |
8 | const SearchProvider = ({ children }) => {
9 | const [state, dispatch] = useReducer(searchReducer, { ...initialSearchState });
10 |
11 | return {children} ;
12 | };
13 |
14 | const useSearchState = () => {
15 | const context = useContext(SearchStateContext);
16 |
17 | return context;
18 | };
19 |
20 | export { useSearchState };
21 |
22 | export default SearchProvider;
23 |
--------------------------------------------------------------------------------
/code-along/src/state/search-reducer.js:
--------------------------------------------------------------------------------
1 | import { SET_FILTER, SET_PRODUCTS } from './actionTypes';
2 |
3 | export const searchReducer = (state, { type, payload }) => {
4 | switch (type) {
5 | case SET_PRODUCTS:
6 | return { ...state, products: payload };
7 | case SET_FILTER:
8 | return { ...state, filters: { ...state.filters, [payload.name]: payload.value } };
9 | default:
10 | return state;
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/code-along/src/state/search-state.js:
--------------------------------------------------------------------------------
1 | export const initialSearchState = {
2 | products: [],
3 | filteredProducts: [],
4 | filters: {
5 | brand: null,
6 | productType: null,
7 | minPrice: null,
8 | maxPrice: null,
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/code-along/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {
6 | fontFamily: {
7 | base: 'Poppins, sans-serif',
8 | krona: '"Krona One", sans-serif',
9 | },
10 | colors: {
11 | dark: '#0C080B',
12 | green: '#1E3329',
13 | yellow: '#ffc94b',
14 | pink: '#fed2d1',
15 | 'dark-grey': '#B8B8B8',
16 | 'light-grey': '#ECECEC',
17 | light: '#FAFAFA',
18 | },
19 | zIndex: {
20 | '-1': -1,
21 | },
22 | container: {
23 | padding: '1rem',
24 | },
25 | },
26 | },
27 | variants: {
28 | extend: {},
29 | },
30 | plugins: [],
31 | };
32 |
--------------------------------------------------------------------------------
/starter-files/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/starter-files/.DS_Store
--------------------------------------------------------------------------------
/starter-files/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-makeup-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "react-scripts start",
7 | "build": "react-scripts build",
8 | "test": "react-scripts test",
9 | "eject": "react-scripts eject"
10 | },
11 | "dependencies": {
12 | "@testing-library/jest-dom": "^5.11.4",
13 | "@testing-library/react": "^11.1.0",
14 | "@testing-library/user-event": "^12.1.10",
15 | "react": "^17.0.2",
16 | "react-dom": "^17.0.2",
17 | "react-scripts": "4.0.3",
18 | "web-vitals": "^1.0.1"
19 | },
20 | "eslintConfig": {
21 | "extends": [
22 | "react-app",
23 | "react-app/jest"
24 | ]
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | }
38 | }
--------------------------------------------------------------------------------
/starter-files/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/starter-files/public/favicon.ico
--------------------------------------------------------------------------------
/starter-files/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/starter-files/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
--------------------------------------------------------------------------------
/starter-files/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/starter-files/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/starter-files/src/.DS_Store
--------------------------------------------------------------------------------
/starter-files/src/App.js:
--------------------------------------------------------------------------------
1 | function App() {
2 | return (
3 |
4 | This is the app!
5 |
6 | );
7 | }
8 |
9 | export default App;
10 |
--------------------------------------------------------------------------------
/starter-files/src/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danascript/react-makeup-app/e81d1176957bd79210f0f33826a1afaba8ce3cbb/starter-files/src/index.css
--------------------------------------------------------------------------------
/starter-files/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/starter-files/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/starter-files/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/starter-files/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------