├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.css ├── App.js ├── App.test.js ├── assets │ └── crown.svg ├── components │ ├── collection-item │ │ ├── collection-item.component.jsx │ │ └── collection-item.styles.scss │ ├── collection-preview │ │ ├── collection-preview.jsx │ │ └── collection-preview.styles.scss │ ├── custom-button │ │ ├── custom-button.component.jsx │ │ └── custom-button.styles.scss │ ├── directory │ │ ├── directory.component.jsx │ │ └── directory.styles.scss │ ├── form-input │ │ ├── form-input.component.jsx │ │ └── form-input.styles.scss │ ├── header │ │ ├── header.component.jsx │ │ └── header.styles.scss │ ├── menu-item │ │ ├── menu-item.component.jsx │ │ └── menu-item.styles.scss │ └── sign-in │ │ ├── sign-in.component.jsx │ │ └── sign-in.styles.scss ├── firebase │ └── firebase.utils.js ├── index.css ├── index.js ├── logo.svg ├── pages │ ├── homepage │ │ ├── homepage.component.jsx │ │ └── homepage.styles.scss │ ├── shop │ │ ├── shop.component.jsx │ │ └── shop.data.js │ └── sign-in-and-sign-up │ │ ├── sign-in-and-sign-up.component.jsx │ │ └── sign-in-and-sign-up.styles.scss └── serviceWorker.js └── yarn.lock /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Lesson-9 2 | 3 | Creating our firebase database and adding the firebase library into our code. Add google sign in and authentication into our application. 4 | 5 | # How to fork and clone 6 | 7 | One quick note about cloning this project. If you wish to make commits and push the code up after cloning this repo, you should fork the project first. In order to own your own copy of this repository, you have to fork it so you get your own copy on your own profile! 8 | 9 | You can see the fork button in the top right corner of every GitHub project; click it and a copy of the project will be added to your GitHub profile under the same name as the original project. 10 | 11 | ![alt text](https://i.ibb.co/1YN7SJ6/Screen-Shot-2019-07-01-at-2-02-40-AM.png "image to fork button") 12 | 13 | After forking the project, simply clone it the way you would from the new forked project in your own GitHub repository and you can commit and push to it freely! 14 | 15 | 16 | # After you fork and clone: 17 | 18 | ## Install dependencies 19 | 20 | In your terminal after you clone your project down, remember to run either `yarn` or `npm install` to build all the dependencies in the project. 21 | 22 | ## Set your firebase config 23 | 24 | Remember to replace the `config` variable in your `firebase.utils.js` with your own config object from the firebase dashboard! Navigate to the project settings and scroll down to the config code. Copy the object in the code and replace the variable in your cloned code. 25 | 26 | ![alt text](https://i.ibb.co/6ywMkBf/Screen-Shot-2019-07-01-at-11-35-02-AM.png "image to firebase config") 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crwn-clothing", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "firebase": "6.0.2", 7 | "node-sass": "4.12.0", 8 | "react": "^17.0.1", 9 | "react-dom": "^16.8.6", 10 | "react-router-dom": "5.0.0" 11 | }, 12 | "devDependencies": { 13 | "react-scripts": "3.0.0" 14 | }, 15 | "resolutions": { 16 | "babel-jest": "24.7.1" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": "react-app" 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhangMYihua/lesson-9/783fd72e3f6290c9c4efcd072d805f210b40efd0/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 26 | React App 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /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 | } 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Open Sans Condensed'; 3 | padding: 20px 40px; 4 | } 5 | 6 | a { 7 | text-decoration: none; 8 | color: black; 9 | } 10 | 11 | * { 12 | box-sizing: border-box; 13 | } 14 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, Route } from 'react-router-dom'; 3 | 4 | import './App.css'; 5 | 6 | import HomePage from './pages/homepage/homepage.component'; 7 | import ShopPage from './pages/shop/shop.component'; 8 | import SignInAndSignUpPage from './pages/sign-in-and-sign-up/sign-in-and-sign-up.component'; 9 | import Header from './components/header/header.component'; 10 | import { auth } from './firebase/firebase.utils'; 11 | 12 | class App extends React.Component { 13 | constructor() { 14 | super(); 15 | 16 | this.state = { 17 | currentUser: null 18 | }; 19 | } 20 | 21 | unsubscribeFromAuth = null; 22 | 23 | componentDidMount() { 24 | this.unsubscribeFromAuth = auth.onAuthStateChanged(user => { 25 | this.setState({ currentUser: user }); 26 | 27 | console.log(user); 28 | }); 29 | } 30 | 31 | componentWillUnmount() { 32 | this.unsubscribeFromAuth(); 33 | } 34 | 35 | render() { 36 | return ( 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 |
45 | ); 46 | } 47 | } 48 | 49 | export default App; 50 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/assets/crown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/collection-item/collection-item.component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './collection-item.styles.scss'; 4 | 5 | const CollectionItem = ({ id, name, price, imageUrl }) => ( 6 |
7 |
13 |
14 | {name} 15 | {price} 16 |
17 |
18 | ); 19 | 20 | export default CollectionItem; 21 | -------------------------------------------------------------------------------- /src/components/collection-item/collection-item.styles.scss: -------------------------------------------------------------------------------- 1 | .collection-item { 2 | width: 22%; 3 | display: flex; 4 | flex-direction: column; 5 | height: 350px; 6 | align-items: center; 7 | 8 | .image { 9 | width: 100%; 10 | height: 95%; 11 | background-size: cover; 12 | background-position: center; 13 | margin-bottom: 5px; 14 | } 15 | 16 | .collection-footer { 17 | width: 100%; 18 | height: 5%; 19 | display: flex; 20 | justify-content: space-between; 21 | font-size: 18px; 22 | 23 | .name { 24 | width: 90%; 25 | margin-bottom: 15px; 26 | } 27 | 28 | .price { 29 | width: 10%; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/collection-preview/collection-preview.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import CollectionItem from '../collection-item/collection-item.component'; 4 | 5 | import './collection-preview.styles.scss'; 6 | 7 | const CollectionPreview = ({ title, items }) => ( 8 |
9 |

{title.toUpperCase()}

10 |
11 | {items 12 | .filter((item, idx) => idx < 4) 13 | .map(({ id, ...otherItemProps }) => ( 14 | 15 | ))} 16 |
17 |
18 | ); 19 | 20 | export default CollectionPreview; 21 | -------------------------------------------------------------------------------- /src/components/collection-preview/collection-preview.styles.scss: -------------------------------------------------------------------------------- 1 | .collection-preview { 2 | display: flex; 3 | flex-direction: column; 4 | margin-bottom: 30px; 5 | 6 | .title { 7 | font-size: 28px; 8 | margin-bottom: 25px; 9 | } 10 | 11 | .preview { 12 | display: flex; 13 | justify-content: space-between; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/custom-button/custom-button.component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './custom-button.styles.scss'; 4 | 5 | const CustomButton = ({ children, isGoogleSignIn, ...otherProps }) => ( 6 | 12 | ); 13 | 14 | export default CustomButton; 15 | -------------------------------------------------------------------------------- /src/components/custom-button/custom-button.styles.scss: -------------------------------------------------------------------------------- 1 | .custom-button { 2 | min-width: 165px; 3 | width: auto; 4 | height: 50px; 5 | letter-spacing: 0.5px; 6 | line-height: 50px; 7 | padding: 0 35px 0 35px; 8 | font-size: 15px; 9 | background-color: black; 10 | color: white; 11 | text-transform: uppercase; 12 | font-family: 'Open Sans Condensed'; 13 | font-weight: bolder; 14 | border: none; 15 | cursor: pointer; 16 | 17 | &:hover { 18 | background-color: white; 19 | color: black; 20 | border: 1px solid black; 21 | } 22 | 23 | &.google-sign-in { 24 | background-color: #4285f4; 25 | color: white; 26 | 27 | &:hover { 28 | background-color: #357ae8; 29 | border: none; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/directory/directory.component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import MenuItem from '../menu-item/menu-item.component'; 4 | 5 | import './directory.styles.scss'; 6 | 7 | class Directory extends React.Component { 8 | constructor() { 9 | super(); 10 | 11 | this.state = { 12 | sections: [ 13 | { 14 | title: 'hats', 15 | imageUrl: 'https://i.ibb.co/cvpntL1/hats.png', 16 | id: 1, 17 | linkUrl: 'hats' 18 | }, 19 | { 20 | title: 'jackets', 21 | imageUrl: 'https://i.ibb.co/px2tCc3/jackets.png', 22 | id: 2, 23 | linkUrl: '' 24 | }, 25 | { 26 | title: 'sneakers', 27 | imageUrl: 'https://i.ibb.co/0jqHpnp/sneakers.png', 28 | id: 3, 29 | linkUrl: '' 30 | }, 31 | { 32 | title: 'womens', 33 | imageUrl: 'https://i.ibb.co/GCCdy8t/womens.png', 34 | size: 'large', 35 | id: 4, 36 | linkUrl: '' 37 | }, 38 | { 39 | title: 'mens', 40 | imageUrl: 'https://i.ibb.co/R70vBrQ/men.png', 41 | size: 'large', 42 | id: 5, 43 | linkUrl: '' 44 | } 45 | ] 46 | }; 47 | } 48 | 49 | render() { 50 | return ( 51 |
52 | {this.state.sections.map(({ id, ...otherSectionProps }) => ( 53 | 54 | ))} 55 |
56 | ); 57 | } 58 | } 59 | 60 | export default Directory; 61 | -------------------------------------------------------------------------------- /src/components/directory/directory.styles.scss: -------------------------------------------------------------------------------- 1 | .directory-menu { 2 | width: 100%; 3 | display: flex; 4 | flex-wrap: wrap; 5 | justify-content: space-between; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/form-input/form-input.component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './form-input.styles.scss'; 4 | 5 | const FormInput = ({ handleChange, label, ...otherProps }) => ( 6 |
7 | 8 | {label ? ( 9 | 16 | ) : null} 17 |
18 | ); 19 | 20 | export default FormInput; 21 | -------------------------------------------------------------------------------- /src/components/form-input/form-input.styles.scss: -------------------------------------------------------------------------------- 1 | $sub-color: grey; 2 | $main-color: black; 3 | 4 | @mixin shrinkLabel { 5 | top: -14px; 6 | font-size: 12px; 7 | color: $main-color; 8 | } 9 | 10 | .group { 11 | position: relative; 12 | margin: 45px 0; 13 | 14 | .form-input { 15 | background: none; 16 | background-color: white; 17 | color: $sub-color; 18 | font-size: 18px; 19 | padding: 10px 10px 10px 5px; 20 | display: block; 21 | width: 100%; 22 | border: none; 23 | border-radius: 0; 24 | border-bottom: 1px solid $sub-color; 25 | margin: 25px 0; 26 | 27 | &:focus { 28 | outline: none; 29 | } 30 | 31 | &:focus ~ .form-input-label { 32 | @include shrinkLabel(); 33 | } 34 | } 35 | 36 | input[type='password'] { 37 | letter-spacing: 0.3em; 38 | } 39 | 40 | .form-input-label { 41 | color: $sub-color; 42 | font-size: 16px; 43 | font-weight: normal; 44 | position: absolute; 45 | pointer-events: none; 46 | left: 5px; 47 | top: 10px; 48 | transition: 300ms ease all; 49 | 50 | &.shrink { 51 | @include shrinkLabel(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/header/header.component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | import { auth } from '../../firebase/firebase.utils'; 5 | 6 | import { ReactComponent as Logo } from '../../assets/crown.svg'; 7 | 8 | import './header.styles.scss'; 9 | 10 | const Header = ({ currentUser }) => ( 11 |
12 | 13 | 14 | 15 |
16 | 17 | SHOP 18 | 19 | 20 | CONTACT 21 | 22 | {currentUser ? ( 23 |
auth.signOut()}> 24 | SIGN OUT 25 |
26 | ) : ( 27 | 28 | SIGN IN 29 | 30 | )} 31 |
32 |
33 | ); 34 | 35 | export default Header; 36 | -------------------------------------------------------------------------------- /src/components/header/header.styles.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 70px; 3 | width: 100%; 4 | display: flex; 5 | justify-content: space-between; 6 | margin-bottom: 25px; 7 | 8 | .logo-container { 9 | height: 100%; 10 | width: 70px; 11 | padding: 25px; 12 | } 13 | 14 | .options { 15 | width: 50%; 16 | height: 100%; 17 | display: flex; 18 | align-items: center; 19 | justify-content: flex-end; 20 | 21 | .option { 22 | padding: 10px 15px; 23 | cursor: pointer; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/menu-item/menu-item.component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import './menu-item.styles.scss'; 5 | 6 | const MenuItem = ({ title, imageUrl, size, history, linkUrl, match }) => ( 7 |
history.push(`${match.url}${linkUrl}`)} 10 | > 11 |
17 |
18 |

{title.toUpperCase()}

19 | SHOP NOW 20 |
21 |
22 | ); 23 | 24 | export default withRouter(MenuItem); 25 | -------------------------------------------------------------------------------- /src/components/menu-item/menu-item.styles.scss: -------------------------------------------------------------------------------- 1 | .menu-item { 2 | min-width: 30%; 3 | height: 240px; 4 | flex: 1 1 auto; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | border: 1px solid black; 9 | margin: 0 7.5px 15px; 10 | overflow: hidden; 11 | 12 | &:hover { 13 | cursor: pointer; 14 | 15 | & .background-image { 16 | transform: scale(1.1); 17 | transition: transform 6s cubic-bezier(0.25, 0.45, 0.45, 0.95); 18 | } 19 | 20 | & .content { 21 | opacity: 0.9; 22 | } 23 | } 24 | 25 | &.large { 26 | height: 380px; 27 | } 28 | 29 | &:first-child { 30 | margin-right: 7.5px; 31 | } 32 | 33 | &:last-child { 34 | margin-left: 7.5px; 35 | } 36 | 37 | .background-image { 38 | width: 100%; 39 | height: 100%; 40 | background-size: cover; 41 | background-position: center; 42 | } 43 | 44 | .content { 45 | height: 90px; 46 | padding: 0 25px; 47 | display: flex; 48 | flex-direction: column; 49 | align-items: center; 50 | justify-content: center; 51 | border: 1px solid black; 52 | background-color: white; 53 | opacity: 0.7; 54 | position: absolute; 55 | 56 | .title { 57 | font-weight: bold; 58 | margin: 0 6px 0; 59 | font-size: 22px; 60 | color: #4a4a4a; 61 | } 62 | 63 | .subtitle { 64 | font-weight: lighter; 65 | font-size: 16px; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/components/sign-in/sign-in.component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import FormInput from '../form-input/form-input.component'; 4 | import CustomButton from '../custom-button/custom-button.component'; 5 | 6 | import { signInWithGoogle } from '../../firebase/firebase.utils'; 7 | 8 | import './sign-in.styles.scss'; 9 | 10 | class SignIn extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.state = { 15 | email: '', 16 | password: '' 17 | }; 18 | } 19 | 20 | handleSubmit = event => { 21 | event.preventDefault(); 22 | 23 | this.setState({ email: '', password: '' }); 24 | }; 25 | 26 | handleChange = event => { 27 | const { value, name } = event.target; 28 | 29 | this.setState({ [name]: value }); 30 | }; 31 | 32 | render() { 33 | return ( 34 |
35 |

I already have an account

36 | Sign in with your email and password 37 | 38 |
39 | 47 | 55 |
56 | Sign in 57 | 58 | Sign in with Google 59 | 60 |
61 | 62 |
63 | ); 64 | } 65 | } 66 | 67 | export default SignIn; 68 | -------------------------------------------------------------------------------- /src/components/sign-in/sign-in.styles.scss: -------------------------------------------------------------------------------- 1 | .sign-in { 2 | width: 380px; 3 | display: flex; 4 | flex-direction: column; 5 | 6 | .title { 7 | margin: 10px 0; 8 | } 9 | 10 | .buttons { 11 | display: flex; 12 | justify-content: space-between; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/firebase/firebase.utils.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app'; 2 | import 'firebase/firestore'; 3 | import 'firebase/auth'; 4 | 5 | const config = { 6 | apiKey: 'AIzaSyCdHT-AYHXjF7wOrfAchX4PIm3cSj5tn14', 7 | authDomain: 'crwn-db.firebaseapp.com', 8 | databaseURL: 'https://crwn-db.firebaseio.com', 9 | projectId: 'crwn-db', 10 | storageBucket: 'crwn-db.appspot.com', 11 | messagingSenderId: '850995411664', 12 | appId: '1:850995411664:web:7ddc01d597846f65' 13 | }; 14 | 15 | firebase.initializeApp(config); 16 | 17 | export const auth = firebase.auth(); 18 | export const firestore = firebase.firestore(); 19 | 20 | const provider = new firebase.auth.GoogleAuthProvider(); 21 | provider.setCustomParameters({ prompt: 'select_account' }); 22 | export const signInWithGoogle = () => auth.signInWithPopup(provider); 23 | 24 | export default firebase; 25 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | 5 | import './index.css'; 6 | import App from './App'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ); 14 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/pages/homepage/homepage.component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Directory from '../../components/directory/directory.component'; 4 | 5 | import './homepage.styles.scss'; 6 | 7 | const HomePage = () => ( 8 |
9 | 10 |
11 | ); 12 | 13 | export default HomePage; 14 | -------------------------------------------------------------------------------- /src/pages/homepage/homepage.styles.scss: -------------------------------------------------------------------------------- 1 | .homepage { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/shop/shop.component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import SHOP_DATA from './shop.data.js'; 4 | 5 | import CollectionPreview from '../../components/collection-preview/collection-preview'; 6 | 7 | class ShopPage extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | collections: SHOP_DATA 13 | }; 14 | } 15 | 16 | render() { 17 | const { collections } = this.state; 18 | return ( 19 |
20 | {collections.map(({ id, ...otherCollectionProps }) => ( 21 | 22 | ))} 23 |
24 | ); 25 | } 26 | } 27 | 28 | export default ShopPage; 29 | -------------------------------------------------------------------------------- /src/pages/shop/shop.data.js: -------------------------------------------------------------------------------- 1 | const SHOP_DATA = [ 2 | { 3 | id: 1, 4 | title: 'Hats', 5 | routeName: 'hats', 6 | items: [ 7 | { 8 | id: 1, 9 | name: 'Brown Brim', 10 | imageUrl: 'https://i.ibb.co/ZYW3VTp/brown-brim.png', 11 | price: 25 12 | }, 13 | { 14 | id: 2, 15 | name: 'Blue Beanie', 16 | imageUrl: 'https://i.ibb.co/ypkgK0X/blue-beanie.png', 17 | price: 18 18 | }, 19 | { 20 | id: 3, 21 | name: 'Brown Cowboy', 22 | imageUrl: 'https://i.ibb.co/QdJwgmp/brown-cowboy.png', 23 | price: 35 24 | }, 25 | { 26 | id: 4, 27 | name: 'Grey Brim', 28 | imageUrl: 'https://i.ibb.co/RjBLWxB/grey-brim.png', 29 | price: 25 30 | }, 31 | { 32 | id: 5, 33 | name: 'Green Beanie', 34 | imageUrl: 'https://i.ibb.co/YTjW3vF/green-beanie.png', 35 | price: 18 36 | }, 37 | { 38 | id: 6, 39 | name: 'Palm Tree Cap', 40 | imageUrl: 'https://i.ibb.co/rKBDvJX/palm-tree-cap.png', 41 | price: 14 42 | }, 43 | { 44 | id: 7, 45 | name: 'Red Beanie', 46 | imageUrl: 'https://i.ibb.co/bLB646Z/red-beanie.png', 47 | price: 18 48 | }, 49 | { 50 | id: 8, 51 | name: 'Wolf Cap', 52 | imageUrl: 'https://i.ibb.co/1f2nWMM/wolf-cap.png', 53 | price: 14 54 | }, 55 | { 56 | id: 9, 57 | name: 'Blue Snapback', 58 | imageUrl: 'https://i.ibb.co/X2VJP2W/blue-snapback.png', 59 | price: 16 60 | } 61 | ] 62 | }, 63 | { 64 | id: 2, 65 | title: 'Sneakers', 66 | routeName: 'sneakers', 67 | items: [ 68 | { 69 | id: 10, 70 | name: 'Adidas NMD', 71 | imageUrl: 'https://i.ibb.co/0s3pdnc/adidas-nmd.png', 72 | price: 220 73 | }, 74 | { 75 | id: 11, 76 | name: 'Adidas Yeezy', 77 | imageUrl: 'https://i.ibb.co/dJbG1cT/yeezy.png', 78 | price: 280 79 | }, 80 | { 81 | id: 12, 82 | name: 'Black Converse', 83 | imageUrl: 'https://i.ibb.co/bPmVXyP/black-converse.png', 84 | price: 110 85 | }, 86 | { 87 | id: 13, 88 | name: 'Nike White AirForce', 89 | imageUrl: 'https://i.ibb.co/1RcFPk0/white-nike-high-tops.png', 90 | price: 160 91 | }, 92 | { 93 | id: 14, 94 | name: 'Nike Red High Tops', 95 | imageUrl: 'https://i.ibb.co/QcvzydB/nikes-red.png', 96 | price: 160 97 | }, 98 | { 99 | id: 15, 100 | name: 'Nike Brown High Tops', 101 | imageUrl: 'https://i.ibb.co/fMTV342/nike-brown.png', 102 | price: 160 103 | }, 104 | { 105 | id: 16, 106 | name: 'Air Jordan Limited', 107 | imageUrl: 'https://i.ibb.co/w4k6Ws9/nike-funky.png', 108 | price: 190 109 | }, 110 | { 111 | id: 17, 112 | name: 'Timberlands', 113 | imageUrl: 'https://i.ibb.co/Mhh6wBg/timberlands.png', 114 | price: 200 115 | } 116 | ] 117 | }, 118 | { 119 | id: 3, 120 | title: 'Jackets', 121 | routeName: 'jackets', 122 | items: [ 123 | { 124 | id: 18, 125 | name: 'Black Jean Shearling', 126 | imageUrl: 'https://i.ibb.co/XzcwL5s/black-shearling.png', 127 | price: 125 128 | }, 129 | { 130 | id: 19, 131 | name: 'Blue Jean Jacket', 132 | imageUrl: 'https://i.ibb.co/mJS6vz0/blue-jean-jacket.png', 133 | price: 90 134 | }, 135 | { 136 | id: 20, 137 | name: 'Grey Jean Jacket', 138 | imageUrl: 'https://i.ibb.co/N71k1ML/grey-jean-jacket.png', 139 | price: 90 140 | }, 141 | { 142 | id: 21, 143 | name: 'Brown Shearling', 144 | imageUrl: 'https://i.ibb.co/s96FpdP/brown-shearling.png', 145 | price: 165 146 | }, 147 | { 148 | id: 22, 149 | name: 'Tan Trench', 150 | imageUrl: 'https://i.ibb.co/M6hHc3F/brown-trench.png', 151 | price: 185 152 | } 153 | ] 154 | }, 155 | { 156 | id: 4, 157 | title: 'Womens', 158 | routeName: 'womens', 159 | items: [ 160 | { 161 | id: 23, 162 | name: 'Blue Tanktop', 163 | imageUrl: 'https://i.ibb.co/7CQVJNm/blue-tank.png', 164 | price: 25 165 | }, 166 | { 167 | id: 24, 168 | name: 'Floral Blouse', 169 | imageUrl: 'https://i.ibb.co/4W2DGKm/floral-blouse.png', 170 | price: 20 171 | }, 172 | { 173 | id: 25, 174 | name: 'Floral Dress', 175 | imageUrl: 'https://i.ibb.co/KV18Ysr/floral-skirt.png', 176 | price: 80 177 | }, 178 | { 179 | id: 26, 180 | name: 'Red Dots Dress', 181 | imageUrl: 'https://i.ibb.co/N3BN1bh/red-polka-dot-dress.png', 182 | price: 80 183 | }, 184 | { 185 | id: 27, 186 | name: 'Striped Sweater', 187 | imageUrl: 'https://i.ibb.co/KmSkMbH/striped-sweater.png', 188 | price: 45 189 | }, 190 | { 191 | id: 28, 192 | name: 'Yellow Track Suit', 193 | imageUrl: 'https://i.ibb.co/v1cvwNf/yellow-track-suit.png', 194 | price: 135 195 | }, 196 | { 197 | id: 29, 198 | name: 'White Blouse', 199 | imageUrl: 'https://i.ibb.co/qBcrsJg/white-vest.png', 200 | price: 20 201 | } 202 | ] 203 | }, 204 | { 205 | id: 5, 206 | title: 'Mens', 207 | routeName: 'mens', 208 | items: [ 209 | { 210 | id: 30, 211 | name: 'Camo Down Vest', 212 | imageUrl: 'https://i.ibb.co/xJS0T3Y/camo-vest.png', 213 | price: 325 214 | }, 215 | { 216 | id: 31, 217 | name: 'Floral T-shirt', 218 | imageUrl: 'https://i.ibb.co/qMQ75QZ/floral-shirt.png', 219 | price: 20 220 | }, 221 | { 222 | id: 32, 223 | name: 'Black & White Longsleeve', 224 | imageUrl: 'https://i.ibb.co/55z32tw/long-sleeve.png', 225 | price: 25 226 | }, 227 | { 228 | id: 33, 229 | name: 'Pink T-shirt', 230 | imageUrl: 'https://i.ibb.co/RvwnBL8/pink-shirt.png', 231 | price: 25 232 | }, 233 | { 234 | id: 34, 235 | name: 'Jean Long Sleeve', 236 | imageUrl: 'https://i.ibb.co/VpW4x5t/roll-up-jean-shirt.png', 237 | price: 40 238 | }, 239 | { 240 | id: 35, 241 | name: 'Burgundy T-shirt', 242 | imageUrl: 'https://i.ibb.co/mh3VM1f/polka-dot-shirt.png', 243 | price: 25 244 | } 245 | ] 246 | } 247 | ]; 248 | 249 | export default SHOP_DATA; 250 | -------------------------------------------------------------------------------- /src/pages/sign-in-and-sign-up/sign-in-and-sign-up.component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import SignIn from '../../components/sign-in/sign-in.component'; 4 | 5 | import './sign-in-and-sign-up.styles.scss'; 6 | 7 | const SignInAndSignUpPage = () => ( 8 |
9 | 10 |
11 | ); 12 | 13 | export default SignInAndSignUpPage; 14 | -------------------------------------------------------------------------------- /src/pages/sign-in-and-sign-up/sign-in-and-sign-up.styles.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhangMYihua/lesson-9/783fd72e3f6290c9c4efcd072d805f210b40efd0/src/pages/sign-in-and-sign-up/sign-in-and-sign-up.styles.scss -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | --------------------------------------------------------------------------------