├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── _redirects
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.css
├── App.js
├── Assets
│ └── Screenshots
│ │ └── Screenshot_01.jpg
├── Components
│ ├── Card
│ │ ├── index.js
│ │ └── styles.module.css
│ ├── Container
│ │ └── index.js
│ ├── Navbar
│ │ ├── CartButton
│ │ │ ├── index.js
│ │ │ └── styles.module.css
│ │ ├── MenuButton
│ │ │ ├── index.js
│ │ │ └── styles.module.css
│ │ ├── index.js
│ │ └── styles.module.css
│ └── Spinner
│ │ ├── index.js
│ │ └── styles.module.css
├── Config
│ └── navbarItemList.js
├── Context
│ ├── AuthContext.js
│ ├── CartContext.js
│ ├── FavoriteContext.js
│ └── ProductContext.js
├── Pages
│ ├── Auth
│ │ ├── Signin
│ │ │ ├── index.js
│ │ │ └── styles.module.css
│ │ └── Signup
│ │ │ ├── index.js
│ │ │ ├── styles.module.css
│ │ │ └── validations.js
│ ├── Cart
│ │ ├── index.js
│ │ └── styles.module.css
│ ├── Error404
│ │ ├── index.js
│ │ └── styles.module.css
│ ├── Favorites
│ │ ├── index.js
│ │ └── styles.module.css
│ ├── ProductDetail
│ │ ├── index.js
│ │ └── styles.module.css
│ ├── Products
│ │ ├── index.js
│ │ └── styles.module.css
│ └── ProtectedRoute.js
├── index.css
└── index.js
└── tailwind.config.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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Alper Tugriçeri
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | [![MIT License][license-shield]](https://github.com/Atugriceri/e-commerce-react-app/blob/main/LICENSE)
5 | [![LinkedIn][linkedin-shield]](https://www.linkedin.com/in/alpertugriceri/)
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
E-Commerce-React-App
14 |
15 |
16 | Shopping site project that includes basic processes such as adding to cart, adding to favorites, product listing and product detail page, which should be on an e-commerce site.
17 |
18 |
19 | View Demo
20 |
21 |
22 |
23 |
24 |
25 | Table of Contents
26 |
27 |
28 | About The Project
29 |
32 |
33 | Getting Started
34 | Usage
35 | Roadmap
36 | License
37 | Contact
38 | Acknowledgments
39 |
40 |
41 |
42 |
43 | ## About The Project
44 |
45 | 
46 |
47 | An e-commerce site with basic shopping functions. You can list the products according to their categories, go to the detail page of the relevant product, add the product to your favorites and to the cart, together with the captured product data.
48 |
49 | The project was developed in conjunction with React.js, including Context, Hooks, and Life Cycles Methods. Styled with Tailwind CSS.
50 |
51 | (back to top )
52 |
53 |
54 | ### Built With
55 |
56 | * [React.js](https://reactjs.org/)
57 | * [Tailwind CSS](https://tailwindui.com/)
58 | * [Axios](https://www.npmjs.com/package/axios)
59 | * [Fake Store Api](https://fakestoreapi.com/)
60 |
61 | (back to top )
62 |
63 |
64 |
65 | ## Getting Started
66 |
67 | - Fork the project and clone it locally.
68 | - In the project directory, ou can follow the steps below to download the dependencies:
69 | - Install with npm:
70 | ```sh
71 | npm i
72 | ```
73 | - Install with yarn:
74 | ```sh
75 | yarn
76 | ```
77 | - In the project directory, you can run:
78 | - Run with npm:
79 | ```sh
80 | npm run
81 | ```
82 | - Run with yarn:
83 | ```sh
84 | yarn start
85 | ```
86 |
87 | (back to top )
88 |
89 | ## Usage
90 |
91 | You can list the products by clicking on the categories, add and remove the products to the cart and favorites. You can view the products you have added to the cart and favorites on the cart and favorites pages.
92 |
93 | [See it live!](https://atugriceri-e-commerce-react-app.netlify.app/)
94 |
95 | (back to top )
96 |
97 | ## Roadmap
98 |
99 | - [x] Fetching product data, listing by categories and product detail page.
100 | - [x] Add to favorites and favorites page.
101 | - [x] Add to cart and cart page.
102 | - [ ] Responsive Design
103 | - [ ] Navbar
104 | - [ ] Card
105 | - [ ] Complete the sign in and sign up process.
106 | - [ ] Create order page.
107 |
108 | (back to top )
109 |
110 | ## License
111 |
112 | Distributed under the MIT License. See `LICENSE.txt` for more information.
113 |
114 | (back to top )
115 |
116 |
117 | ## Contact
118 |
119 | Alper Tugriçeri - [Linkedin](https://www.linkedin.com/in/alpertugriceri/) - alpertugriceri@gmail.com
120 |
121 | Project Link: [https://github.com/Atugriceri/e-commerce-react-app](https://github.com/Atugriceri/e-commerce-react-app)
122 |
123 | (back to top )
124 |
125 |
126 |
127 | ## Acknowledgments
128 |
129 | * [Hero Icons](https://heroicons.com/)
130 |
131 | (back to top )
132 |
133 |
134 |
135 |
136 |
137 | [license-shield]: https://img.shields.io/github/license/othneildrew/Best-README-Template.svg?style=for-the-badge
138 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "e-commerce-react-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@headlessui/react": "^1.5.0",
7 | "@heroicons/react": "^1.0.6",
8 | "@testing-library/jest-dom": "^5.16.2",
9 | "@testing-library/react": "^12.1.4",
10 | "@testing-library/user-event": "^13.5.0",
11 | "axios": "^0.26.1",
12 | "firebase": "^9.6.8",
13 | "react": "^17.0.2",
14 | "react-dom": "^17.0.2",
15 | "react-router-dom": "^6.2.2",
16 | "react-scripts": "5.0.0",
17 | "web-vitals": "^2.1.4"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | },
43 | "devDependencies": {
44 | "autoprefixer": "^10.4.2",
45 | "postcss": "^8.4.8",
46 | "tailwindcss": "^3.0.23"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Atugriceri/e-commerce-react-app/2b77667538c9b40acd44c064f2da5076bb966bd3/public/favicon.ico
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Atugriceri/e-commerce-react-app/2b77667538c9b40acd44c064f2da5076bb966bd3/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Atugriceri/e-commerce-react-app/2b77667538c9b40acd44c064f2da5076bb966bd3/public/logo512.png
--------------------------------------------------------------------------------
/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 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './App.css'
3 | import { Routes, Route } from 'react-router-dom'
4 | import Navbar from './Components/Navbar'
5 | import Signin from './Pages/Auth/Signin'
6 | import Signup from './Pages/Auth/Signup'
7 | import Products from './Pages/Products'
8 | import Error404 from './Pages/Error404'
9 | import Container from './Components/Container'
10 | import ProductDetail from './Pages/ProductDetail'
11 | import Cart from './Pages/Cart'
12 | import Favorites from './Pages/Favorites'
13 |
14 | function App() {
15 | return (
16 |
17 |
18 |
19 |
20 | } />
21 | } />
22 | } />
23 | } />
24 | } />
25 | } />
26 | } />
27 | } />
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default App
--------------------------------------------------------------------------------
/src/Assets/Screenshots/Screenshot_01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Atugriceri/e-commerce-react-app/2b77667538c9b40acd44c064f2da5076bb966bd3/src/Assets/Screenshots/Screenshot_01.jpg
--------------------------------------------------------------------------------
/src/Components/Card/index.js:
--------------------------------------------------------------------------------
1 | import styles from './styles.module.css'
2 | import { StarIcon, ShoppingCartIcon, HeartIcon } from '@heroicons/react/solid'
3 | import { Link } from 'react-router-dom'
4 |
5 | const Card = ({
6 | item,
7 | addToFavorite,
8 | findFavoriteItem,
9 | addToCart,
10 | findCartItem,
11 | }) => {
12 |
13 | return (
14 |
15 |
16 |
{
21 | addToFavorite(item, findFavoriteItem)
22 | }}
23 | >
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | Brand,
36 | {" "}
37 | {item.title}
38 |
39 |
40 |
41 | {[...Array(Math.round(item.rating.rate))].map((e, i) => (
42 |
47 | ))}
48 | {[...Array(5 - Math.round(item.rating.rate))].map((e, i) => (
49 |
54 | ))}
55 |
56 | ({item.rating.count})
57 |
58 |
59 |
60 |
61 | $
62 | {Math.trunc(item.price)}
63 | {parseInt((item.price % 1).toFixed(2).substring(2)) !== 0 ? (
64 |
65 | {parseInt((item.price % 1).toFixed(2).substring(2))}
66 |
67 | ) : (
68 | ""
69 | )}
70 |
71 |
72 |
73 |
addToCart(item, findCartItem)}
78 | >
79 |
83 |
84 |
85 | {findCartItem ? "Remove from cart" : "Add to Cart"}
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | )
94 | }
95 |
96 | export default Card
--------------------------------------------------------------------------------
/src/Components/Card/styles.module.css:
--------------------------------------------------------------------------------
1 | /***** CARD STYLES START *****/
2 |
3 | .card {
4 | @apply
5 | w-full
6 | sm:w-1/2
7 | md:w-1/2
8 | xl:w-1/4
9 | p-4
10 | }
11 |
12 | .cardLink {
13 | @apply
14 | block
15 | shadow-sm
16 | hover:shadow-md
17 | border-2
18 | border-zinc-900/10
19 | shadow-zinc-900/10
20 | rounded-md
21 | overflow-hidden
22 | h-full
23 | }
24 |
25 | /***** CARD HEADER STYLES START *****/
26 |
27 | .cardHeader {
28 | @apply
29 | relative
30 | pb-64
31 | overflow-hidden
32 | shadow-sm
33 | }
34 |
35 | /***** FAVORITES BUTTON STYLES START *****/
36 |
37 | .favButton, .removeFavButton {
38 | @apply
39 | h-6
40 | w-6
41 | m-2
42 | ml-auto
43 | }
44 |
45 | .favButton {
46 | @apply
47 | text-zinc-900/80
48 | hover:text-red-500
49 | flex
50 | }
51 |
52 | .removeFavButton {
53 | @apply
54 | text-red-500 flex
55 | }
56 |
57 | /***** FAVORITES BUTTON STYLES END *****/
58 |
59 | /***** CARD IMAGE STYLES START *****/
60 |
61 | .cardImg {
62 | @apply
63 | absolute
64 | inset-0
65 | max-h-56
66 | w-full
67 | object-contain
68 | my-4
69 | px-4
70 | }
71 |
72 | .cardLink img {
73 | transition: transform .3s ease-in-out;
74 | }
75 |
76 | .cardLink img:hover {
77 | transform: scale(1.02)
78 | }
79 |
80 | /***** CARD IMAGE STYLES START *****/
81 |
82 | /***** CARD HEADER STYLES END *****/
83 |
84 | /***** CARD BODY STYLES START *****/
85 |
86 | .cardBody {
87 | @apply
88 | p-4
89 | h-full
90 | flex
91 | flex-1
92 | flex-col
93 | text-sm
94 | text-zinc-900/80
95 | }
96 |
97 | .cardBody .brand, .cardTitle {
98 | @apply
99 | mt-1
100 | mb-2
101 | }
102 |
103 | .brand {
104 | @apply
105 | font-bold
106 | hover:text-blue-600
107 | }
108 |
109 | .cardTitle {
110 | @apply
111 | font-light
112 | truncate
113 | }
114 |
115 | /***** STAR ICON STYLES START *****/
116 |
117 | .rating {
118 | @apply
119 | flex
120 | flex-row
121 | mb-2
122 | }
123 |
124 | .rating .starIcon, .emptyStarIcon {
125 | @apply
126 | h-4
127 | w-4
128 | my-auto
129 | }
130 |
131 | .starIcon {
132 | @apply
133 | text-yellow-300
134 | }
135 |
136 | .emptyStarIcon {
137 | @apply
138 | text-zinc-900/80
139 | }
140 |
141 | /***** STAR ICON STYLES END *****/
142 |
143 | /***** PRICE STYLES START *****/
144 |
145 | .priceSub, .priceTop {
146 | @apply
147 | text-zinc-900/80
148 | }
149 |
150 | .priceSub {
151 | @apply
152 | font-extralight
153 | text-xs
154 | inline-block
155 | align-sub
156 | }
157 |
158 | .priceTop {
159 | @apply
160 | font-normal
161 | text-lg
162 | mx-0.5
163 | inline-block
164 | align-text-top
165 | }
166 |
167 | /***** PRICE STYLES END *****/
168 |
169 | /***** ADD TO CART BUTTON STYLES START *****/
170 |
171 | .addToCart {
172 | @apply
173 | mx-auto
174 | text-center
175 | w-full
176 | mt-2
177 | }
178 |
179 | .addToCartButton, .removeButton {
180 | @apply
181 | inline-flex
182 | justify-center
183 | w-full
184 | rounded-md
185 | shadow-sm
186 | shadow-zinc-900/10
187 | text-sm
188 | text-zinc-50
189 | px-2
190 | py-2
191 | hover:bg-yellow-300
192 | }
193 |
194 | .addToCartButton {
195 | @apply
196 | bg-blue-600
197 | }
198 |
199 | .removeButton {
200 | @apply
201 | bg-red-600
202 | }
203 |
204 | .shoppingCartIcon {
205 | @apply
206 | my-auto
207 | h-5
208 | w-6
209 | }
210 |
211 | .buttonText {
212 | @apply
213 | font-bold
214 | }
215 |
216 | /***** ADD TO CART BUTTON STYLES END *****/
217 |
218 | /***** CARD BODY STYLES END *****/
219 |
220 | /***** CARD STYLES END *****/
--------------------------------------------------------------------------------
/src/Components/Container/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Container = (props) => {
4 | return (
5 |
6 | {props.children}
7 |
8 | )
9 | }
10 |
11 | export default Container
--------------------------------------------------------------------------------
/src/Components/Navbar/CartButton/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from './styles.module.css'
3 | import { Link } from 'react-router-dom'
4 | import { ShoppingCartIcon } from "@heroicons/react/outline"
5 | import { useCart } from '../../../Context/CartContext'
6 |
7 | const CartButton = () => {
8 | const {items} = useCart()
9 |
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | {items.length > 0 && (
17 |
18 | {items.length}
19 |
20 | )}
21 |
22 |
23 |
24 |
25 | )
26 | }
27 |
28 | export default CartButton
--------------------------------------------------------------------------------
/src/Components/Navbar/CartButton/styles.module.css:
--------------------------------------------------------------------------------
1 | .cartMenu {
2 | @apply
3 | relative
4 | text-left
5 | lg:inline-block
6 | }
7 |
8 | .cartButton {
9 | @apply
10 | py-2
11 | pr-2
12 | flex
13 | bg-transparent
14 | text-zinc-900/80
15 | }
16 |
17 | .cartIcon {
18 | @apply
19 | my-auto
20 | h-10
21 | w-10
22 | relative
23 | }
24 |
25 | .cartCount {
26 | @apply
27 | bg-red-600
28 | rounded-full
29 | w-[14px]
30 | h-[14px]
31 | text-[10px]
32 | text-center
33 | flex
34 | justify-center
35 | text-zinc-50
36 | absolute
37 | top-2
38 | pt-[0.5px]
39 | font-bold
40 | left-8
41 | }
--------------------------------------------------------------------------------
/src/Components/Navbar/MenuButton/index.js:
--------------------------------------------------------------------------------
1 | import { Fragment } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { Menu, Transition } from '@headlessui/react'
4 | import { ChevronDownIcon } from '@heroicons/react/solid'
5 | import {
6 | UserCircleIcon,
7 | LogoutIcon,
8 | } from '@heroicons/react/outline'
9 | import styles from './styles.module.css'
10 | import { useAuth } from '../../../Context/AuthContext'
11 | import NAVIGATION from '../../../Config/navbarItemList'
12 |
13 | const MenuButton = () => {
14 | const { loggedIn, currentUser, setIsSubmitting, logout } = useAuth()
15 |
16 | const handleLogout = async () => {
17 | setIsSubmitting(true)
18 | try {
19 | await logout()
20 | } catch {
21 | alert("Error")
22 | }
23 | setIsSubmitting(false)
24 | }
25 |
26 | return (
27 |
28 | {!loggedIn && (
29 |
30 |
31 |
32 |
36 |
37 |
38 | Login
39 |
40 |
or Sign Up
41 |
42 |
46 |
47 |
48 |
49 |
58 |
59 |
60 | {!loggedIn &&
61 | NAVIGATION.map(
62 | ({
63 | id,
64 | name,
65 | link,
66 | icon,
67 | loggedIn,
68 | underlined,
69 | onclick,
70 | }) => (
71 |
72 | {({ active }) => (
73 |
78 |
90 | {icon}
91 | {name}
92 |
93 |
94 | )}
95 |
96 | )
97 | )}
98 |
99 |
100 |
101 |
102 | )}
103 |
104 | {loggedIn && (
105 |
106 |
107 |
108 |
112 |
113 |
114 | Hello,
115 |
116 |
{currentUser?.firstName}
117 |
118 |
122 |
123 |
124 |
125 |
134 |
135 |
136 | {loggedIn &&
137 | NAVIGATION.map(
138 | ({
139 | id,
140 | name,
141 | link,
142 | icon,
143 | loggedIn,
144 | underlined,
145 | onclick,
146 | }) => (
147 |
148 | {({ active }) => (
149 |
154 |
166 | {icon}
167 | {name}
168 |
169 |
170 | )}
171 |
172 | )
173 | )}
174 |
175 |
176 | {({ active }) => (
177 |
178 |
187 |
191 | Logout
192 |
193 |
194 | )}
195 |
196 |
197 |
198 |
199 |
200 | )}
201 |
202 | )
203 | }
204 |
205 | export default MenuButton
206 |
--------------------------------------------------------------------------------
/src/Components/Navbar/MenuButton/styles.module.css:
--------------------------------------------------------------------------------
1 | .menu {
2 | @apply
3 | relative
4 | text-left
5 | lg:inline-block
6 | z-40
7 | }
8 |
9 | .menuButton {
10 | @apply
11 | inline-flex
12 | justify-center
13 | w-full
14 | border-2
15 | border-zinc-900/10
16 | shadow-zinc-900/10
17 | rounded-md
18 | shadow-sm
19 | px-4
20 | py-2
21 | bg-transparent
22 | text-sm
23 | font-medium
24 | text-zinc-900/80
25 | hover:bg-zinc-400/10
26 | }
27 |
28 | .userCircleIcon {
29 | @apply
30 | mr-2
31 | my-auto
32 | h-10
33 | w-10
34 | }
35 |
36 | .chevronDownIcon {
37 | @apply
38 | -mr-1
39 | ml-2
40 | my-auto
41 | h-8
42 | w-8
43 | }
44 |
45 | .menuItems {
46 | @apply
47 | origin-top-right
48 | absolute
49 | right-0
50 | mt-2
51 | w-full
52 | rounded-md
53 | shadow-md
54 | shadow-zinc-900/10
55 | bg-zinc-50
56 | ring-2
57 | ring-zinc-900/10
58 | divide-y-2
59 | divide-zinc-900/10
60 | focus:outline-none
61 | }
--------------------------------------------------------------------------------
/src/Components/Navbar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import MenuButton from './MenuButton'
4 | import CartButton from './CartButton'
5 | import styles from './styles.module.css'
6 | import { useProduct } from '../../Context/ProductContext'
7 | import { useAuth } from '../../Context/AuthContext'
8 | import { Disclosure, } from '@headlessui/react'
9 | import { MenuIcon, XIcon, LogoutIcon } from '@heroicons/react/outline'
10 | import NAVIGATION from '../../Config/navbarItemList'
11 |
12 | const Navbar = () => {
13 | const { categories, setCategory } = useProduct()
14 | const { loggedIn, currentUser, setIsSubmitting, logout } = useAuth()
15 |
16 | const handleLogout = async () => {
17 | setIsSubmitting(true)
18 | try {
19 | await logout()
20 | } catch {
21 | alert("Error")
22 | }
23 | setIsSubmitting(false)
24 | }
25 |
26 | return (
27 | <>
28 |
29 | {({ open }) => (
30 | <>
31 |
32 |
33 |
34 | {/* Mobile menu button*/}
35 |
36 | Open main menu
37 | {open ? (
38 |
39 | ) : (
40 |
41 | )}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
LOGO
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | {/* Profile dropdown */}
62 |
63 |
64 |
65 |
66 |
67 |
68 | {!loggedIn && NAVIGATION.map(({
69 | id,
70 | name,
71 | link,
72 | icon,
73 | underlined,
74 | loggedIn,
75 | onclick,
76 | }) => (
77 |
85 |
86 | {icon}
87 | {name}
88 |
89 |
90 | ))}
91 | {loggedIn && NAVIGATION.map(({
92 | id,
93 | name,
94 | link,
95 | icon,
96 | underlined,
97 | loggedIn,
98 | onclick,
99 | }) => (
100 |
108 |
109 | {icon}
110 | {name}
111 |
112 |
113 | ))}
114 | {loggedIn && (
115 |
119 |
120 |
124 | Logout
125 |
126 |
127 | )}
128 |
129 |
130 |
131 | >
132 | )}
133 |
134 |
135 |
136 |
137 | setCategory("")}
141 | >
142 |
All
143 |
144 |
145 | {categories &&
146 | categories.map((item, index) => {
147 | return (
148 |
149 | {
153 | setCategory(item.toLowerCase());
154 | }}
155 | >
156 |
{item}
157 |
158 |
159 | );
160 | })}
161 |
162 |
163 | >
164 | );
165 | };
166 |
167 | export default Navbar;
168 |
--------------------------------------------------------------------------------
/src/Components/Navbar/styles.module.css:
--------------------------------------------------------------------------------
1 | .nav {
2 | @apply
3 | flex
4 | justify-between
5 | bg-transparent
6 | p-6
7 | z-10
8 | }
9 |
10 | .logo {
11 | @apply
12 | flex
13 | items-center
14 | flex-shrink-0
15 | }
16 |
17 | .logo .link {
18 | @apply
19 | font-semibold
20 | text-xl
21 | tracking-tight
22 | flex
23 | flex-row
24 | }
25 |
26 | .categoryNav {
27 | @apply
28 | flex
29 | py-2
30 | max-w-7xl
31 | mx-auto
32 | bg-transparent
33 | rounded-lg
34 | overflow-x-auto
35 | sm:justify-start
36 | md:justify-center
37 | }
38 |
39 | .categoryLink {
40 | @apply
41 | tracking-widest
42 | text-black
43 | md:hover:border-x-4
44 | md:hover:border-yellow-300
45 | decoration-dashed
46 | px-6
47 | py-2
48 | hover:underline-offset-[5px]
49 | mx-0
50 | flex
51 | }
52 |
53 | .categoryLink h1 {
54 | @apply mx-4
55 | }
56 |
57 | .logoText {
58 | font-family: 'Oswald', sans-serif;
59 | @apply
60 | text-3xl
61 | text-yellow-300
62 | ml-2
63 | my-auto
64 | tracking-widest
65 |
66 | }
67 |
68 | .disclosureButton {
69 | @apply
70 | bg-zinc-50
71 | hover:bg-zinc-400/10
72 | text-zinc-900/80
73 | items-center
74 | w-full
75 | py-2
76 | px-2
77 | text-sm
78 | font-medium
79 | flex
80 | }
81 |
82 | .logoBox {
83 | @apply
84 | bg-gradient-to-b
85 | from-blue-600
86 | via-blue-700
87 | to-blue-600
88 | w-[5.6rem]
89 | h-10
90 | rounded-md
91 | }
92 |
93 | .disclosurePanel {
94 | @apply
95 | sm:hidden
96 | flex
97 | flex-col
98 | border-2
99 | rounded-md
100 | border-zinc-900/10
101 | shadow-md
102 | shadow-zinc-900/10
103 | mb-4
104 | mx-2
105 | }
--------------------------------------------------------------------------------
/src/Components/Spinner/index.js:
--------------------------------------------------------------------------------
1 | import Styles from './styles.module.css'
2 | import React from 'react'
3 |
4 | const Spinner = () => {
5 | return (
6 | <>
7 |
8 |
11 |
Loading...
12 |
This may take a few seconds, please don't close this page.
13 |
14 | >
15 |
16 | )
17 | }
18 |
19 | export default Spinner
--------------------------------------------------------------------------------
/src/Components/Spinner/styles.module.css:
--------------------------------------------------------------------------------
1 | .spinner {
2 | @apply
3 | flex
4 | flex-col
5 | justify-between
6 | items-center
7 | mx-auto
8 | mt-44
9 | }
10 |
11 | .firstCircle {
12 | @apply
13 | relative
14 | w-12
15 | h-12
16 | animate-spin
17 | rounded-full
18 | bg-gradient-to-l
19 | from-blue-400
20 | via-blue-600
21 | to-blue-500
22 | }
23 |
24 | .secondCircle {
25 | @apply
26 | absolute
27 | top-1/2
28 | left-1/2
29 | transform
30 | -translate-x-1/2
31 | -translate-y-1/2
32 | w-9
33 | h-9
34 | bg-neutral-50
35 | rounded-full
36 | border-2
37 | border-dashed
38 | border-white
39 | }
40 |
41 | .status {
42 | @apply
43 | mt-5
44 | text-lg
45 | font-semibold
46 | text-center
47 | mx-auto
48 | }
49 |
50 | .statusText {
51 | @apply
52 | font-light
53 | }
--------------------------------------------------------------------------------
/src/Config/navbarItemList.js:
--------------------------------------------------------------------------------
1 | import {
2 | LoginIcon,
3 | IdentificationIcon,
4 | UserIcon,
5 | ClipboardListIcon,
6 | HeartIcon,
7 | ShoppingCartIcon,
8 | } from '@heroicons/react/outline'
9 |
10 | const NAVIGATION = [
11 | {
12 | id: 1,
13 | name: "Login",
14 | link: "/signin",
15 | icon: ,
16 | loggedIn: false,
17 | underlined: false,
18 | },
19 | {
20 | id: 2,
21 | name: "Sign Up",
22 | link: "/signup",
23 | icon: (
24 |
28 | ),
29 | loggedIn: false,
30 | underlined: true,
31 | },
32 | {
33 | id: 3,
34 | name: "Profile",
35 | link: "/profile",
36 | icon: ,
37 | loggedIn: true,
38 | underlined: true,
39 | },
40 | {
41 | id: 4,
42 | name: "Cart",
43 | link: "/cart",
44 | icon: (
45 |
46 | ),
47 | loggedIn: "public",
48 | underlined: false,
49 | },
50 | {
51 | id: 5,
52 | name: "Orders",
53 | link: "/orders",
54 | icon: (
55 |
59 | ),
60 | loggedIn: true,
61 | underlined: false,
62 | },
63 | {
64 | id: 6,
65 | name: "Favorites",
66 | link: "/favorites",
67 | icon: ,
68 | loggedIn: "public",
69 | underlined: true,
70 | },
71 | ]
72 |
73 | export default NAVIGATION
--------------------------------------------------------------------------------
/src/Context/AuthContext.js:
--------------------------------------------------------------------------------
1 | import { createContext, useState, useEffect, useContext } from "react"
2 | import { useNavigate } from "react-router-dom"
3 |
4 | const AuthContext = createContext()
5 |
6 | const defaultUser = JSON.parse(localStorage.getItem("user")) || {
7 | firstName: "",
8 | lastName: "",
9 | email: "",
10 | password: "",
11 | passwordConfirm: "",
12 | address: "",
13 | }
14 | const defaultUsers = JSON.parse(localStorage.getItem("users")) || []
15 |
16 | const AuthProvider = ({ children }) => {
17 | const [users, setUsers] = useState(defaultUsers)
18 | const [currentUser, setCurrentUser] = useState(defaultUser)
19 | const [isSubmitting, setIsSubmitting] = useState(false)
20 | const [loggedIn, setLoggedIn] = useState(false)
21 | const [errors, setErrors] = useState({})
22 |
23 | const login = (email, password) => {
24 | const userData = Object.values(users)
25 | const indexOfUser = users.map((item) => item.email).indexOf(email)
26 | if (indexOfUser && userData[indexOfUser].password === password) {
27 | const finalUser = userData[indexOfUser]
28 | setCurrentUser(finalUser)
29 | setLoggedIn(true)
30 | localStorage.setItem("user", JSON.stringify(finalUser))
31 | }
32 | }
33 |
34 | const logout = () => {
35 | localStorage.removeItem("user")
36 | setCurrentUser({
37 | firstName: "",
38 | lastName: "",
39 | email: "",
40 | password: "",
41 | passwordConfirm: "",
42 | address: "",
43 | })
44 | setLoggedIn(false)
45 | }
46 |
47 | useEffect(() => {
48 | const isEmpty = Object.values(currentUser).every(value => value ? true : false)
49 | if(Object.keys(errors).length > 0) {
50 | setLoggedIn(false)
51 | } else if (!isEmpty) {
52 | setLoggedIn(false)
53 | } else {
54 | const userData = [...users, currentUser]
55 | setUsers(userData)
56 | localStorage.setItem("users", JSON.stringify(userData))
57 | localStorage.setItem("user", JSON.stringify(currentUser))
58 | setLoggedIn(true)
59 | }
60 | }, [errors])
61 |
62 | const value = {
63 | currentUser,
64 | setCurrentUser,
65 | users,
66 | loggedIn,
67 | errors,
68 | loggedIn,
69 | setErrors,
70 | setIsSubmitting,
71 | logout,
72 | login,
73 | }
74 |
75 | return {children}
76 | }
77 |
78 | const useAuth = () => useContext(AuthContext)
79 |
80 | export { AuthProvider, useAuth }
--------------------------------------------------------------------------------
/src/Context/CartContext.js:
--------------------------------------------------------------------------------
1 | import { useState, createContext, useContext, useEffect } from 'react'
2 |
3 | const CartContext = createContext()
4 |
5 | const defaultCart = JSON.parse(localStorage.getItem('cart')) || []
6 |
7 | const CartProvider = ({children}) => {
8 | const [items, setItems] = useState(defaultCart)
9 |
10 | useEffect(() => {
11 | localStorage.setItem('cart', JSON.stringify(items))
12 | }, [items])
13 |
14 | const addToCart = (data, findCartItem) => {
15 | if(!findCartItem) {
16 | return setItems((items) => [data, ...items] )
17 | }
18 |
19 | const filtered = items.filter((item) => item.id !== findCartItem.id)
20 | setItems(filtered)
21 | }
22 |
23 | const removeFromCart = (item_id) => {
24 | const filtered = items.filter((item) => item.id !== item_id)
25 | setItems(filtered)
26 | }
27 |
28 | const values = {
29 | items,
30 | setItems,
31 | addToCart,
32 | removeFromCart,
33 | }
34 |
35 | return {children}
36 | }
37 |
38 | const useCart = () => useContext(CartContext)
39 |
40 | export { CartProvider, useCart }
--------------------------------------------------------------------------------
/src/Context/FavoriteContext.js:
--------------------------------------------------------------------------------
1 | import { useState, createContext, useContext, useEffect } from 'react'
2 |
3 | const FavoriteContext = createContext()
4 |
5 | const defaultFavorite = JSON.parse(localStorage.getItem('favorite')) || []
6 |
7 | const FavoriteProvider = ({children}) => {
8 |
9 | const [favoriteItems, setFavoriteItems] = useState(defaultFavorite)
10 |
11 | useEffect(() => {
12 | localStorage.setItem('favorite', JSON.stringify(favoriteItems))
13 | }, [favoriteItems])
14 |
15 | const addToFavorite = (data, findFavoriteItem) => {
16 | if(!findFavoriteItem) {
17 | return setFavoriteItems((items) => [data, ...items] )
18 | }
19 |
20 | const filtered = favoriteItems.filter((item) => item.id !== findFavoriteItem.id)
21 | setFavoriteItems(filtered)
22 | }
23 |
24 | const values = {
25 | favoriteItems,
26 | addToFavorite,
27 | }
28 |
29 | return {children}
30 |
31 | }
32 |
33 | const useFavorite = () => useContext(FavoriteContext)
34 |
35 | export { FavoriteProvider, useFavorite }
--------------------------------------------------------------------------------
/src/Context/ProductContext.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { createContext, useContext, useEffect, useState } from 'react'
3 | const ProductContext = createContext()
4 |
5 |
6 | export const ProductProvider = ({ children }) => {
7 | const [productList, setProductList] = useState([])
8 | const [categories, setCategories] = useState()
9 | const [category, setCategory] = useState("/products")
10 | const [productID, setProductID] = useState("")
11 | const [product, setProduct] = useState({})
12 | const [loading, setLoading] = useState(false)
13 |
14 | useEffect(() => {
15 | setLoading(true)
16 | const getCategories = async () => {
17 |
18 | let categoriesData
19 | await axios("https://fakestoreapi.com/products/categories").then(
20 | (res) =>
21 | (categoriesData = res.data.map((item) =>
22 | item.replace(/^(.)|\s+(.)/g, (c) => c.toUpperCase())
23 | ))
24 | )
25 | setCategories(categoriesData)
26 | }
27 | getCategories()
28 | setLoading(false)
29 | }, [])
30 |
31 | useEffect(() => {
32 | setLoading(true)
33 | const getProductData = async () => {
34 |
35 | if (category && category.length > 0) {
36 | await axios.get(
37 | `https://fakestoreapi.com/products/category/${category}`
38 | ).then((res) => {
39 | setProductList(res.data)
40 | setLoading(false)
41 | })
42 | } else {
43 | await axios.get(`https://fakestoreapi.com/products`).then((res) => {
44 | setProductList(res.data)
45 | setCategory("")
46 | setLoading(false)
47 | })
48 | }
49 | }
50 | getProductData()
51 | }, [category])
52 |
53 | useEffect(() => {
54 | setLoading(true)
55 | const getProductDetail = async () => {
56 |
57 | productID && productID.length > 0 && await axios.get(`https://fakestoreapi.com/products/${productID}`).then(
58 | (res) => {
59 | setProduct(res.data)
60 | setLoading(false)
61 | }
62 | )
63 | }
64 | getProductDetail()
65 | }, [productID])
66 |
67 | const values = {
68 | product,
69 | productList,
70 | productID,
71 | setProductID,
72 | categories,
73 | setCategory,
74 | loading,
75 | }
76 |
77 | return (
78 | {children}
79 | )
80 | }
81 |
82 | export const useProduct = () => useContext(ProductContext)
--------------------------------------------------------------------------------
/src/Pages/Auth/Signin/index.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react'
2 | import { Link, useNavigate } from 'react-router-dom'
3 | import { useAuth } from '../../../Context/AuthContext'
4 | import styles from './styles.module.css'
5 | import { LoginIcon } from '@heroicons/react/outline'
6 |
7 | const Signin = () => {
8 |
9 | const { currentUser, login, setCurrentUser, setIsSubmitting, loggedIn } = useAuth()
10 |
11 | const [email, setEmail] = useState("")
12 | const [password, setPassword] = useState("")
13 |
14 | const emailRef = useRef()
15 | const passwordRef = useRef()
16 |
17 | const handleSignIn = async (e) => {
18 | e.preventDefault()
19 | setIsSubmitting(true)
20 | try {
21 | await login(emailRef.current.value, passwordRef.current.value)
22 | } catch {
23 | alert("Error!")
24 | }
25 | setIsSubmitting(false)
26 | }
27 |
28 | const navigate = useNavigate()
29 |
30 | useEffect(() => {
31 | loggedIn && navigate('/')
32 | }, [loggedIn])
33 |
34 | return (
35 |
36 |
37 |
38 |
Login
39 |
40 |
89 |
90 |
91 | )
92 | }
93 |
94 | export default Signin
--------------------------------------------------------------------------------
/src/Pages/Auth/Signin/styles.module.css:
--------------------------------------------------------------------------------
1 | .formGroupContainer {
2 | @apply
3 | min-h-full
4 | flex items-center
5 | justify-center
6 | py-12
7 | px-4
8 | sm:px-6
9 | lg:px-8
10 | }
11 |
12 | .formGroup {
13 | @apply
14 | max-w-md
15 | w-full
16 | space-y-8
17 | pt-6
18 | pb-12
19 | my-auto
20 | shadow-sm
21 | border-2
22 | border-zinc-900/10
23 | shadow-zinc-900/10
24 | rounded-md
25 | }
26 |
27 | .title{
28 | @apply
29 | mt-6
30 | text-center
31 | text-5xl
32 | font-extralight
33 | tracking-wide
34 | }
35 |
36 | .signInForm {
37 | @apply
38 | mt-8
39 | space-y-6
40 | px-20
41 | }
42 |
43 | .inputGroup {
44 | @apply
45 | rounded-md
46 | -space-y-px
47 | relative
48 | }
49 |
50 | .input {
51 | @apply
52 | appearance-none
53 | relative
54 | block
55 | w-full
56 | mb-5
57 | px-3
58 | py-2
59 | border
60 | shadow-sm
61 | border-zinc-900/10
62 | shadow-zinc-900/10
63 | placeholder-zinc-900/50
64 | rounded-md
65 | focus:outline-none
66 | focus:ring-1
67 | focus:ring-blue-600
68 | focus:z-10
69 | sm:text-sm
70 | }
71 |
72 | .button {
73 | @apply
74 | inline-flex
75 | justify-center
76 | w-full
77 | rounded-md
78 | shadow-sm
79 | shadow-zinc-900/10
80 | text-sm
81 | text-zinc-50
82 | px-2
83 | py-2
84 | bg-blue-600
85 | hover:bg-yellow-300
86 | }
87 |
88 | .linkBox {
89 | @apply
90 | flex
91 | items-center
92 | pb-5
93 | }
94 |
95 | .linkDiv {
96 | @apply
97 | flex
98 | items-center
99 | mx-auto
100 | }
101 |
102 |
103 |
--------------------------------------------------------------------------------
/src/Pages/Auth/Signup/index.js:
--------------------------------------------------------------------------------
1 | import { IdentificationIcon } from '@heroicons/react/outline'
2 | import { useEffect } from 'react'
3 | import { Link, useNavigate } from 'react-router-dom'
4 | import { useAuth } from '../../../Context/AuthContext'
5 | import styles from './styles.module.css'
6 | import validations from './validations'
7 |
8 | const Signup = () => {
9 | const {
10 | currentUser,
11 | setCurrentUser,
12 | users,
13 | loggedIn,
14 | errors,
15 | setErrors,
16 | setIsSubmitting
17 | } = useAuth()
18 |
19 | const navigate = useNavigate()
20 |
21 | useEffect(() => {
22 | loggedIn && navigate('/')
23 | }, [loggedIn])
24 |
25 | const handleSignUpFormChange = (e) => {
26 | setCurrentUser({ ...currentUser, [e.target.name]: e.target.value })
27 | }
28 |
29 | const handleSignUpSubmit = (e) => {
30 | e.preventDefault()
31 | setErrors(validations(currentUser, users))
32 | setIsSubmitting(true)
33 | localStorage.setItem('user', JSON.stringify(currentUser))
34 | localStorage.setItem('users', JSON.stringify(users))
35 | }
36 |
37 | return (
38 |
39 |
40 |
41 |
Sign Up
42 |
43 |
137 |
138 |
139 | )
140 | }
141 |
142 | export default Signup
143 |
--------------------------------------------------------------------------------
/src/Pages/Auth/Signup/styles.module.css:
--------------------------------------------------------------------------------
1 | .formGroupContainer {
2 | @apply
3 | min-h-full
4 | flex items-center
5 | justify-center
6 | py-12
7 | px-4
8 | sm:px-6
9 | lg:px-8
10 | }
11 |
12 | .formGroup {
13 | @apply
14 | max-w-md
15 | w-full
16 | space-y-8
17 | pt-6
18 | pb-12
19 | my-auto
20 | shadow-sm
21 | border-2
22 | border-zinc-900/10
23 | shadow-zinc-900/10
24 | rounded-md
25 | }
26 |
27 | .title{
28 | @apply
29 | mt-6
30 | text-center
31 | text-5xl
32 | font-extralight
33 | tracking-wide
34 | }
35 |
36 | .signUpForm {
37 | @apply
38 | mt-8
39 | space-y-6
40 | px-20
41 | }
42 |
43 | .inputGroup {
44 | @apply
45 | rounded-md
46 | -space-y-px
47 | relative
48 | }
49 |
50 | .input {
51 | @apply
52 | appearance-none
53 | relative
54 | block
55 | w-full
56 | mb-4
57 | px-3
58 | py-2
59 | border
60 | shadow-sm
61 | border-zinc-900/10
62 | shadow-zinc-900/10
63 | placeholder-zinc-900/50
64 | rounded-md
65 | focus:outline-none
66 | focus:ring-1
67 | focus:ring-blue-600
68 | focus:z-10
69 | sm:text-sm
70 | }
71 |
72 | .button {
73 | @apply
74 | inline-flex
75 | justify-center
76 | w-full
77 | rounded-md
78 | shadow-sm
79 | shadow-zinc-900/10
80 | text-sm
81 | font-bold
82 | text-zinc-50
83 | px-2
84 | py-2
85 | bg-blue-600
86 | hover:bg-yellow-300
87 | }
88 |
89 | .linkBox {
90 | @apply
91 | flex
92 | items-center
93 | pb-5
94 | }
95 |
96 | .linkDiv {
97 | @apply
98 | flex
99 | items-center
100 | mx-auto
101 | }
102 |
103 | .error {
104 | @apply
105 | text-xs
106 | text-red-500
107 | inline-block
108 | mx-auto
109 | my-0
110 | py-0
111 | }
--------------------------------------------------------------------------------
/src/Pages/Auth/Signup/validations.js:
--------------------------------------------------------------------------------
1 |
2 | const validations = (currentUser, users) => {
3 | console.log(users)
4 | const isUser = users.some((item) => item.email === currentUser.email)
5 |
6 | let errors = {}
7 |
8 | if (!currentUser.firstName) {
9 | errors.firstName = "First name is required."
10 | }
11 | if (!currentUser.lastName) {
12 | errors.lastName = "Last name is required."
13 | }
14 | if (!currentUser.email) {
15 | errors.email = "Email is required."
16 | } else if (!/\S+@\S+\.\S+/.test(currentUser.email)) {
17 | errors.email = "Email is invalid."
18 | } else if (isUser) {
19 | errors.email = "This email address is already being used!"
20 | }
21 | if (!currentUser.password) {
22 | errors.password = "Password is required."
23 | } else if (currentUser.password.length < 8) {
24 | errors.password = "Password must be more than eight characters."
25 | }
26 | if (!currentUser.passwordConfirm) {
27 | errors.passwordConfirm = "Password confirmation is required."
28 | } else if (currentUser.passwordConfirm !== currentUser.password) {
29 | errors.passwordConfirm = "Password doesn't match."
30 | }
31 |
32 | return errors
33 |
34 | }
35 |
36 | export default validations
37 |
--------------------------------------------------------------------------------
/src/Pages/Cart/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ShoppingCartIcon, TrashIcon } from "@heroicons/react/outline";
3 | import { Link } from "react-router-dom";
4 | import { useCart } from "../../Context/CartContext";
5 | import styles from "./styles.module.css";
6 |
7 | const Cart = () => {
8 | const { items, removeFromCart } = useCart();
9 |
10 | const subtotal = items.reduce((acc, obj) => acc + obj.price, 0).toFixed(1)
11 |
12 | return (
13 |
14 | {items.length < 1 && (
15 |
16 |
17 |
18 |
19 |
20 | There are no products in your cart.
21 |
22 |
23 | Add the products you like to the cart and buy.
24 |
25 |
26 |
27 |
28 |
29 |
30 | Continue Shopping
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | )}
40 |
41 | {items.length > 0 && (
42 |
43 |
44 | {items.map((item) => {
45 | return (
46 |
50 |
51 |
52 |
57 |
58 |
59 |
60 | Brand
61 |
62 |
{item.title}
63 |
64 |
65 | $ {item.price}
66 |
67 |
68 |
69 | removeFromCart(item.id)}>
70 |
71 |
72 |
73 |
74 |
75 |
76 | );
77 | })}
78 |
79 |
80 |
81 |
82 |
83 | Order Summary
84 |
85 |
86 | Subtotal $ {subtotal}
87 |
88 |
89 | Shipping Estimate $ 5
90 |
91 |
92 | Tax Estimate $ 5
93 |
94 |
95 | Order Total $ {parseFloat(subtotal) + 10}
96 |
97 |
98 |
99 |
100 |
101 | )}
102 |
103 | );
104 | };
105 |
106 | export default Cart;
107 |
--------------------------------------------------------------------------------
/src/Pages/Cart/styles.module.css:
--------------------------------------------------------------------------------
1 | .continueButton {
2 | @apply
3 | mx-auto
4 | text-center
5 | mt-2
6 | }
7 |
8 | .button {
9 | @apply
10 | inline-flex
11 | justify-center
12 | rounded-md
13 | border
14 | border-neutral-300
15 | shadow-sm
16 | shadow-zinc-900/10
17 | px-4
18 | py-2
19 | bg-gradient-to-l from-blue-500 via-blue-600 to-blue-500
20 | text-sm
21 | font-medium
22 | text-white
23 | hover:bg-gradient-to-r
24 | hover:from-blue-500
25 | hover:via-blue-700
26 | hover:to-blue-500
27 | hover:shadow-blue-700
28 | hover:shadow-inner
29 | mt-4
30 | }
31 |
32 | .buttonText {
33 | @apply
34 | font-light
35 | }
36 |
37 | .cardBg {
38 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%239C92AC' fill-opacity='0.09'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
39 | @apply
40 | block
41 | bg-white
42 | shadow-sm
43 | border-t
44 | border-stone-500/10
45 | shadow-zinc-900/10
46 | rounded-lg
47 | overflow-hidden
48 | h-full
49 | }
50 |
51 | .bgCart {
52 | @apply
53 | block
54 | bg-white
55 | shadow-sm
56 | border-t
57 | border-stone-500/10
58 | shadow-zinc-900/10
59 | rounded-lg
60 | overflow-hidden
61 | h-full
62 | }
--------------------------------------------------------------------------------
/src/Pages/Error404/index.js:
--------------------------------------------------------------------------------
1 | import { ExclamationCircleIcon } from "@heroicons/react/outline";
2 | import { ArrowCircleRightIcon } from "@heroicons/react/solid";
3 | import { Link } from "react-router-dom";
4 | import styles from "./styles.module.css";
5 | import React from "react";
6 |
7 | const Error404 = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | OOPS! NOTHING WAS FOUND!
15 |
16 |
17 | The page you are looking for might have been removed had its name
18 | changed or is temporarily unavailable.
19 |
20 |
21 |
22 |
23 |
24 | Continue Shopping
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default Error404;
39 |
--------------------------------------------------------------------------------
/src/Pages/Error404/styles.module.css:
--------------------------------------------------------------------------------
1 | .cardBg {
2 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%239C92AC' fill-opacity='0.09'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
3 | @apply
4 | bg-zinc-50
5 | shadow-sm
6 | border-2
7 | border-zinc-900/10
8 | shadow-zinc-900/10
9 | rounded-md
10 | overflow-hidden
11 | h-full
12 | flex
13 | flex-col
14 | justify-center
15 | my-auto
16 | }
17 |
18 | .headerIcon {
19 | @apply
20 | h-20
21 | w-20
22 | mx-auto
23 | }
24 |
25 | .errorTitle {
26 | @apply
27 | text-2xl
28 | font-bold
29 | tracking-widest
30 | text-center
31 | pt-6
32 | }
33 |
34 | .errorMessage {
35 | @apply
36 | text-center
37 | mt-2
38 | font-light
39 | tracking-wide
40 | }
41 |
42 | .continueButton {
43 | @apply
44 | mx-auto
45 | text-center
46 | mt-4
47 | }
48 |
49 | .button {
50 | @apply
51 | inline-flex
52 | justify-center
53 | rounded-md
54 | px-4
55 | py-2
56 | bg-blue-600
57 | text-sm
58 | font-medium
59 | text-zinc-50
60 | hover:bg-yellow-300
61 | mt-4
62 | }
63 |
64 | .buttonText {
65 | @apply
66 | font-bold
67 | }
68 |
69 | .buttonIcon {
70 | @apply
71 | my-auto
72 | ml-1
73 | h-5
74 | w-6
75 | }
76 |
--------------------------------------------------------------------------------
/src/Pages/Favorites/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { HeartIcon } from '@heroicons/react/outline'
4 | import styles from './styles.module.css'
5 | import { useFavorite } from '../../Context/FavoriteContext'
6 | import { useCart } from '../../Context/CartContext'
7 | import Card from '../../Components/Card'
8 |
9 | const Favorites = () => {
10 | const { favoriteItems, addToFavorite } = useFavorite()
11 | const { addToCart, items } = useCart()
12 |
13 | return (
14 |
15 | {favoriteItems.length < 1 && (
16 |
17 |
18 |
19 |
20 |
21 | There is no product in your favorites.
22 |
23 |
24 | Add the products you like to your favorites to buy them later.
25 |
26 |
27 |
28 |
29 |
30 |
31 | Continue Shopping
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | )}
41 |
42 | {favoriteItems.length > 0 && (
43 |
44 | {favoriteItems.map((item) => {
45 | const findFavoriteItem = favoriteItems.find(
46 | (fav_item) => fav_item.id === item.id
47 | )
48 | const findCartItem = items.find((cart_item) => cart_item.id === item.id)
49 | return (
50 |
58 | )
59 | })}
60 |
61 | )}
62 |
63 | )
64 | }
65 |
66 | export default Favorites
67 |
--------------------------------------------------------------------------------
/src/Pages/Favorites/styles.module.css:
--------------------------------------------------------------------------------
1 | .cardGroup {
2 | @apply
3 | flex
4 | flex-wrap
5 | max-w-7xl
6 | mx-auto
7 | my-4
8 | }
9 |
10 | .continueButton {
11 | @apply
12 | mx-auto
13 | text-center
14 | mt-2
15 | }
16 |
17 | .button {
18 | @apply
19 | inline-flex
20 | justify-center
21 | rounded-md
22 | border
23 | border-neutral-300
24 | shadow-sm
25 | shadow-zinc-900/10
26 | px-4
27 | py-2
28 | bg-gradient-to-l from-blue-500 via-blue-600 to-blue-500
29 | text-sm
30 | font-medium
31 | text-white
32 | hover:bg-gradient-to-r
33 | hover:from-blue-500
34 | hover:via-blue-700
35 | hover:to-blue-500
36 | hover:shadow-blue-700
37 | hover:shadow-inner
38 | mt-4
39 | }
40 |
41 | .buttonText {
42 | @apply
43 | font-light
44 | }
45 |
46 | .cardBg {
47 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%239C92AC' fill-opacity='0.09'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
48 | @apply
49 | block
50 | bg-white
51 | shadow-sm
52 | border-t
53 | border-stone-500/10
54 | shadow-zinc-900/10
55 | rounded-lg
56 | overflow-hidden
57 | h-full
58 | }
59 |
60 | .bgCart {
61 | @apply
62 | block
63 | bg-white
64 | shadow-sm
65 | border-t
66 | border-stone-500/10
67 | shadow-zinc-900/10
68 | rounded-lg
69 | overflow-hidden
70 | h-full
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/src/Pages/ProductDetail/index.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useParams } from "react-router-dom";
3 | import { ShoppingCartIcon, HeartIcon, StarIcon } from "@heroicons/react/solid";
4 | import Spinner from "../../Components/Spinner";
5 | import { useProduct } from "../../Context/ProductContext";
6 | import { useCart } from "../../Context/CartContext";
7 | import { useFavorite } from "../../Context/FavoriteContext";
8 | import styles from "./styles.module.css";
9 |
10 | const ProductDetail = () => {
11 | const { addToCart, items } = useCart();
12 | const { addToFavorite, favoriteItems } = useFavorite();
13 | const { product, loading, setProductID } = useProduct();
14 |
15 | const findCartItem = items.find((item) => item.id === product.id);
16 | const findFavoriteItem = favoriteItems.find((item) => item.id === product.id);
17 |
18 | const { product_id } = useParams();
19 |
20 | useEffect(() => {
21 | setProductID(product_id);
22 | }, []);
23 |
24 | return (
25 | <>
26 | {!loading && product?.id ? (
27 |
28 |
29 |
34 |
35 |
36 | BRAND
37 |
38 |
39 | {product.title}
40 |
41 |
42 | {[...Array(Math.round(product?.rating?.rate))].map((e, i) => (
43 |
48 | ))}
49 | {[...Array(5 - Math.round(product?.rating?.rate))].map(
50 | (e, i) => (
51 |
56 | )
57 | )}
58 |
59 | ({product?.rating?.count})
60 |
61 |
62 |
63 | {product.description}
64 |
65 |
66 |
67 |
68 | $ {product.price}
69 |
70 |
71 |
72 | {" "}
73 |
74 |
addToCart(product, findCartItem)}
77 | >
78 |
82 |
83 |
84 |
85 | {findCartItem ? "Remove from cart" : "Add to Cart"}
86 |
87 |
88 |
89 |
90 |
91 |
92 | {
99 | addToFavorite(product, findFavoriteItem);
100 | }}
101 | >
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | ) : (
110 |
111 | )}
112 | >
113 | );
114 | };
115 |
116 | export default ProductDetail;
117 |
--------------------------------------------------------------------------------
/src/Pages/ProductDetail/styles.module.css:
--------------------------------------------------------------------------------
1 | .brand {
2 | @apply
3 | text-sm
4 | tracking-widest
5 | mt-4
6 | mb-2
7 | hover:text-blue-600
8 | }
9 |
10 | .image {
11 | @apply
12 | lg:w-1/3
13 | inset-0
14 | max-h-max
15 | w-full
16 | py-4
17 | my-auto
18 | px-4
19 | object-scale-down
20 | shadow-sm
21 | border-2
22 | border-zinc-900/10
23 | shadow-zinc-900/10
24 | rounded-md
25 | }
26 |
27 | .productDetailText {
28 | @apply
29 | border-b-2
30 | mb-4
31 | border-zinc-900/10
32 | pb-6
33 | }
34 |
35 | /***** ADD TO CART BUTTON STYLES START *****/
36 |
37 | .addToCart {
38 | @apply
39 | mx-auto
40 | text-center
41 | w-full
42 | mt-2
43 | px-4
44 | }
45 |
46 | .addToCartButton {
47 | @apply
48 | inline-flex
49 | justify-center
50 | w-full
51 | rounded-md
52 | shadow-sm
53 | shadow-zinc-900/10
54 | px-10
55 | py-2
56 | bg-blue-600
57 | text-white
58 | text-sm
59 | hover:bg-gradient-to-b
60 | hover:bg-yellow-300
61 | }
62 |
63 | .removeButton {
64 | @apply
65 | inline-flex
66 | justify-center
67 | w-full
68 | rounded-md
69 | shadow-sm
70 | shadow-zinc-900/10
71 | px-2
72 | py-2
73 | bg-red-600
74 | text-sm
75 | text-white
76 | hover:bg-yellow-300
77 | }
78 |
79 | .shoppingCartIcon {
80 | @apply
81 | my-auto
82 | h-5
83 | w-6
84 | }
85 |
86 | .buttonText {
87 | @apply
88 | font-bold
89 | }
90 |
91 | /***** ADD TO CART BUTTON STYLES END *****/
92 |
93 | .favButton {
94 | @apply
95 | h-10
96 | w-10
97 | ml-auto
98 | text-zinc-900/80
99 | hover:text-red-500
100 | block
101 | }
102 |
103 | .removeFavButton {
104 | @apply
105 | h-10
106 | w-10
107 | ml-auto
108 | text-red-500 flex
109 | }
110 |
111 | .heartIcon {
112 | @apply
113 | h-10
114 | w-10
115 | mx-auto
116 | my-auto
117 | }
118 |
119 | .rating {
120 | @apply
121 | flex
122 | flex-row
123 | mb-2
124 | }
125 |
126 | .rating .starIcon, .emptyStarIcon {
127 | @apply
128 | h-4
129 | w-4
130 | my-auto
131 | }
132 |
133 | .starIcon {
134 | @apply
135 | text-yellow-300
136 | }
137 |
138 | .emptyStarIcon {
139 | @apply
140 | text-zinc-900/80
141 | }
--------------------------------------------------------------------------------
/src/Pages/Products/index.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect} from "react";
2 | import { useProduct } from "../../Context/ProductContext";
3 | import styles from "./styles.module.css";
4 | import Spinner from "../../Components/Spinner";
5 | import { useParams } from "react-router-dom";
6 | import { useCart } from '../../Context/CartContext'
7 | import { useFavorite } from '../../Context/FavoriteContext'
8 | import Card from "../../Components/Card";
9 |
10 | const Products = () => {
11 | const {addToCart, items} = useCart()
12 | const {addToFavorite, favoriteItems} = useFavorite()
13 |
14 | const { productList, loading, setProductID, setCategory } = useProduct();
15 |
16 | const {category_id} = useParams()
17 |
18 | useEffect(() => {
19 | setCategory(category_id)
20 | }, [category_id])
21 |
22 | return (
23 |
24 | {!loading ? (
25 | productList?.map((item, index) => {
26 | const findCartItem = items.find((cart_item) => cart_item.id === item.id)
27 | const findFavoriteItem = favoriteItems.find((favorite_item) => favorite_item.id === item.id)
28 | return (
29 |
30 | );
31 | })
32 | ) : (
33 |
34 | )}
35 |
36 | );
37 | };
38 |
39 | export default Products;
40 |
--------------------------------------------------------------------------------
/src/Pages/Products/styles.module.css:
--------------------------------------------------------------------------------
1 | .cardGroup {
2 | @apply
3 | flex
4 | flex-wrap
5 | max-w-7xl
6 | mx-auto
7 | my-4
8 | }
9 |
--------------------------------------------------------------------------------
/src/Pages/ProtectedRoute.js:
--------------------------------------------------------------------------------
1 | import { useAuth } from '../Context/AuthContext'
2 | import React from 'react'
3 |
4 | const ProtectedRoute = () => {
5 |
6 | const { loggedIn, role } = useAuth()
7 |
8 | return (
9 | {
12 | if (loggedIn && role === admin) {
13 | return ;
14 | }
15 |
16 | if (loggedIn) {
17 | return ;
18 | }
19 |
20 | return ;
21 | }}
22 | />
23 | )
24 | }
25 |
26 | export default ProtectedRoute
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap');
6 |
7 | @import url('https://fonts.googleapis.com/css2?family=Oswald:wght@700&display=swap');
8 |
9 |
10 | body {
11 | font-family: 'Inter', sans-serif;
12 | @apply
13 | bg-zinc-50/10
14 | text-zinc-900/80
15 | }
--------------------------------------------------------------------------------
/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 { AuthProvider } from './Context/AuthContext'
6 | import { ProductProvider } from './Context/ProductContext'
7 | import { CartProvider } from './Context/CartContext'
8 | import { FavoriteProvider } from './Context/FavoriteContext'
9 | import { BrowserRouter } from 'react-router-dom'
10 |
11 | ReactDOM.render(
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ,
25 | document.getElementById("root")
26 | )
27 |
28 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: [
3 | "./src/**/*.{js,jsx,ts,tsx}",
4 | ],
5 | theme: {
6 | extend: {},
7 | },
8 | plugins: [],
9 | }
10 |
--------------------------------------------------------------------------------